dynamicHTTP
This page describes how the HTTP profile works and configuration options
Last updated
This page describes how the HTTP profile works and configuration options
Last updated
This profile uses HTTP Get and Post messages to communicate with the main Mythic server. Unlike the default HTTP though, this profile allows a lot of customization from both the client and server. There are two pieces to this as with most C2 profiles - Server side code and Agent side code. In general, the flow looks like:
From the UI perspective, there are only two parameters - a JSON configuration and the auto-populated operation specific AES key. For the JSON configuration, there is a generic structure:
There are some important pieces to notice here:
GET
- this block defines what "GET" messages look like. This section is used for requesting tasking.
ServerBody
- this block defines what the body of the server's response will look like
ServerHeaders
- this block defines what the server's headers will look like
ServerCookies
- this block defines what the server's cookies will look like
AgentMessage
- This block defines the different forms of agent messages for doing GET requests. This defines what query parameters are used, what cookies, headers, URLs, etc.The format here is generally the same as the "POST" messages and will be described in the next section.
POST
- this block defines what "POST" messages look like
ServerBody
- this block defines what the body of the server's response will look like
ServerHeaders
- this block defines what the server's headers will look like
ServerCookies
- this block defines what the server's cookies will look like
AgentMessage
- This block defines the different forms of agent messages for doing POST requests. This defines what query parameters are used, what cookies, headers, URLs, etc.
jitter
- this is the jitter percentage for callbacks
interval
- this is the interval in seconds between callbacks
chunk_size
- this is the chunk size used for uploading/downloading files
key_exchange
- this specifies if Apfell does an encrypted key exchange or just uses a static encryption key
kill_date
- this defines the date agents should stop checking in. This date is checked when the agent first starts and before each tasking request, if it is the specified date or later, the agent will automatically exit. This is in the YYYY-MM-DD format.
Now let's talk about the AgentMessage
parameter. This is where you define all of the key components about how your GET and POST messages from agent to Mythic look, as well as indicating where in those requests you want to put the actual message the agent is trying to send. The format in general looks like this:
In the AgentMessage
section for the "GET"s and "POST"s, you can have 1 or more instances of the above AgentMessage
format (the above is an example of one instance). When the agent goes to make a GET or POST request, it randomly picks one of the formats listed and uses it. Let's look into what's actually being described in this AgentMessage
:
urls
- this is a list of URLs. One of these is randomly selected each time this overall AgentMessage
is selected. This allows you to supply fallback mechanisms in case one IP or domain gets blocked.
uri
- This is the URI to be used at the end of each of the URLs specified. This can be a static value, like /downloads.php
, or can be one that's changed for each request. For example, in the above scenario we supply /<test:string>
. The meaning behind that format is explained in the HTTP for the server side configuration, but the point here to look at is the next piece - urlFunctions
urlFunctions
- This describes transforms for modifying the URI of the request. In the above example, we replace the <test:string>
with a random selection from ["jquery-3.3.1.min.js", "jquery-3.3.1.map"]
.
AgentHeaders
- This defines the different headers that the agent will set when making requests
Note: if you're doing domain fronting, this is where you'd set that value
QueryParameters
- This defines the query parameters (if any) that will be sent with the request. When doing transforms and dynamic modifications, there is a standard format that's described in the next section.
When doing query parameters, if you're going to do anything base64 encoded, make sure it's URL safe encoding. Specifically, /
, +
, =
, and \n
characters need to be URL encoded (i.e. with their %hexhex equivalents)
Cookies
- This defines any cookies that are sent with the agent messages
Body
- This defines any modifications to the body of the request that should be made
The defining feature of the HTTP profile is being able to do transforms on the various elements of HTTP requests. What does this look like though?
These transforms have a few specific parts:
name
- this is the parameter name supplied in the request. For query parameters, this is the name in front of the =
sign (ex:/test.php?q=abc123
). For cookie parameters, this is the name of the cookie (ex: q=abc123;id=abc1
).
value
- this is the starting value before the transforms take place. You can set this to whatever you want, but if you set it to message
, then the starting value for the transforms will be the message that the agent is trying to send to Apfell.
transforms
- this is a list of transforms that are executed. The value starts off as indicated in the value
field, then each resulting value is passed on to the next parameter. In this case, the value starts as ""
, then gets 30 random alphabet letters, then those letters are base64 encoded.
Transforms have 2 parameters: the name of the function to execute and an array of parameters to pass in to it.
The initial set of supported functions are:
base64
takes no parameters
prepend
takes a single parameter of the thing to prepend to the input value
append
takes a single parameter of the thing to append to the input value
random_mixed
takes a single parameter of the number of elements to append to the input value. The elements are chosen from upper case, lower case, and numbers.
random_number
takes a single parameter of the number of elements to append to the input value. The elements are chosen from numbers 0-9.
random_alpha
takes a single parameter of the number of elements to append to the input value. The elements are chosen from upper case and lower case letters.
choose_random
takes an array of elements to choose from
To add new transforms, a few things need to happen:
In the HTTP profile's server
code, the function and a reverse of the function need to be added. The options need to be added to the create_value
and get_value
functions.
This allows the server to understand the new transforms
If you look in the server
code, you'll see functions like prepend
(which prepends the value) and r_prepend
which does the reverse.
In the agent's HTTP profile code, the options for the functions need to also be added so that the agent understands the functions. Ideally, when you do this you add the new functions to all agents, otherwise you start to lose parity.
A final example of an agent configuration can be seen below:
Like with all C2 profiles, the HTTP profile has its own docker container that handles connections. The purpose of this container is to accept connections from HTTP agents, undo all of the special configurations you specified for your agent to get the real message back out, then forward that message to the actual Mythic server. Upon getting a response from Mythic, the container performs more transforms on the message and sends it back to the Agent.
The docker container just abstracts all of the C2 features out from the actual Mythic server so that you're free to customize and configure the C2 as much as you want without having to actually adjust anything in the main server.
There is only ONE HTTP docker container per Mythic instance though, not one per operation. Because of this, the HTTP profile's server-side configuration will have to do that multiplexing for you. Below is an example of the setup:
Let's look into what this sort of configuration entails. We already discussed the agent side configuration above, so now let's look into what's going on in the HTTP C2 Docker container. The Server configuration has the following general format:
There are a couple key things to notice here:
instances
- You generally have one instance per operation, but there's no strict limit there. This is an array of configurations, so when the Docker server starts up, it loops through these instances and starts each one
no_match
- this allows you to specify what happens if there's an issue reaching the main apfell server or if you get a request to the docker container that doesn't match one of your specified endpoints. This is also what happens if you get a message to the right endpoint, but it's not properly encoded with the proper agent message (ex: fails to decrypt the agent message or fails to get the agent message from the url)
action
- this allows you to specify which of the options you want to leverage
redirect
- this simply returns a 302 redirect to the specified url
proxy_get
- this proxies the request to the following url and returns that url's contents with the status code specified
proxy_post
- this proxies the request to the following url and returns that url's contents with the specified status code
return_file
- this allows you to return the contents of a specified file. This is useful to have a generic 404 page or saved static page for a sight you might be faking.
port
- which port this instance should listen on
key_path
- the path locally to where the key file is located for an SSL connection. If you upload the file through the web UI then the path here should simply be the name of the file.
cert_path
- the path locally to where the cert file is located for an SSL connection.If you upload the file through the web UI, then the path here should simply be the name of the file. Both this and the key_path
must be specified and have valid files for the connection to the container to be SSL.
debug
- set this to true to allow debug messages to be printed. There can be a lot though, so once you have everything working, be sure to set this to false
to speed things up a bit.
GET
and POST
- simply take the GET
and POST
sections from your agent configuration
mentioned above and paste those here. No changes necessary.
When it comes to the URIs you choose to use for this, there's an additional feature you can leverage. You can choose to keep them static (like /download.php
) and specify/configure the query parameters/cookies/body, but you can also choose to register more dynamic URIs. There's an example of this above:
What this does for the server side is register a pattern via Sanic's Request Parameters. This allows you to register URIs that can change every time, but still be valid. For example:
this would require you to have the URI of something like /downloads/category/abcdefh/page/5
or the docker container will hit a not_found
case and follow that action path. Combine this with the urlFunctions
and you can have something to always generate unique URIs as follows:
Specifically for urlFunctions
, the "name" must match the thing that'll be replaced. Unlike query parameters and cookie values where the name specifies the name for the value, the name here specifies which field is to be replaced with the result of the transforms
One last thing to note about this. You cannot have two URIs in a single instance within the GET or the POST that collide. For example, you can't have two URIs that are /download.php
but vary in query parameters. As far as the docker container's service is concerned, the differentiation is between the URIs, not the query parameters, cookies, or body configuration. Now, in two different instances you can have overlapping URIs, but that's because they are different web servers bound to different ports.
What if you want all of your messages to be "POST"
requests or "GET"
requests? Well, Apfell by default tries to do GET requests when getting tasking and POST requests for everything else; however, if there are no GET agent_message
instances in that array (i.e. {"GET":{"AgentMessage":[]}}
) then the agent should use POST messages instead and vise versa. This allows you to have yet another layer of customization in your profiles.
Because this is a profile that offers a LARGE amount of flexibility, it can get quite confusing. So, included with the dynamicHTTP is a linter (a program to check your configurations for errors). In C2_Profiles/dynamicHTTP/c2_code/
is a config_linter.py
file. Run this program with ./config_linter.py [path_to_agent_config.json]
where [path_to_agent_config.json] is a saved version of the configuration you'd supply your agent. The program will then check that file for errors, will check your server's config.json
for errors, and then verify that there's a match between the agent config and server config (if there was no match, then your server wouldn't be able to understand your agent's messages). Here's an example: