Analysis of an Encoded Cobalt Strike Beacon
Last Updated: 2022-09-06 07:52:59 UTC
by Didier Stevens (Version: 1)
I also created a video for this diary entry.
Someone reached out to me for the analysis of a Cobalt Strike beacon. This is the sample.
My tool 1768.py (a tool to analyse Cobalt Strike beacons) isn't able to find the configuration:
When something like this happens, I always try option -r. Option -r is raw mode: by default, 1768.py analyses the relevant sections of a PE file, but in raw mode, it takes a look at the complete file.
But this too doesn't work.
What you can do in a case like this, is execute the sample inside a sandbox, make a process memory dump of it, and then have 1768.py analyse the process memory dump. This often works for obfuscated/packed samples.
But first, I took a look at the PE file with my tool pecheck.py, to see if I could recognize anything that my tool didn't catch.
And there is an overlay (data appended to the end of the PE file). This overlay has a high entropy, and it's 256 KB and represents more than 90% of the total size of the PE file.
Let's take a look at the sections of the PE file to confirm this:
These are indeed all small sections, the largest is 10 KB. So that's too small to contain a stageless Cobalt Strike beacon, but the overlay is large enough.
Let's take a look at the overlay:
That doesn't ring a bell to me. But it seems that there is a repeating byte sequence at the end. Let's take a closer look:
Indeed: there is a repeating sequence of 18 bytes here (I highlighted 2 of them in red and green). This often happens when a PE file is XORed with a key: the end of a PE file is often a series of NUL bytes (0x00), and thus it reveals the XOR key.
I did recover the XOR key with trial-and-error, but I'm not going to explain this here (I do explain it in the video, about halfway). What I did do, is update my tool xor-kpa.py that I use to perform XOR known plaintext attacks. I added a definition for the encoded public key header found in a Cobalt Strike configuration: cs-key-dot.
Let's try that:
And indeed, a key was found, and it's very likely a good key, because 15 extra bytes where found. This means that a repetition of the key found 15 extra bytes matching the cs-key-dot signature.
I now use option -d to let xor-kpa.py decode the payload with the XOR key that was listed as last (thus with the highest probability of being correct), and I feed this output into 1768.py:
And now we have recovered the configuration.