Skip to content

Commit

Permalink
Merge pull request #2 from MythicAgents/dev
Browse files Browse the repository at this point in the history
Merge of improved commands and api handling
  • Loading branch information
spenceradolph authored Apr 22, 2024
2 parents 3098c59 + 472007f commit 5ffa4cc
Show file tree
Hide file tree
Showing 69 changed files with 843 additions and 303 deletions.
93 changes: 87 additions & 6 deletions Payload_Type/sliverapi/sliverapi/SliverRequests/SliverAPI.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,105 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
from mythic_container.PayloadBuilder import *

import asyncio

sliver_clients = {}

async def create_sliver_client(taskData: PTTaskMessageAllData):
if (f"{taskData.Callback.ID}" in sliver_clients.keys()):
return sliver_clients[f"{taskData.Callback.ID}"]
# builder.py should have cached it by calling create_sliver_client_with_config
if (f"{taskData.Payload.UUID}" not in sliver_clients.keys()):
# TODO: throw error
return None

return sliver_clients[f"{taskData.Payload.UUID}"]


# TODO: could refactor this more
async def create_sliver_client_with_config(payload_uuid, configFileId):
filecontent = await SendMythicRPCFileGetContent(MythicRPCFileGetContentMessage(
# TODO: could possibly mirror this in the implant create_client, and get rid of extraInfo? (payload vs callback....)
AgentFileId=taskData.BuildParameters[0].Value
AgentFileId=configFileId
))

config = SliverClientConfig.parse_config(filecontent.Content)
client = SliverClient(config)

await client.connect()

sliver_clients[f"{taskData.Callback.ID}"] = client
sliver_clients[f"{payload_uuid}"] = client

# TODO: refactor this into the builder.py
async def read_server_events():
async for data in client.events():
await handleSliverEvent(data, configFileId)
asyncio.create_task(read_server_events())

# TODO: sync callbacks and payloads here

return client

async def handleSliverEvent(event: client_pb2.Event, configFileId):
print(event.EventType)

if (event.EventType == 'session-connected'):
# print(event.Session)

# create payload
sliver_os_table = {
'linux': 'Linux'
}

# TODO: only include 'shell' for interactive sessions, not beacons

new_payload = MythicRPCPayloadCreateFromScratchMessage(
# TODO: this may need some mythic improvements
TaskID=1,

PayloadConfiguration=MythicRPCPayloadConfiguration(
payload_type="sliverimplant",
uuid=event.Session.ID,
selected_os=sliver_os_table[event.Session.OS],
description=f"(no download) using sliver interactive implant for {event.Session.ID}",
build_parameters=[],
c2_profiles=[],
# TODO: figure out if possible to not specify these manually
commands=['ifconfig', 'download', 'upload', 'ls', 'ps', 'ping', 'whoami', 'screenshot', 'netstat', 'getgid', 'getuid', 'getpid', 'cat', 'cd', 'pwd', 'info', 'execute', 'mkdir', 'shell', 'terminate', 'rm']
),
)
scratchBuild = await SendMythicRPCPayloadCreateFromScratch(new_payload)

# create callback
extra_info = json.dumps({
# TODO: if buildparams changes, then this won't work anymore (could make it more resilient)
"slivercfg_fileid": configFileId,
"type": 'session'
})
response = await SendMythicRPCCallbackCreate(MythicRPCCallbackCreateMessage(
PayloadUUID=event.Session.ID,

C2ProfileName="",
IntegrityLevel=3,
Host=event.Session.Hostname,
User=event.Session.Username,
Ip=event.Session.RemoteAddress.split(':')[0],
ExtraInfo=extra_info,
PID=event.Session.PID
))

if (event.EventType == 'session-disconnected'):
# TODO: often hard-coding ID=1 cause not sure how else to get results back...
# This thread isn't running on behalf of a specific callback
# Could potentially pass down the CallbackID of the instantiated sliverapi callback
# All the way from the parent function that called this?
# it works for now tho........
callbacks = await SendMythicRPCCallbackSearch(MythicRPCCallbackSearchMessage(
AgentCallbackID=1,
SearchCallbackPID=event.Session.PID
))

