Skip to content

Commit

Permalink
Merge pull request #1 from MythicAgents/dev
Browse files Browse the repository at this point in the history
Merge of added commands and refactors
  • Loading branch information
spenceradolph authored Apr 19, 2024
2 parents 7584285 + 5f5d022 commit fcc4c24
Show file tree
Hide file tree
Showing 70 changed files with 3,400 additions and 622 deletions.
203 changes: 8 additions & 195 deletions Payload_Type/sliverapi/sliverapi/SliverRequests/SliverAPI.py
Original file line number Diff line number Diff line change
@@ -1,214 +1,27 @@
from tabulate import tabulate
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import SendMythicRPCFileGetContent, MythicRPCFileGetContentMessage
from sliver import SliverClientConfig, SliverClient, client_pb2
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
from mythic_container.PayloadBuilder import *
import json


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}"]

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
))

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

# TODO: cache this (global dict?) - can verify in this function if need to re-create
await client.connect()

return client

async def sessions_list(taskData: PTTaskMessageAllData):
client = await create_sliver_client(taskData)
sessions = await client.sessions()

# This is the sliver formatting

# ID Transport Remote Address Hostname Username Operating System Health
# ========== =========== ====================== ========== ========== ================== =========
# 78c06ded mtls 192.168.17.129:51042 ubuntu root linux/amd64 [ALIVE]

# TODO: match sliver formatting
# what to show when no sessions?

headers = ["ID", "Transport", "Remote Address", "Hostname", "Username", "Operating System", "Health"]
data = [(session.ID, session.Transport, session.RemoteAddress, session.Hostname, session.Username, session.OS, "[DEAD]" if session.IsDead else "[ALIVE]") for session in sessions]
table = tabulate(data, headers=headers)

return table

async def profiles_list(taskData: PTTaskMessageAllData):
client = await create_sliver_client(taskData)
profiles = await client.implant_profiles()

# TODO: match sliver formatting
# show nothing if no profiles

return f"{profiles}"

async def beacons_list(taskData: PTTaskMessageAllData):
client = await create_sliver_client(taskData)
beacons = await client.beacons()

# TODO: match sliver formatting

# ID Name Transport Hostname Username Operating System Last Check-In Next Check-In
# ========== ============= =========== ========== ========== ================== =============== ===============
# d90a2ec6 DARK_MITTEN mtls ubuntu ubuntu linux/amd64 2s 1m4s

# What to show if no beacons?

return f"{beacons}"

async def implants_list(taskData: PTTaskMessageAllData):
client = await create_sliver_client(taskData)
implants = await client.implant_builds()

# This is the sliver formatting

# Name Implant Type Template OS/Arch Format Command & Control Debug
# ================ ============== ========== ============= ============ =============================== =======
# DARK_MITTEN beacon sliver linux/amd64 EXECUTABLE [1] mtls://192.168.17.129:443 false

# TODO: match sliver formatting
# how to show Template?
# implant.Format is ValueType?
# C2 only shows first URL
# What to show if no implants?

headers = ["Name", "Implant Type", "OS/Arch", "Command & Control", "Debug"]
data = [(implant.FileName, "beacon" if implant.IsBeacon else "session", f"{implant.GOOS}/{implant.GOARCH}", implant.C2[0].URL, implant.Debug) for implant in implants.values()]
table = tabulate(data, headers=headers)

return table

async def jobs_list(taskData: PTTaskMessageAllData):
client = await create_sliver_client(taskData)
jobs = await client.jobs()

# TODO: match sliver formatting

# ID Name Protocol Port Stage Profile
# ==== ====== ========== ====== ===============
# 1 mtls tcp 443

# [*] No active jobs

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

async def version(taskData: PTTaskMessageAllData):
client = await create_sliver_client(taskData)
version_results = await client.version()

# TODO: match sliver formatting

# [*] Client v1.5.42 - 85b0e870d05ec47184958dbcb871ddee2eb9e3df - linux/amd64
# Compiled at 2024-02-28 13:46:53 -0600 CST
# Compiled with go version go1.20.7 linux/amd64


# [*] Server v1.5.42 - 85b0e870d05ec47184958dbcb871ddee2eb9e3df - linux/amd64
# Compiled at 2024-02-28 13:46:53 -0600 CST

return f"{version_results}"

async def jobs_kill(taskData: PTTaskMessageAllData, job_id: int):
client = await create_sliver_client(taskData)
kill_response = await client.kill_job(job_id=job_id)

# TODO: match sliver formatting

# [*] Killing job #1 ...
# [!] Job #1 stopped (tcp/mtls)
# [*] Successfully killed job #1

return f"{kill_response}"

async def mtls_start(taskData: PTTaskMessageAllData, port: int):
client = await create_sliver_client(taskData)

mtls_start_result = await client.start_mtls_listener(
host = "0.0.0.0",
port = port,
persistent = False,
)

# TODO: match sliver formatting

# [*] Starting mTLS listener ...
# [*] Successfully started job #1

return f"{mtls_start_result}"

async def use(taskData: PTTaskMessageAllData, sliver_id: int):
client = await create_sliver_client(taskData)

beacon_info = await client.beacon_by_id(sliver_id)
session_info = await client.session_by_id(sliver_id)

