Message Flow
This page describes how messages flow within Mythic

Operator Submits Tasking

There's a lot of moving pieces within Mythic and its agents, so it's helpful to take a step back and see how messages are flowing between the different components.
Here we can see an operator issue tasking to the Mythic server. The Mythic server registers the task as "preprocessing" and informs the operator that it got the task. Mythic then sends the task off to the corresponding Payload Type container for processing. The container looks up the corresponding command python file, parses the arguments, validates the arguments, and passes the resulting parameters to the create_tasking function. This function can leverage a bunch of RPC functionality going back to Mythic to register files, send output, etc. When it's done, it sends the final parameters back to Mythic which updates the Task to either Submitted or Error. Now that the task is out of the preprocessing state, when an agent checks in, it can receive the task.
The corresponding mermaid diagram code is:
1
{{<mermaid>}}
2
sequenceDiagram
3
participant O as Operator
4
participant M as Mythic
5
participant P as Payload Container
6
O ->>+ M: Issues Task
7
M ->> M: Validate Operator Can Submit Task
8
M ->> M: Creates Preprocessing Task
9
M -->>- O: Indicate new task exists
10
M ->>+ P: Forward Task
11
P ->> P: Parse Arguments
12
P ->> P: Validate Arguments
13
P ->> P: Perform OPSEC Pre-Check
14
loop create_tasking
15
P -->> M: Use RPC Functionality
16
M -->> P: RPC Replies
17
end
18
Note over P: Finish Processing
19
P ->> P: Perform OPSEC Post-Check
20
P -->>- M: Send finished task
21
M ->> M: Updates Task to Submitted or Error
22
M -->> O: Update with new Task Status
23
{{< /mermaid >}}
Copied!

Agent Sends Message

Agent Sends Message
1
{{<mermaid>}}
2
sequenceDiagram
3
participant T as Translation Container
4
participant M as Mythic
5
participant C as C2 Profile Container
6
participant A as Agent
7
A ->>+ C: Agent Sends Message
8
C ->>+ M: C2 Forwards to Mythic
9
Note over M: Start Processing Message
10
M ->> M: Base64 Decode and fetch UUID
11
M ->>+ T: Send to Translator
12
T ->> T: Decrypt Message
13
T ->> T: Translate To Mythic JSON
14
T -->>- M: Return JSON Message
15
M ->> M: Process Message
16
M ->> M: Process Delegates
17
Note over M: Stop Processing Message
18
M ->> M: Create Response
19
M ->>+ T: Send to Translator
20
T ->> T: Translate to Custom Format
21
T ->> T: Encrypt Message
22
T -->>- M: Return Final Blob
23
M -->>- C: Return Final Blob
24
C -->>- A: Return Final Message
25
{{< /mermaid >}}
Copied!
Here we can see an agent sends a message to Mythic. The C2 Profile container is simply a fancy redirector that know show to pull the message off the wire, it doesn't do anything else than that. From there, Mythic starts processing the message. It pulls out the UUID so it can determine which agent/callback we're talking about. This is where a decision point happens:
  • If the Payload Type associated with the payload/callback for the UUID of the message has a translation container associated with it, then Mythic will send the message there. It's here that the rest of the message is converted from the agent's special sauce C2 format into the standard JSON that Mythic expects. Additionally, if the Payload Type handles encryption for itself, then this is where that happens.
  • If there is not translation container associated with the payload/callback for the UUID in the message, then Mythic moves on to the next step and starts processing the message.
Mythic then processes the message according to the "action" listed.
Mythic then potentially goes back to the translation container to convert the response message back to the agent's custom C2 spec before finally returning everything back through the C2 Profile docker container.

What happens for building payloads?

What happens when an operator tasks a payload to build?
Here we can see that the operator selects the different payload options they desire in the web user interface and clicks submit. That information goes to Mythic which looks up all the database objects corresponding to the user's selection. Mythic then registers a payload in a building state. Mythic sends all this information to the corresponding Payload Type container to build an agent to meet the desired specifications. The corresponding build command parses these parameters, stamps in any required user parameters (such as callback host, port, jitter, etc) and uses any user supplied build parameters (such as exe/dll/raw) to build the agent.
In the build process, there's a lot of room for customizing. Since it's all async through rabbitMQ, you are free to stamp code together, spin off subprocesses (like mono or go) to build your agent, or even make web requests to CI/CD pipelines to build the agent for you. Eventually, this process either returns an agent or some sort of error. That final result gets send back to Mythic via rabbitMQ which then updates the database and user interface to allow an operator to download their payload.
The corresponding mermaid diagram code is:
1
{{<mermaid>}}
2
sequenceDiagram
3
participant O as Operator
4
participant M as Mythic
5
participant C as C2 Profile
6
participant P as Payload Container
7
participant B as Compiler
8
O -->> O: Selects Payload Options
9
O ->>+ M: Sends Build Request
10
M -->> M: Looks up all Components
11
M ->> M: Registers Payload
12
M -->>- O: Indicate that build has started
13
loop C2 Profiles
14
M -->+ C: Invoke OPSEC Check
15
C ->>- M: Return Result
16
end
17
M ->>+ P: Forward Build Parameters
18
P ->> P: Parse Parameters
19
loop create_tasking
20
P -->> P: Stamp in Parameters
21
P -->> B: Build Payload
22
B -->> P: Return Payload or Error
23
end
24
Note over P: Finish Building
25
P -->>- M: Send finished Payload
26
M ->> M: Updates Payload build status
27
M -->> O: Update with new Build Status
28
{{< /mermaid >}}
Copied!

File uploads from Mythic -> Agent

What happens when you want to transfer a file from Mythic -> Agent? There's two different options: tracking a file via a UUID and pulling down chunks or just sending the file as part of your tasking.
File registration and chunked uploads
This is an example of an operator uploading a file, it getting processed at the Payload Type's create_tasking function where it tracks and registers the file within Mythic. Now the tasking has a UUID for the file rather than the file contents itself. This allows Mythic and the Agent to uniquely reference a file. The agent gets tasking, sees the file id, and submits more requests to fetch the file. Upon finally getting the full file, it resolves the relative upload path into an absolute path and sends an update back to Mythic to let it know that the file the operator said to upload to ./test is actually at /abs/pah/to/test on the target host.
1
{{<mermaid>}}
2
sequenceDiagram
3
participant O as Operator
4
participant M as Mythic
5
participant P as PayloadType
6
participant H as HTTP Container
7
participant A as Agent
8
O ->>+ M: Upload file1 with task to ./test
9
M ->>+ P: Here's a task & file1
10
P ->>+ M: Register this file1
11
M -->>- P: Here's a file1 UUID
12
P -->>- M: Tasking is ready
13
M -->>- O: Tasking is Submitted
14
A ->>+ H: Get Tasking
15
H ->>+ M: Get Tasking
16
M -->>- H: Here's your task
17
H -->>- A: Here's your task
18
A ->>+ H: Give me chunk1 of UUID with size X
19
H ->>+ M: Give me chunk1 of UUID with size X
20
M -->>- H: Here's chunk1, you have 3 total
21
H -->>- A: Here's chunk1, you have 3 total
22
A ->>+ H: Give me chunk2 of UUID with size X
23
H ->>+ M: Give me chunk2 of UUID with size X
24
M -->>- H: Here's chunk2, you have 3 total
25
H -->>- A: Here's chunk2, you have 3 total
26
A ->>+ H: Give me chunk3 of UUID with size X
27
H ->>+ M: Give me chunk3 of UUID with size X
28
M -->>- H: Here's chunk3, you have 3 total
29
H -->>- A: Here's chunk3, you have 3 total
30
A ->> A: Process file1
31
A ->>+ H: file1 (UUID) is now at /abs/path/to/test
32
H ->>+ M: file1 (UUID) is now at /abs/path/to/test
33
M --> M: update file info
34
M -->- H: updated tracking success
35
H -->- A: updated tracking success
36
{{< /mermaid >}}
Copied!
Conversely, you can opt to not track the file (or track the file within Mythic, but not send the UUID down to the agent). In this case, you can't easily reference the same instance of the file between the Agent and Mythic:
You're able to upload and transfer the file just fine, but when it comes to reporting back information on it, Mythic and the Agent can't agree on the same file, so it doesn't get updated. You might be thinking that this is silly, of course the two know what the file is, it was just uploaded. Consider the case of files being deleted or multiple instances of a file being uploaded.
1
{{<mermaid>}}
2
sequenceDiagram
3
participant O as Operator
4
participant M as Mythic
5
participant P as PayloadType
6
participant H as HTTP Container
7
participant A as Agent
8
O ->>+ M: Upload file2 with task to ./test2
9
M ->>+ P: Here's a task & file2
10
P -->>- M: Tasking is ready
11
M -->>- O: Tasking is Submitted
12
A ->>+ H: Get Tasking
13
H ->>+ M: Get Tasking
14
M -->>- H: Here's your task
15
H -->>- A: Here's your task
16
A ->> A: Process file2
17
A ->>+ H: file2 is now at /abs/path/to/test2
18
H ->>+ M: file2 is now at /abs/path/to/test2
19
M --> M: WTF is this?
20
M -->- H: wtf is this? no update
21
H -->- A: wtf is this? no update
22
{{< /mermaid >}}
Copied!

Downloading a file from Agent -> Mythic

When you're downloading a file from the Agent to Mythic (such as a file on disk, a screenshot, or some large piece of memory that you want to track as a file), you have to indicate in some way that this data is specific to a file and not destined to be part of the information displayed to the user. The way this works is pretty much the inverse of what happens for uploads. Specifically, an agent has a file it wants to transfer, so it tells Mythic "i have data, i'm gonna send it as X chunks of size Y, can you give me a UUID so we can track this". Mythic tracks the data and gives back a UUID. Now the agent sends each chunk individually and Mythic can track it. This allows a single task to be able to send back multiple files concurrently or sequentially and still track it all.
1
{{<mermaid>}}
2
sequenceDiagram
3
participant O as Operator
4
participant M as Mythic
5
participant P as PayloadType
6
participant H as HTTP Container
7
participant A as Agent
8
O ->>+ M: Download test
9
M ->>+ P: Here's a task
10
P -->>- M: Tasking is ready
11
M -->>- O: Tasking is Submitted
12
A ->>+ H: Get Tasking
13
H ->>+ M: Get Tasking
14
M -->>- H: Here's your task
15
H -->>- A: Here's your task
16
A ->>+ H: Track new file, 5 total_chunks
17
H ->>+ M: Track new file, 5 total_chunks
18
M ->> M: Create File in DB
19
M -->>- H: Here's a file UUID
20
H -->>- A: Here's a file UUID
21
loop send chunks 1-5
22
A ->>+ H: Here's chunkX for file UUID
23
H ->>+ M: Here's chunkX for file UUID
24
M ->> M: Stores data to disk
25
M -->>- H: Success
26
H -->>- A: Success
27
end
28
A ->>+ H: file (UUID) is at /abs/path/to/test
29
H ->>+ M: file (UUID) is at /abs/path/to/test
30
M --> M: update file info
31
M -->- H: updated tracking success
32
H -->- A: updated tracking success
33
{{< /mermaid >}}
Copied!

P2P Message Flow

More information on P2P message communication can be found here.
1
{{<mermaid>}}
2
sequenceDiagram
3
participant O as Operator
4
participant M as Mythic
5
participant P as PayloadType
6
participant H as HTTP Container
7
participant A as Agent1
8
participant B as Agent2
9
O ->>+ M: Deploy P2P Agent2 with ReverseTCP
10
M ->>+ P: Here's a task
11
P -->>- M: Tasking is ready
12
M -->>- O: Tasking is Submitted
13
A ->>+ H: Get Tasking
14
H ->>+ M: Get Tasking
15
M -->>- H: Here's your task
16
H -->>- A: Here's your task
17
A ->> A: Prep to catch P2P connection
18
A ->> B: Execute Agent2 on remote box
19
B ->> B: Start executing
20
B ->> A: Connect to TCP Port
21
A ->> A: Generate tempUUID for Agent2
22
B ->>+ A: Send Checkin Message (1)
23
A ->>+ H: Message + Delegate msg for agent tempUUID (2)
24
H ->>+ M: Message + Delegate msg for agent tempUUID
25
M ->> M: Process Message
26
M ->> M: Process Delegate msg
27
M ->> M: Process Checkin Message for Agent2
28
M ->> M: Auto link Agent2 -> Agent1 (3)
29
M ->> M: Generate Checkin Response for Agent2
30
M ->> M: Add Response as Delegate to Agent1 Response (4)
31
M ->> M: Wrap up Agent1 Response
32
M -->>- H: Agent1 Response
33
H -->>- A: Agent1 Response
34
A ->> A: Process Response
35
A ->> A: Pull out Delegate Message
36
A ->> A: update tempUUID with Agent2UUID (5)
37
A ->> B: Send Checkin Response
38
loop get tasking
39
A ->>+ H: Get Tasking
40
H ->>+ M: Get Tasking
41
M ->> M: Checks tasks for Agent1
42
M ->> M: Checks tasks for reachable agents from Agent1 (6)
43
M ->> M: Adds reachable tasks as Delegate Messages
44
M -->>- H: Sends Tasks
45
H -->>- A: Sends Tasks
46
A ->> B: Sends Delegated Tasks
47
end
48
{{< /mermaid >}}
Copied!
1. P2P agents do their "C2 Comms" (which in this case is reaching out to agent1 over the decided TCP port) and start their Checkin/Encrypted Key Exchange/etc.
2. When Agent1 gets a connection from Agent2, there's a lot of unknowns. Agent2 could be a new payload that isn't registered as a callback in Mythic yet. It could be an already existing callback that you're re-linking to or linking to for the first time from this callback. Either way, Agent1 doesn't know anything about Agent2 other than it connected to the right port, so it generates a temporary UUID to refer to that connection and waits for a message from Agent 2 (the first message sent through should always be from Agent2->Agent1 with a checkin message). Agent1 sends this information out with its next message as a "Delegate" Message of the form:
1
{
2
"action": "some action here",
3
"delegates": [
4
{
5
"message": agentMessage,
6
"uuid": UUID,
7
"c2_profile": "ProfileName"
8
}
9
]
10
}
Copied!
This "delegates" key sits at the same level as the "action" key and can go with any message (upload, checkin, post_response, etc). The "message" field is the checkin message from Agent2, the "uuid" field is the tempUUID that agent1 generated, and the "c2_profile" is the name of the C2 profile that the two agents are using to connect.
3. When Mythic parses this delegate message, it can automatically assume that there's a connection between Agent1 and Agent2 because Agent1's message has a Deleggate from Agent 2.
4. When Mythic is done processing Agent2's checkin message, it takes that result and adds it as a "delegate" message back for Agent1's message.
5. When Agent1 gets its message back, it sees that there is a delegate message. That message is of the format:
1
{
2
"action": "some action here",
3
"delegates": [
4
{
5
"message": agentMessage,
6
"uuid": "same UUID as the message agent -> mythic",
7
"mythic_uuid": UUID that mythic uses
8
}
9
]
10
}
Copied!
6. You can see that the response format is a little different. We don't need to echo back the C2 Profile because the agent already knows that information. The "message" field is the Mythic response that goes back to Agent 2. The "uuid" field is the same tempUUID that the agent sent in the message to Mythic. The "mythic_uuid" field though is Mythic indicating back to Agent1 that it doesn't know what tempUUID is, but the agent that sent that message actually has this UUID. That allows the agent to update its records. The main reason this is important is in the case where the connection between Agent1 and Agent2 goes away. Agent1 has to have some way of indicating to Mythic that Agent2 is no longer talking to it. Mythic only knows Agent2 by its UUID, so if Agent1 tried to report that it could no longer talk to tempUUID, Mythic would have no idea who that is.