TryHackMe Event Horizon—Writeup
Welcome back to another write-up! After recently finishing 2nd place in the Trend Micro uCTF, I’ve decided to keep sharpening my skills by tackling more challenges on TryHackMe. This time, I’ll be breaking down the Event Horizon room — a forensics-focused challenge that really puts your investigative skills to the test. In this write-up, I’ll guide you step by step through my thought process, the tools I used, and the techniques that helped me uncover the hidden clues.
So, grab your toolkit and let’s dive into Event Horizon!

For this challenge, we are given 2 files.
powershell.DMP and traffic.pcapng
Now let's analyze the pcap file using wireshark!
Question 1: The attacker was able to find the correct pair of credentials for the email service. What were they? Format: email:password
As I continue analyzing the network capture, we eventually come across SMTP traffic. Within this section, signs of a brute-force attempt become evident, and by the time we reach packet 4665, a successful login can be observed.

Next thing I did is to follow the traffic in TCP.

Here, we can see the Base64 encoded credentials, all we need to do is to decode it to reveal the email and the password.
echo "<base64_string>" | base64 -d

Question 2: What was the body of the email that was sent by the attacker?
From the analyzed TCP flow, we successfully retrieve the attacker’s email content.

Question 3: What command initiated the malicious script download?
Beneath the email, we notice a Base64-encoded string. To reveal the command, we first need to decode it, as this was the attacker’s method of delivering the script.
echo "<base64_string>" | base64 -d
Here's the output

Question 4: What is the initial AES key that is used for decrypting the C2 traffic?
While searching through the traffic for the malicious download, we find it located in packet 4722.

Because the script was not readable in its raw form, I made a Python tool to decompile and analyze it.
import base64, zlib, pathlib
# decode + decompress
data = zlib.decompress(base64.b64decode(pathlib.Path("b64.txt").read_text().strip()), wbits=-15)
pathlib.Path("program.bin").write_bytes(data)After running the script a new file named program.bin has been generated.

Since it’s a .NET binary, we can leverage specialized decompilers such as ILSpy, which makes the decompilation process straightforward.

Next thing I did is to transfer the binary from my Linux machine to my Windows machine to reverse engineer it.

After inspecting, we've successfully found the key.
Question 5: What is the Administrator NTLM hash that the attacker found?
This is where it's getting harder.
From my decompilation efforts, I determined that the sample I was working on was very likely a Covenant C2 agent. While analyzing Covenant, I quickly came across a tool known as CovenantDecryptor, which is designed to decode Covenant’s network traffic.
The challenge itself provided me with everything necessary to reconstruct and decrypt Covenant communications:
Network traffic containing Covenant communication, extracted from a packet capture.
The AES setup key, embedded inside the stage 0 binary.
A process minidump obtained from an infected host.
Based on the documentation of CovenantDecryptor and my own testing, the communication workflow can be broken down into three phases:
Stage 0
The agent begins by sending an RSA public key, which has been encrypted using the embedded AES setup key.
Before transmission, the data is formatted following the GruntHTTPStager structure with the type set to 0.
In response, the C2 server provides a SessionKey that is encrypted with the RSA public key.
Stage 1
use the AES setup key to decrypt the message, then apply the RSA private key to recover the SessionKey.
Using that key, the agent encrypts four random bytes and sends them, again formatted as type 1 in the stager.
The C2 then decrypts these bytes, appends four new random bytes of its own, and sends back the resulting eight bytes.
Stage 2
Decrypt the eight-byte message, verify that the first four matched what had been sent earlier, and then sent back the remaining four bytes to the C2 using the type 2 stager format.
The server performed the same verification, completing the handshake.
At this point, the session is fully established, and regular encrypted data can flow between the agent and the server.
The next step involves applying the decryption process outlined in the repository to analyze the C2 traffic. Prior to that, it is necessary to isolate the Stage0 POST payload. This request is first observed beginning at packet 4742.

Next thing I did is to save the content to a txt file.
It's time to use the CovenantDecryptor.
Retrieve the modulus value from the Stage 0 request made by the compromised host.
Using the AESSetupKey, the modulus of the RSA private key can be identified. To reach that point, the traffic must first be filtered. I relied on grep -Po to refine the captured data. The command is crafted to extract only the necessary values, isolating the modulus line (i=) and the portion that follows the Hello World! marker. This ensures that irrelevant HTML tags and other text are excluded, leaving only the key material exchanged between the client and the C2 server.
grep -Po '(^(i=.*))|(// Hello World! \K.*)' traffic.txt > traffic2.txtNow let's extract the modulus.
python3 decrypt_covenent_traffic.py modulus -i traffic2.txt -k "Secret_hehe" -t base64
Here's the output.

Recover the RSA private key from the minidump of the compromised Covenant process.
python3 extract_privatekey.py -i powershell.DMP -m "Modulus" -o <folder_to_save_the_key>
Here's the output

Retrieve the SessionKey contained in the stage 0 reply of Covenant C2, which secures the network traffic through encryption.
python3 decrypt_covenent_traffic.py key -i traffic2.txt --key <secret> -t base64 -r key/privkey1.pem -s 1
Here's the output

Now let's decrypt the communication
python3 decrypt_covenent_traffic.py decrypt -i traffic2.txt -k "<secret_new_aes_key>" -t hex
Here's the output

Question 6: What is the flag?
The final flag is in the decrypted output also, the 15th response message. For this we use cyberchef to decode it.

Here, we see that the magic header was PNG. So it's obviously a photo. All we need to do is to render it.

We've successfully solved Event Horizon!!!
Through careful traffic analysis, forensic investigation, and script deobfuscation, we were able to piece together the attacker’s activity. By tracing SMTP traffic, we identified brute-force attempts and the eventual successful login. Following TCP streams allowed us to extract the attacker’s email, where we uncovered a Base64-encoded payload that, once decoded, revealed the delivered command. Further inspection of the capture highlighted the malicious file download, which we later analyzed.
We then dealt with obfuscation by writing a Python script to unpack the attacker’s code, giving us clearer insight into its behavior. Diving deeper, we retrieved critical artifacts such as the RSA private key from a Covenant process minidump and the SessionKey from Covenant C2’s stage 0 response—both essential in understanding how encrypted communication was being handled.
This challenge demonstrated the importance of looking beyond surface-level indicators and leveraging forensic techniques to reconstruct the attacker’s chain of actions. Event Horizon was not only a test of technical skill but also a reminder of how persistence and methodical analysis can unravel even the most concealed traces of an attack.
Last updated