HackTheBox's yearly University CTF is one of the most interesting capture the flag in my opinion. It always has great original challenges and themes. 2021 was the first edition I played, along with HackademINT. We ended-up finishing 11th in the qualifiers and 5th at the finals. There is usually 3 challenges of increasing difficulty in the forensics category each year, and Keep the steam activated was the hardest one of the 2021 qualifiers. I did not solve it in time, and got the flag around 20 minutes after the CTF ended, so we did not get the points. I got revenge in the finals, where I solved all forensics challenges in a few hours (and found an honest mistake in one of the flags).
The challenge involved analyzing a network capture to find out how an Active Directory domain had been compromised.
Initial Recon
We are provided with a single pcap file that can be opened in Wireshark. Let's have a quick glance at what is going on.
There are two IPs in play, 192.168.1.9
, and 192.168.1.10
, communicating back and forth through different protocols.
The exchange can be summarized as follows:
192.168.1.9
tries to establish SMB2 connections to192.168.1.10
with multiple usernames, and fails multiple times, as if it was enumerating username and password combination until one worked. It manages to open 2 sessions ascorp.local\asmith
, which are encrypted SMB3 traffic that cannot be decrypted at this point.- As that 2nd SMB3 session continues, some DCERPC packets are exchanged, just business as usual in an AD domain.
- A
rev1.ps1
file is downloaded from192.168.1.9
by192.168.1.10
using an HTTP GET request. The file extension hints at a PowerShell script. Red flag here. - An
n.exe
file is downloaded the same way. Red flag again. - Lots of raw TCP packets follow, which seem to contain valid text data.
192.168.1.9
then makes a few HTTP POST requests to awsman
endpoint on192.168.1.10
and establishes an encryptedspnego
session as\Administrator
on top of HTTP. Since192.168.1.9
has previously been refused to open a connection as Administrator, this is a red flag, and we might want to look at it more deeply latter. The user agent of the client points us towards a WinRM protocol client, so this is apparently a remote administration endpoint.192.168.1.10
downloads adrop.ps1
file from192.168.1.9
with an HTTP get request. Another PowerShell script, so a red flag again.192.168.1.10
does some HTTP requests to192.168.1.9
for files with innocent looking names, but with unusually long GET parameters.
Payloads analysis
That quick look lets us with the impression that 192.168.1.9
attacked 192.168.1.10
, starting with a brute-force attack.
Since the HTTP requests at step 3, 4 and 7 download executable files,
they are a good start to try figuring out how the attacked was conducted.
We can extract those files using Wireshark's "Save value" option in the packet dissector.
rev.ps1
is quite short, and therefore unlikely to contain anything useful.
It might just have been used to retrieve the executable file downloaded shortly after.
So let's have a look at n.exe
.
The easiest way to get something to start with would be to drop it into VirusTotal.
The community tab makes it quite clear that this is in fact Netcat.
That gives an indication as to why there are raw TCP that wireshark doesn't know how to decode in the pcap. It's probably data sent over TCP using necat.
Let's look at the last powershell script:
v o (New-Object IO.MemoryStream);
sv d (New-Object IO.Compression.DeflateStream([IO.MemoryStream]
[Convert]::FromBase64String('huge base64 string here'),
[IO.Compression.CompressionMode]::Decompress));
sv b (New-Object Byte[](1024));
sv r (gv d).Value.Read((gv b).Value,0,1024);
while((gv r).Value -gt 0){
(gv o).Value.Write((gv b).Value,0,(gv r).Value);
sv r (gv d).Value.Read((gv b).Value,0,1024);
}
[Reflection.Assembly]::Load((gv o)
.Value.ToArray()).EntryPoint
.Invoke(0,@(,[string[]]@())) | Out-Null
A quick read indicates that the chunk of text in the middle is compressed data encoded as base64. Let's decode it in CyberChef:
The data starts with the header of a Windows PE binary, so let's save the file as drop.exe. Virus total is then once again useful and tells us it is an exploit from a framework we can find on GitHub (Covenant). A quick look at the code reveals that the data exchanged is encrypted, and we did not see any evident flaw in the encryption. It is also clear that the remaining http requests are from that malware, but this is most likely a dead end because of the encryption.
Attacker process
We now have a rough idea of what happened:
- The attacker tried to open an SMB session with various users and passwords until one combinaison worked, using the unprivileged
asmith
account. - They used the sessions to download
netcat
and used it to exfiltrate some data - They used that data to log in as
Administrator
using WinRM - They used that WinRM session to download a Remote Access Tool, so they can control the machine with their preferred C2 Framework.
- They keep exploiting the system with Covenant.
With that in mind, the next logical step is to replicate what the attacker did to gain further knowledge. The attacker gets complete access to the system at step 3, so the interesting data probably lies at step 3 or 4. Step 1 is therefore unlikely to be interesting here.
Data exfiltration
Since the data at steps 3 and 4 are encrypted, let's look at the data filtered at step 2.
There may be multiple TCP connections in the huge chunk of TCP packets,
and Wireshark's capability to filter packets by TCP stream is useful to ensure none is overlooked.
The following filter only shows the TCP connection for HTTP connection that transferred the first powershell script and Netcat: tcp.stream eq 20
.
Starting there and Incrementing the TCP stream identifier,
it is possible to identify raw HTTP connections by their pink-gray color,
and to display them using Wireshark's follow TCP stream's function.
3 interesting clear text connections can be identified this way:
- N° 21 contains what looks like commands from a shell.
- N° 23 looks like a 46MB PEM certificate file. That size is way too big, this is a red flag.
- N° 24 is another such certificate.
Analysing the TCP stream n°21 shows output from the reverse shell the attacker got with the first powershell script.
We get the following additional information:
- The attacker indeed got access to the system as the
corp\asmith
account. - They dumped the active directory files to an
ntds.dit
database. - They indeed downloaded Ncat.
- They then base64 encoded the
ntds.dit
database and theSYSTEM
registry hive using thecertutil
tool. - They exfiltrated those two base64 encoded files with Netcat.
We then know that TCP streams 23 and 24 respectively contain
ntds.dit
andSYSTEM
. The PEM files can be saved to individual files with WiresharkSave as
option in theFollow TCP stream
vue. We can then decode them from base64, omitting the first and last lines which are the PEM headers and footers:
cat ntds.dit.pem | head -n -1 | tail -n -1 | base64 -d > ntds.dit
cat SYSTEM.pem | head -n -1 | tail -n -1 | base64 -d > SYSTEM
Data leakage exploitation
Looking for a way to extract data from the two files we got out of the reverse shell, I found this step-by-step guide. The third step featured a tool that did exactly what I needed. It required powershell, but since I had a Windows VM started already, I decided to go through with it.
We got plenty of information on the administrator account:
WinRM decryption
Now that we have some credentials, let's see if it is possible to decrypt the administrator WinRM session. After lots of internet research, I ended up finding this amazing winrm-decrypt gist that does exactly what's needed here, and does not require the password but only the hash. Unfortunately, the script did not work out of the box:
After some ugly python debugging, the main issue turned out to be that the way Wireshark (and by extension, Pyshark) organized the output for mime multipart messages was not the same as the one the script expected. Fixing that in the most rushed way possible, and suppressing a few exceptions, the script ended up working fine.
Looking at the output, it seems that the interesting part of messages is base64 encoded:
Writing down the most horrible python script in history solved that, and grep came to the rescue to print out the flag:
Thanks to HackTheBox for this alluring challenge!