agentName/mythic
are now in a PyPi package (mythic-payloadtype-container
) hosted at (https://github.com/MythicMeta/Mythic_PayloadType_Container). Specifically, your agentName/mythic
folder will now look like:
itsafeaturemythic
repo, update to the latest:
mythic_payloadtype_container==0.0.45
PyPi package.
debug=True
.
requirements.txt
file so it’s easy to see what software is needed if you want to create your own Docker image or turn a VM into the Mythic “container” for your agent.
For agents, the requirements.txt is as follows:
mythic-payloadtype-container
version of 0.0.45
corresponds to container version 9. Mythic now tracks the range of supported versions for all of the containers that connect up to it. This makes it easier to determine if you’ve updated mythic but don’t have updated containers or visa versa.
rabbitmq_config.json
file OR via environment variables of the same names, but prefixed with MYTHIC_
. So, if your rabbitmq instance isn’t on the same host as your payload type container (or VM), you could either:
host
key to the ip of your rabbitmq instance
MYTHIC_HOST
environment variable to the ip of your rabbitmq instance
agentName/mythic/agent_functions/
(typically called builder.py) has some updates.
Replace your previous import:
PayloadType
- mythic_encrypts and translation_container.
Translation Container
section, but your agent can now handle its own encryption/decryption if it wants. To signal this to Mythic, add mythic_encrypts = False
in the same set of parameters where you declare the agent name, extension, and author.
Translation Container
section, but if you have one for your payload type, declare it here with the name of the container. For example, if my translation container will be called “translator”, then I’d add translation_container = "translator"
to the same set of parameters where you declare the agent name, extension, and author.
There’s a pre-existing Docker container you can use for this, FROM itsafeaturemythic/python38_translator_container:0.0.3
or you can look at the corresponding GitHub repo (https://github.com/MythicMeta/Mythic_Docker_Templates/tree/master/Docker_Translation_base_files) to create your own.
Translation containers, just like the other containers, have a simple mythic_service.py
script to kick them off which consists of:
self.c2info
) where each entry is related to one of the c2 profiles that the user is trying to add to your payload. For each of those entries you can still call the get_c2profile()
function to get information about the profile and get_parameters.dict().items()
to iterate over each key,value pair of parameter values. This is where the change happens:
Crypto
When doing crypto with Mythic, there used to be a lot of hard coded components, such as needing a parameter name to be exactly AESPSK
. Now, that is no longer the case. C2 Profiles can declare any parameter to be a crypto related one simply by putting crypto=True
in that C2 Profile’s python definition file. As the payload creator though, what this means for you is that you don’t just get a single base64 string back. The point here is to make crypto more modular and expansive, so you might get matching encryption/decryption keys, you might get a public key and a private key, you might get blank values to use for plaintext, etc. To suppor this, if a C2 profile parameter has crypto=True
, then the value
you get when iterating will be a dictionary
. This dictionary has three components:
http
profile, the crypto parameter gives the user a choice to select aes256_hmac
or none
. One of those two values will be the value
, then there will either be None
or the base64 of an aes256 key for the enc_key
and dec_key
components. You can then use these values however you need inside of your agent.
Arrays / Dictionaries
C2 Profiles can also provide a parameter type of dictionary
which, when creating the agent, allows the operator to provide Key-Value pairs. This is useful for things like specifying Header values for HTTP (User-Agent, Host, etc). Instead of trying to deal with nested data structures for arrays vs single values in these dictionaries, the end result is an array of tuples. So, when creating your agent and looping through C2 parameter values, you might get an array
of key-value pairs.
Example
Let’s look at what this stuff all means with an example. For the apfell
agent, stamping in C2 profile parameter values used to look like this:
http
Profile’s headers
and we have the new crypto components, let’s see what the section of code now looks like:
dict
which means we’re looking at crypto information, we check if the value is a str
which is the normal other data, and if it’s neither of those, then it’s the array of dictionaires for our header values. You can of course also check if the key
matches the name of the C2 Profile Parameter values (AESPSK
and headers
) and do your check that way too.
BuildResponse
object:
myBuildResp.build_message = "congrats, new agent created"
or myBuildResp.set_build_message("congrats, new agent created")
.
myBuildResp.build_stderr = "compile error here"
or myBuildResp.set_build_stderr("compile error here")
.
myBuildResp.build_stdout = "additional debugging info here"
or myBuildResp.set_build_stdout("additional debugging info here")
. )
message
instead of build_message
, so you will have to change that one.agentName/mythic/agent_functions/commandName.py
. This section will use Apfell’s shell
command as an example.
from CommandBase import *
needs to change to from mythic_payloadtype_container.MythicCommandBase import *
. Secondly, if you were importing various files for RPC, such as from MythicResponseRPC import *
, those have all been collapsed into a single file now, so you’d need to import from mythic_payloadtype_container.MythicRPC import *
.
ui_position
attribute you can set to order your arguments in a specific way
name
attribute does NOT have to match the value of the args key. The name
attribute is what’s presented to the user for the short name of the parameter, the description
is what’s displayed when the user hovers over that short name, and the dictionary key that’s associated with the CommandParameter
object overall is what’s used when sending information down to the agent. Let’s take an example:
Select a File
with a file selection button. If they hovered their mouse over Select a File
, they’d see the description, file to upload
, and when the data goes down to the agent, the agent will get {"file": "uuid here"}
. When interacting with these kinds of mis-matched names from the create_tasking
function, you want to reference the dictionary key value, not the name
value (i.e. task.args.get_arg("file")
).
ChooseOne
or ChooseMultiple
, choices is an array of choices for the user. If your command needs you to pick from the set of commands (rather than a static set of values), then there are a few other components that come into play. If you want the user to be able to select any command for this payload type, then set choices_are_all_commands
to True. Alternatively, you could specify that you only want the user to choose from commands that are already loaded into the callback, then you’d set choices_are_loaded_commands
to True. As a modifier to either of these, you can set choice_filter_by_command_attributes
to filter down the options presented to the user even more based on the parameters of the Command’s attributes parameter. This would allow you to limit the user’s list down to commands that are loaded into the current callback that support MacOS for example. An example of this would be:
TaskArgments
class and a CommandBase
class, there’s a CommandOPSEC
class now that you can implement for your commands. This will expand over time, but for now there are a few things you can do:
injection_method
, process_creation
, and authentication
which all are free-form text fields that you can set to help describe what all your command might be doing on host that’s an OPSEC consideration
opsec_pre
and opsec_post
functions. This is more detailed, so let’s take the next few sections to walk through an example.
opsec_class = ShellOPSEC
(but the name of your Subclass) to the same area where you have cmd
, needs_admin
, and description
in your command class.
async def opsec_pre(self, task: MythicTask)
if implemented, is called before a task’s create_tasking
function. The point of this function is to do some operational security pre-flight tests before passing execution on to your create_tasking
function.
Let’s take an example - you’re operating in an environment and are about to run a command that does spawn and inject. Before you do that command, you want to query what you know so far about the environment to see if there are any EDR products running that might alert on that activity. So, before even passing execution to the create_tasking
function, in the opsec_pre
function, you can use RPC calls back to Mythic to query information. In our example, let’s query the Process
table to see if we have any process data for the host where our task is running. We can do this with a simple RPC call:
processes
now has a response object back from Mythic that holds some information:
Success
or Error
for the RPC call overall
MythicStatus.Error
, then this is populated with the error message
Microsoft Defender
:
opsec_pre
, except it happens after the create_tasking
call. This is useful for when you’re generating artifacts as part of your tasking (such as generating new DLLs) and want to make sure they’re properly sanitized or obfuscated before allowing an agent to pick it up. All of the same components apply, it’s just opsec_post_*
.
attributes
component to Commands that describes non-opsec related attributes. Currently, this only includes two things: if a command is spawn_and_injectable and what kind of operating systems the command supports.
This looks like the following:
supported_os
attribute lists the same supported OS types as the Payload Type. Most of the time, these two will match up; however, if your agent can compile to multiple different operating systems, this is one way to make it so that during payload creation, the user can only see the commands that are associated with the kind of payload they’re trying to make. The spawn_and_injectable variable helps provide some quality of life to operators in case they try to inject a command like “exit” or “cd” into a remote process, which doesn’t really make sense.
is_exit
, is_process_list
, etc. That was fine initially, but doesn’t make it easy to expand. So, all of these are now grouped up into a new attribute called ui_features
in a way that’s more explicit about where a function will be referenced. These are now mapped as follows:
is_*
attributes are True
, then you don’t even need to supply the supported_ui_features
attribute.
This now allows us to go from:
create_tasking
function got a few updates as well. Firstly, all of the calls for RPC functionality has changed. It used to be the case where you had to know which RPC function you want to execute, you had to know which RPC file had it, and you had to know all of the parameters for it. This gets complicated fast, and there isn’t always a clear location for a function. For example, where would a search_database
function go?
To help with this, there is now a single RPC file, from mythic_payloadtype_container.MythicRPC import *
that has all of the functionality within it. Every function call will be of the form:
get_functions
function. More information and all of the current 2.2.2 functions can be found on the MythicRPC page:
display_params
by simply doing:
create_tasking
function returns, the commandline information that the operator sees is updated to this new custom value. If you select the status for the task though, you can select to “view all parameters” - this will create a new popup with information for three different stages of parameters:
process_response
function is now finally callable within the Command files. The point of this function is to have a custom, programmatic execution based on the output of a command. When reporting data back from your agent, in your post_response
array (the same place you’d set user_output
), you can specify process_response
with whatever data you want. This is then wrapped up with the task information into an AgentResponse
object with two attributes:
create_tasking
process_response
key.
create_tasking
has. Here we’re simply updating the callback’s sleep_info
with the result of the response. This allows us to programmatically update it based on if the agent was successful or not, without requiring Mythic to expose a custom response attribute for this field.
script_only=True
in your Command file and this will be exactly the case.
Commands that are marked as script_only=True
will NOT appear when you go to build a payload because these commands are transparent to your agent. They WILL appear in the type hints when you start typing commands though and they appear in the list of full available commands when you view metadata about a callback.
You might be wondering why this is useful? Consider the following: psexec
is a command you want to implement in your agent, but you implement this functionality manually rather than simply running the Microsoft psexec binary. You can create a script_only
psexec
command that when a user types it, will spin of further sub-tasks to check that the computer is reachable, that the port is open, that you have access, copies over the file, creates the service, then cleans it all up. The psexec
command in that case is kind of like a conductor that controls all the other tasks, switches based on success/error for each sub-task, and ultimately accomplishes the task dynamically without needing the command itself to be a compiled specific task in your agent.