await SendMythicRPCCallbackUpdate(MythicRPCCallbackUpdateMessage(
CallbackID=callbacks.Results[0].ID,
TaskID=1,
PID=event.Session.PID,

Description='disconnected!'
))
2 changes: 0 additions & 2 deletions Payload_Type/sliverapi/sliverapi/agent_functions/armory.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,9 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa

# Flags:
# ======
# TODO: -h, --help display help
# TODO: -c, --ignore-cache ignore metadata cache, force refresh
# TODO: -I, --insecure skip tls certificate validation
# TODO: -p, --proxy string specify a proxy url (e.g. http://localhost:8080)
# TODO: -t, --timeout string download timeout (default: 15m)

# Sub Commands:
# =============
Expand Down
2 changes: 0 additions & 2 deletions Payload_Type/sliverapi/sliverapi/agent_functions/beacons.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,8 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa
# TODO: -f, --filter string filter beacons by substring
# TODO: -e, --filter-re string filter beacons by regular expression
# TODO: -F, --force force killing of the beacon
# TODO: -h, --help display help
# TODO: -k, --kill string kill a beacon
# TODO: -K, --kill-all kill all beacons
# TODO: -t, --timeout int command timeout in seconds (default: 60)

# Sub Commands:
# =============
Expand Down
50 changes: 50 additions & 0 deletions Payload_Type/sliverapi/sliverapi/agent_functions/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,53 @@
from mythic_container.PayloadBuilder import *
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
from ..SliverRequests import SliverAPI

from sliver import SliverClientConfig, SliverClient

import asyncio


async def setupApiThreads():
# print('setup api threads')

sliverapi_payloads = await SendMythicRPCPayloadSearch(MythicRPCPayloadSearchMessage(
CallbackID=1,
PayloadTypes=['sliverapi']
))

for sliverapi_payload in sliverapi_payloads.Payloads:
# print('got payload to setup')
client = await SliverAPI.create_sliver_client_with_config(sliverapi_payload.UUID, sliverapi_payload.BuildParameters[0].Value)

# TODO: could further improve here by looking for sessions that now exist (that were created while the Mythic service was offline)
# Create those payloads and callbacks
# Would fit the usecase of connecting mythic to an already existing sliver operation with lots of callbacks

# sessions = await client.sessions()
# for session in sessions:
# # if payload uuid doesn't exist, create it and then create the callback?
# sliverimplant_payloads = await SendMythicRPCPayloadSearch(MythicRPCPayloadSearchMessage(
# CallbackID=1,
# PayloadUUID=session.ID
# ))
# if (len(sliverimplant_payloads.Payloads) == 0):
# create the payload and callback associated and thread?

# TODO: better name for this
initial_thread_to_handle_api_events = None

class SliverApi(PayloadType):
# TODO: understand why this fires off twice
def __init__(self, **kwargs):
super().__init__(**kwargs)

# This class is instantiated during start_and_run_forever, as well as when payloads are generated
# In this case, I only want this to run setupApiThreads when the service first starts up
global initial_thread_to_handle_api_events
if (initial_thread_to_handle_api_events == None):
initial_thread_to_handle_api_events = asyncio.create_task(setupApiThreads())

name = "sliverapi"
author = "Spencer Adolph"
note = """This payload connects to sliver to run meta commands."""
Expand Down Expand Up @@ -42,6 +86,12 @@ async def build(self) -> BuildResponse:
IntegrityLevel=3,
ExtraInfo=self.uuid,
))

# TODO: fail building if callback already exists for this sliver config?

# doing this will cache the connection and start to read events
await SliverAPI.create_sliver_client_with_config(self.uuid, self.build_parameters[0].value)

if not create_callback.Success:
logger.info(create_callback.Error)
else:
Expand Down
7 changes: 0 additions & 7 deletions Payload_Type/sliverapi/sliverapi/agent_functions/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,10 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa
# External builders allow the Sliver server offload implant builds onto external machines.
# For more information: https://github.com/BishopFox/sliver/wiki/External-Builders


# Usage:
# ======
# builders [flags]

# Flags:
# ======
# TODO: -h, --help display help
# TODO: -t, --timeout int command timeout in seconds (default: 60)


response = await builders(taskData)

