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 34.65.77.169, 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 [CTRL
+ALT
+SHIFT
+H
], we can see 2 types of interesting packets:
- Some
GET
requests tohttp://34.65.77.169/pixie
that 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 34.65.77.169… 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://34.65.77.169: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 ZlNH
:
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]:
So, the ZlNH
file which was downloaded from the suspicious IP 34.65.77.169
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
HTTP
beacon which “phones home” on port 8080. - It tries to hide its traffic by using the same
user-agent
as Internet Explorer 10 on Windows 8. - The beacon carries out
GET
requests onhttp://34.65.77.169/pixel
to 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
POSTed
to http://34.65.77.169/submit.php
. - 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 AES
and HMAC
keys:
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:
- A
getuid
command [packet 5217, in response to theGET
request from packet 4915]. - A
ps
command [packet 43734, in response to theGET
request from packet 43728]. - The
ls C:\Users\
command [packet 55178, in response to theGET
request from packet 55174]. - 2 stacked commands,
cd C:\Users\frogito\Documents
andexecute tar -cvzf exfil.tar *.txt
[packet 68218, in response to theGET
request from packet 68198]. - The
download exfil.tar
command [packet 98041, in response to theGET
request from packet 98035]. - And finally 3 stacked commands that involved 2 unknown operations on the file
\\127.0.0.1\ADMIN$\540f232.exe
before deleting it [packet 145194, in response to theGET
request from packet 144676].
The 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 LPE
or 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==34.65.77.169 && http.response==200 && http.content_length > 0" -e netmon.pcap
So, let’s dig the
command received by the beacon [download exfil.tar
CTRL
+G
-> 98041
]. By following HTTP
traffic, we see that the GET
request to http://34.65.77.169/pixel
ends on packet 98045:
And the next POST
to the C2
is right afterwards, at packet 98048
:
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: