Designing a DFIR challenge around DNS key exfiltration
Operation RedShadow started as a NexZero forensics challenge with a key hidden in TCP port numbers. Rebuilding it as v3 for FLAGS CTF meant moving the key into DNS queries spread over six hours, letting file timestamps carry the timeline, and removing every shortcut that let a player skip the investigation.
Operation RedShadow is a ransomware investigation I wrote as a forensics challenge. The player gets one damaged file and has to work out, from a disk image and a day of network traffic, how an attacker got onto a server, what was encrypted, and how the key left the building. The first version ran at NexZero, the CTF of the Nexus security club at ESTIN. The third ran at FLAGS CTF, organised by the IT Club in Blida, and most of what changed between them came from a single rule: every artifact has to agree with every other one, the way real evidence does.
This post is about how the challenge was built. The solve path belongs to the players.
Scenario
Both versions open the same way. The file is a JPEG with its first two bytes damaged, and a gzip archive appended after the image data ends. Repairing the signature and carving the archive gives the evidence bundle: a disk image of the compromised server, a packet capture, and one more file that points the investigator somewhere.
From there the story is a small company’s database server. An SSH brute force from one address succeeds on a user called zuko. The attacker downloads a script, encrypts the customer database with AES-256-CBC, leaves a ransom note and sets up a cron beacon to a C2 server. In the intended story the key to the database never touches the disk. It leaves through a covert channel, and finding that channel is the hard part.
Problems in v1
Version one hid the key in TCP destination ports: four packets to ports 582, 565, 577 and 589. Drop the leading 5 and the rest is ASCII, R, A, M, Y. A hint image, white on white in the blue channel, gave the player a Wireshark filter with the four exact packet timestamps.
Playing it back as an investigator, three things were wrong.
The hint did the investigation for the player. A filter with four exact epoch times is the answer to “which packets matter”, and that question is the whole point of a network forensics stage.
The disk gave the key away. The attacker’s script had been shredded from the home directory, but a staged copy under /usr/local/bin held the full openssl command line, -K included. A player who read that file had the key without ever opening the capture.
And the evidence contradicted itself. The attacker’s .bash_history ended with history -c, yet the commands were all still in the file. history -c clears bash’s in-memory list, and that list is what gets written to the file at logout, so a session that ran it wouldn’t leave those lines behind. A careful player notices that, and from then on trusts nothing in the image.
DNS key exfiltration in v3
In v3 the key travels as DNS queries, which is how real exfiltration tooling moves small secrets out of networks that watch their web traffic. The capture has 3,195 packets, 120 of them DNS. Eight of those are queries for names under sync.cdn-relay-01.net, a domain that appears nowhere else in the capture and never gets a response, because the attacker only needs the queries to arrive.
Each name starts with a two-hex-digit sequence number and eight hex digits of key, four bytes. Eight queries carry 32 bytes, an AES-256 key, and sorting by the sequence number puts them back in order.
What makes this stage an investigation is the timing. The eight queries are spread over about six hours, with 25 to 70 minutes between them, the low-and-slow pattern exfiltration tooling uses to stay under burst-based detection. Scrolling the DNS view shows noise, then one odd name, then more noise. Nothing clusters, and no hint points at timestamps. The player finds the channel by doing what an analyst does: reading the names, noticing one domain that’s queried and never answered, and filtering on it.
The IV isn’t exfiltrated at all. It’s the first 16 bytes of clients.db.enc, which is how a lot of real implementations store it, since an IV has to be unique and doesn’t have to be secret. The player strips those 16 bytes and decrypts the rest.
Timestamps
In v3 the file modification times tell the story on their own, so a player who sorts the disk by mtime gets the incident in order:
| mtime | file | what it says |
|---|---|---|
| 2019-11-20 | clients_2019_archive.sql.bak |
an old backup, a decoy |
| 2023-08-01 | /etc/passwd, /etc/crontab, backup.sh |
the server was built |
| 2023-11-02 | /etc/sudoers.d/zuko |
the misconfiguration predates the attack by months |
| 2024-03-09 | zuko/notes.txt |
written days before the incident |
| 2024-03-14 13:53:18 | db_maintenance.sh |
overwritten by the attacker |
| 2024-03-14 13:56:47 | clients.db.enc, .mL_runtime.log |
encryption finishes |
| 2024-03-14 13:57:05 | crontabs/zuko |
persistence, right after |
| 2024-03-14 13:59:20 | zuko/.bash_history |
the session closes |
| 2024-03-14 18:30 to 18:35 | auth.log, admin/.bash_history |
the admin’s evening login |
Getting a timeline like that right means building the image the way the incident would have built it: files created in story order, with timestamps set to match. One detail I like is the admin’s own .bash_history, which restarts postgresql, while syslog records the same restart. Two independent logs agreeing is how an investigator gains confidence in a reconstruction, so the challenge rewards checking it.
Privilege escalation
Version three gives the privilege escalation a cause the player can find on disk. /etc/sudoers.d/zuko grants NOPASSWD on /usr/local/bin/db_maintenance.sh, and that script is writable by zuko. sudo trusts the path it was given and never looks at what the file contains, so the attacker checks sudo -l, confirms the script is writable, copies the ransomware over it and runs it through sudo. The cp is in .bash_history, and the script’s mtime matches it to the second.
The attacker in v3 doesn’t run history -c. They’re careless, which is realistic, and it means the history file and the rest of the disk agree. zuko/notes.txt explains the password reuse that let the brute force land, and it’s dated days before the attack, so it reads as something the user wrote and the attacker never touched.
Background activity
A disk with only the attacker’s files on it teaches nothing about triage. v3 has other users, admin and mona, with clean and unrelated home directories, an old SQL backup next to the encrypted database, and the ordinary contents of syslog, /etc/hosts and /etc/hostname. The brute force in auth.log is 379 attempts with mixed usernames, the way bots actually spray, before the one success on zuko at 13:47:32. Each of these is something a player has to read and rule out, which is most of the work in a real case.
Steganography stage
The steganography in v1 was a white image where the hidden text sat at blue value 254 against 255. That isolates one channel, and it’s a trick a tool reveals in one click. v3 uses LSB steganography across the combined RGB channels in row-major order, with a 32-bit big-endian length header before the message, which is the layout real tools look for first.
The carrier is a real photo, converted from JPEG to PNG before embedding. That conversion isn’t cosmetic. JPEG compression is lossy, and saving the image as JPEG again would destroy the least significant bits that carry the message. The recovered message doesn’t hand over any values either. It tells the investigator, in the attacker’s words, that the data now goes out as “small questions, one after another, nobody stops to check where they’re really headed”, which is a hint at DNS only for someone who already thinks like an analyst.
Design rules
- A hint can describe the attacker’s behaviour, but never point to a packet, an offset or a file.
- Every secret should have exactly one way out, and the disk should never hold a copy of what the network stage is meant to recover.
- Before release, sort the image by mtime and read the capture in time order. If either one tells a different story from the write-up, fix the evidence.
The original RedShadow and RedShadow v2, the DNS tunnelling version, both live in NEONGYM, the practice mode of NeonHat, so players can compare the three versions of the same case.