await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage(
Expand Down
4 changes: 2 additions & 2 deletions Payload_Type/sliverapi/sliverapi/agent_functions/canaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa
# Flags:
# ======
# TODO: -b, --burned show only triggered/burned canaries
# TODO: -h, --help display help
# TODO: -t, --timeout int command timeout in seconds (default: 60)
# -h, --help display help
# -t, --timeout int command timeout in seconds (default: 60)

response = await canaries(taskData)

Expand Down
4 changes: 2 additions & 2 deletions Payload_Type/sliverapi/sliverapi/agent_functions/cursed.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa

# Flags:
# ======
# TODO: -h, --help display help
# TODO: -t, --timeout int command timeout in seconds (default: 60)
# -h, --help display help
# -t, --timeout int command timeout in seconds (default: 60)

# Sub Commands:
# =============
Expand Down
10 changes: 6 additions & 4 deletions Payload_Type/sliverapi/sliverapi/agent_functions/dns.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa
# ======
# TODO: -D, --disable-otp disable otp authentication
# TODO: -d, --domains string parent domain(s) to use for DNS c2
# TODO: -h, --help display help
# -h, --help display help
# TODO: -L, --lhost string interface to bind server to
# TODO: -l, --lport int udp listen port (default: 53)
# TODO: -c, --no-canaries disable dns canary detection
# TODO: -p, --persistent make persistent across restarts
# TODO: -t, --timeout int command timeout in seconds (default: 60)
# -t, --timeout int command timeout in seconds (default: 60)

response = await dns(taskData)

Expand All @@ -63,8 +63,10 @@ async def process_response(self, task: PTTaskMessageAllData, response: any) -> P


async def dns(taskData: PTTaskMessageAllData):
# client = await SliverAPI.create_sliver_client(taskData)
client = await SliverAPI.create_sliver_client(taskData)

dns_results = await client.start_dns_listener(domains=['1.example.com.'], host='192.168.17.129')

# TODO: match sliver formatting

return "This command not yet implemented..."
return f"{dns_results}"
53 changes: 50 additions & 3 deletions Payload_Type/sliverapi/sliverapi/agent_functions/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,58 @@ class Generate(CommandBase):
attackmapping = []

async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllData) -> MythicCommandBase.PTTaskCreateTaskingMessageResponse:
# TODO: paste all the config options here
# Command: generate <options>
# About: Generate a new sliver binary and saves the output to the cwd or a path specified with --save.

# Flags:
# ======
# TODO: -a, --arch string cpu architecture (default: amd64)
# TODO: -c, --canary string canary domain(s)
# TODO: -d, --debug enable debug features
# TODO: -O, --debug-file string path to debug output
# TODO: -G, --disable-sgn disable shikata ga nai shellcode encoder
# TODO: -n, --dns string dns connection strings
# TODO: -e, --evasion enable evasion features (e.g. overwrite user space hooks)
# TODO: -E, --external-builder use an external builder
# TODO: -f, --format string Specifies the output formats, valid values are: 'exe', 'shared' (for dynamic libraries), 'service' (see `psexec` for more info) and 'shellcode' (windows only) (default: exe)
# TODO: -b, --http string http(s) connection strings
# TODO: -X, --key-exchange int wg key-exchange port (default: 1337)
# TODO: -w, --limit-datetime string limit execution to before datetime
# TODO: -x, --limit-domainjoined limit execution to domain joined machines
# TODO: -F, --limit-fileexists string limit execution to hosts with this file in the filesystem
# TODO: -z, --limit-hostname string limit execution to specified hostname
# TODO: -L, --limit-locale string limit execution to hosts that match this locale
# TODO: -y, --limit-username string limit execution to specified username
# TODO: -k, --max-errors int max number of connection errors (default: 1000)
# TODO: -m, --mtls string mtls connection strings
# TODO: -N, --name string agent name
# TODO: -p, --named-pipe string named-pipe connection strings
# TODO: -o, --os string operating system (default: windows)
# TODO: -P, --poll-timeout int long poll request timeout (default: 360)
# TODO: -j, --reconnect int attempt to reconnect every n second(s) (default: 60)
# TODO: -R, --run-at-load run the implant entrypoint from DllMain/Constructor (shared library only)
# TODO: -l, --skip-symbols skip symbol obfuscation
# TODO: -Z, --strategy string specify a connection strategy (r = random, rd = random domain, s = sequential)
# TODO: -T, --tcp-comms int wg c2 comms port (default: 8888)
# TODO: -i, --tcp-pivot string tcp-pivot connection strings
# TODO: -I, --template string implant code template (default: sliver)
# TODO: -g, --wg string wg connection strings

