Create_Tasking
Manipulate tasking before it's sent to the agent
create_tasking
All commands must have a create_tasking function with a base case like:
When an operator types a command in the UI, whatever the operator types (or whatever is populated based on the popup modal) gets sent to this function after the input is parsed and validated by the TaskArguments and CommandParameters functions mentioned in Commands.
It's here that the operator has full control of the task before it gets sent down to an agent. The task is currently in the "preprocessing" stage when this function is executed and allows you to do many things via Remote Procedure Calls (RPC) back to the Mythic server.
Available Context
So, from this create_tasking function, what information do you immediately have available?
task.task_id
- the task number associated with this task (not the UUID the agent sees, the integer value that operators see)task.agent_task_id
- the UUID associated with this task that an agent seestask.original_params
- the string of original parameters that was passed to this function before anything was swapped out for default values or edited.task.completed
- boolean indicating if the task is marked as completed or nottask.operator
- the name of the operator that issued the tasktask.status
- the current status of the task (defaults to MythicStatus.Success).Options are
MythicStatus.Error, MythicStatus.Completed
,MythicStatus.Processed
,MythicStatus.Processing
If you set the status to
Success
, then Mythic will set the task toSubmitted
so that it's ready to be picked up by the agent. If you set it to error, completed, processed, or processing, then the agent won't pick it up.
task.callback
- information about the task's associated callback. This has a LOT of information, so let's break it down more:task.callback.build_parameters
- this is a dictionary of all the build parameters used to generate the payload that the callback is based on. This is the same sort of information from Payload Type Info. So, if you had a build parameter called "version", you could see that associated value withtask.callback.build_parameters.['version']
task.callback.c2info
- this is an array of dictionaries of the different c2 profiles built into the callback. Just like Payload Type Info where you iterate over them, you can do the same here. For example, to see a parameter called "callback_host" in our first c2 profile istask.callback.c2info[0]['callback_host']
.task.callback.user
is the user for the callbacktask.callback.host
is the hostname of the callback... These match up with all the fields used for doing the initial callback - Action: Checkin
Something to note about
integrity_level
values - these are based on Windows standard integrity levels where 2 == normal account, 3 == high integrity (orroot
), and 4 == SYSTEM (but in most cases for operators, 3 and 4 are the same).
task.args
- access to the associated arguments class for this command that already has all of the values populated and validated. Let's say you have an argument called "remote_path", you can access it viatask.args.get_arg("remote_path")
.Want to change the value of that to something else?
task.args.add_arg("remote_path", "new value")
.Want to change the value of that to a different type as well?
task.args.add_arg("remote_path", 5, ParameterType.Number)
Want to add a new argument entirely for this specific instance as part of the JSON response?
task.args.add_arg("new key", "new value")
. Theadd_arg
functionality will overwrite the value if the key exists, otherwise it'll add a new key with that value. The default ParameterType for args isParameterType.String
, so if you're adding something else, be sure to change the type.You can also remove args
task.args.remove_arg("key")
, rename argstask.args.rename_arg("old key", "new key")
You can also get access to the user's commandline as well via
task.args.commandline
Want to know if an arg is in your args?
task.args.has_arg("key")
task.token
- information about the token that was used with the task. This requires that the callback has at some point returned tokens for Mythic to track, otherwise this will be NULL.task.opsec_*
- these opsec* components come into play before and after thecreate_tasking
function is actually called. These are broken out into two main pieces -opsec_pre_*
andopsec_post_*
. Each one tracks:_blocked
- indicates if the corresponding opsec checking function blocked the operator_message
- the associated message after running the corresponding opsec checking function_bypassed
- if a user bypassed the check anyway_bypass_role
- who is allowed to bypass the check, any operator or just the lead_bypass_user
- the user who ended up bypassing the check
task.set_stdout(string)
- Allows you to provide some task-specific standard output that you can then query and view at a later time through the Mythic UI. This helps if you need to go back and debug a task.task.set_stderr(string)
- Allows you to provide some task-specific standard error output that you can then query and view at a later time through the Mythic UI. This helps if you need to go back and debug a task.task.display_params
- you can set this value to a string that you'd want the user to see instead of thetask.original_params
. This allows you to leverage the JSON structure of the popup modals for processing, but return a more human-friendly version of the parameters for operators to view. There's a new menu-item in the UI when viewing a task that you can select to view all the parameters, so on a case-by-case basis an operator can view the original JSON parameters that were sent down, but this provides a nice way to prevent large JSON blobs that are hard to read for operators while still preserving the nice JSON scripting features on the back-end.
RPC Functionality
This additional functionality is broken out into a MythicRPC file that you can import at the top of your Python command file. You can pull the current set of available functions, their prototypes, their args, and their docstrings by executing the get_functions
method:
Once you have that set of functions, they are all callable via the same specific format:
And they all return a ReponseRPC object with a few attributes:
All RPC Functions
All RPC functions are available in the MythicRPC page, but some of them are called out below from a higher level standpoint on how you might use them:
create_file
- Register a file in the Mythic database and get back a file UUID. This UUID allows an agent to chunk files as it sends them from Mythic -> agent and allows the file to be tracked by Mythic.get_file
- Search the current operation for a file that hasn't been deleted with the current name, if it exists, return the contents of the file and all metadata about it. This allows you to provide short-hand names on the command-line for an operator, but still translate that into a file UUID that the agent/Mythic can track.delete_file_browser
- Mark the files in thefiles
parameter as "removed" from the file browser. Thefiles
parameter takes in the same format as in File Browsercreate_file_browser
- add files to the file browser. This takes in the same format of information as would be returned from File Browserget_payload
- Search the current operator for a payload with a specific UUID, if it exists, return it and the contents of the associated file. If there's a command parameter type of "Payload" and the user selects something you don't want or the Payload Type is right, but there's a parameter used that's incompatible with the tasking, this is where you're able to find that information.create_payload_on_host
- Register a payload's UUID with a specific host. This informs Mythic that somehow, a specific payload is on a specific host. This allows Mythic to do some back-end auto tabulation and make more complete dialogs when operators then try to link to specific payloads.create_payload_from_uuid
- Given a payload's UUID, ask Mythic to kick off the creation of a new payload based on the parameters and information associated with the given UUID. Since payload creation is an asynchronous task with potentially other Docker containers, there's a bit of a catch here. When doing this, you will get back the UUID of the new payload. You have to then loop within your tasking function to repeatedly call theget_payload
function to see when the creation process is done (successfully or if it errored). When creating the payload, if you specify the additional parameter ofdestination_host
, then Mythic will track that the newly created payload exists on that host. This will allow you to automatically populate payloads for doing P2P connections.create_payload_from_parameters
- Task Mythic to make a new payload from explicit parameters. When creating the payload, if you specify the additional parameter ofdestination_host
, then Mythic will track that the newly created payload exists on that host. This will allow you to automatically populate payloads for doing P2P connections.This includes the payload type, list of commands, build parameters, etc.
The C2 profile list takes a specific format:
[{ "c2_profile": "HTTP", "c2_profile_parameters": { "param_name": value, "param": value} }]
The build parameter list also takes a specific format:
[ {"name": "param name", "value": "param value"} ]
build_payload_from_MythicPayloadRPCResponse
- If you got information about a payload via theget_payload_by_uuid
and wanted to make a new payload from that, but with a few adjustments, then this function allows you to do just that. The following is an example of getting a payload based on a template, modifying a c2 parameter value, and tasking a build. When creating the payload, if you specify the additional parameter ofdestination_host
, then Mythic will track that the newly created payload exists on that host. This will allow you to automatically populate payloads for doing P2P connections.There are two helper functions on
MythicPayloadRPCResponses
for adjusting a few key parameters:set_profile_parameter_value
- give the name of a c2 profile, parameter name, and a new value - this function makes that replacement or addition.set_build_parameter_value
- give the name of a build parameter name and value - this function makes the replacement or addition.
create_output
- Add a new response that the user can see.update_callback
- Update a piece of information about the current callback.create_artifact
- Register a new artifact to be tracked within the current operation.create_token
- Register Windows Token information on a host.delete_token
- Mark Windows Token as deletedcreate_logon_session
- Register Windows Logon Session information on a hostdelete_logon_session
- Mark Windows Logon Session as deletedcreate_callback_token
- Register Windows Tokens to be associated with your callback. This allows you to then select those tokens when issuing tasking, assuming your Payload Type supports that level of tracking.delete_callback_token
- Unregister a windows token with your callbackcreate_processes
- Register new processes on a host. This will feed into the unified callback listing in the user interface.create_process
- Register a single new processget_tasks
- Get the tasks and their associated token information for all tasks that haven't completed yet on a host.create_keylog
- Register keystrokes with Mythiccreate_credential
- Register new credentials with Mythicsearch_database
- Specify a table and use regex matches to search for specific values in specific columns. This allows you to search for specific process names on a host for example. This currently only supports searching for process information but will be expanding going forward.control_socks
- Ask Mythic to start a new socks instance/stop an existing instance for this callback on a specific port
There can only be one socks instance per callback
Example - Upload to Target
The above example takes in a file from the operator and wants to upload it to a specific remote path on the target. In the UploadArguments
class, we define two main arguments in self.args
, one for the file and one for the remote path. By default, all arguments are required
. In the parse_arguments
function we expect to get a JSON string of our file and arguments, so we parse it as so. If it's not actually JSON, that function will throw an error and it'll be echoed back to the operator.
When it comes to the create_tasking
function, we want to register this file in the Mythic database so we can track that the file exists and is being uploaded to a specific remote target. This helps us track artifacts. Taking this exact case an example, when you upload files, 2 things happen:
the
task.original_params
(this is what the user sees initially), we'll have the file swapped out with the name of the file they uploaded. So, if we uploaded a file called "bug.jpg", then an example of thetask.original_params
would be:{"file": "bug.jpg", "remote_path": "/blah/evil.jpg"}
.The actual parameters that get sent to this function have the raw file contents though, so if you look at
task.args.get_arg("file")
, you'll get the raw bytes of thebug.jpg
file. This is meant simply as a way to capture the name of the file as well as preventing files from cluttering up the UI for operators. When we send information down to Mythic to track the file, we need to send the base64 encoded contents of the file.
When we register the file, we want to track the original name of the file as well, this is why we load the original parameters and get the "file" value (which is the name of the file).
Once the file registration is done, we want the agent to be able to pull this down in chunks, so we swap out the raw contents of the file with a file UUID instead (remember, if you do task.args.add_arg
for an argument that already exists, it just replaces the value).
Last updated