This page has the various different ways the initial checkin can happen and the encryption schemes used.
A note about UUIDs
You will see a bunch of UUIDs mentioned throughout this section. All UUIDs are UUIDv4 formatted UUIDs (36 characters in length) and formatted like:
b446b886-ab97-49b2-b240-969a75393c06
In general, the UUID concatenated with the encrypted message provides a way to give context to the encrypted message without requiring a lot of extra pieces and without having to do a bunch of nested base64 encodings. 99% of the time, your messages will use your callbackUUID in the outer message. The outer UUID gives Mythic information about how to decrypt or interpret the following encrypted blob. In general:
payloadUUID as the outer UUID tells Mythic to look up that payload UUID, then look up the C2 profile associated with it, find a parameter called AESPSK, and use that as the key to decrypt the message
tempUUID as the outer UUID tells Mythic that this is a staging process. So, look up the UUID in the staging database to see information about the blob, such as if it's an RSA encrypted blob or is part of a Diffie-Hellman key exchange
callbackUUID as the outerUUID tells Mythic that this is a full callback with an established encryption key or in plaintext.
However, when your payload first executes, it doesn't have a callbackUUID, it's just a payloadUUID. This is why you'll see clarifiers as to which UUID we're referring to when doing specific messages. The whole goal of the checkin process is to go from a payload (and thus payloadUUID) to a full callback (and thus callbackUUID), so at the end of staging and everything you'll end up with a new UUID that you'll use as the outer UUID.
If your already existing callback sends a checkin message more than once, Mythic simply uses that information to update information about the callback rather than trying to register a new callback.
In egress agent messages, you can opt for a 16 Byte big endian format for the UUID. If Mythic gets a message from an agent with this format of UUID, then it will respond with the same format for the UUID. However, currently for P2P messages Mythic doesn't track the format for the UUID of the agent, so these will get the standard 36 character long UUID String.
Plaintext Checkin
The plaintext checkin is useful for testing or when creating an agent for the first time. When creating payloads, you can generate encryption keys per c2 profile. To do so, the C2 Profile will have a parameter that has an attribute called crypto_type=True. This will then signal to Mythic to either generate a new per-payload AES256_HMAC key or (if your agent is using a translation container) tell your agent's translation container to generate a new key. In the http profile for example, this is a ChooseOne option between aes256_hmac or none. If you're doing plaintext comms, then you need to set this value to none when creating your payload. Mythic looks at that outer PayloadUUID and checks if there's an associated encryption key with it in the database. If there is, Mythic will automatically try to decrypt the rest of the message, which will fail. This checkin has the following 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 }))
integrity_level is an integer from 1-4 that indicates the integrity level of the callback. On Windows, these levels correspond to low integrity (1) , medium integrity (2), high integrity (3), or SYSTEM integrity (4). On Linux, these don't have a great mapping, but you can think of (2) as a standard user, (3) as a user that's in the sudoers file or is able to run sudo, and (4) as the root user.
The JSON section is not encrypted in any way, it's all plaintext.
Base64( PayloadUUID + JSON({"action":"checkin","id":"UUID",// new UUID for the agent to use"status":"success" }))
From here on, the agent messages use the new UUID instead of the payload UUID. This allows Mythic to track a payload trying to make a new callback vs a callback based on a payload.
Static Encryption Checkin
This method uses a static AES256 key for all communications. This will be different for each payload that's created. When creating payloads, you can generate encryption keys per c2 profile. To do so, the C2 Profile will have a parameter that has an attribute called crypto_type=True. This will then signal to Mythic to either generate a new per-payload AES256_HMAC key or (if your agent is using a translation container) tell your agent's translation container to generate a new key. In the http profile for example, this is a ChooseOne option between aes256_hmac or none. The key passed down to your agent during build time will be the base64 encoded version of the 32Byte key.
The message sent will be of the form:
Base64( PayloadUUID + AES256( 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 }) ))
Here's an example message with encryption key of hfN9Nk29S8LsjrE9ffbT9KONue4uozk+/TVMyrxDvvM= and message:
From here on, the agent messages use the new UUID instead of the payload UUID.
This first message from Agent -> Mythic has the Payload UUID as the outer UUID and the Payload UUID inside the checkin JSON message. Once the agent gets the reply with a callbackUUID, all future messages will have this callbackUUID as the outer UUID.
With that same example from above, the agent gets back a response of success with a new callback UUID. From there on, since it's a static encryption, we'll see a get tasking message like the following:
Notice how the outer UUID is different, but the encryption key is still the same.
AES256 Encryption Details
Padding: PKCS7, block size of 16
Mode: CBC
IV is 16 random bytes
Final message: IV + Ciphertext + HMAC
where HMAC is SHA256 with the same AES key over (IV + Ciphertext)
Encrypted Key Exchange Checkins
There are two currently supported options for doing an encrypted key exchange in Mythic:
Client-side generated RSA keys
leveraged by the apfell-jxa and poseidon agents
Agent specific custom EKE
EKE by generating client-side RSA keys
The agent starts running and generates a new 4096 bit Pub/Priv RSA key pair in memory. The agent then sends the following message to Mythic:
Base64( PayloadUUID + AES256( JSON({"action":"staging_rsa","pub_key":"base64 of public RSA key","session_id":"20char string",// unique session ID for this callback }) ))
where the AES key initially used is defined as the initial encryption value when generating the payload. When creating payloads, you can generate encryption keys per c2 profile. To do so, the C2 Profile will have a parameter that has an attribute called crypto_type=True. This will then signal to Mythic to either generate a new per-payload AES256_HMAC key or (if your agent is using a translation container) tell your agent's translation container to generate a new key. In the http profile for example, this is a ChooseOne option between aes256_hmac or none.
When it says "base64 of public RSA key" you can do one of two things:
Base64 encode the entire PEM exported key (including the ---BEGIN and ---END blocks)
Use the already base64 encoded data that's inbetween the ---BEGIN and ---END blocks
Here is an example of the first message using encryption key hfN9Nk29S8LsjrE9ffbT9KONue4uozk+/TVMyrxDvvM=:
Base64( PayloadUUID + AES256( JSON({"action":"staging_rsa","uuid":"UUID",// new UUID for the next message"session_key": Base64( RSAPub( new aes session key ) ),"session_id":"same 20 char string back" }) ))
Here's that sample response from our above sample message:
The response is encrypted with the same initial AESPSK value as before. However, the session_key value is encrypted with the public RSA key that was in the initial message and base64 encoded. The response also includes a new staging UUID for the agent to use. This is not the final UUID for the new callback, this is a temporary UUID to indicate that the next message will be encrypted with the new AES key.
The next message from the agent to Mythic is as follows:
Base64( tempUUID + AES256( 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 }) ))
With our new temp UUID, the agent sends the following:
This checkin data is the same as all the other methods of checking in, the key things here are that the tempUUID is the temp UUID specified in the other message, the inner uuid is the payload UUID, and the AES key used is the negotiated one. It's with this information that Mythic is able to track the new messages as belonging to the same staging sequence and confirm that all of the information was transmitted properly. The final response is as follows:
Base64( tempUUID + AES256( JSON({"action":"checkin","id":"UUID",// new UUID for the agent to use"status":"success" }) ))
With our example, the agent gets back the following:
where HMAC is SHA256 with the same AES key over (IV + Ciphertext)
RSA Encryption Details
PKCS1_OAEP
This is specifically OAEP with SHA1
4096Bits in size
Your Own Custom EKE
Base64( payloadUUID + customMessage )
Mythic looks up the information for the payloadUUID and calls your translation container's translate_from_c2_format function. That function gets a dictionary of information like the following:
{"enc_key": None or base64 of key if Mythic knows of one,"dec_key": None or base64 of key if Mythic knows of one,"uuid": uuid of the message,"profile": name of the c2 profile,"mythic_encrypts": True or False if Mythic thinks Mythic does the encryption or not,"type": None or a keyword for the type of encryption. currently only option besides None is "AES256""message": base64 of the message that's currently in c2 specific format}
To get the enc_key, dec_key, and type, Mythic uses the payloadUUID to then look up information about the payload. It uses the profile associated with the message to look up the C2 Profile parameters and look for any parameter with a crypto_type set to true. Mythic pulls this information and forwards it all to your translate_from_c2_format function.
Ok, so that message gets your payloadUUID/crypto information and forwards it to your translation container, but then what?
Normally, when the translate_to_c2_format function is called, you just translate from your own custom format to the standard JSON dictionary format that Mythic uses. No big deal. However, we're doing EKE here, so we need to do something a little different. Instead of sending back an action of checkin, get_tasking, post_response, etc, we're going to generate an action of staging_translation.
Mythic is able to do staging and EKE because it can save temporary pieces of information between agent messages. Mythic allows you to do this too if you generate a response like the following:
{"action":"staging_translation","session_id":"some string session id you want to save","enc_key": the bytes of an encryption key for the next message,"dec_key": the bytes of a decryption key for the next message,"crypto_type":"what type of crypto you're doing","next_uuid":"the next UUID that'll be in front of the message","message":"the raw bytes of the message that'll go back to your agent"}
Let's break down these pieces a bit:
action - this must be "staging_translation". This is what indicates to Mythic once the message comes back from the translate_from_c2_format function that this message is part of staging.
session_id - this is some random character string you generate so that we can differentiate between multiple instances of the same payload trying to go through the EKE process at the same time.
enc_key / dec_key - this is the raw bytes of the encryption/decryption keys you want for the next message. The next time you get the translate_from_c2_format message for this instance of the payload going through staging, THESE are the keys you'll be provided.
crypto_type - this is more for you than anything, but gives you insight into what the enc_key and dec_key are. For example, with the http profile and the staging_rsa, the crypto type is set to aes256_hmac so that I know exactly what it is. If you're handling multiple kinds of encryption or staging, this is a helpful way to make sure you're able to keep track of everything.
next_uuid - this is the next UUID that appears in front of your message (instead of the payloadUUID). This is how Mythic will be able to look up this staging information and provide it to you as part of the next translate_from_c2_format function call.
message - this is the actual raw bytes of the message you want to send back to your agent.
This process just repeats as many times as you want until you finally return from translate_from_c2_format an actual checkin message.
What if there's other information you need/want to store though? There are three RPC endpoints you can hit that allow you to store arbitrary data as part of your build process, translation process, or custom c2 process:
create_agentstorage - this take a unique_id string value and the raw bytes data value. The unique_id is something that you need to generate, but since you're in control of it, you can make sure it's what you need. This returns a dictionary:
{"unique_id": "your unique id", "data": "base64 of the data you supplied"}
get_agentstorage - this takes the unique_id string value and returns a dictionary of the stored item:
{"unique_id": "your unique id", "data": "base64 of the data you supplied"}
delete_agentstorage - this takes the unique_id string value and removes the entry from the database