Skip to content

Commit 89d303c

Browse files
committed
updating for forge support and fixing an upload bug
1 parent a0ffbfb commit 89d303c

File tree

7 files changed

+238
-60
lines changed

7 files changed

+238
-60
lines changed

Payload_Type/apollo/CHANGELOG.MD

+9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [v2.2.25] - 2025-01-30
8+
9+
### Changed
10+
11+
- Fixed a bug with upload if remote_path wasn't specified causing regex to break
12+
- Updated execute_coff to optionally take in a file at execution time to support forge
13+
- Updated execute_assembly to optionally take in a file at execution time to support forge
14+
- Updated inline_assembly to optionally take in a file at execution time to support forge
15+
716
## [v2.2.24] - 2025-01-08
817

918
### Changed

Payload_Type/apollo/apollo/mythic/agent_functions/builder.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class Apollo(PayloadType):
2121
supported_os = [
2222
SupportedOS.Windows
2323
]
24-
version = "2.2.24"
24+
version = "2.2.25"
2525
wrapper = False
2626
wrapped_payloads = ["scarecrow_wrapper", "service_wrapper"]
2727
note = """

Payload_Type/apollo/apollo/mythic/agent_functions/execute_assembly.py

+56-12
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ def __init__(self, command_line, **kwargs):
3333
)
3434
],
3535
),
36+
CommandParameter(
37+
name="assembly_file",
38+
display_name="New Assembly",
39+
type=ParameterType.File,
40+
description="A new assembly to execute. After uploading once, you can just supply the assembly_name parameter",
41+
parameter_group_info=[
42+
ParameterGroupInfo(
43+
required=True, group_name="New Assembly", ui_position=1,
44+
)
45+
]
46+
),
3647
CommandParameter(
3748
name="assembly_arguments",
3849
cli_name="Arguments",
@@ -43,6 +54,9 @@ def __init__(self, command_line, **kwargs):
4354
ParameterGroupInfo(
4455
required=False, group_name="Default", ui_position=2
4556
),
57+
ParameterGroupInfo(
58+
required=False, group_name="New Assembly", ui_position=2
59+
),
4660
],
4761
),
4862
]
@@ -91,7 +105,7 @@ class ExecuteAssemblyCommand(CommandBase):
91105
cmd = "execute_assembly"
92106
needs_admin = False
93107
help_cmd = "execute_assembly [Assembly.exe] [args]"
94-
description = "Executes a .NET assembly with the specified arguments. This assembly must first be known by the agent using the `register_assembly` command."
108+
description = "Executes a .NET assembly with the specified arguments. This assembly must first be known by the agent using the `register_assembly` command or by supplying an assembly with the task."
95109
version = 3
96110
author = "@djhohnstein"
97111
argument_class = ExecuteAssemblyArguments
@@ -134,6 +148,47 @@ async def create_go_tasking(
134148
Success=True,
135149
)
136150
global EXEECUTE_ASSEMBLY_PATH
151+
originalGroupNameIsDefault = taskData.args.get_parameter_group_name() == "Default"
152+
if taskData.args.get_parameter_group_name() == "New Assembly":
153+
fileSearchResp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
154+
TaskID=taskData.Task.ID,
155+
AgentFileID=taskData.args.get_arg("assembly_file")
156+
))
157+
if not fileSearchResp.Success:
158+
raise Exception(f"Failed to find uploaded file: {fileSearchResp.Error}")
159+
if len(fileSearchResp.Files) == 0:
160+
raise Exception(f"Failed to find matching file, was it deleted?")
161+
searchedTaskResp = await SendMythicRPCTaskSearch(MythicRPCTaskSearchMessage(
162+
TaskID=taskData.Task.ID,
163+
SearchCallbackID=taskData.Callback.ID,
164+
SearchCommandNames=["register_file"],
165+
SearchParams=taskData.args.get_arg("assembly_file")
166+
))
167+
if not searchedTaskResp.Success:
168+
raise Exception(f"Failed to search for matching tasks: {searchedTaskResp.Error}")
169+
if len(searchedTaskResp.Tasks) == 0:
170+
# we need to register this file with apollo first
171+
subtaskCreationResp = await SendMythicRPCTaskCreateSubtask(MythicRPCTaskCreateSubtaskMessage(
172+
TaskID=taskData.Task.ID,
173+
CommandName="register_file",
174+
Params=json.dumps({"file": taskData.args.get_arg("assembly_file")})
175+
))
176+
if not subtaskCreationResp.Success:
177+
raise Exception(f"Failed to create register_file subtask: {subtaskCreationResp.Error}")
178+
179+
taskData.args.add_arg("assembly_name", fileSearchResp.Files[0].Filename)
180+
taskData.args.remove_arg("assembly_file")
181+
182+
taskargs = taskData.args.get_arg("assembly_arguments")
183+
if originalGroupNameIsDefault:
184+
if taskargs == "" or taskargs is None:
185+
response.DisplayParams = "-Assembly {}".format(
186+
taskData.args.get_arg("assembly_name")
187+
)
188+
else:
189+
response.DisplayParams = "-Assembly {} -Arguments {}".format(
190+
taskData.args.get_arg("assembly_name"), taskargs
191+
)
137192
taskData.args.add_arg("pipe_name", str(uuid4()))
138193
if not path.exists(EXEECUTE_ASSEMBLY_PATH):
139194
# create
@@ -153,17 +208,6 @@ async def create_go_tasking(
153208
raise Exception(
154209
"Failed to register execute_assembly binary: " + file_resp.Error
155210
)
156-
157-
taskargs = taskData.args.get_arg("assembly_arguments")
158-
if taskargs == "" or taskargs is None:
159-
response.DisplayParams = "-Assembly {}".format(
160-
taskData.args.get_arg("assembly_name")
161-
)
162-
else:
163-
response.DisplayParams = "-Assembly {} -Arguments {}".format(
164-
taskData.args.get_arg("assembly_name"), taskargs
165-
)
166-
167211
return response
168212

169213
async def process_response(

Payload_Type/apollo/apollo/mythic/agent_functions/execute_coff.py

+106-29
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import asyncio
99
import platform
1010

11-
if platform.system() == 'Windows':
11+
if platform.system() == 'Windows':
1212
RUNOF_HOST_PATH= "C:\\Mythic\\Apollo\\srv\\RunOF.dll"
1313
else:
1414
RUNOF_HOST_PATH= "/srv/RunOF.dll"
@@ -34,6 +34,17 @@ def __init__(self, command_line, **kwargs):
3434
ui_position=1
3535
)
3636
]),
37+
CommandParameter(
38+
name="bof_file",
39+
display_name="New Bof",
40+
type=ParameterType.File,
41+
description="A new bof to execute. After uploading once, you can just supply the coff_name parameter",
42+
parameter_group_info=[
43+
ParameterGroupInfo(
44+
required=True, group_name="New", ui_position=1,
45+
)
46+
]
47+
),
3748
CommandParameter(
3849
name="function_name",
3950
cli_name="Function",
@@ -47,6 +58,11 @@ def __init__(self, command_line, **kwargs):
4758
group_name="Default",
4859
ui_position=2
4960
),
61+
ParameterGroupInfo(
62+
required=False,
63+
group_name="New",
64+
ui_position=2
65+
),
5066
]),
5167
CommandParameter(
5268
name="timeout",
@@ -61,6 +77,11 @@ def __init__(self, command_line, **kwargs):
6177
group_name="Default",
6278
ui_position=3
6379
),
80+
ParameterGroupInfo(
81+
required=False,
82+
group_name="New",
83+
ui_position=3
84+
),
6485
]),
6586
CommandParameter(
6687
name="coff_arguments",
@@ -82,11 +103,15 @@ def __init__(self, command_line, **kwargs):
82103
group_name="Default",
83104
ui_position=4
84105
),
106+
ParameterGroupInfo(
107+
required=False,
108+
group_name="New",
109+
ui_position=4
110+
),
85111
]),
86112
]
87113

88114
async def get_arguments(self, arguments: PTRPCTypedArrayParseFunctionMessage) -> PTRPCTypedArrayParseFunctionMessageResponse:
89-
argumentResponse = PTRPCTypedArrayParseFunctionMessageResponse(Success=True)
90115
argumentSplitArray = []
91116
for argValue in arguments.InputArray:
92117
argSplitResult = argValue.split(" ")
@@ -98,18 +123,19 @@ async def get_arguments(self, arguments: PTRPCTypedArrayParseFunctionMessage) ->
98123
value = value.strip("\'").strip("\"")
99124
if argType == "":
100125
pass
101-
elif argType == "int16" or argType == "-s":
126+
elif argType == "int16" or argType == "-s" or argType == "s":
102127
coff_arguments.append(["int16",int(value)])
103-
elif argType == "int32" or argType == "-i":
128+
elif argType == "int32" or argType == "-i" or argType == "i":
104129
coff_arguments.append(["int32",int(value)])
105-
elif argType == "string" or argType == "-z":
130+
elif argType == "string" or argType == "-z" or argType == "z":
106131
coff_arguments.append(["string",value])
107-
elif argType == "wchar" or argType == "-Z":
132+
elif argType == "wchar" or argType == "-Z" or argType == "Z":
108133
coff_arguments.append(["wchar",value])
109-
elif argType == "base64" or argType == "-b":
134+
elif argType == "base64" or argType == "-b" or argType == "b":
110135
coff_arguments.append(["base64",value])
111136
else:
112-
return PTRPCTypedArrayParseFunctionMessageResponse(Success=False, Error=f"Failed to parse argument: {argument}: Unknown value type.")
137+
return PTRPCTypedArrayParseFunctionMessageResponse(Success=False,
138+
Error=f"Failed to parse argument: {argument}: Unknown value type.")
113139

114140
argumentResponse = PTRPCTypedArrayParseFunctionMessageResponse(Success=True, TypedArray=coff_arguments)
115141
return argumentResponse
@@ -177,7 +203,7 @@ async def registered_runof(self, taskData: PTTaskMessageAllData) -> str:
177203
fileSearch = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
178204
TaskID=taskData.Task.ID,
179205
Filename="RunOF.dll",
180-
LimitByCallback=True,
206+
LimitByCallback=False,
181207
MaxResults=1
182208
))
183209
if not fileSearch.Success:
@@ -190,7 +216,7 @@ async def registered_runof(self, taskData: PTTaskMessageAllData) -> str:
190216
Filename="RunOF.dll",
191217
IsScreenshot=False,
192218
IsDownloadFromAgent=False,
193-
Comment=f"Shared RunOF.dll for all execute_coff tasks within Callback {taskData.Callback.DisplayID}"
219+
Comment=f"Shared RunOF.dll for all execute_coff tasks within apollo"
194220
))
195221
if fileRegister.Success:
196222
return fileRegister.AgentFileId
@@ -199,33 +225,84 @@ async def registered_runof(self, taskData: PTTaskMessageAllData) -> str:
199225

200226
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
201227
response = PTTaskCreateTaskingMessageResponse( TaskID=taskData.Task.ID, Success=True)
228+
originalGroupNameIsDefault = taskData.args.get_parameter_group_name() == "Default"
229+
if taskData.args.get_parameter_group_name() == "New":
230+
fileSearchResp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
231+
TaskID=taskData.Task.ID,
232+
AgentFileID=taskData.args.get_arg("bof_file")
233+
))
234+
if not fileSearchResp.Success:
235+
raise Exception(f"Failed to find uploaded file: {fileSearchResp.Error}")
236+
if len(fileSearchResp.Files) == 0:
237+
raise Exception(f"Failed to find matching file, was it deleted?")
238+
searchedTaskResp = await SendMythicRPCTaskSearch(MythicRPCTaskSearchMessage(
239+
TaskID=taskData.Task.ID,
240+
SearchCallbackID=taskData.Callback.ID,
241+
SearchCommandNames=["register_file"],
242+
SearchParams=taskData.args.get_arg("bof_file")
243+
))
244+
if not searchedTaskResp.Success:
245+
raise Exception(f"Failed to search for matching tasks: {searchedTaskResp.Error}")
246+
if len(searchedTaskResp.Tasks) == 0:
247+
# we need to register this file with apollo first
248+
subtaskCreationResp = await SendMythicRPCTaskCreateSubtask(MythicRPCTaskCreateSubtaskMessage(
249+
TaskID=taskData.Task.ID,
250+
CommandName="register_coff",
251+
Params=json.dumps({"file": taskData.args.get_arg("bof_file")})
252+
))
253+
if not subtaskCreationResp.Success:
254+
raise Exception(f"Failed to create register_file subtask: {subtaskCreationResp.Error}")
255+
256+
taskData.args.add_arg("coff_name", fileSearchResp.Files[0].Filename)
257+
taskData.args.add_arg("loader_stub_id", taskData.args.get_arg("bof_file"))
258+
taskData.args.remove_arg("bof_file")
259+
else:
260+
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(TaskID=taskData.Task.ID, Filename=taskData.args.get_arg("coff_name")))
261+
if file_resp.Success and len(file_resp.Files) > 0:
262+
taskData.args.add_arg("loader_stub_id", file_resp.Files[0].AgentFileId)
263+
else:
264+
raise Exception("Failed to fetch uploaded file from Mythic (ID: {})".format(taskData.args.get_arg("coff_name")))
202265
registered_runof_id = await self.registered_runof(taskData)
203266
taskData.args.add_arg("runof_id", registered_runof_id)
204-
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(TaskID=taskData.Task.ID, Filename=taskData.args.get_arg("coff_name")))
205-
if file_resp.Success and len(file_resp.Files) > 0:
206-
taskData.args.add_arg("loader_stub_id", file_resp.Files[0].AgentFileId)
207-
else:
208-
raise Exception("Failed to fetch uploaded file from Mythic (ID: {})".format(taskData.args.get_arg("coff_name")))
209-
210267
timeout = taskData.args.get_arg("timeout")
211268
if timeout is None or timeout == "":
212269
taskData.args.set_arg("timeout", "30")
213270

214271
taskargs = taskData.args.get_arg("coff_arguments")
215-
if taskargs == "" or taskargs is None:
216-
response.DisplayParams = "-Coff {} -Function {} -Timeout {}".format(
217-
taskData.args.get_arg("coff_name"),
218-
taskData.args.get_arg("function_name"),
219-
taskData.args.get_arg("timeout")
220-
)
221-
else:
222-
response.DisplayParams = "-Coff {} -Function {} -Timeout {} -Arguments {}".format(
223-
taskData.args.get_arg("coff_name"),
224-
taskData.args.get_arg("function_name"),
225-
taskData.args.get_arg("timeout"),
226-
taskargs
227-
)
272+
if originalGroupNameIsDefault:
273+
if taskargs == "" or taskargs is None:
274+
response.DisplayParams = "-Coff {} -Function {} -Timeout {}".format(
275+
taskData.args.get_arg("coff_name"),
276+
taskData.args.get_arg("function_name"),
277+
taskData.args.get_arg("timeout")
278+
)
279+
else:
280+
argsString = ""
281+
normalizedArgs = []
282+
for argEntry in taskargs:
283+
if argEntry[0] in ['s', 'int16']:
284+
argsString += f"-Arguments {argEntry[0]}:{argEntry[1]} "
285+
normalizedArgs.append(['s', argEntry[1]])
286+
elif argEntry[0] in ['i', 'int32']:
287+
argsString += f"-Arguments {argEntry[0]}:{argEntry[1]} "
288+
normalizedArgs.append(['i', argEntry[1]])
289+
elif argEntry[0] in ['z', 'string']:
290+
argsString += f"-Arguments {argEntry[0]}:\"{argEntry[1]}\" "
291+
normalizedArgs.append(['z', argEntry[1]])
292+
elif argEntry[0] in ['Z', 'wchar']:
293+
argsString += f"-Arguments {argEntry[0]}:\"{argEntry[1]}\" "
294+
normalizedArgs.append(['Z', argEntry[1]])
295+
else:
296+
argsString += f"-Arguments {argEntry[0]}:\"{argEntry[1]}\" "
297+
normalizedArgs.append(['b', argEntry[1]])
298+
taskData.args.set_arg("coff_arguments", normalizedArgs)
228299

300+
response.DisplayParams = "-Coff {} -Function {} -Timeout {} {}".format(
301+
taskData.args.get_arg("coff_name"),
302+
taskData.args.get_arg("function_name"),
303+
taskData.args.get_arg("timeout"),
304+
argsString
305+
)
229306
return response
230307

231308
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:

0 commit comments

Comments
 (0)