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.get_commandline()
Want to know if an arg is in your args?
task.args.has_arg("key")
RPC Functionality
This additional functionality is broken out into separate files that you can import at the top of your Python command file. For each of the below bullets, simply import like from MythicFileRPC import *
MythicC2RPC
call_c2_func
- Call custom functions within C2 Docker containers
MythicCryptoRPC
encrypt_bytes
- Ask Mythic to encrypt a series of bytes with the current callback's encryption informationdecrypt_bytes
- Ask Mythic to decrypt a series of bytes with the current callback's decryption information
MythicFileRPC
register_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_by_name
- 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.
MythicPayloadRPC
get_payload_by_uuid
- 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.register_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.build_payload_from_template
- 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_by_uuid
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.build_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.
MythicResponseRPC
user_output
- Add a new response that the user can see.update_callback
- Update a piece of information about the current callback.register_artifact
- Register a new artifact to be tracked within the current operation.
MythicSocksRPC
start_socks
- Ask Mythic to start a new socks instance for this callback on a specific portstop_socks
- Ask Mythic to stop the socks instance associated with this callback.
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 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 if the command_line is <= 0 or doesn't start with a "{", then we know we're not looking at a JSON string, so raise an error.
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), 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 down have the base64 of the file contents though, so if you look at
task.args.get_arg("file")
, you'll get the base64 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 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 base64 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