if (not beacon_info and not session_info):
# TODO: throw error and catch in use.py, and handle sending mythic errors gracefully
# taskResponse = PTTaskCreateTaskingMessageResponse(
# TaskID=taskData.Task.ID,
# Success=False,
# Completed=True,
# Error="id not found in sliver",
# TaskStatus=f"[!] no session or beacon found with ID {sliver_id}",
# )
# return taskResponse
return f"[!] no session or beacon found with ID {sliver_id}"

# TODO: match sliver formatting
# [*] Active session FUNNY_DRIVEWAY (586a4bdf-ffaf-4136-8387-45cc983ecc0f)

isBeacon = beacon_info is not None
implant_info = beacon_info or session_info

# check if payload already exists, if so, skip to creating the callback
search = await SendMythicRPCPayloadSearch(MythicRPCPayloadSearchMessage(
PayloadUUID=sliver_id
))

if (len(search.Payloads) == 0):
# create the payload
# TODO: figure out mappings for windows or mac...
sliver_os_table = {
'linux': 'Linux'
}

new_payload = MythicRPCPayloadCreateFromScratchMessage(
TaskID=taskData.Task.ID,
PayloadConfiguration=MythicRPCPayloadConfiguration(
payload_type="sliverimplant",
uuid=sliver_id,
selected_os=sliver_os_table[implant_info.OS],
description=f"(no download) using sliver {'beaconing' if isBeacon else 'interactive'} implant for {sliver_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 the callback
extra_info = json.dumps({
# TODO: if buildparams changes, then this won't work anymore (could make it more resilient)
"slivercfg_fileid": taskData.BuildParameters[0].Value,
"type": 'beacon' if isBeacon else 'session'
})
response = await SendMythicRPCCallbackCreate(MythicRPCCallbackCreateMessage(
PayloadUUID=sliver_id,
C2ProfileName="",
IntegrityLevel=3,
Host=implant_info.Hostname,
User=implant_info.Username,
Ip=implant_info.RemoteAddress.split(':')[0],
ExtraInfo=extra_info,
PID=implant_info.PID
))

return f"[*] Active session FUNNY_DRIVEWAY ({sliver_id})"
return client
76 changes: 76 additions & 0 deletions Payload_Type/sliverapi/sliverapi/agent_functions/armory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from ..SliverRequests import SliverAPI

from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
from mythic_container.PayloadBuilder import *

# from sliver import common_pb2

class ArmoryArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = []

async def parse_arguments(self):
pass


class Armory(CommandBase):
cmd = "armory"
needs_admin = False
help_cmd = "armory"
description = "Automatically download and install extensions/aliases"
version = 1
author = "Spencer Adolph"
argument_class = ArmoryArguments
attackmapping = []

async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllData) -> MythicCommandBase.PTTaskCreateTaskingMessageResponse:
# Automatically download and install extensions/aliases

# Usage:
# ======
# armory [flags]

# 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:
# =============
# TODO: install Install an alias or extension
# TODO: search Search for aliases and extensions by name (regex)
# TODO: update Update installed an aliases and extensions

response = await armory(taskData)

await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage(
TaskID=taskData.Task.ID,
Response=response.encode("UTF8"),
))

taskResponse = MythicCommandBase.PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
Completed=True
)

return taskResponse

async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp


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

# armory_results = await client.armory()

# TODO: match sliver formatting

return "This command not yet implemented..."
17 changes: 16 additions & 1 deletion Payload_Type/sliverapi/sliverapi/agent_functions/beacons.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa


# 'beacons' with no options
response = await SliverAPI.beacons_list(taskData)
response = await beacons_list(taskData)

await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage(
TaskID=taskData.Task.ID,
Expand All @@ -66,3 +66,18 @@ async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllDa
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp

async def beacons_list(taskData: PTTaskMessageAllData):
client = await SliverAPI.create_sliver_client(taskData)
beacons = await client.beacons()

# TODO: match sliver formatting

# ID Name Transport Hostname Username Operating System Last Check-In Next Check-In
# ========== ============= =========== ========== ========== ================== =============== ===============
# d90a2ec6 DARK_MITTEN mtls ubuntu ubuntu linux/amd64 2s 1m4s

# What to show if no beacons?

return f"{beacons}"

71 changes: 71 additions & 0 deletions Payload_Type/sliverapi/sliverapi/agent_functions/builders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from ..SliverRequests import SliverAPI

from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
from mythic_container.PayloadBuilder import *


class BuildersArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = []

async def parse_arguments(self):
pass


class Builders(CommandBase):
cmd = "builders"
needs_admin = False
help_cmd = "builders"
description = "Lists external builders currently registered with the server."
version = 1
author = "Spencer Adolph"
argument_class = BuildersArguments
attackmapping = []

async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllData) -> MythicCommandBase.PTTaskCreateTaskingMessageResponse:
# Command: builders
# About: Lists external builders currently registered with the server.

# 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(
TaskID=taskData.Task.ID,
Response=response.encode("UTF8"),
))

taskResponse = MythicCommandBase.PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
Completed=True
)

return taskResponse

async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp


async def builders(taskData: PTTaskMessageAllData):
# client = await SliverAPI.create_sliver_client(taskData)
# client._stub.bu

# TODO: match sliver formatting

return "This command not yet implemented, requires re-build of gRPC (or sliver 1.6)"
Loading

0 comments on commit fcc4c24

Please sign in to comment.