Insomni’hack 2023 – hex-filtrate writeup

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 to http://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 on http://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 the GET request from packet 4915].
  • A ps command [packet 43734, in response to the GET request from packet 43728].
  • The ls C:\Users\ command [packet 55178, in response to the GET request from packet 55174].
  • 2 stacked commands, cd C:\Users\frogito\Documents and execute tar -cvzf exfil.tar *.txt [packet 68218, in response to the GET request from packet 68198].
  • The download exfil.tar command [packet 98041, in response to the GET 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 the GET 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 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: