Sub-tasking / Task Callbacks
Last updated
Last updated
Sub-tasking is the ability for a task to spin off sub-tasks and wait for them to finish before continuing execution of its own. Tasks will wait for all of their sub-tasks to complete before potentially entering a "submitted" state themselves for an agent to pick them up.
When a task has outstanding subtasks, its status will change to "delegating" while it waits for them all to finish.
Task callbacks are functions that get executed when a task enters a "completed=True" state (i.e. when it completes successfully or encounters an error). These can be registered on a task itself
or on a subtask:
Like everything else associated with a Command, all of this information is stored in your command's Python file. Sub-tasks are created via RPC functions from within your command's create_tasking
function (or any other function - i.e. you can issue more sub-tasks from within task callback functions). Let's look at an example below: this is an example of a shell
tasking that checks if the user supplied the parameters of whoami
, if so, it spins off two sub-tasks (each with their own callback functions).
When issuing a subtask, we can simply do:
When supplying the subtask_callbackfunction
for a subtask, you need to supply the NAME of the function. You can either just supply it as a string "shell_subtask_completed"
or you can use Python's build in __name__
capability to pull the name automatically from the path to a function. The function MUST EXIST IN THE SAME CLASS AS YOUR CREATE_TASKING.
This means that our shell
command will spin off a subtask of pwd
with no parameters. When that ask completes, the function called shell_subtask_completed
is executed. This function gets two parameters:
an instance of the shell task (this is the same context with all the same information as you'd get from your create_tasking
function.
a dictionary with the information about the subtask itself. This does NOT include that task's response output. This does NOT include that task's response output. The task could have a lot of output or it could simply register data within Mythic (like credentials, files, etc) and not really display anything to the user. So, if you want the actual content that was generated by the task, you need to do another RPC call. We can see this in the function.
This RPC call gets all of the response data about that subtask. This returns a dictionary with a few fields:
files - this is an array of dictionaries about all of the files uploaded/downloaded/registered with that task
artifacts - this is an array of dictionaries for all the artifacts generated with the task
credentials - this is an array of dictionaries for all the credentials stored from the task
user_output - this is an array of dictionaries for all of the output displayed to the user from the task
We just saw an example of how a task registered a callback function for itself for when its subtask enters a "completed" state. Tasks can also register completion functions for when their own task enters a completed state. So, we can see in the following example that the shell
task registers a completion function for when itself (not a subtask) is completed:
This is useful for when you want to do some post-task processing, actions, analysis, etc when a task completes or errors out. In this example, the shell_completed
task simply just displays a message to the user that the task is done. In more interesting examples though, you could use the get_responses
RPC call like we saw above to get information about all of the files that were downloaded from a task, then process them for potential credentials, interesting data, OCR, etc and provide that output back to the user.