In this forensic challenge, a company has been compromised and their initial investigation led to a suspicious workstation. The CEO was very anxious about a potential exfiltration, and we were provided with a network dump of that workstation in the hope that we would be able to help him make some sweet dreams again.
After decompressing the
netmon.7z archive, we are facing a 514 Mb network dump which was carried out on the 20th of March 2023. This dump is in ETL format, so let’s first convert it to a PCAP through etl2pcapng:
etl2pcapng netmon.etl netmon.pcap
We can now open it with Wireshark for analysis. It turns out that nearly all the empty HTTP traffic involves the IP address 18.104.22.168, which could be an artifact of C2 beaconing [i.e., when an infected system periodically phones back a malicious server to see if there is any command to retrieve and execute]:
By following HTTP stream [
H], we can see 2 types of interesting packets:
http://22.214.171.124/pixiethat seem to have an encrypted cookie.
- And some POST requests that also seem to transport encrypted data.
Let’s have a look at heavier HTTP packets:
There are 285 packets, but most of them are related to uninteresting
OCSP traffic. Let’s filter them out:
On the 40 remaining packets, we can see again traffic from 126.96.36.199… So let’s also focus on them:
The first packet is indeed interesting, since it reveals that a file named
ZlNH has been downloaded from
http://188.8.131.52:8080 through a GET request at 15:18:04 GMT:
But maybe we missed something before, who knows? Let’s grab all the files that Wireshark can see:
File -> Export Objects -> HTTP
We can then run a YARA scan on them, for example with the signatures from Florian ROTH. Since we are still not sure about the nature of the threat, let’s use all the rules against the 654 objects exported from HTTP streams:
There is only 1 match, with the rule
Cobaltbaltstrike_Beacon_XORed_x86 against the file
It seems that we are facing a
Cobalt Strike infection, so let’s download the great tools from Didier Stevens and confirm this theory. Shellcodes generated by
Cobalt Strike and
Metasploit for their HTTP listeners all involve an URI whose last path segment consists of 4 alphanumeric characters. If those characters seem random for humans, they can still be identified by an 8-bit checksum [which is simply the sum of the ASCII value of those characters]:
ZlNH file which was downloaded from the suspicious IP
184.108.40.206 turns to really be an x86 beacon for
Cobalt Strike. Let’s dissect it with the great 1768 Python script from Didier, who made all the heavy analysis of
Cobalt Strike for us:
So, it turns out that:
- The infected host is connected to the C2 through a reverse
HTTPbeacon which “phones home” on port 8080.
- It tries to hide its traffic by using the same
user-agentas Internet Explorer 10 on Windows 8.
- The beacon carries out
http://220.127.116.11/pixelto check if there is any command.
- Those checks are carried out every minute [i.e., 60’000 ms without jitter].
- If there are pending commands, they are executed as part of a sacrificial process which is either
%windir%\syswow64\rundll32.exe[on 32 bits versions of Windows] or
%windir%\sysnative\rundll32.exe[on 64 bits versions].
- The results of executed commands are then
- The C2 seems to be running on
Cobalt Strike v4.
But the most interesting information is in section 0x0007, which informs us that the
Cobalt Strike beacon relies on a
public key for which a
private key is known. How is it possible? Simply because cracked versions of
Cobalt Strike are heavily used, and people tend to be lazy. If a
.cobaltstrike.beacon_keys file exists and gets reused, then the
RSA key pair which was previously generated during
C2 installation will also be kept. At the end of 2021, Didier found 6 distinct private keys on VirusTotal, and he added the corresponding
public keys to his script.
So, here we can see that the
public key used to retrieve
RSA-encrypted data [as part of
GET requests] belongs to [at least] one of the cracked versions of
Cobalt Strike that that have been uploaded to VirusTotal, and whose private key is now available to us.
The commands retrieved by the beacon through GET requests and the results which then get POSTed to the
C2 are all encrypted with AES, through a symmetrical key that was defined by the beacon itself during the initial infection. That key is subsequently sent to the
C2 during each GET request as part of an RSA-encrypted metadata [a cookie by default]:
Let’s extract this encrypted metadata:
We can then decrypt our BLOB thanks to the leaked private key:
So, it turns out that the workstation
WKSWIN10-2 behind IP
192.168.9.110 has been infected by the user
frogito when he ran the
Rechnung_20230324_253.pdf.exe file (probably tricked by the double extension). More interestingly, we can also see encryption materials, like the
raw key and the derived
So, let’s now focus on the (encrypted) commands sent by the C2 operator:
We can decrypt them since we now have the raw key:
We can now see the commands which were run by the
C2 operator on the compromised workstation, in that order:
getuidcommand [packet 5217, in response to the
GETrequest from packet 4915].
pscommand [packet 43734, in response to the
GETrequest from packet 43728].
ls C:\Users\command [packet 55178, in response to the
GETrequest from packet 55174].
- 2 stacked commands,
execute tar -cvzf exfil.tar *.txt[packet 68218, in response to the
GETrequest from packet 68198].
download exfil.tarcommand [packet 98041, in response to the
GETrequest from packet 98035].
- And finally 3 stacked commands that involved 2 unknown operations on the file
\\127.0.0.1\ADMIN$\540f232.exebefore deleting it [packet 145194, in response to the
GETrequest from packet 144676].
cs-parse-http-traffic script from Didier supports payloads extraction through the
-e parameter, but here it doesn’t really help since it does not recover the exfiltrated TAR file. The only file it can extract here [besides command names] is the the binary involved on the very last step, which was probably for sent to the beacon for
pivoting purpose [i.e., the 280 bytes long file called “payload-d25fa3f613987c80c998d956891f64a3.vir”]:
python cs-parse-http-traffic.py -r 3a98877c6e900b317ce536b1beadf773 -Y "http && ip.addr==18.104.22.168 && http.response==200 && http.content_length > 0" -e netmon.pcap
So, let’s dig the
command received by the beacon [
98041]. By following
HTTP traffic, we see that the
GET request to
http://22.214.171.124/pixel ends on packet 98045:
And the next
POST to the
C2 is right afterwards, at packet
So let’s apply a filter on that frame for a target decryption of the exfiltrated data:
Or less surgical, to directly attempt to decrypt all the traffic
POSTed to the C2 and extract payloads to disk:
In both cases, we are interested by the
DOWNLOAD_WRITE callback which wrote 153 bytes of data. This led to the newly extracted file named
payload-08d50b8c93fee1b70070f63ddfe6a5cb.vir, which can be unTARed as expected to reveal the content of exfiltrated data, hence the flag: