its-a-feature / Mythic

A collaborative, multi-platform, red teaming framework
Other
3.15k stars 426 forks source link

Issue decrypting check-in messages #403

Closed ChoboSyk closed 2 weeks ago

ChoboSyk commented 3 weeks ago

Hi,

I'm building an C2 profile that needs to decrypt/re-encrypt messages for some modifications as they come in. I've been using SendMythicRPCCallbackDecryptBytes and SendMythicRPCCallbackEncryptBytes. I currently get an error using these methods when providing the implants default UUID before it is changed after the check-in. This means that the check-in messages cannot be decrypted.

When the original UUID is provided with a message for decryption I get the following error: "sql: no rows in result set". Following messages once the UUID is changed can be decrypted without issue.

Would it be possible to add that when a payload is created, an entry is made in the database matching it's base UUID and it's crypto information?

I was also wondering if there was a way to get the actual base64 key value via the RPC methods instead of delegating the actual encrypt/decrypt functionalities to the RPC functions.

Thank you!

its-a-feature commented 3 weeks ago

You're getting that no rows error because until you do the checkin, you don't have a "Callback" as far as Mythic is concerned (no new row in your UI). The CallbackEncryptBytes and CallbackDecryptBytes only work on "Callbacks", which is why you're not able to use them before that spot.

I can expand that functionality to allow you to specify any UUID (payload, staging, or callback) along with a C2 Profile name so that this can work more broadly.

I'm also curious about what you're doing with your C2 profile that you need to decrypt/encrypt before handing it off to Mythic. A C2 Profile is supposed to be doing stuff that's more Agent agnostic, and for things that are more agent specific, you should be using a translation container instead (that would allow you to modify data after decryption and before encryption again). That would also prevent the need for all of the extra encrypt/decrypt steps (if possible).

ChoboSyk commented 3 weeks ago

Thanks for the quick answer!

I'm modifying the traffic in the c2 profile to achieve a few things

The profile is meant to be used with slow communicating external c2s. I hope to limit the client side communications to as few requests as possible. Currently the com flow of mythic is mostly 2 http requests for a round trip

  1. Get-Tasking
  2. Send Tasking Results This results in 4 external c2 frames exchanged when using upload/download on a third party service. My idea is to intercept the traffic so that I can combine them to only have 2 frames exchanged per round trip. The implant would send an array instead that could combine actions [{"action": "get_tasking"...}, {"task-id": xxx, "response": xxx ....}] and the server would then decrypt and send it as 2 seperate requests as arrays aren't supported. The server could then package both responses in 1 frame and post that. In the case of the checkin requests, I thought it would be possible to also combine it with a get-tasking request if I had stuff like beacon_initial in a cna ready to execute as soon as the check-in happens.

I also want to intercept the Download and Upload requests to save the first round trip. Currently when you want to download a file, the implant needs to first confirm the files existence to get a file-id it can use to start transferring the file. This results in an extra round trip before the file ever starts downloading. When your implant only communicates every few hours this can become an issue. I want to intercept those and make the server confirm the file existence (even if it's unsure at this point) so the implant receives a file-id in it's download tasking and can include the file content in it's response directly. If the file doesn't exist I'll return an error in the downloading portion of the exchange. Similar thing with upload.

My approach to all this may be bad lol I'd be all ears if you'd do this differently with Mythic features :)

its-a-feature commented 3 weeks ago

I think that all makes sense, so hopefully I can help provide a solution that might be a bit easier for you.

You're already familiar with get_tasking and post_response "action" fields you can send. What you might not know though is that you can actually send all of your responses fields in the get_tasking message as well. https://docs.mythic-c2.net/customizing/payload-type-development/create_tasking/agent-side-coding/action_get_tasking <-- the last helpful hint at the bottom. That means that if you have your sleep cycle end and you need to both get for new tasking and submit some output from tasks, you can do both in the same one message. The main difference between the get_tasking and post_response actions is what you get back - in the get_tasking you can get tasks back in addition to socks, rpfwd, interactive, and responses. In a post_response message, you won't get new tasks back, just the others. This provides some level of flexibility depending on your agent structure, but you don't have to send them as separate message. You can send the following as your agent and be just fine:

{"action": "get_tasking", "responses": [ {"task_id": "uuid", "user_output": "hello" }, {"task_id": "uuid2", "user_output": "helloooo"} ] }

you'd just get back:

{"action": "get_tasking", "tasks": [ {maybe some tasks} ], "responses": [ responses to each message ] }

Hopefully that makes the first part easier.

For uploads and downloads. For upload (transfer of file from Mythic -> Agent), your agent should already have the file_id it wants to fetch, so it can start fetching from chunk 1 right away. https://docs.mythic-c2.net/customizing/hooking-features/action-upload#example-agent-pull-down. So, you specify your file_id and chunk number, and you start fetching. I'm not sure what extra round trip you're referring to here.

For downloads (transfer of file from Agent -> Mythic), you're right. There's an extra round trip to get the initial file_id so you can start transferring data. That said, now that you point that out, https://github.com/its-a-feature/Mythic/actions/runs/10675952576 <-- once that finishes, you can pull down the latest Mythic, run make and sudo ./mythic-cli build mythic_server to rebuild to get the latest server component, and I added a new feature in there for you. When you send your first message, you can send the following:

"download": {
            "total_chunks": 4, 
            "full_path": "/test/test2/test3.file", // optional full path to the file downloaded
            "host": "hostname the file is downloaded from", // optional
            "filename": "filename for Mythic/operator if full_path doesn't make sense", // optional
            "is_screenshot": false, //indicate if this is a file or screenshot (default is false)
            "chunk_size": 512000, // indicate chunk size if intending to send chunks out of order or parallelized
            "chunk_num": 1,
            "chunk_data": "base64_blob==",
        }

^ notice that you're saying how many total_chunks there will be, and, you're sending chunk_num with chunk_data. You'd get back your normal:

{
        "status": "success",
        "file_id": "UUID Here"
        "task_id": "task uuid here",
    }

This will allow you to cut out that one extra round trip and start sending data with the initial registration message.

Thoughts?

ChoboSyk commented 2 weeks ago

For the download feature the fix is perfect thank you!

I have Download implemented already but not Upload and just re-read the documentation and missed the part about using create_tasking so the first request includes file data directly instead of just file_id and file_path. I thought that the implant had to first send a request to Mythic specifying which chunk it wants with a file ID. Implying that the first round trip would just be mythic telling the implant to start the upload process. With the create_tasking trick I think the issue is solved and I can just tell my implant to start its upload requests at chunk n.2 as the first one came with the tasking itself.

For the fusion of get_tasking and post_response I didn't know that feature existed and it's almost perfect but still doesn't resolve my need to decrypt messages on the fly. One issue I find of External C2 configurations is that when an operator sends a new command during the sleep time of a beacon, the get_tasking message has already been posted and thus the operator needs to wait for an extra callback for the task to be fetched. If I use the feature the following happens:

  1. Beacon Uploads a Get-Tasking + responses frame
  2. C2 Fetches that frame and and uploads the tasks that were queued up + responses
  3. Beacon sleeps
  4. Operator pushes a new command
  5. Beacon wakes up and fetches the frame but it does not contain the command from n.4 as the get-tasking was done before the operator made the command

With C2s that communicate only a few times a day this can be pretty frustrating.

What I'm hoping to do, is receive the response + get-tasking at the same time. Post the response so the operator can see the result of his previous task, but hold on making the get-tasking request for as long as I know the beacon will be sleeping. The server will then make it ~1 min before the beacon is planned to check for it's next frame and push that fresh get-tasking so an operator doesn't have to wait the extra round trip. This is a an issue proper to extC2s but I've had to work/write a few for CS and it would tilt me every time lol. With CS I'd write a utility script that would simulate the beacon sending a null frame forcing CS to push the new tasking in it's socket connection. Always found that pretty wonky and I'm trying to make something more seamless with Mythic. I don't really see a way to keep that logic separate from my c2 profile though.

its-a-feature commented 2 weeks ago

So, if I'm understanding your flow correctly, you have:

ChoboSyk commented 2 weeks ago

Yup exactly. When the agents sleeps hours at a time this can become blocking instead of a small inconvenience when agents communicate fairly frequently.

its-a-feature commented 2 weeks ago

ok, i know this C2 is communicating very infrequently, but how important is message size?

Mythic has the idea of PushC2 and PushC2OneToMany. In both cases, the C2 Profile has a held-open gRPC connection to Mythic and is getting a stream of messages destined for the callbacks at the other end. You can still send get_tasking and post_response messages like normal, but this would allow your C2 Profile to collect tasks as they come in and send them off to your dead drop 3rd party service. Your agent would still reach out to the dead drop as necessary and fetch tasking. The only complication is that each new task would be a new message, so I'm not sure if the agent could get multiple messages from the dead drop at once or if that would break what you're doing here

ChoboSyk commented 2 weeks ago

I'm trying to limit the amount of requests made by the agent. With a simple channel like GraphApi + Onedrive you'll have a max size of ~200mb per frame so you'll naturally want to stuff all the tasks into a singular frame to limit the traffic of the agent to a single download. With a more throttled channel like a messaging system or wtv you'll often have frame limits of a few kb. In those cases I'd still prefer to have Mythic generate a single frame that is bigger than the max channel size and have the C2 profile handle the chunking on an encrypted blob. If I end up uploading a new frame everytime an operator pushes a new task, I could end up with the agent making multiple download requests for what could actually fit in a single.

So yeah I'd like Mythic to create the least amount of frames to be exchanged possible so the c2 can do the chunking in an optimal way and not on a per command basis.

In a way I'm trying to simulate a Push server that pushes based on a timer that is synced with the beacon sleep patterns. Making sure the server only pushes its tasks frames right before the beacon wakes up to fetch it.

its-a-feature commented 2 weeks ago

From your C2, how do you know an agent is about to wake up and fetch tasking?

ChoboSyk commented 2 weeks ago

When the beacon sends the get-tasking + responses frame it appends it's next sleep duration to the message. The server side generally has a lot less request throttling restriction so it'll get the frame relatively in real time and be able to calculate to a few minutes off when the beacon will wake up.

its-a-feature commented 2 weeks ago

Ok, I think I'm tracking now. I appreciate you providing some context! I think you're right in that Mythic's current feature set doesn't quite cover this case. Of the following two, which do you think is the most beneficial to your work:

ChoboSyk commented 2 weeks ago

Any of these solutions would work for me. The RPC calls would allow me not to have useless crypto code in the C2 code so probably the better solution if it's not too much trouble on your end. No key exchange as of now.

Thank you so much for taking the time to help with such a funky edge case. The tool you built is amazing and the support you give is honestly ridiculous for something free.

its-a-feature commented 2 weeks ago

I'll try to get that added this week for you.

its-a-feature commented 2 weeks ago

Ok, you'll need to fetch the latest Mythic and the latest either PyPi or Go package that you're using for the C2 Profile that communicates with Mythic, but I think I got it updated where you specify the payload, staging, or callback id and the c2 profile name and you should be good to go

ChoboSyk commented 2 weeks ago

Hey, I just tried the RPC change and I'm still having an SQL error when providing the profile name with a payloads UUID. I'm probably forgetting to update something on my end. This is my setup.

  1. Created a Payload with the HTTP profile for ease of testing

image

image

  1. Updated my C2 profile docker to use the latest MythicContainer package with the feature added. This test profile is a simple HTTP server that attempts to decrypt the frame and output it back.

image

  1. Image builds successfully but when I try to decrypt a check-in message using the payload UUID + c2 profile name I get the SQL error .

image

  1. Forwarding the same check-in to the real HTTP listener works so the crypto should be good.

image

  1. Mythic itself is currently on this commit. Rebuilt the CLI and restarted the server after the latest pull

image

Would you have any idea what the issue might be?

its-a-feature commented 2 weeks ago

if you click the hamburger icon in the top left of Mythic's UI, what does it show for the Mythic server version and the UI version? Should be:

Mythic Version: v3.3.1-rc6
UI Version: v0.2.40

Also, which version of the PyPi package did you install? v0.5.10? Did you make sure to rebuild that container too (sudo ./mythic-cli build [c2 name])?

ChoboSyk commented 2 weeks ago

I have these version and the docker has been rebuilt:

Mythic Version: v3.3.1-rc6
UI Version: v0.2.38

For PyPi do you mean which version of MythicContainer is used in the c2 profile server code?

For the actual web server doing the RPC call it's using

image

The mythic_go_service binary is using the previous version still . Would that be the issue?

image

its-a-feature commented 2 weeks ago

Where are you seeing the mythic_go_service binary? That shouldn't be in use in what you're doing. If your compiled code that you're using to make the RPC call is using 1.4.5 though then I think that should be fine. I'll have to look into this more. I don't see where you're getting that sql error anymore.

ChoboSyk commented 2 weeks ago

The server code making the RPC is 1.4.5. I added changes to the logs to be 100% and the build would break otherwise I believe as the C2Profile param of the json object wouldn't exist. The mythic_go_service runs in the docker in parallel to the actual webserver. It contains the code to register the c2 with mythic from what I understand.

Service Binary image

Web server image

Request image

What is current running the docker: image

Hopefully this isn't some dumb mistake on my end lol thanks for looking into it

its-a-feature commented 2 weeks ago

Can you try one more thing for me - run:

sudo ./mythic-cli config set debug_level debug
sudo ./mythic-cli build mythic_server
sudo ./mythic-cli logs mythic_server -f <-- this will hang and stream output from the mythic_server container

then re-run your example and see if any errors are getting logged that look like they're related to this

ChoboSyk commented 2 weeks ago

Getting the same error without much more info in the logs image

I did some tests and hardcoded the payload UUID instead of using the Callback's present in the messages and the decryption works for all messages after the check-in one. The issue only happens for the check-in message and the server's answer to it. At first I thought it was because the row in the DB didn't exist yet for the payload when the check-in comes in but even if I replay the check-in after successfully decrypting a get-tasking with the same payload UUID I still get the SQL error. Not sure what is different about the check-in. The server accepts it successfully so the crypto should be good.

its-a-feature commented 2 weeks ago

Ok, your mythic server isn't updated. You did a git pull on the master branch right? Then a sudo make and a sudo ./mythic-cli build mythic_server? I'm confident that it's not quite updated because that error in your screenshot points to line 113: https://github.com/its-a-feature/Mythic/blob/bec5422883b3c87cb0c30967e3305b5826c623ba/mythic-docker/src/rabbitmq/recv_mythic_rpc_callback_decrypt_bytes.go#L113 ^ in the old code, that makes sense In the latest master branch though, https://github.com/its-a-feature/Mythic/blob/master/mythic-docker/src/rabbitmq/recv_mythic_rpc_callback_decrypt_bytes.go#L113, it's not a logging message

ChoboSyk commented 2 weeks ago

I really thought I did but clearly not. I had the latest version pulled but I somehow failed to rebuild I guess. It works now. Sorry about that!

Thanks again for all the help :)

its-a-feature commented 2 weeks ago

Glad it's working now!