
7 February 2022
Author: Frédéric Bourla
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:
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:
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:
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 download exfil.tar command received by the beacon [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: