TryHackMe Mayhem—Writeup

Welcome back to my writeup! Today I will show you the step-by-step on how I solved Mayhem from TryHackMe!
File Analyzation
The challenge provides a .pcap
file, which I examined using Wireshark for network traffic analysis.

Initially, the captured network data reveals HTTP traffic, which appears to come from a Python-based web server. Upon closer inspection, we can see that two files—Install.ps1
and notepad.exe
—are being transferred over port 1337.
As I dug deeper into the packet data, I came across this particular traffic.

Once the transfer of notepad.exe
is complete, additional HTTP traffic emerges—this time strictly over port 80. A series of POST requests are made at consistent intervals, and the data within these requests seems to be encrypted.
Next, I proceeded to extract the HTTP objects for a more in-depth analysis.

I began by reverse engineering notepad.exe
, but it didn’t reveal anything useful.
Given that this is an executable file, I had a strong suspicion it might be malicious. To verify, I generated its MD5 hash using md5sum
and then submitted it to VirusTotal for a malware scan.

Just as I suspected, the file was flagged as malicious. Interestingly, it’s linked to Havoc, a command-and-control (C2) framework. This suggests that the file might function as a beacon—essentially a small, stealthy agent designed to maintain communication with a Havoc C2 server. These beacons usually run silently in the background, regularly reaching out to the server to check for new commands or deliver updates, allowing an attacker to remotely manage the compromised system.
At this stage, I wasn't sure how to analyze traffic from a Havoc beacon. Fortunately, I came across some documentation that provided the answers I was looking for.
The traffic is encrypted, with Havoc using AES in CTR mode for encryption. The key material, including both the AES key and IV, is also transmitted along with the traffic. Given that we’re dealing with HTTP traffic, it’s likely that we have access to this data.
Additionally, a specific "magic byte" is included to help identify the beacon traffic. While reviewing the Havoc C2 GitHub repository, I discovered the definition of the 0xDEADBEEF magic value in the Defines.h
file, which confirmed its role in recognizing the traffic.
The next step involves creating a program to decrypt the communication between the C2 server and the client in order to answer the task’s questions. The Havoc C2 server exchanges keys during the process, and if we manage to capture them, we can use them to decrypt the traffic. Fortunately, I found an existing script on GitHub that is specifically designed to decrypt traffic between a Havoc C2 server and an agent, which proved to be very useful for this task.
When we execute it, this is the result we get.