# Beacon specific things
# TODO: -D, --days int beacon interval days (default: 0)
# TODO: -H, --hours int beacon interval hours (default: 0)
# TODO: -J, --jitter int beacon interval jitter in seconds (default: 30)
# TODO: -M, --minutes int beacon interval minutes (default: 0)
# TODO: -S, --seconds int beacon interval seconds (default: 60)

# Sub Commands:
# =============
# TODO: beacon Generate a beacon binary
# TODO: info Get information about the server's compiler
# TODO: stager Generate a stager using Metasploit (requires local Metasploit installation)

os = taskData.args.get_arg('os')
mtls = taskData.args.get_arg('mtls')

sliverconfig_file_uuid = taskData.BuildParameters[0].Value

sliver_os_table = {
Expand All @@ -73,7 +120,7 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa
),
MythicRPCPayloadConfigurationBuildParameter(
name='mtls',
value=mtls
value=f"mtls://{mtls}"
),
],
C2Profiles=[],
Expand Down
4 changes: 2 additions & 2 deletions Payload_Type/sliverapi/sliverapi/agent_functions/hosts.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa

# Flags:
# ======
# TODO: -h, --help display help
# TODO: -t, --timeout int command timeout in seconds (default: 60)
# -h, --help display help
# -t, --timeout int command timeout in seconds (default: 60)

# Sub Commands:
# =============
Expand Down
4 changes: 2 additions & 2 deletions Payload_Type/sliverapi/sliverapi/agent_functions/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa
# ======
# TODO: -D, --disable-otp disable otp authentication
# TODO: -d, --domain string limit responses to specific domain
# TODO: -h, --help display help
# -h, --help display help
# TODO: -L, --lhost string interface to bind server to
# TODO: -J, --long-poll-jitter string server-side long poll jitter (default: 2s)
# TODO: -T, --long-poll-timeout string server-side long poll timeout (default: 1s)
# TODO: -l, --lport int tcp listen port (default: 80)
# TODO: -p, --persistent make persistent across restarts
# TODO: -t, --timeout int command timeout in seconds (default: 60)
# -t, --timeout int command timeout in seconds (default: 60)
# TODO: -w, --website string website name (see websites cmd)

response = await http(taskData)
Expand Down
4 changes: 2 additions & 2 deletions Payload_Type/sliverapi/sliverapi/agent_functions/https.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa
# TODO: -D, --disable-otp disable otp authentication
# TODO: -E, --disable-randomized-jarm disable randomized jarm fingerprints
# TODO: -d, --domain string limit responses to specific domain
# TODO: -h, --help display help
# -h, --help display help
# TODO: -k, --key string PEM encoded private key file
# TODO: -e, --lets-encrypt attempt to provision a let's encrypt certificate
# TODO: -L, --lhost string interface to bind server to
# TODO: -J, --long-poll-jitter string server-side long poll jitter (default: 2s)
# TODO: -T, --long-poll-timeout string server-side long poll timeout (default: 1s)
# TODO: -l, --lport int tcp listen port (default: 443)
# TODO: -p, --persistent make persistent across restarts
# TODO: -t, --timeout int command timeout in seconds (default: 60)
# -t, --timeout int command timeout in seconds (default: 60)
# TODO: -w, --website string website name (see websites cmd)

response = await https(taskData)
Expand Down
4 changes: 2 additions & 2 deletions Payload_Type/sliverapi/sliverapi/agent_functions/implants.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa
# ======
# TODO: -a, --arch string filter builds by cpu architecture
# TODO: -f, --format string filter builds by artifact format
# TODO: -h, --help display help
# -h, --help display help
# TODO: -d, --no-debug filter builds by debug flag
# TODO: -b, --only-beacons filter beacons
# TODO: -s, --only-sessions filter interactive sessions
# TODO: -o, --os string filter builds by operating system
# TODO: -t, --timeout int command timeout in seconds (default: 60)
# -t, --timeout int command timeout in seconds (default: 60)

# Sub Commands:
# =============
Expand Down
Loading

0 comments on commit 5ffa4cc

Please sign in to comment.