Skip to content

Commit 95e07ac

Browse files
committed
v0.1.4
updated task_output fetching functions to account for subtasks
1 parent 4f8bbfa commit 95e07ac

3 files changed

Lines changed: 73 additions & 3 deletions

File tree

mythic/mythic.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1155,9 +1155,51 @@ async def waitfor_for_task_output(
11551155
final_output = b""
11561156
for output in aggregated_output:
11571157
final_output += base64.b64decode(output["response_text"])
1158+
subtaskIds = await get_all_subtask_ids(mythic=mythic, task_display_id=task_display_id, fetch_display_id_instead=True)
1159+
for subtask in subtaskIds:
1160+
subtaskOutput = await get_all_task_output_by_id(mythic=mythic, task_display_id=subtask)
1161+
for r in subtaskOutput:
1162+
final_output += base64.b64decode(r["response_text"])
11581163
return final_output
11591164

11601165

1166+
async def get_all_subtask_ids(mythic: mythic_classes.Mythic, task_display_id: int,
1167+
fetch_display_id_instead: bool) -> List[int]:
1168+
subtaskIds = []
1169+
idQuery = f"""
1170+
query taskIdFromDisplayID($task_display_id: Int!){{
1171+
task(where: {{display_id: {{_eq: $task_display_id}}}}){{
1172+
id
1173+
}}
1174+
}}
1175+
"""
1176+
subtaskQuery = """
1177+
query subtaskList($task_id: Int!){
1178+
task(where: {parent_task_id: {_eq: $task_id}}){
1179+
id
1180+
display_id
1181+
}
1182+
}
1183+
"""
1184+
initial = await mythic_utilities.graphql_post(
1185+
mythic=mythic, query=idQuery, variables={"task_display_id": task_display_id}
1186+
)
1187+
if initial["task"]:
1188+
taskIdsToCheck = [initial["task"][0]["id"]]
1189+
while len(taskIdsToCheck) > 0:
1190+
currentTaskId = taskIdsToCheck.pop()
1191+
subtasks = await mythic_utilities.graphql_post(
1192+
mythic=mythic, query=subtaskQuery, variables={"task_id": currentTaskId}
1193+
)
1194+
for t in subtasks["task"]:
1195+
taskIdsToCheck.append(t["id"])
1196+
if fetch_display_id_instead:
1197+
subtaskIds.append(t["display_id"])
1198+
else:
1199+
subtaskIds.append(t["id"])
1200+
return subtaskIds
1201+
1202+
11611203
async def get_all_task_output(
11621204
mythic: mythic_classes.Mythic, custom_return_attributes: str = None, batch_size: int = 10
11631205
) -> AsyncGenerator:
@@ -1208,6 +1250,33 @@ async def get_all_task_output_by_id(
12081250
return initial["response"]
12091251

12101252

1253+
async def get_all_task_and_subtask_output_by_id(
1254+
mythic: mythic_classes.Mythic, task_display_id: int, custom_return_attributes: str = None
1255+
) -> List[dict]:
1256+
"""
1257+
Execute a query to get all responses for a given task.
1258+
The default set of attributes returned in the dictionary can be found at graphql_queries.task_output_fragment.
1259+
If you want to use your own `custom_return_attributes` string to identify what information you want back, you have to include the `id` and `timestamp` fields, everything else is optional.
1260+
"""
1261+
query = f"""
1262+
query AllTaskResponses($task_display_id: Int!){{
1263+
response(order_by: {{id: asc}}, where: {{task:{{display_id: {{_eq: $task_display_id}}}}}}) {{
1264+
{custom_return_attributes if custom_return_attributes is not None else '...task_output_fragment'}
1265+
}}
1266+
}}
1267+
{graphql_queries.task_output_fragment if custom_return_attributes is None else ''}
1268+
"""
1269+
initial = await mythic_utilities.graphql_post(
1270+
mythic=mythic, query=query, variables={"task_display_id": task_display_id}
1271+
)
1272+
subtaskIds = await get_all_subtask_ids(mythic=mythic, task_display_id=task_display_id, fetch_display_id_instead=True)
1273+
for subtask in subtaskIds:
1274+
subtaskOutput = await get_all_task_output_by_id(mythic=mythic, task_display_id=subtask)
1275+
for r in subtaskOutput:
1276+
initial["response"].append(r)
1277+
return initial["response"]
1278+
1279+
12111280
async def subscribe_new_task_output(
12121281
mythic: mythic_classes.Mythic,
12131282
timeout: int = None,
@@ -2256,7 +2325,8 @@ async def create_saved_c2_instance(
22562325
}
22572326
"""
22582327
resp = await mythic_utilities.graphql_post(mythic=mythic, query=mutation, variables={
2259-
"instance_name": instance_name, "c2profile_id": resp["c2profile"][0]["id"], "c2_instance": json.dumps(c2_parameters)
2328+
"instance_name": instance_name, "c2profile_id": resp["c2profile"][0]["id"],
2329+
"c2_instance": json.dumps(c2_parameters)
22602330
})
22612331
return resp["create_c2_instance"]
22622332

mythic/mythic_classes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def __init__(
2626
self.http = "http://" if not ssl else "https://"
2727
self.ws = "ws://" if not ssl else "wss://"
2828
self.global_timeout = global_timeout if global_timeout is not None else -1
29-
self.scripting_version = "0.1.2"
29+
self.scripting_version = "0.1.4"
3030
self.current_operation_id = 0
3131
self.schema = schema
3232

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# This call to setup() does all the work
1212
setup(
1313
name="mythic",
14-
version="0.1.2",
14+
version="0.1.4",
1515
description="Interact with Mythic C2 Framework Instances",
1616
long_description=README,
1717
long_description_content_type="text/markdown",

0 commit comments

Comments
 (0)