1.0 Payload Definition
Payload Type information must be set and pulled from a definition either in Python or in GoLang. Below are basic examples in Python and GoLang:- Python
- Go
- line 6 defines the new class (our agent). This can be called whatever you want, but the important piece is that it extends the
PayloadType
class as shown with the()
. - the rest defines the parameters for the payload type that you’d see throughout the UI.
- the name is the name of the payload type
- supported_os is an array of supported OS versions
- supports_dynamic_loading indicates if the agent allows you to select only a subset of commands when creating an agent or not
- build_parameters is an array describing all of the build parameters when creating your agent
- c2_profiles is an array of c2 profile names that the agent supports
- c2_parameter_deviations allows you to modify the parameters of supported c2 profiles. In this case the apollo agent doesn’t support GET requests (only POST), so we mark the
http
profile’s GET parameters as unsupported. - the “translation container” is something we will talk about in another section, but this allows you to support your own, non-mythic message format, custom crypto, etc.
- The last piece is the function that’s called to build the agent based on all of the information the user provides from the web UI.
PayloadType
base class is in the PayloadBuilder.py
file. This is an abstract class, so your instance needs to provide values for all these fields.1.1 Wrapper Payloads
A quick note about wrapper payload types - there’s only a few differences between a wrapper payload type and a normal payload type. A configuration variable,agent_type = AgentType.Wrapper
, determines if something is a wrapper or not. A wrapper payload type takes as input the output of a previous build (normal payload type or wrapper payload type) along with build parameters and generates a new payload. A wrapper payload type does NOT have any c2 profiles associated with it because it’s simply wrapping an existing payload.
An easy example is thinking of the service_wrapper
- this wrapper payload type takes in the shellcode version of another payload and “wraps” it in the execution of a service so that it’ll properly respond to the service control manager on windows. A similar example would be to take an agent and wrap it in an MSBuild format. These things don’t have their own C2, but rather just package/wrap an existing agent into a new, more generic, format.
- Python
- Go
To access the payload that you’re going to wrap, use the
self.wrapped_payload
attribute during your build
execution. This will be the base64 encoded version of the payload you’re going to wrap.2.0 Build Parameters
Build parameters define the components shown to the user when creating a payload. TheBuildParameter
class has a couple of pieces of information that you can use to customize and validate the parameters supplied to your build:
- Python
- Go
name
is the name of the parameter, if you don’t provide a longer description, then this is what’s presented to the user when building your payloadparameter_type
describes what is presented to the user - valid types are:BuildParameterType.String
- During build, this is a string
BuildParameterType.ChooseOne
- During build, this is a string
BuildParameterType.ChooseOneCustom
- During build, this is a string
BuildParameterType.ChooseMultiple
- During build, this is an array of strings
BuildParameterType.Array
- During build, this is an array of strings
BuildParameterType.Date
- During build, this is a string of the format
YYYY-MM-DD
- During build, this is a string of the format
BuildParameterType.Dictionary
- During build, this is a dictionary
BuildParameterType.Boolean
- During build, this is a boolean
BuildParameterType.File
- During build, this is a string UUID of the file (so that you can use a MythicRPC call to fetch the contents of the file)
BuildParameterType.TypedArray
- During build, this is an arrray of arrays, always in the format
[ [ type, value], [type value], [type, value] ...]
- During build, this is an arrray of arrays, always in the format
required
indicates if there must be a value supplied. If no value is supplied by the user and no default value supplied here, then an exception is thrown before execution gets to thebuild
functionverifier_regex
is a regex the web UI can use to provide some information to the user about if they’re providing a valid value or notdefault_value
is the default value used for building if the user doesn’t supply anythingchoices
is where you can supply an array of options for the user to pick from if the parameter_type is ChooseOnedictionary_choice
s are the choices and metadata about what to display to the user for key-value pairs that the user might need to supplyvalue
is the component you access when building your payload - this is the final value (either the default value or the value the user supplied)verifier_func
is a function you can provide for additional checks on the value the user supplies to make sure it’s what you want. This function should either return nothing or raise an exception if something isn’t right
3.0 Building
You have to implement thebuild
function and return an instance of the BuildResponse
class. This response has these fields:
status
- an instance of BuildStatus (Success or Error)- Specifically,
BuildStatus.Success
orBuildStatus.Error
- Specifically,
payload
- the raw bytes of the finished payload (if you failed to build, set this toNone
or empty bytes likeb''
in Python.build_message
- any stdout data you want the user to seebuild_stderr
- any stderr data you want the user to seebuild_stdout
- any stdout data you want the user to seeupdated_filename
- if you want to update the filename to something more appropriate, set it here- For example: the user supplied a filename of
apollo.exe
but based on the build parameters, you’re actually generating a dll, so you can update the filename to beapollo.dll
. This is particularly useful if you’re optionally returning a zip of information so that the user doesn’t have to change the filename before downloading. If you plan on doing this to update the filename for a wide variety of options, then it might be best to leave the file extension field in your payload type definition blank""
so that you can more easily adjust the extension.
- For example: the user supplied a filename of
updated_command_list
- if you want to adjust the list of commands that are included by default in the payload - this is helpful if you need to include extra commands or dependencies based on what the user selected. The most basic version of the build function would be:
- Python
- Go
build
function is called, all of your BuildParameters
will already be verified (all parameters marked as required
will have a value
of some form (user supplied or default_value) and all of the verifier functions will be called if they exist). This allows you to know that by the time your build
function is called that all of your parameters are valid.
Your build function gets a few pieces of information to help you build the agent (other than the build parameters):
From within your build function, you’ll have access to the following pieces of information:
- Python
- Go
self.uuid
- the UUID associated with your payload- This is how your payload identifies itself to Mythic before getting a new Staging and final Callback UUID
self.commands
- a wrapper class around the names of all the commands the user selected.- Access this list via
self.commands.get_commands()
- Access this list via
self.agent_code_path
- apathlib.Path
object pointing to the path of theagent_code
directory that holds all the code for your payload. This is something you pre-define as part of your agent definition.- To access “test.js” in that “agent_code” folder, simply do:
f = open(self.agent_code_path / "test.js", 'r')
. - With
pathlib.Path
objects, the/
operator allows you to concatenate paths in an OS agnostic manner. This is the recommended way to access files so that your code can work anywhere.
- To access “test.js” in that “agent_code” folder, simply do:
self.get_parameter("parameter name here")
- The build parameters that are validated from the user. If you have a build_parameter with a name of “version”, you can access the user supplied or default value with
self.get_parameter("version")
- The build parameters that are validated from the user. If you have a build_parameter with a name of “version”, you can access the user supplied or default value with
self.selected_os
- This is the OS that was selected on the first step of creating a payloadself.c2info
- this holds a list of dictionaries of the c2 parameters and c2 class information supplied by the user. This is a list because the user can select multiple c2 profiles (maybe they want HTTP and SMB in the payload for example). For each element in self.c2info, you can access the information about the c2 profile withget_c2profile()
and access to the parameters viaget_parameters_dict()
. Both of these return a dictionary of key-value pairs.- the dictionary returned by
self.c2info[0].get_c2profile()
contains the following:name
- name of the c2 profiledescription
- description of the profileis_p2p
- boolean of if the profile is marked as a p2p profile or not
- the dictionary returned by
self.c2info[0].get_parameters_dict()
contains the following:key
- value- where each
key
is thekey
value defined for the c2 profile’s parameters andvalue
is what the user supplied. You might be wondering where to get these keys? Well, it’s not too crazy and you can view them right in the UI - Name Fields. - If the C2 parameter has a value of
crypto_type=True
, then the “value” here will be a bit more than just a string that the user supplied. Instead, it’ll be a dictionary with three pieces of information:value
- the value that the user supplied,enc_key
- a base64 string (or None) of the encryption key to be used,dec_key
- a base64 string (or None) of the decryption key to be used. This gives you more flexibility in automatically generating encryption/decryption keys and supporting crypto types/schemas that Mythic isn’t aware of. In the HTTP profile, the keyAESPSK
has this type set to True, so you’d expect that dictionary. - If the C2 parameter has a type of “Dictionary”, then things are a little different.
- Let’s take the “headers” parameter in the
http
profile for example. This allows you to set header values for yourhttp
traffic such as User-Agent, Host, and more. When you get this value on the agent side, you get an array of values that look like the following:
{"User-Agent": "the user agent the user supplied", "MyCustomHeader": "my custom value"}
. You get the final “dictionary” that’s created from the user supplied fields.
- Let’s take the “headers” parameter in the
- where each
- the dictionary returned by
- One way to leverage this could be:
When building your payload, if you have to modify files on disk, then it’s helpful to do this in a “copy” of the files. You can make a temporary copy of your code and operate there with the following sample:
3.1 Build Steps
The last thing to mention are build steps. These are defined as part of the agent and are simply descriptions of what is happening during your build process. The above example makes some RPC calls forSendMythicRPCPayloadUpdatebuildStep
to update the build steps back to Mythic while the build process is happening. For something as fast as the apfell
agent, it’ll appear as though all of these happen at the same time. For something that’s more computationally intensive though, it’s helpful to provide information back to the user about what’s going on - stamping in values? obfuscating? compiling? more obfuscation? opsec checks? etc. Whatever it is that’s going on, you can provide this data back to the operator complete with stdout and stderr.
3.2 Execution flow
So, what’s the actual, end-to-end execution flow that goes on? A diagram can be found here: #what-happens-for-building-payloads.- PayloadType container is started, it connects to Mythic and sends over its data (by parsing all these python files or GoLang structs)
- An operator wants to create a payload from it, so they click the hazard icon at the top of Mythic, click the “Actions” dropdown and select “Generate New Payload”.
- The operator selects an OS type that the agent supports (ex. Linux, macOS, Windows)
- The operator selects the payload type they want to build (this one)
- edits all build parameters as needed
- The operator selects all commands they want included in the payload
- The operator selects all c2 profiles they want included
- and for each c2 selected, provides any c2 required parameters
- Mythic takes all of this information and sends it to the payload type container
- The container sends the
BuildResponse
message back to the Mythic server.
4.0 On New Callback
Starting with Mythic v3.2.12, PyPi version 0.4.1, and MythicContainer version 1.3.1, there’s a new function you can define as part of your Payload Type definition. In addition to defining abuild
process, you can also define a on_new_callback
(or onNewCallbackFunction
) function that will get executed whenever there’s a new callback based on this payload type.
Below are examples in Python and in Golang for how to define and leverage this new functionality. One of the great things about this is that you can use this to automatically issue tasking for new callbacks. The below examples will automatically issue a shell
command with parameters of whoami
.
These function calls get almost all the same data that you’ll see in your Create Tasking calls, except they’re missing information about a Task
. That’s simply because there’s no task yet, this is the moment that a new callback is created.
Mythic tracks an operator for all issued tasking. Since there’s no operator directly typing out and issuing these tasks, Mythic associates the operator that built the payload with any tasks automatically created in this function.
- Python
- Go
5.0 CustomRPCFunctions
Payload types have an optional field that can be defined:- Python
- Go