"""
Sliver Implant Framework
Copyright (C) 2021 Bishop Fox
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import logging
from typing import AsyncGenerator, Dict, Iterable, List, Optional, Union
import grpc
from sliver.pb.commonpb.common_pb2 import Request
from .beacon import InteractiveBeacon
from .config import SliverClientConfig
from .pb.rpcpb.services_pb2_grpc import SliverRPCStub
from .protobuf import client_pb2, common_pb2, sliver_pb2
from .session import InteractiveSession
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
TIMEOUT = 60
[docs]class BaseClient(object):
# 2GB triggers an overflow error in the gRPC library so we do 2GB-1
MAX_MESSAGE_LENGTH = (2 * GB) - 1
KEEP_ALIVE_TIMEOUT = 10000
CERT_COMMON_NAME = "multiplayer"
def __init__(self, config: SliverClientConfig):
self.config = config
self._channel: grpc.aio.Channel = None # type: ignore[assignment]
self._stub: SliverRPCStub = None # type: ignore[assignment]
self._log = logging.getLogger(self.__class__.__name__)
[docs] def is_connected(self) -> bool:
return self._channel is not None
@property
def target(self) -> str:
return "%s:%d" % (
self.config.lhost,
self.config.lport,
)
@property
def credentials(self) -> grpc.ChannelCredentials:
return grpc.composite_channel_credentials(
grpc.ssl_channel_credentials(
root_certificates=self.config.ca_certificate.encode(),
private_key=self.config.private_key.encode(),
certificate_chain=self.config.certificate.encode(),
),
grpc.access_token_call_credentials(
access_token=self.config.token,
),
)
@property
def options(self):
return [
("grpc.keepalive_timeout_ms", self.KEEP_ALIVE_TIMEOUT),
("grpc.ssl_target_name_override", self.CERT_COMMON_NAME),
("grpc.max_send_message_length", self.MAX_MESSAGE_LENGTH),
("grpc.max_receive_message_length", self.MAX_MESSAGE_LENGTH),
]
[docs]class SliverClient(BaseClient):
"""Asyncio client implementation"""
beacon_event_types = ["beacon-registered"]
session_event_types = ["session-connected", "session-disconnected"]
job_event_types = ["job-started", "job-stopped"]
canary_event_types = ["canary"]
[docs] async def connect(self) -> client_pb2.Version:
"""Establish a connection to the Sliver server
:return: Protobuf Version object, containing the server's version information
:rtype: client_pb2.Version
"""
self._channel = grpc.aio.secure_channel(
target=self.target,
credentials=self.credentials,
options=self.options,
)
self._stub = SliverRPCStub(self._channel)
return await self.version()
[docs] async def interact_session(
self, session_id: str, timeout=TIMEOUT
) -> Optional[InteractiveSession]:
"""Interact with a session, returns an :class:`InteractiveSession`
:param session_id: Session ID
:type session_id: str
:param timeout: gRPC timeout, defaults to 60 seconds
:return: An interactive session
:rtype: Optional[InteractiveSession]
"""
session = await self.session_by_id(session_id, timeout)
if session:
return InteractiveSession(session, self._channel, timeout)
[docs] async def interact_beacon(
self, beacon_id: str, timeout=TIMEOUT
) -> Optional[InteractiveBeacon]:
"""Interact with a beacon, returns an :class:`InteractiveBeacon`
:param beacon_id: Beacon ID
:type beacon_id: str
:param timeout: gRPC timeout, defaults to 60 seconds
:return: An interactive beacon
:rtype: Optional[AsyncInteractiveBeacon]
"""
beacon = await self.beacon_by_id(beacon_id, timeout)
if beacon:
return InteractiveBeacon(beacon, self._channel, timeout)
[docs] async def session_by_id(
self, session_id: str, timeout=TIMEOUT
) -> Optional[client_pb2.Session]:
"""Get the session information from a session ID
:param session_id: Session ID
:type session_id: str
:param timeout: gRPC timeout, defaults to 60 seconds
:return: Protobuf Session object
:rtype: Optional[client_pb2.Session]
"""
sessions = await self.sessions(timeout)
for session in sessions:
if session.ID == session_id:
return session
[docs] async def beacon_by_id(
self, beacon_id: str, timeout=TIMEOUT
) -> Optional[client_pb2.Beacon]:
"""Get the beacon information from a beacon ID
:param beacon_id: Beacon ID
:type beacon_id: str
:param timeout: gRPC timeout, defaults to 60 seconds
:return: Protobuf Beacon object
:rtype: Union[client_pb2.Beacon, None]
"""
beacons = await self.beacons(timeout)
for beacon in beacons:
if beacon.ID == beacon_id:
return beacon
[docs] async def events(self) -> AsyncGenerator[client_pb2.Event, None]:
"""All events
:yield: A stream of events
:rtype: client_pb2.Event
"""
async for event in self._stub.Events(common_pb2.Empty()):
yield event
[docs] async def on(
self, event_types: Union[str, List[str]]
) -> AsyncGenerator[client_pb2.Event, None]:
"""Iterate on a specific event or list of events
:param event_types: An event type or list of event types
:type event_types: Union[str, List[str]]
:yield: A stream of events of the given type(s)
:rtype: client_pb2.Event
"""
if isinstance(event_types, str):
event_types = [event_types]
async for event in self.events():
if event.EventType in event_types:
yield event
[docs] async def version(self, timeout=TIMEOUT) -> client_pb2.Version:
"""Get server version information
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf Version object
:rtype: client_pb2.Version
"""
return await self._stub.GetVersion(common_pb2.Empty(), timeout=timeout)
[docs] async def operators(self, timeout=TIMEOUT) -> List[client_pb2.Operator]:
"""Get a list of operators and their online status
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: List of protobuf Operator objects
:rtype: List[client_pb2.Operator]
"""
operators = await self._stub.GetOperators(common_pb2.Empty(), timeout=timeout)
return list(operators.Operators)
[docs] async def sessions(self, timeout=TIMEOUT) -> List[client_pb2.Session]:
"""Get a list of active sessions
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: List of protobuf Session objects
:rtype: List[client_pb2.Session]
"""
sessions: client_pb2.Sessions = await self._stub.GetSessions(
common_pb2.Empty(), timeout=timeout
)
return list(sessions.Sessions)
[docs] async def rename_session(self, session_id: str, name: str, timeout=TIMEOUT) -> None:
"""Rename a session
:param session_id: Session ID to update
:type session_id: str
:param name: Rename session to this value
:type name: str
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: None
:rtype: None
"""
rename_req = client_pb2.RenameReq(SessionID=session_id, Name=name)
await self._stub.Rename(rename_req, timeout=timeout)
[docs] async def kill_session(self, session_id: str, force=False, timeout=TIMEOUT) -> None:
"""Kill a session
:param session_id: Session ID to kill
:type session_id: str
:param force: Force kill the session, defaults to False
:type force: bool, optional
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
"""
request = Request(SessionID=session_id, Timeout=timeout)
kill_req = sliver_pb2.KillReq(Force=force, Request=request)
await self._stub.Kill(kill_req, timeout=timeout)
[docs] async def beacons(self, timeout=TIMEOUT) -> List[client_pb2.Beacon]:
"""Get a list of active beacons
:param timeout: gRPC timeout, defaults to 60 seconds
:rtype: List[client_pb2.Beacon]
"""
beacons: client_pb2.Beacons = await self._stub.GetBeacons(
common_pb2.Empty(), timeout=timeout
)
return list(beacons.Beacons)
[docs] async def rename_beacon(self, beacon_id: str, name: str, timeout=TIMEOUT) -> None:
"""Rename a beacon
:param beacon_id: Beacon ID to update
:type beacon_id: str
:param name: Rename beacon to this value
:type name: str
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: None
:rtype: None
"""
rename_req = client_pb2.RenameReq(BeaconID=beacon_id, Name=name)
await self._stub.Rename(rename_req, timeout=timeout)
[docs] async def kill_beacon(self, beacon_id: str, timeout=TIMEOUT) -> None:
"""Kill a beacon
:param beacon_id: Numeric beacon ID to remove
:type beacon_id: str
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
"""
beacon_rm = client_pb2.Beacon(ID=beacon_id)
await self._stub.RmBeacon(beacon_rm, timeout=timeout)
[docs] async def beacon_tasks(
self, beacon_id: str, timeout=TIMEOUT
) -> List[client_pb2.BeaconTask]:
"""Get a list of tasks for a beacon
:param beacon_id: Beacon ID to get tasks for
:type beacon_id: str
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: List of protobuf Task objects
:rtype: List[client_pb2.Task]
"""
beacon = client_pb2.Beacon(ID=beacon_id)
tasks = await self._stub.GetBeaconTasks(beacon, timeout=timeout)
return list(tasks.Tasks)
[docs] async def beacon_task_content(
self, task_id: str, timeout=TIMEOUT
) -> List[client_pb2.BeaconTask]:
"""Get a list of tasks for a beacon
:param task_id: Task ID get contents for
:type task_id: sts
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: List of protobuf Task objects
:rtype: List[client_pb2.Task]
"""
beacon = client_pb2.Beacon(ID=task_id)
task = await self._stub.GetBeaconTaskContent(beacon, timeout=timeout)
return task
[docs] async def jobs(self, timeout=TIMEOUT) -> List[client_pb2.Job]:
"""Get a list of active jobs
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: List of protobuf Job objects
:rtype: List[client_pb2.Job]
"""
jobs: client_pb2.Jobs = await self._stub.GetJobs(
common_pb2.Empty(), timeout=timeout
)
return list(jobs.Active)
[docs] async def job_by_id(self, job_id: int, timeout=TIMEOUT) -> Optional[client_pb2.Job]:
"""Get job by id
:param job_id: Beacon ID to get tasks for
:type job_id: str
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: List of protobuf Job objects
:rtype: List[client_pb2.Job]
"""
for job in await self.jobs(timeout=timeout):
if job.ID == job_id:
return job
[docs] async def job_by_port(
self, job_port: int, timeout=TIMEOUT
) -> Optional[client_pb2.Job]:
"""Get job by port
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: List of protobuf Job objects
:rtype: List[client_pb2.Job]
"""
for job in await self.jobs(timeout=timeout):
if job.Port == job_port:
return job
[docs] async def kill_job(self, job_id: int, timeout=TIMEOUT) -> client_pb2.KillJob:
"""Kill a job
:param job_id: Numeric job ID to kill
:type job_id: int
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf KillJob object
:rtype: client_pb2.KillJob
"""
kill_req = client_pb2.KillJobReq(ID=job_id)
return await self._stub.KillJob(kill_req, timeout=timeout)
[docs] async def start_mtls_listener(
self,
host: str = "0.0.0.0",
port: int = 8888,
persistent: bool = False,
timeout=TIMEOUT,
) -> client_pb2.MTLSListener:
"""Start a mutual TLS (mTLS) C2 listener
:param host: Host interface to bind the listener to, an empty string will bind to all interfaces
:type host: str
:param port: TCP port number to start listener on
:type port: int
:param persistent: Register the listener as a persistent job (automatically start with server), defaults to False
:type persistent: bool, optional
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf MTLSListener object
:rtype: client_pb2.MTLSListener
"""
mtls_req = client_pb2.MTLSListenerReq(
Host=host, Port=port, Persistent=persistent
)
return await self._stub.StartMTLSListener(mtls_req, timeout=timeout)
[docs] async def start_wg_listener(
self,
tun_ip: str,
port: int = 53,
n_port: int = 8888,
key_port: int = 1337,
persistent: bool = False,
timeout: int = TIMEOUT,
) -> client_pb2.WGListener:
"""Start a WireGuard (wg) C2 listener
:param tun_ip: Virtual TUN IP listen address
:type tun_ip: str
:param port: UDP port to start listener on
:type port: int
:param n_port: Virtual TUN port number
:type n_port: int
:param key_port: Virtual TUN port number for key exchanges
:type key_port: int
:param persistent: Register the listener as a persistent job (automatically start with server), defaults to False
:type persistent: bool, optional
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf WGListener object
:rtype: client_pb2.WGListener
"""
wg_req = client_pb2.WGListenerReq(
TunIP=tun_ip,
Port=port,
NPort=n_port,
KeyPort=key_port,
Persistent=persistent,
)
return await self._stub.StartWGListener(wg_req, timeout=timeout)
[docs] async def start_dns_listener(
self,
domains: List[str],
host: str = "0.0.0.0",
port: int = 53,
canaries: bool = True,
persistent: bool = False,
enforce_otp=True,
timeout: int = TIMEOUT,
) -> client_pb2.DNSListener:
"""Start a DNS C2 listener
:param domains: C2 domains to listen for
:type domains: List[str]
:param canaries: Enable/disable DNS canaries
:type canaries: bool
:param host: Host interface to bind the listener to, an empty string will bind to all interfaces
:type host: str
:param port: TCP port number to start listener on
:type port: int
:param persistent: Register the listener as a persistent job (automatically start with server), defaults to False
:type persistent: bool, optional
:param enforce_otp: Enforce OTP auth for DNS C2, defaults to True
:type enforce_otp: bool, optional
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf DNSListener object
:rtype: client_pb2.DNSListener
"""
# Ensure domains always have a trailing dot
domains = list(map(lambda d: d + "." if d[-1] != "." else d, domains))
dns_req = client_pb2.DNSListenerReq(
Domains=domains,
Canaries=canaries,
Host=host,
Port=port,
Persistent=persistent,
EnforceOTP=enforce_otp,
)
return await self._stub.StartDNSListener(dns_req, timeout=timeout)
[docs] async def start_http_listener(
self,
host: str = "0.0.0.0",
port: int = 80,
website: str = "",
domain: str = "",
persistent: bool = False,
timeout: int = TIMEOUT,
) -> client_pb2.HTTPListener:
"""Start an HTTP C2 listener
:param host: Host interface to bind the listener to, an empty string will bind to all interfaces
:type host: str
:param port: TCP port number to start listener on
:type port: int
:param website: Name of the "website" to host on listener
:type website: str
:param domain: Domain name for HTTP server (one domain per listener)
:type domain: str
:param persistent: Register the listener as a persistent job (automatically start with server), defaults to False
:type persistent: bool, optional
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf HTTPListener object (NOTE: HTTP/HTTPS both return HTTPListener objects)
:rtype: client_pb2.HTTPListener
"""
http_req = client_pb2.HTTPListenerReq(
Domain=domain,
Host=host,
Port=port,
Secure=False,
Website=website,
Persistent=persistent,
)
return await self._stub.StartHTTPListener(http_req, timeout=timeout)
[docs] async def start_https_listener(
self,
host: str = "0.0.0.0",
port: int = 443,
website: str = "",
domain: str = "",
cert: bytes = b"",
key: bytes = b"",
acme: bool = False,
persistent: bool = False,
enforce_otp: bool = True,
randomize_jarm: bool = True,
long_poll_timeout: int = 1,
long_poll_jitter: int = 2,
timeout: int = TIMEOUT,
) -> client_pb2.HTTPListener:
"""Start an HTTPS C2 listener
:param domain: Domain name for HTTPS server (one domain per listener)
:type domain: str
:param host: Host interface to bind the listener to, an empty string will bind to all interfaces
:type host: str
:param port: TCP port number to start listener on
:type port: int
:param website: Name of the "website" to host on listener
:type website: str
:param cert: TLS certificate (leave blank to generate self-signed certificate)
:type cert: bytes
:param key: TLS private key (leave blank to generate self-signed certificate)
:type key: bytes
:param acme: Automatically provision TLS certificate using ACME (i.e., Let's Encrypt)
:type acme: bool
:param persistent: Register the listener as a persistent job (automatically start with server), defaults to False
:type persistent: bool, optional
:param enforce_otp: Enforce OTP auth for HTTPS C2, defaults to True
:type enforce_otp: bool, optional
:param randomize_jarm: Randomize JARM fingerprint for HTTPS C2, defaults to True
:type randomize_jarm: bool, optional
:param long_poll_timeout: Long poll timeout for HTTPS C2, defaults to 1
:type long_poll_timeout: int, optional
:param long_poll_jitter: Long poll jitter for HTTPS C2, defaults to 2
:type long_poll_jitter: int, optional
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf HTTPListener object (NOTE: HTTP/HTTPS both return HTTPListener objects)
:rtype: client_pb2.HTTPListener
"""
https_req = client_pb2.HTTPListenerReq(
Domain=domain,
Host=host,
Port=port,
Secure=True,
Website=website,
Cert=cert,
Key=key,
ACME=acme,
Persistent=persistent,
EnforceOTP=enforce_otp,
LongPollTimeout=long_poll_timeout,
LongPollJitter=long_poll_jitter,
RandomizeJARM=randomize_jarm,
)
return await self._stub.StartHTTPSListener(https_req, timeout=timeout)
[docs] async def start_tcp_stager_listener(
self, host: str, port: int, data: bytes, timeout=TIMEOUT
) -> client_pb2.StagerListener:
"""Start a TCP stager listener
:param host: Host interface to bind the listener to, an empty string will bind to all interfaces
:type host: str
:param port: TCP port number to start listener on
:type port: int
:param data: Binary data of stage to host on listener
:type data: bytes
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf StagerListener object
:rtype: client_pb2.StagerListener
"""
stage_req = client_pb2.StagerListenerReq(
Protocol=client_pb2.TCP,
Host=host,
Port=port,
Data=data,
)
return await self._stub.StartTCPStagerListener(stage_req, timeout=timeout)
[docs] async def start_http_stager_listener(
self,
host: str,
port: int,
data: bytes,
timeout: int = TIMEOUT,
) -> client_pb2.StagerListener:
"""Start an HTTP stager listener
:param host: Host interface to bind the listener to, an empty string will bind to all interfaces
:type host: str
:param port: TCP port number to start listener on
:type port: int
:param data: Binary data of stage to host on listener
:type data: bytes
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf StagerListener object
:rtype: client_pb2.StagerListener
"""
stage_req = client_pb2.StagerListenerReq(
Protocol=client_pb2.HTTP, Host=host, Port=port, Data=data
)
return await self._stub.StartHTTPStagerListener(stage_req, timeout=timeout)
[docs] async def start_https_stager_listener(
self,
host: str,
port: int,
data: bytes,
cert: bytes,
key: bytes,
acme: bool,
timeout: int = TIMEOUT,
) -> client_pb2.StagerListener:
"""Start an HTTPS stager listener
:param host: Host interface to bind the listener to, an empty string will bind to all interfaces
:type host: str
:param port: TCP port number to start listener on
:type port: int
:param data: Binary data of stage to host on listener
:type data: bytes
:param cert: TLS certificate, leave blank to start listener as HTTP
:type cert: bytes
:param key: TLS key, leave blank to start listener as HTTP
:type key: bytes
:param acme: Automatically provision TLS certificate using ACME (i.e., Let's Encrypt)
:type acme: bool
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf StagerListener object
:rtype: client_pb2.StagerListener
"""
stage_req = client_pb2.StagerListenerReq(
Protocol=client_pb2.HTTPS,
Host=host,
Port=port,
Data=data,
Cert=cert,
Key=key,
ACME=acme,
)
return await self._stub.StartHTTPStagerListener(stage_req, timeout=timeout)
[docs] async def generate_implant(
self, config: client_pb2.ImplantConfig, timeout: int = 360
) -> client_pb2.Generate:
"""Generate a new implant using a given configuration
:param config: Protobuf ImplantConfig object
:type config: client_pb2.ImplantConfig
:param timeout: gRPC timeout, defaults to 360
:type timeout: int, optional
:return: Protobuf Generate object containing the generated implant
:rtype: client_pb2.Generate
"""
req = client_pb2.GenerateReq(Config=config)
return await self._stub.Generate(req, timeout=timeout)
[docs] async def regenerate_implant(
self, implant_name: str, timeout=TIMEOUT
) -> client_pb2.Generate:
"""Regenerate an implant binary given the implants "name"
:param implant_name: The name of the implant to regenerate
:type implant_name: str
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf Generate object
:rtype: client_pb2.Generate
"""
regenerate = client_pb2.RegenerateReq(ImplantName=implant_name)
return await self._stub.Regenerate(regenerate, timeout=timeout)
[docs] async def implant_builds(
self, timeout=TIMEOUT
) -> Dict[str, client_pb2.ImplantConfig]:
"""Get information about historical implant builds
:return: Protobuf Map object, the keys are implant names the values are implant configs
:rtype: Dict[str, client_pb2.ImplantConfig]
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
"""
builds: client_pb2.ImplantBuilds = await self._stub.ImplantBuilds(
common_pb2.Empty(), timeout=timeout
)
return dict(builds.Configs)
[docs] async def delete_implant_build(self, implant_name: str, timeout=TIMEOUT) -> None:
"""Delete a historical implant build from the server by name
:param implant_name: The name of the implant build to delete
:type implant_name: str
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
"""
delete = client_pb2.DeleteReq(Name=implant_name)
await self._stub.DeleteImplantBuild(delete, timeout=timeout)
[docs] async def canaries(self, timeout=TIMEOUT) -> List[client_pb2.DNSCanary]:
"""Get a list of canaries that have been generated during implant builds, includes metadata about those canaries
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: List of Protobuf DNSCanary objects
:rtype: List[client_pb2.DNSCanary]
"""
canaries = await self._stub.Canaries(common_pb2.Empty(), timeout=timeout)
return list(canaries.Canaries)
[docs] async def generate_wg_client_config(
self, timeout=TIMEOUT
) -> client_pb2.WGClientConfig:
"""Generate a new WireGuard client configuration files
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf WGClientConfig object
:rtype: client_pb2.WGClientConfig
"""
return await self._stub.GenerateWGClientConfig(
common_pb2.Empty(), timeout=timeout
)
[docs] async def generate_wg_ip(self, timeout=TIMEOUT) -> client_pb2.UniqueWGIP:
"""Generate a unique IP address for use with WireGuard
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf UniqueWGIP object
:rtype: client_pb2.UniqueWGIP
"""
return await self._stub.GenerateUniqueIP(common_pb2.Empty(), timeout=timeout)
[docs] async def implant_profiles(
self, timeout=TIMEOUT
) -> List[client_pb2.ImplantProfile]:
"""Get a list of all implant configuration profiles on the server
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: List of Protobuf ImplantProfile objects
:rtype: List[client_pb2.ImplantProfile]
"""
profiles = await self._stub.ImplantProfiles(common_pb2.Empty(), timeout=timeout)
return list(profiles.Profiles)
[docs] async def delete_implant_profile(self, profile_name, timeout=TIMEOUT) -> None:
"""Delete an implant configuration profile by name
:param profile_name: Name of the profile to delete
:type profile_name: str
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
"""
delete = client_pb2.DeleteReq()
delete.Name = profile_name
await self._stub.DeleteImplantProfile(delete, timeout=timeout)
[docs] async def save_implant_profile(
self, profile: client_pb2.ImplantProfile, timeout=TIMEOUT
) -> client_pb2.ImplantProfile:
"""Save an implant configuration profile to the server
:param profile: An implant configuration profile (a Protobuf ImplantProfile object)
:type profile: client_pb2.ImplantProfile
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf ImplantProfile object
:rtype: client_pb2.ImplantProfile
"""
return await self._stub.SaveImplantProfile(profile, timeout=timeout)
[docs] async def generate_msf_stager(
self,
arch: str,
format: str,
host: str,
port: int,
os: str,
protocol: client_pb2.StageProtocol.ValueType,
badchars: Optional[Iterable[str]],
timeout: int = TIMEOUT,
) -> client_pb2.MsfStager:
"""Create a Metasploit stager (if available on the server)
:param arch: CPU architecture
:type arch: str
:param format: Binary format (MSF)
:type format: str
:param host: LHOST (MSF)
:type host: str
:param port: LPORT (MSF)
:type port: int
:param os: Operating System (MSF)
:type os: str
:param protocol: Stager protocol (Protobuf StageProtocol object)
:type protocol: client_pb2.StageProtocol
:param badchars: Bad characters, defaults to []
:type badchars: list, optional
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf MsfStager object
:rtype: client_pb2.MsfStager
"""
stagerReq = client_pb2.MsfStagerReq(
Arch=arch,
Format=format,
Host=host,
Port=port,
OS=os,
Protocol=protocol,
BadChars=badchars if badchars else [],
)
return await self._stub.MsfStage(stagerReq, timeout=timeout)
[docs] async def shellcode(
self, data: bytes, function_name: str, arguments: str = "", timeout=TIMEOUT
) -> client_pb2.ShellcodeRDI:
"""Generate Donut shellcode
:param data: The DLL file to wrap in a shellcode loader
:type data: bytes
:param function_name: Function to call on the DLL
:type function_name: str
:param arguments: Arguments to the function called
:type arguments: str
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf ShellcodeRDI object
:rtype: client_pb2.ShellcodeRDI
"""
shellReq = client_pb2.ShellcodeRDIReq(
Data=data, FunctionName=function_name, Arguments=arguments
)
return await self._stub.ShellcodeRDI(shellReq, timeout=timeout)
[docs] async def websites(self, timeout=TIMEOUT) -> List[client_pb2.Website]:
"""Get a list of websites
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: List of Protobuf Website objects
:rtype: List[client_pb2.Website]
"""
websites = await self._stub.Websites(common_pb2.Empty(), timeout=timeout)
return list(websites.Websites)
[docs] async def update_website(
self, website: client_pb2.Website, timeout=TIMEOUT
) -> client_pb2.Website:
"""Update an entire website object on the server
:param website: The updated Protobuf Website object
:type website: client_pb2.Website
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf Website object
:rtype: client_pb2.Website
"""
return await self._stub.Websites(website, timeout=timeout)
[docs] async def remove_website(self, name: str, timeout=TIMEOUT) -> None:
"""Remove an entire website and its content
:param name: The name of the website to remove
:type name: str
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
"""
website = client_pb2.Website(Name=name)
await self._stub.WebsiteRemove(website, timeout=timeout)
[docs] async def add_website_content(
self,
name: str,
web_path: str,
content_type: str,
content: bytes,
timeout: int = TIMEOUT,
) -> client_pb2.Website:
"""Add content to a specific website
:param name: Name of the website to add the content to
:type name: str
:param web_path: Bind content to web path
:type web_path: str
:param content_type: Specify the Content-type response HTTP header
:type content_type: str
:param content: The raw response content
:type content: bytes
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf Website object
:rtype: client_pb2.Website
"""
web = client_pb2.WebContent(
Path=web_path, ContentType=content_type, Content=content, Size=len(content)
)
web_add = client_pb2.WebsiteAddContent(Name=name, Contents={web_path: web})
return await self._stub.WebsiteAddContent(web_add, timeout=timeout)
[docs] async def update_website_content(
self,
name: str,
web_path: str,
content_type: str,
content: bytes,
timeout: int = TIMEOUT,
) -> client_pb2.Website:
"""Update content on a specific website / web path
:param name: Name of the website to add the content to
:type name: str
:param web_path: Bind content to web path
:type web_path: str
:param content_type: Specify the Content-type response HTTP header
:type content_type: str
:param content: The raw response content
:type content: bytes
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf Website object
:rtype: client_pb2.Website
"""
web = client_pb2.WebContent(
Path=web_path, ContentType=content_type, Content=content, Size=len(content)
)
web_update = client_pb2.WebsiteAddContent(Name=name, Contents={web_path: web})
return await self._stub.WebsiteUpdateContent(web_update, timeout=timeout)
[docs] async def remove_website_content(
self, name: str, paths: List[str], timeout=TIMEOUT
) -> client_pb2.Website:
"""Remove content from a specific website
:param name: The name of the website from which to remove the content
:type name: str
:param paths: A list of paths to content that should be removed from the website
:type paths: List[str]
:param timeout: gRPC timeout, defaults to 60 seconds
:type timeout: int, optional
:return: Protobuf Website object
:rtype: client_pb2.Website
"""
web = client_pb2.WebsiteRemoveContent(Name=name, Paths=paths)
return await self._stub.WebsiteRemoveContent(web, timeout=timeout)