Part 2: Getting a Callback
translation containers and checking in
Agent
Data Serialization
I decided to build off of the Talon agent by @Cracked5pider. It uses a data serialization format “type, size, data” to pack and parse data. This allows the agent to send serialized binary data instead of worrying about creating JSON in C which would have been a nightmare and require pulling in different libraries.
For example, a binary packet of the string “Xenon” sent from the agent might look like this in hexadecimal representation:
\\x02\\x00\\x00\\x00\\x05\\x58\\x65\\x6e\\x6f\\x6e
\\x02
- one byte for the message type\\x00\\x00\\x00\\x05
- a 4 byte integer for the size of the data\\x58\\x65\\x6e\\x6f\\x6e
- the actual data itself (”Xenon”)
This can then be parsed on the server side and processed as needed.
Translation Plz?
So that's all cool and everything… but the Mythic server expects JSON. In order for Mythic to understand these packets of data I had to use what Mythic calls a translation container.
Yes… another container. 🫠
It sits in the middle of the agent and server, and it translates the data into the necessary format.
From Agent → C2: Translates to JSON
From C2 → Agent: Translates to type, size, data serialized format
This is a simplified overview of how the translation container works for Xenon:

The folder for the translation container is located under Xenon/Payload_Type/xenon/translator
. It defines a XenonTranslator
class which defines two asynchronous functions for translate_to_c2_format
and translate_from_c2_format
, which do exactly what they say.
Mythic has a few different task types that we care about for now:
checkin
- Initial agent check-in data (hostname, username, pid, etc)get_tasking
- Asking for any tasks to executepost_response
- Results from task executions
The task type is defined in the JSON of the request type. The easiest way to represent these different task types in the binary format would be with a single byte:
0xf1
- checkin task type0x00
- get_tasking task type0x01
- post_response task type
This single byte can be correlated to the task type on the Translation Container. We can read off the first byte to know what type of task we received and then handle the message payload accordingly.
MYTHIC_CHECK_IN = 0xf1
MYTHIC_GET_TASKING = 0x00
MYTHIC_POST_RESPONSE = 0x01
Check-In
The first data transmitted from the agent should be the checkin
message. This usually contains a bunch of metadata about the victim host (hostname, OS, username, IP address, etc). The different nuances to this is covered in detail in the Mythic documentation.
Check-in message format:
Base64( PayloadUUID + JSON({
"action": "checkin", // required
"uuid": "payload uuid", //uuid of the payload - required
"ips": ["127.0.0.1"], // internal ip addresses - optional
"os": "macOS 10.15", // os version - optional
"user": "its-a-feature", // username of current user - optional
"host": "spooky.local", // hostname of the computer - optional
"pid": 4444, // pid of the current process - optional
"architecture": "x64", // platform arch - optional
"domain": "test", // domain of the host - optional
"integrity_level": 3, // integrity level of the process - optional
"external_ip": "8.8.8.8", // external ip if known - optional
"encryption_key": "base64 of key", // encryption key - optional
"decryption_key": "base64 of key", // decryption key - optional
"process_name": "osascript", // name of the current process - optional
})
)
Mythic check-in request format
So we can basically reformat this message but in a binary serialized format. It would look something like this:
Base64( PayloadUUID bytes + 0xf1 + uuid bytes + IP address bytes + OS bytes + etc + etc)
Our check-in format
Then on the Translation Container side, we read the data in the expected order, parse it, and format it into JSON to pass to the Mythic server.
Xenon calls a bunch of Windows APIs in order to collect the information above from the host, then it packs the data into the serialized format, prepends the payload UUID string, base64 encodes the whole thing and sends it off.
Check-in response format:
Base64( PayloadUUID + JSON({
"action": "checkin",
"id": "UUID", // New UUID for the agent to use
"status": "success"
})
)
Mythic check-in response format
The new UUID must be used for all agent requests going forward. In the agent code there is a simple function to replace the global agent UUID with the new one.

If all goes well, you will see the new callback under Active Callbacks.
Next Steps
So all we’ve done here was send the “check-in” request which sends some metadata about the victim host and registers the callback within Mythic.
In order to actually be a C2 agent, we need to continuously loop over the “get-tasking” request and process and relevant data returned from it. I’ll cover that process in the next blog.