Skip to content

Commit

Permalink
Merge pull request #144 from MythicAgents/Mythic3.3
Browse files Browse the repository at this point in the history
Mythic3.3
  • Loading branch information
its-a-feature authored Aug 28, 2024
2 parents 4083ca6 + 6cd5532 commit ad99f2d
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 11 deletions.
2 changes: 1 addition & 1 deletion Payload_Type/apollo/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ RUN apt-get update && apt-get install python3 python3-pip python3.11-venv -y

WORKDIR /Mythic/
RUN python3 -m venv /venv
RUN /venv/bin/python -m pip install mythic-container==0.4.10
RUN /venv/bin/python -m pip install mythic-container==0.5.9
RUN /venv/bin/python -m pip install donut-shellcode
RUN /venv/bin/python -m pip install mslex

Expand Down
54 changes: 49 additions & 5 deletions Payload_Type/apollo/apollo/mythic/agent_functions/builder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import datetime
import time

from mythic_container.PayloadBuilder import *
from mythic_container.MythicCommandBase import *
import os, fnmatch, tempfile, sys, asyncio
from distutils.dir_util import copy_tree
from mythic_container.MythicGoRPC.send_mythic_rpc_callback_next_checkin_range import *
import traceback
import shutil
import json
Expand All @@ -26,9 +30,9 @@ class Apollo(PayloadType):
supports_dynamic_loading = True
build_parameters = [
BuildParameter(
name = "output_type",
name="output_type",
parameter_type=BuildParameterType.ChooseOne,
choices=[ "WinExe", "Shellcode"],
choices=["WinExe", "Shellcode"],
default_value="WinExe",
description="Output as shellcode, executable, or dynamically loaded library.",
)
Expand Down Expand Up @@ -106,7 +110,7 @@ async def build(self) -> BuildResponse:
for csFile in get_csharp_files(agent_build_path.name):
templateFile = open(csFile, "rb").read().decode()
templateFile = templateFile.replace("#define C2PROFILE_NAME_UPPER", "\n".join(defines_profiles_upper))
templateFile = templateFile.replace("#define COMMAND_NAME_UPPER", "\n".join(defines_commands_upper) )
templateFile = templateFile.replace("#define COMMAND_NAME_UPPER", "\n".join(defines_commands_upper))
for specialFile in special_files_map.keys():
if csFile.endswith(specialFile):
for key, val in special_files_map[specialFile].items():
Expand Down Expand Up @@ -176,13 +180,15 @@ async def build(self) -> BuildResponse:
shellcode_path = "{}/loader.bin".format(agent_build_path.name)
donutPath = os.path.abspath(self.agent_code_path / "donut")
command = "chmod 777 {}; chmod +x {}".format(donutPath, donutPath)
proc = await asyncio.create_subprocess_shell(command, stdout=asyncio.subprocess.PIPE, stderr= asyncio.subprocess.PIPE)
proc = await asyncio.create_subprocess_shell(command, stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
stdout, stderr = await proc.communicate()

command = "{} -f 1 {}".format(donutPath, output_path)
# need to go through one more step to turn our exe into shellcode
proc = await asyncio.create_subprocess_shell(command, stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE, cwd=agent_build_path.name)
stderr=asyncio.subprocess.PIPE,
cwd=agent_build_path.name)
stdout, stderr = await proc.communicate()

stdout_err += f'[stdout]\n{stdout.decode()}\n'
Expand Down Expand Up @@ -228,6 +234,44 @@ async def build(self) -> BuildResponse:
resp.build_message = "Error building payload: " + str(traceback.format_exc())
return resp

async def check_if_callbacks_alive(self,
message: PTCheckIfCallbacksAliveMessage) -> PTCheckIfCallbacksAliveMessageResponse:
response = PTCheckIfCallbacksAliveMessageResponse(Success=True)
for callback in message.Callbacks:
if callback.SleepInfo == "":
continue # can't do anything if we don't know the expected sleep info of the agent
try:
sleep_info = json.loads(callback.SleepInfo)
except Exception as e:
continue
atLeastOneCallbackWithinRange = False
try:
for activeC2, info in sleep_info.items():
if activeC2 == "websocket" and callback.LastCheckin == "1970-01-01 00:00:00Z":
atLeastOneCallbackWithinRange = True
continue
checkinRangeResponse = await SendMythicRPCCallbackNextCheckinRange(
MythicRPCCallbackNextCheckinRangeMessage(
LastCheckin=callback.LastCheckin,
SleepJitter=info["jitter"],
SleepInterval=info["interval"],
))
if not checkinRangeResponse.Success:
continue
lastCheckin = datetime.datetime.strptime(callback.LastCheckin, '%Y-%m-%dT%H:%M:%S.%fZ')
minCheckin = datetime.datetime.strptime(checkinRangeResponse.Min, '%Y-%m-%dT%H:%M:%S.%fZ')
maxCheckin = datetime.datetime.strptime(checkinRangeResponse.Max, '%Y-%m-%dT%H:%M:%S.%fZ')
if minCheckin <= lastCheckin <= maxCheckin:
atLeastOneCallbackWithinRange = True
response.Callbacks.append(PTCallbacksToCheckResponse(
ID=callback.ID,
Alive=atLeastOneCallbackWithinRange,
))
except Exception as e:
logger.info(e)
logger.info(callback.to_json())
return response


def get_csharp_files(base_path: str) -> list[str]:
results = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ def __init__(self, command_line, **kwargs):
name="credential",
cli_name="Credential",
display_name="Credential",
type=ParameterType.Credential_JSON)
type=ParameterType.Credential_JSON,
limit_credentials_by_type=["plaintext"]
)
]

async def parse_arguments(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class MimikatzCommand(CommandBase):
needs_admin = False
help_cmd = "mimikatz [command1] [command2] [...]"
description = "Execute one or more mimikatz commands (e.g. `mimikatz coffee sekurlsa::logonpasswords`)."
version = 2
version = 3
author = "@djhohnstein"
argument_class = MimikatzArguments
attackmapping = [
Expand Down
3 changes: 2 additions & 1 deletion Payload_Type/apollo/apollo/mythic/agent_functions/pth.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(self, command_line, **kwargs):
display_name="Credential",
type=ParameterType.Credential_JSON,
description="Saved credential of the user to impersonate (either an NTLM hash or AES key).",
limit_credentials_by_type=["hash"],
parameter_group_info=[
ParameterGroupInfo(
ui_position=1, required=True, group_name="Credential"
Expand Down Expand Up @@ -225,7 +226,7 @@ class PthCommand(CommandBase):
description = (
"Spawn a new process using the specified domain user's credential material."
)
version = 3
version = 4
author = "@djhohnstein"
argument_class = PthArguments
attackmapping = ["T1550"]
Expand Down
4 changes: 2 additions & 2 deletions agent_capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"payload_output": ["exe", "shellcode"],
"architectures": ["x86_64"],
"c2": ["http", "smb", "tcp", "websocket"],
"mythic_version": "3.2",
"agent_version": "2.2.5",
"mythic_version": "3.3.0",
"agent_version": "2.2.13",
"supported_wrappers": ["service_wrapper", "scarecrow_wrapper"]
}

0 comments on commit ad99f2d

Please sign in to comment.