We got the Key and IV, and also the magic bytes deadbeef
.
By examining the request, you'll notice these commands.
COMMAND_NOJOB
COMMAND_MEM_FILE
COMMAND_PROC
The next step I took was to create my own script, one that not only reveals the key but also decrypts the entire traffic.
import pyshark
from Crypto.Cipher import AES
from Crypto.Util import Counter
from binascii import unhexlify, hexlify
import struct
from colorama import init, Fore, Style
# Initialize colorama for colored terminal output
init(autoreset=True)
# Configuration
PCAP_PATH = 'traffic.pcapng' # Path to the PCAP file
AGENT_SESSIONS = {} # Dictionary to store agent session information, such as AES key and IV
# Mapping of command IDs to human-readable command names
COMMAND_MAP = {
1: 'GET_JOB', 10: 'NO_JOB', 11: 'SLEEP', 12: 'PROCESS_LIST',
15: 'FILESYSTEM', 20: 'INLINE_EXECUTE', 21: 'START_JOB',
22: 'INJECT_DLL', 24: 'INJECT_SHELLCODE', 26: 'SPAWN_DLL',
27: 'PPID_SPOOF', 40: 'TOKEN', 99: 'INIT', 100: 'CHECKIN',
2100: 'NETWORK', 2500: 'CONFIG', 2510: 'SCREENSHOT',
2520: 'PIVOT', 2530: 'TRANSFER', 2540: 'SOCKET',
2550: 'KERBEROS', 2560: 'MEMORY_FILE', 4112: 'SHELL_COMMAND',
4113: 'PS_IMPORT', 8193: 'INLINE_ASSEMBLY',
8195: 'LIST_ASSEMBLY_VERSIONS'
}
# Function to convert hex string to bytes
def hex_to_bytes(hex_str):
return unhexlify(hex_str.replace(':', ''))
# Function to decrypt data using AES CTR mode
def decrypt_data(key, iv, encrypted):
# Set up the counter for AES CTR mode based on the IV
counter = Counter.new(128, initial_value=int.from_bytes(iv, 'big'))
cipher = AES.new(key, AES.MODE_CTR, counter=counter)
return cipher.decrypt(encrypted)
# Function to parse the Havoc header and extract key information
def parse_havoc_header(data):
if len(data) < 20:
raise ValueError("Insufficient data for header")
size, magic, agent_id, cmd_id, mem_id = struct.unpack(">I4s4sI4s", data[:20])
return {
'size': size,
'magic': hexlify(magic).decode(),
'agent': hexlify(agent_id).decode(),
'cmd_id': cmd_id,
'mem_id': hexlify(mem_id).decode(),
'command': COMMAND_MAP.get(cmd_id, f"UNKNOWN_{cmd_id}")
}
# Function to display session keys for a new agent
def display_session_keys(agent_id, key, iv):
print(f"{Fore.GREEN}[+] New Session Key Stored")
print(f" Agent ID : {agent_id}")
print(f" AES Key : {hexlify(key).decode()}")
print(f" AES IV : {hexlify(iv).decode()}")
# Function to process the response for a GET_JOB command
def process_job_response(response, agent_id):
try:
# Convert the file data to bytes
raw_data = hex_to_bytes(response.get('file_data', ''))
command_id = struct.unpack('<H', raw_data[:2])[0]
decrypted = decrypt_data(
AGENT_SESSIONS[agent_id]['key'],
AGENT_SESSIONS[agent_id]['iv'],
raw_data[12:] # Skip the header portion of the raw data
)
output = decrypted.decode('utf-16le', errors='ignore') # Decode the decrypted output
print(f"{Fore.CYAN}[=] Server Response to GET_JOB")
print(f" Command : {COMMAND_MAP.get(command_id, f'UNKNOWN_{command_id}')}")
print(f" Output : {output.strip()}")
except Exception as e:
print(f"{Fore.RED}[!] Failed to process GET_JOB response for Agent {agent_id}: {e}")
# Function to analyze each HTTP stream
def analyze_stream(stream_data, stream_id):
try:
request = stream_data['request']
response = stream_data['response']
raw_packet = hex_to_bytes(request.get('file_data', ''))
header = parse_havoc_header(raw_packet)
# Print the stream and packet information
print(f"{Fore.YELLOW}{'-'*60}")
print(f"{Fore.MAGENTA}[STREAM ID: {stream_id}] Analyzing HTTP Stream")
print(f"{Fore.BLUE}[+] Request Info")
print(f" URL : {request['uri']}")
print(f" Method : {request['method']}")
print(f"{Fore.BLUE}[+] Havoc Packet Info")
print(f" Agent ID : {header['agent']}")
print(f" Command : {header['command']} (ID: {header['cmd_id']})")
print(f" Magic : {header['magic']}")
print(f" Mem ID : {header['mem_id']}")
agent_id = header['agent']
payload = raw_packet[20:]
# Handle the INIT command and store the session key and IV
if header['command'] == 'INIT':
key = raw_packet[20:52]
iv = raw_packet[52:68]
AGENT_SESSIONS[agent_id] = {'key': key, 'iv': iv}
display_session_keys(agent_id, key, iv)
# Process GET_JOB command responses
if header['command'] == 'GET_JOB' and agent_id in AGENT_SESSIONS:
process_job_response(response, agent_id)
# Decrypt and display ASCII payload for other commands
if agent_id in AGENT_SESSIONS and payload:
try:
decrypted = decrypt_data(
AGENT_SESSIONS[agent_id]['key'],
AGENT_SESSIONS[agent_id]['iv'],
payload
)
ascii_output = decrypted[16:-16].decode('ascii', errors='ignore')
print(f"{Fore.CYAN}[=] Decrypted ASCII Payload")
print(ascii_output)
except Exception as e:
print(f"{Fore.RED}[!] Failed to decrypt ASCII payload: {e}")
except Exception as e:
pass
# Function to process the PCAP file and analyze HTTP traffic
def process_pcap_file():
print(f"{Fore.CYAN}[~] Starting PCAP Analysis: {PCAP_PATH}")
try:
streams = {}
capture = pyshark.FileCapture(PCAP_PATH, display_filter='http')
for packet in capture:
try:
stream_id = packet.tcp.stream
if stream_id not in streams:
streams[stream_id] = {'request': None, 'response': None}
# Extract HTTP request and response data from packets
if hasattr(packet.http, 'request_method'):
streams[stream_id]['request'] = {
'method': packet.http.request_method,
'uri': packet.http.request_full_uri,
'file_data': getattr(packet.http, 'file_data', None)
}
elif hasattr(packet.http, 'response_code'):
streams[stream_id]['response'] = {
'code': packet.http.response_code,
'file_data': getattr(packet.http, 'file_data', None)
}
# Analyze the stream if both request and response are captured
if streams[stream_id]['request'] and streams[stream_id]['response']:
analyze_stream(streams[stream_id], stream_id)
streams[stream_id] = {'request': None, 'response': None}
except Exception as e:
print(f"{Fore.RED}[!] Packet processing error: {e}")
capture.close()
except Exception as e:
print(f"{Fore.RED}[!] Failed to parse PCAP file: {e}")
# Main execution
if __name__ == '__main__':
process_pcap_file()
Run and we should obtain the complete decrypted traffic.
Now, let's answer the questions based on the output generated by the script I created.
What is the SID of the user that the attacker is executing everything under?

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

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

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

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

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

We've successfully solved the Mayhem!
Last updated