Intro
Interstellar C2 was one of the hard forensics challenges, not because there was anything super obscure or esoteric, but because there were just so, so many layers. We’re given a single Wireshark packet capture, where we quickly find a Powershell dropper being used. After reversing the dropper, we uncover the .NET implant, which is easy to reverse. The rest of the challenge entails understanding the code of the implant to ultimately find how it’s exfiltrating data, which is by appending a compressed then encrypted file to a PNG.
Description
We noticed some interesting traffic coming from outer space. An unknown group is using a Command and Control server. After an exhaustive investigation, we discovered they had infected multiple scientists from Pandora's private research lab. Valuable research is at risk. Can you find out how the server works and retrieve what was stolen?
Stage 1: Wireshark + Finding the Payload
We start off with a single Wireshark packet capture. Looking at the “Statistics” tab, we see that most of it is HTTP.
Finding what’s wrong also is not very hard, the very first request captured is downloading a Powershell dropper.
I can save all of the HTTP requests by doing File > Export Objects > HTTP
and saving them to a folder called artifacts/
. Looking at what I get, there’s a lot of stuff here.
We can start by reversing the PowerShell dropper. It’s really not all too obfuscated- if I just separate things by putting them on new lines, we get the below code.
The IP address 64[.]226[.]84[.]2200 is hosting a file with some random numbers and letters, which gets downloaded to the Windows Temp folder. That file is then decrypted with a hardcoded key and IV. Looking at the Microsoft Documentation, we see that it’s probably using CBC mode. We could decrypt the payload with this script, but I’ll rewrite stuff in Python.
If I run my script, we can see that we get a .NET executable back.
Stage 2: Reversing the Implant
I’ll move this over to my FLARE-VM which has dnSpy. We covered this in my writeup of Perfect Match X-treme from Sekai CTF, but because the executable is written in .NET, it’s much easier to reverse due to reliance on the Common Language Runtime. When we get the decompiled output back, it’s very clear that this is some kind of C2 implant or beacon.
I won’t go through every function here, but we can follow the execution of the program. The Main()
function directly calls a new function called Sharp()
.
This function looks like it’s checking if it’s safe to execute. If there’s a username, and we can reach out to the C2 server, we will continue execution, otherwise we wait and try 30 more times. The primer()
function comes next, where there’s two main parts that we care about. You can see full decompiles of some important functions in the Appendix.
The first part makes a web request to the teamserver.
The first few lines gather some information, and constructs a string out of them. This is then passed to a GetWebRequest
function, which will submit it as an encrypted cookie. The request is being made to the /Kettie/Emmie/Anni?Theda=Merrilee?c
endpoint, whose contents are decrypted and stored in text2
. From this, the base64 encoded value in text6
appears to be the encryption key, which we can confirm by looking at the Encryption()
function.
The control flow for this function is as follows:
array
stores the plaintext (unByte
)- If
comp
is true, we compress the data
- If
CreateCam()
initializes aSymmetricAlgorithm
object with a random IV in CBC mode.array2
stores the result of the encryption- We base64 encode the output and return it
If we look at what we got from Wireshark, we can see a huge block of base64 data in the request to that endpoint.
Knowing the key and the IV, we can decrypt.
Well what do you know, more base64. If we look at what the implant does with this output, we see some regex getting involved.
Stage 3: ImplantCore()
Reversing Core Functionality
Looking at the Decryption()
function, we see that it does decode the base64, so we can recreate this in Python to see what’s getting passed into ImplantCore
.
So we see we get a number of variables, but I’m not really sure what any of them do. Looking at the ImplantCore()
code, we get a better idea of what’s happening.
Here’s what we know:
baseURL
,RandomURI
, andstringURLs
all define the behavior of the implant as it communicates over HTTP- There’s also some kind of interaction with images happening with
stringIMGS
andImgGen
Key
is a new AES keyJitter
andSleep
define how long the implant spends asleep in between commandsKillDate
defines when the program should finally stop executing
This ImplantCore()
function defines the main loop of the implant.
The first part of this loop does a bunch of error handling, but most notably makes a request to a random URL, and then decrypts the output.
If the output from the decrypted string starts with multicmd
, it gets processed as a command that the implant will handle. As someone who has written their own implant before, this is pretty standard, the level of obfuscation is just the tricky part.
Looking at the HTTP requests we dumped out, because of how long the URI’s were, they were all named %3fdVfhJmc2ciKvPOC
. Of those files, the first one and the 14th file seem to have big base64 blobs that are genuinely worth investigating. I’ll write a function to parse the blob since we have 2, and who knows if we have more. We have the implant source code, so if we just do exactly what the implant does, we should be good.
Nice! Let’s look at the implant and see what calling these tasks does. loadmodule
seems to load a .NET assembly into memory.
loadpowers
seems to not exist in the binary, so we’ll circle back to that one, but run-dll
seems to do exactly what you think it does, which is run the DLL.
Investigating the Assemblies
Let’s dump out the assemblies and see what’s up.
If we check what file is what, we confirm we see .NET files.
If we stick this into dnSpy, the metadata immediately tells us what is what:
mal1-0.dll
is actually anexe
file called Core. Simply scrolling through the source code tells us that this is a PoshC2 implantmal1-1.dll
is a DLL calledPwrStatusTracker
which handles how Windows turns on and off? I’m not entirely sure.mal2-0.dll
is just a copy of SharpSploit, with Mimikatz in it
I definitely spent a while trying to reverse each of these .NET files, but ultimately, it doesn’t really get us any closer to the flag. What we really need to do is figure out what data is being exfiltrated.
Stage 4: Exfiltration
Analyzing Exfiltration Function
If we look at the Program.Exec()
function, it doesn’t actually execute anything, rather, it exfiltrates output data.
We encrypt the output and store this into array
. However, this array
variable is then passed into Program.ImgGen.GetImgData()
. If we look at the source code for this function, things start to come together.
We take a random image stored in the implant, and then pad it out to 1500 bytes with some garbage data (Encoding.UTF8.GetBytes(Program.ImgGen.RandomString())
). The encrypted output is then appended to the PNG as is. The while
loop in the Exec
function will then upload this image to the teamserver, with 5 attempts. One thing that’s important to note is that the call to Exec
will call the encryption function with compression, so we’ll need to do both to recover the data.
Recovering The Data
Looking though the artifacts we dumped from Wireshark, we can identify which ones are images.
We can then add some code to our decryption script to open the file, take the bytes after byte 1500, and then decrypt and decompress.
If we look at output-3.bin
, we see more base64 encoded data, so we can decode and see if anything changes.
I guess they put a PNG inside a PNG. If we open it, we finally find the flag.
Flag: HTB{h0w_c4N_y0U_s3e_p05H_c0mM4nd?}
There’s definitely more investigating to be done, such as finding the Mimikatz output, but I encourage you to go try the challenge on HackTheBox when they put it on the main site.
Appendix
Not putting all of the functions here, but mostly just the long ones that I didn’t want to flood the main writeup with.
primer()
ImplantCore()
Decryption()
Compress()