1. Payload Type Development
This section describes new Payload Types
This section describes new Payload Types
You want to create a new agent that fully integrates with Mythic. Since everything in Mythic revolves around Docker containers, you will need to ultimately create one for your payload type. This can be done with docker containers on the same host as the Mythic server or with an external VM/host machine.
What does Mythic's setup look like? We'll use the diagram below - don't worry though, it looks complicated but isn't too bad really:
Mythic itself is a docker-compose
file that stands up a bunch of microservices. These services expose various pieces of functionality like a database (PostgreSQL
), the web UI (React Web UI
), internal documentation (Hugo Documentation
), and a way for the various services to communicate with each other (RabbitMQ
). Don't worry though, you don't worry about 99% of this.
Mythic by itself doesn't have any agents or command-and-control profiles - these all are their own Docker containers that connect up via RabbitMQ
and gRPC
. This is what you're going to create - a separate container that connects in (the far right hand side set of containers in the above diagram).
When you clone down the Mythic repo, run make
to generate the mythic-cli
binary, and then run sudo ./mythic-cli start
, you are creating a docker-compose
file automatically that references a bunch of Dockerfile
in the various folders within the Mythic
folder. These folders within Mythic
are generally self-explanatory for their purpose, such as postgres-docker
, rabbitmq-docker
, mythic-docker
, and MythicReactUI
.
When you use the mythic-cli
to install an agent or c2 profile, these all go in the Mythic/InstalledServices
folder. This makes it super easy to see what you have installed.
Throughout development you'll have a choice - do development remotely from the Mythic server and hook in manually, or do development locally on the Mythic server. After all, everything boils down to code that connects to RabbitMQ
and gRPC
- Mythic doesn't really know if the connection is locally from Docker or remotely from somewhere else.
The first step is to clone down the example repository https://github.com/MythicMeta/ExampleContainers. The format of the repository is that of the External Agent template. This is the format you'll see for all of the agents and c2 profiles on the overview page.
Inside of the Payload_Type
folder, there are two folders - one for GoLang and one for Python depending on which language you prefer to code your agent definitions in (this has nothing to do with the language of your agent itself, it's simply the language to define commands and parameters). We're going to go step-by-step and see what happens when you install something via mythic-cli
, but doing it manually.
Pick whichever service you're interested in and copy that folder into your Mythic/InstalledServices
folder. When you normally install via mythic-cli
, it clones down your repository and does the same thing - it copies what's in that repository's Payload_Type
and C2_Profiles
folders into the Mythic/InstalledServices
folder.
Now that a folder is in the Mythic/InstalledServices
folder, we need to let the docker-compose
file know that it's there. Assuming you copied over python_services
, you then need to run sudo ./mythic-cli add python_services
. This adds that python_services
folder to the docker-compose
. This is automatically done normally as part of the install process.
As part of updating docker-compose
, this process adds a bunch of environment variables to what will be the new container.
Now that docker-compose
knows about the new service, we need to build the image that will be used to make the agent's container. We can use sudo ./mythic-cli build python_services
. This tells docker
to look in the Mythic/InstalledServices/python_services
folder for a Dockerfile
and use it to build a new image called python_services
. As part of this, Mythic will automatically then use that new image to create a container and run it. If it doesn't, then you can create and start the container with sudo ./mythic-cli start python_services
.
Again, all of this happens automatically as part the normal installation process when you use sudo ./mythic-cli install
. We're doing this step-by-step though so you can see what happens.
At this point, your new example agent should be visible within Mythic. If it's not, we can check logs to see what the issue might be with sudo ./mythic-cli logs python-services
(this is a wrapper around sudo docker logs python-services
and truncates to the latest 500 lines).
Steps 2.1-2.4 all happen automatically when you install a service via mythic-cli
. If you don't want to install via mythic-cli
then you can do these steps manually like we did here.
Now that you've seen the pieces and steps for installing an existing agent, it's time to start diving into what's going on within that python_services
folder.
The only thing that absolutely MUST exist within this folder is a Dockerfile
so that docker-compose
can build your image and start the container. You can use anything as your base image, but Mythic provides a few to help you get started with some various environments:
itsafeaturemythic/mythic_go_base
has GoLang 1.21 installed
itsafeaturemythic/mythic_go_dotnet
has GoLang 1.21 and .NET
itsafeaturemythic/mythic_go_macos
has GoLang 1.21 and the macOS SDK
itsafeaturemythic/mythic_python_base
has Python 3.11 and the mythic_container
pypi package
itsafeaturemythic/mythic_python_go
has Python 3.11, the mythic_container
pypi package, and GoLang v1.21
itsafeaturemythic/mythic_python_macos
has Python 3.11, the mythic_container
pypi package, and the macOS SDK
This allows two payload types that might share the same language to still have different environment variables, build paths, tools installed, etc. Docker containers come into play for a few things:
Sync metadata about the payload type (this is in the form of python classes or GoLang structs)
Contains the payload type code base (whatever language your agent is in)
The code to create the payload based on all of the user supplied input (builder function)
Sync metadata about all of the commands associated with that payload type
The code for all of those commands (whatever language your agent is in)
Browser scripts for commands (JavaScript)
The code to take user supplied tasking and turn it into tasking for your agent
Start your Dockerfile
off with one of the above images:
On the next lines, just add in any extra things you need for your agent to properly build, such as:
This happens all in a script for docker, so if a command might make a prompt for something (like apt-get), make sure to auto handle that or your stuff won't get installed properly
If you're curious what else goes into these containers, look in the docker-templates
folder within the Mythic repository.
The Mythic/InstalledServices/[agent name]
folder is mapped to /Mythic
in the docker container. Editing the files on disk results in the edits appearing in the docker container and visa versa.
Within the Dockerfile
you will then need to do whatever is needed to kick off your main program that imports either the MythicContainer PyPi package or the MythicContainer GoLang package. As some examples, here's what you can do for Python and GoLang:
Mythic/InstalledServices/[agent name]/main.py
<-- if you plan on using Python as your definition language, this main.py
file is what will get executed by Python 3.11 assuming you use the Dockerfile shown below. If you want a different structure, just change the CMD
line to execute whatever it is you want.
At that point, your main.py
file should import any other folders/files needed to define your agent/commands and import the mythic_container
PyPi package.
Any changes you make to your Python code is automatically reflected within the container. Simply do sudo ./mythic-cli start [agent name]
to restart the container and have python reprocess your files.
If you want to do local testing without docker
, then you can add a rabbitmq_config.json
in the root of your directory (i.e. [agent name]/rabbitmq_config.json
) that defines the environment parameters that help the container connect to Mythic:
If your container/service is running on a different host than the main Mythic instance, then you need to make sure the rabbitmq_password
is shared over to your agent as well. By default, this is a randomized value stored in the Mythic/.env
file and shared across containers, but you will need to manually share this over with your agent either via an environment variable (MYTHIC_RABBITMQ_PASSWORD
) or by editing the rabbitmq_password
field in your rabbitmq_config.json file. You also need to make sure that the MYTHIC_RABBITMQ_LISTEN_LOCALHOST_ONLY
is set to false
and restart Mythic to make sure the RabbitMQ
port isn't bound exclusively to 127.0.0.1.
The containers will automatically sync all of their information with the Mythic server when they start, so the first time the Mythic server gets a message from a container it doesn't know about, it'll ask to sync. Similarly, as you do development and restart your Payload Type container, updates will automatically get synced to the main UI.
The folder that gets copied into Mythic/InstalledServices
is what's used to create the docker
image and container names. It doesn't necessarily have to be the same as the name of your agent / c2 profile (although that helps).
Docker does not allow capital letters in container names. So, if you plan on using Mythic's mythic-cli
to control and install your agent, then your agent's name can't have any capital letters in it. Only lowercase, numbers, and _. It's a silly limitation by Docker, but it's what we're working with.
The example services has a single container that offers multiple options (Payload Type, C2 Profile, Translation Container, Webhook, and Logging). While a single container can have all of that, for now we're going to focus on just the payload type piece, so delete the rest of it.
For the python_services
folder this would mean deleting the mywebhook
, translator
, and websocket
folders. For the go_services
folder, this would mean deleting the http
, my_logger
, my_webhooks
, no_actual_translation
folders. For both cases, this will result in removing some imports at the top of the remaining main.py
and main.go
files.
For the python_services
folder, we'll update the basic_python_agent/agent_functions/builder.py
file. This file can technically be anywhere that main.py
can reach and import, but for convenience it's in a folder, agent_functions
along with all of the command definitions for the agent. Below is an example from that builder that defines the agent:
Check out the Payload Type page for information on what the various components in the agent definition means and how to start customizing how your agent looks within Mythic.
To make your agent installable via mythic-cli
, the repo/folder needs to be in a common format. This format just makes it easier for mythic-cli
to add things to the right places. This is based on the External Agent format here (https://github.com/MythicMeta/Mythic_External_Agent). If you're creating a new payload type, then add your entire folder into the Payload_Type
folder. Similarly, when you get around to making documentation for your agent, you can add it to the documentation folder. If there's things you don't want to include, then in the config.json
file you can mark specific sections to exclude.
If you want your new C2 profile or Agent to show up on the overview page (https://mythicmeta.github.io/overview/) then you need to reach out to @its_a_feature_
on twitter or @its_a_feature_
in the Bloodhound slack to get your agent added to the agents list here (https://github.com/MythicMeta/overview/blob/main/agent_repos.txt). You could also make a PR to that file if you wanted too.
Having your agent hosted on the https://github.com/MythicAgents
organization means that it's easier for people to find your agent and we can collect stats on its popularity. For an example of what this means, check out the overview page and see the biweekly clone stats as well as the green chart icon for a historic list of view/clones of the repo.
If you don't want to have your agent hosted on the MythicAgents organization, but still want to make it available on that site, that's fine too. Just let me know or update the PR for that file appropriately.
In addition to simply hosting the agent/c2 profile, there's now a sub-page that shows off all of the agent's capabilities so it's easier to compare and see which ones meet your needs. That page is here (https://mythicmeta.github.io/overview/agent_matrix.html) and is populated based on a agent_capabilities.json
file in the root of your repository. This is just a json
file that gets ingested at midnight every day and used to update that matrix. The format is as follows:
The os
key provides all the operating systems your agent supports. These are the things that would be available after installing your agent for the user to select when building a payload. The languages
key identifies what languages your agent supports (typically only one, but could be multiple). The features
section identifies which features your agent supports. For the mythic
sub-key, the options are at the bottom of the matrix page, along with their descriptions and links to documentation for if you want to implement that feature in your agent. The custom
sub-key is just additional features that your agent supports that you want to call out. The payload_output
key identifies which output formats your agent supports as well as the architectures
key identifying which architectures your agent can be built for. The c2
key identifies which C2 Profiles your agent supports and the supported_wrappers
key identifies which wrapper
payloads your agent supports. As you might expect, the mythic_version
is which Mythic version your agent supports and the agent_version
is the current agent version in use.
The latest container versions and their associated mythic_container
PyPi versions can be found here: . The mythic_python_*
containers will always have the latest PyPi
version installed if you're using the :latest
version.