$ / 6 min read/forensics

Breach: Zero Day

Three answers buried in 2.7 MB of Windows security logs, a packet capture and a memory dump — most of it synthetic filler with a fingerprint that gives it away.

section
CSAW26
event
ctf.csaw.io ↗
category
forensics
status
● solved

Format: csaw{REDACTED} (case-insensitive) Exhibits: fenwick_capture.pcap, fenwick_security.xml, fenwick_memdump.txt

One component per exhibit — but each one is the second thing you’d reach for, not the first. That is the whole challenge.


0. Separating signal from filler

The synthetic data has a tell that identifies the author’s planted artifacts:

  • XML: planted events have timestamps ending .000Z — 16 of 3193 (~3 expected by chance, so ~13 are deliberate)
  • pcap: planted packets have 10 ms-round timestamps — 46 of 4236, in exactly four flows

This is the single most useful technique here and generalises to any synthetic-data forensics challenge. It reduces 3193 log events and 4236 packets to about twenty artifacts that matter.

1. The two chains

Chain A — benign decoy

23:58:05  Task \Fenwick\Backup\NightlyBackup Execute
23:58:09  DNS backup-vault.fenwick.local
23:58:10  POST /api/sync  {"id":"QkFDS1VQU1ZD"}  -> BACKUPSVC
23:58:43  7045 BkupAgent64 -> drivers\BkupAgent64.sys
23:58:48  SecurityHealthService stopped
00:04:05  Task ResultCode 0        <- completes cleanly
00:04:07  Agent stopped

Six minutes, clean exit. A real nightly backup job dressed to look alarming.

Chain B — the intrusion

01:13:41  DNS docs-fenwick-portal.com -> 52.112.198.39
01:13:42  POST /api/sync  {"id":"UkVMQVlTSEVMTA=="}  -> RELAYSHELL
01:14:15  7045 Afd4Eop12 -> drivers\Afd4Eop12_x64.dll
01:14:20  WinDefend stopped        <- NEVER RESTARTS
01:14:25  First TLS session to the C2
01:13–05:58  Beacon: 73 connections, ~237 s mean, CV 0.10

2. C2INDICATOR = RelayShell

Not the domain. The indicator is the implant’s own identifier, posted to the C2 in its first check-in:

POST /api/sync   Host: docs-fenwick-portal.com
{"id": "UkVMQVlTSEVMTA==", "v": "1.4"}   ->  RELAYSHELL

The decoy chain carries a structurally identical BACKUPSVC, which is how you know the pair is deliberate — the author built an A/B so you can tell which identifier belongs to the real intrusion.

Gotcha: the malicious POST is split across two TCP segments, so a naive regex over individual packets only catches the decoy’s id. Reassemble the stream first.

3. SERVICENAME = WinDefend

Not the installed service. Afd4Eop12 is the attacker’s dropped driver — it looks like the answer because it’s the anomalous 7045 install (registered as a kernel driver but pointing at a .dll, which is invalid). But the question asks for the service involved in the attack, and that is the one the attacker killed.

All WinDefend state changes:

20:12:07  stopped
20:12:44  running     <- 37 s, benign
01:14:20  stopped     <- 5 s after Afd4Eop12 installs, NEVER restarts
03:41:52  stopped
03:42:21  running     <- 29 s, benign

The discriminator is mechanical: two stopped events in a row with no intervening running means the service stayed down. That is the attacker’s kill, and it lands five seconds after the driver install.

SecurityHealthService is the decoy chain’s equivalent — stopped at 23:58:48 during the backup job.

4. BACKDOORNAME = ForestTiger

Not any one of the planted names — two of them joined.

The memdump contains exactly four simple string reversals (verified exhaustive against a 97k-word dictionary, a malware-name list, all 26 Caesar shifts, base64/base85, acrostic, and embedded substrings, forward and reversed):

Token Reversed What it is
ELUDOMDUF FUDMODULE real Lazarus rootkit
EGDEHREPPOC COPPERHEDGE real Lazarus RAT (CISA MAR-10288834)
REGIT TIGER not malware — ordinary word
TSEROF FOREST not malware — ordinary word

The brief says:

Public data shows that the patterns we are seeing could align with actions of a specific group, but we have not been able to identify which one. These groups are known to borrow from each other’s playbooks, so keep an eye out for any anomalies.

COPPERHEDGE and FUDMODULE are the borrowed playbooks — genuine, publicly documented tools planted to make you attribute the intrusion to Lazarus. The anomaly is that the other two are not malware names at all. They are fragments, and they concatenate:

FOREST + TIGER  ->  ForestTiger

The joke is that both halves are threat-actor naming conventions from different vendors — Microsoft’s “Forest Blizzard” (APT28, Russia) and CrowdStrike’s “…Tiger” suffix (India) — welded into one fictional name. Neither half means anything alone, which is exactly why they’re the odd ones out.


5. Solver

~/ctf/forenn/solve.py derives all three components mechanically:

planted reversals:
  7ff6000f02ed  TIGER        fragment
  7ff600100c29  FUDMODULE    decoy (real malware)
  7ff60011181c  COPPERHEDGE  decoy (real malware)
  7ff60011a103  FOREST       fragment

C2INDICATOR   RELAYSHELL    (beacon id posted to docs-fenwick-portal.com)
SERVICENAME   WinDefend     (stopped, never restarted)
BACKDOORNAME  ForestTiger   (non-malware fragments joined)

csaw{REDACTED}

The service is found by scanning 7036 events for two consecutive stopped states; the backdoor by filtering the reversed tokens against a known-malware list and joining whatever is left.


6. Post-mortem — why this took ~60 failed submissions

Every individual artifact was recovered correctly and early. The failure was entirely in mapping artifacts to slots, and all three errors share one cause: reaching for the most conspicuous item rather than the one the question asked for.

Slot What I submitted Correct Why I was wrong
C2 indicator docs-fenwick-portal.com RelayShell anchored on “indicator = address”. The beacon id was tested, but only paired with the wrong other two.
Service name Afd4Eop12 WinDefend took the anomalous installed service instead of the attacked one. Never tested WinDefend, despite having identified the permanent kill as the key event.
Backdoor name each of the four, separately ForestTiger found all four tokens and correctly identified FOREST/TIGER as the odd pair — even wrote “possibly concatenated as FORESTTIGER” once — but only ever offered it alongside the wrong C2 and service.

The compounding effect is the real lesson: with three slots and one wrong assumption in each, a correct guess in any single slot still returns “incorrect”, which reads as evidence against that guess. I burned two 16-cell grids (domain × service × name, then beacon-id × service × name) without ever varying the service slot away from the two 7045 installs — so the correct value was never in any grid I searched.

Transferable rules:

  • When a flag has N labelled slots, enumerate candidates per slot before combining. A wrong value in one slot makes every correct value elsewhere look wrong.
  • “Service involved in the attack” can mean the service attacked, not the service installed. Read the label literally.
  • If two recovered tokens are individually meaningless, try concatenating them before discarding either.
  • Planted real names (COPPERHEDGE, FUDMODULE) in a challenge that explicitly warns about “borrowed playbooks” are decoys by construction. The answer is the thing that isn’t publicly attested.

## challenge files

46 files · 318 KB
  • HANDOFF.md
  • acro.py
  • all7045.py
  • allhttp.py
  • beac.py
  • blob.py
  • caesar.py
  • candidates.txt
  • cov.py
  • d7045.py
  • dec2.py
  • deep.py
  • dns2.py
  • dnsdeep.py
  • +32 more
download .zip

flags redacted; flag images and local flag.txt files removed