Introduction
Nearly every forensics guide, including the ones on this site, assumes a Linux target: an ext4 image, a pcap, a memory dump of a Linux box. Then a challenge hands you a Security.evtx, a SYSTEM hive, or a BitLocker-encrypted VHD, and none of the reflexes transfer. strings gives you nothing readable, file says data, and the tooling everyone recommends is a Windows GUI you do not have.
You do not need Windows. Every artifact in this guide can be parsed, cracked, mounted, and read from a Linux shell, usually with one tool per artifact type. What you need instead is the map: knowing that Windows keeps its evidence in a small number of specific places, in formats that are structured rather than textual, and that each of those formats has a dedicated parser.
Windows artifacts are not hidden. They are structured, and structure is invisible to the text tools most people reach for first.
This guide walks the five artifact families that actually appear in CTF challenges, with the Linux command for each. It pairs with disk forensics for the imaging and filesystem layer and Volatility 3 for memory.
The artifact map
When you are handed a Windows image or a loose file and do not know where to look, this is the order of value. It is sorted by how often the flag turns out to be there.
| Artifact | Where it lives | What it answers |
|---|---|---|
| Event logs | C:\Windows\System32\winevt\Logs\*.evtx | What happened, when, and under whose account |
| Registry hives | C:\Windows\System32\config\{SYSTEM,SOFTWARE,SAM,SECURITY} | Configuration, installed software, autoruns, USB history |
| User hive | C:\Users\<name>\NTUSER.DAT | Per-user settings, recent documents, typed paths |
| Prefetch | C:\Windows\Prefetch\*.pf | Which executables ran, how often, and when last |
| Recycle bin | $Recycle.Bin\<SID>\$I* | Deleted file names, original paths, deletion times |
| Memory image | *.raw, *.mem, *.dmp, hiberfil.sys | Everything the disk encrypted away |
Event logs (.evtx)
The .evtx format is binary XML: chunked, partially compressed, and completely opaque to strings. That opacity is why event log challenges frustrate people who try to grep them. Parse first, then grep.
pip install python-evtx# Dump the whole log as readable XMLevtx_dump.py Security.evtx > security.xml# Or, if you have the Rust reimplementation, it is much faster:evtx_dump -o xml Security.evtx > security.xml# Now ordinary text tools workgrep -c '<Event ' security.xmlgrep -oP '<EventID[^>]*>\K[0-9]+' security.xml | sort | uniq -c | sort -rn | head -20
That last command is the one to run first on any event log. It histograms the event IDs, and the histogram tells you what kind of story the log contains before you read a single record.
Event IDs are the vocabulary of this artifact. You do not need to memorise many, but these come up constantly:
| Event ID | Meaning | Why it matters |
|---|---|---|
| 4624 | Successful logon | Who got in, from where, and by which logon type |
| 4625 | Failed logon | Brute force attempts show up as bursts |
| 4657 | A registry value was modified | Persistence and configuration tampering |
| 4688 | A new process was created | The closest thing to a command history |
| 1033 | Application install completed | Carries free-text Manufacturer and Comment fields |
| 1074 | System shutdown initiated | Records which process and user requested it, plus a reason string |
| 7045 | A service was installed | Classic persistence, and a favourite of challenge authors |
Event Viewing is built directly on this vocabulary. Three events tell the story of a compromised host that keeps shutting down: an install (1033), a registry change (4657), and a shutdown (1074). Each carries one Base64 fragment of the flag, hidden in exactly the place the format invites, which is the free-text fields.
# Pull one event ID with surrounding contextgrep -B5 -A30 '<EventID[^>]*>1074<' security.xml# Sweep every Base64-looking blob in the log and try decoding eachgrep -oE '[A-Za-z0-9+/]{16,}={0,2}' security.xml | sort -u | \while read -r b; do printf '%s -> ' "$b"; echo "$b" | base64 -d 2>/dev/null; echo; done | grep -i pico
Registry hives
The registry is a set of binary tree files, one per hive, and mounting the disk gives you the files directly. There is no need for a Windows machine to read them; there is a mature Linux parser.
sudo apt install libhivex-bin libwin-hivex-perl registry-tools chntpw# Dump an entire hive to text (hivexregedit comes from libwin-hivex-perl)hivexregedit --export SYSTEM '\' > system.reg# Export the same thing with reged, from registry-toolsreged -x SYSTEM '\' '\' out.reg# Browse interactively insteadhivexsh SYSTEMchntpw -e SOFTWARE
The keys worth knowing, because they answer the questions challenges ask:
| Key | Answers |
|---|---|
| SYSTEM\CurrentControlSet\Control\ComputerName | What was this machine called |
| SYSTEM\CurrentControlSet\Enum\USBSTOR | Which USB storage devices were ever attached |
| SOFTWARE\Microsoft\Windows\CurrentVersion\Run | What starts automatically. The first place to look for persistence |
| SAM\Domains\Account\Users | Local accounts and their password hashes |
| NTUSER.DAT\Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs | What the user recently opened |
Two habits pay off. First, registry values are frequently stored as UTF-16LE, so a flag in a value looks like p.i.c.o.C.T.F. to a byte-oriented search. Always search both encodings:
strings -e l SOFTWARE | grep -i pico # UTF-16LEstrings -e s SOFTWARE | grep -i pico # ASCII
Second, when a hive comes out of a mounted image and looks truncated or refuses to parse, check for transaction logs beside it (SYSTEM.LOG1 and SYSTEM.LOG2). Windows writes changes there before merging, so a hive captured from a running system can be missing its most recent and most interesting edits.
file on the artifact settles it in one second.BitLocker volumes
BitLocker is full-volume encryption, and a BitLocker image looks like noise with a small readable header. Under Linux the workflow is a two-step: recover the key protector, then mount through dislocker, which presents a decrypted virtual NTFS file you then loop-mount normally.
When the volume is protected by a user-chosen password, the attack is an offline dictionary attack. The volume metadata contains a derivable hash, and hashcat has a mode for it.
sudo apt install dislocker# bitlocker2john is not a pip package: it ships with John the Ripper jumbogit clone --depth 1 https://github.com/openwall/john && cd john/src && ./configure && make -s# 1. Extract the crackable hash from the volumejohn/run/bitlocker2john -i drive.dd > hashes.txtgrep '$bitlocker$0$' hashes.txt > target.hash# 2. Crack it (mode 22100 is BitLocker)hashcat -m 22100 -a 0 target.hash /usr/share/wordlists/rockyou.txt# 3. Unlock and mountmkdir -p /tmp/dis /tmp/mntsudo dislocker -V drive.dd -u<PASSWORD> -- /tmp/dissudo mount -o loop,ro /tmp/dis/dislocker-file /tmp/mntls -la /tmp/mnt
BitLocker-1 is this exact sequence, and it is worth doing precisely because the sequence is fiddly and the individual steps are easy to look up wrong. The conceptual point it teaches is that full-disk encryption inherits the strength of whatever guards the key, so a weak password reduces AES-128 to a wordlist lookup.
When the disk is locked, take the RAM
The most important idea in this whole guide, and the one that generalises furthest: encryption protects data at rest. A mounted volume is not at rest. While the drive is unlocked, the operating system holds the key and the plaintext in memory, so a RAM capture walks straight past the encryption regardless of how strong the password was.
BitLocker-2 is designed to teach exactly this. The password gets strengthened so that cracking is off the table, and in exchange you are handed a memory image captured while the drive was mounted. The plaintext file contents are simply present.
# Fastest first pass: the flag may be sitting there in plaintextstrings -n 8 memdump.raw | grep -i 'picoCTF{'strings -e l -n 8 memdump.raw | grep -i 'picoCTF{' # UTF-16LE# Structured pass with Volatility 3vol -f memdump.raw windows.infovol -f memdump.raw windows.pslistvol -f memdump.raw windows.cmdlinevol -f memdump.raw windows.filescan | grep -i flag# Registry hives live in memory toovol -f memdump.raw windows.registry.hivelistvol -f memdump.raw windows.registry.printkey --key 'Software\Microsoft\Windows\CurrentVersion\Run'
The full treatment of memory analysis is in the Volatility 3 guide; everything there applies to Windows images directly, since Windows is Volatility's best-supported target.
SMB shares and network artifacts
SMB is the Windows file-sharing protocol, and the reason it shows up in CTF is that devices ship with anonymous access enabled and nobody notices. Network printers and NAS boxes are the usual offenders, and they accumulate whatever people accidentally sent them.
sudo apt install smbclient# List shares with a null session (no credentials)smbclient -L //TARGET -N# Connect to onesmbclient //TARGET/shares -Nsmb: \> lssmb: \> recurse ONsmb: \> prompt OFFsmb: \> mget *# Or mount it and use normal toolssudo mount -t cifs //TARGET/shares /mnt/smb -o guest,ro,vers=3.0
The Printer Shares series runs this ladder: part 2 and part 3 extend the same scenario, where a document sent to a print server is still sitting in a share nobody secured. In an assessment this is a genuine first stop rather than a contrivance.
Windows binaries and YARA
Windows executables are PE files rather than ELF, which changes the tooling slightly and the concepts not at all. Ghidra reads PE natively. The differences worth knowing:
| Concept | Linux ELF | Windows PE |
|---|---|---|
| Magic bytes | \x7fELF | MZ ... PE\0\0 |
| Imports | PLT and GOT entries | Import Address Table naming DLL plus function |
| Calling convention | System V: rdi, rsi, rdx, rcx | Microsoft x64: rcx, rdx, r8, r9 |
| Debugger | gdb | x64dbg, or gdb under Wine |
strcmp in a PE binary and read rsi out of habit, you get garbage. On Windows x64 the second argument is in rdx.The WinAntiDbg series (0x100, 0x200, and 0x300) are Windows binaries built around IsDebuggerPresent, which reads a flag the OS sets in the Process Environment Block when a debugger attaches. They are covered in depth in patching binaries and cracking crackmes; the short version is that 0x100 wants a register patched, 0x200 wants all three checks mapped before you attach, and 0x300 wants you to give up on debugging and edit the file.
PowerShelly covers the scripting side: a PowerShell transformation script plus its output, to be inverted step by step. Cumulative XOR is undone by XORing adjacent output lines, a seeded shuffle is undone by regenerating the same seed sequence, and a bit-encoding substitution is undone by reading the encoding table backwards. It is a custom-cipher problem wearing Windows clothes, and custom cipher reversing covers the general method.
YARA comes up when the task is not to analyse one sample but to describe a family of them. A rule is a set of conditions over strings and file structure:
rule suspicious_sample{meta:author = "ctf"description = "Catches the sample by its anti-debug imports and marker strings"strings:$mz = { 4D 5A }$api1 = "IsDebuggerPresent" ascii$api2 = "CheckRemoteDebuggerPresent" ascii$mark = "suspicious_marker" wide asciicondition:$mz at 0 and 2 of ($api*, $mark)}
yara -s rule.yar sample.exeyara -r rule.yar ./samples/
YaraRules0x100 asks for a rule that catches one specific sample when submitted to a remote harness. The design lesson is real: rules that pin exact byte offsets break on recompilation, while rules built from behavioural indicators (API names, packer artifacts, distinctive strings) survive it. Note also the wide ascii modifier above, which is the YARA answer to the UTF-16 problem that bit us in the registry section.
The Linux toolkit
Everything in this guide, installable in two commands.
sudo apt update && sudo apt install -y \libhivex-bin registry-tools chntpw \dislocker cifs-utils smbclient \yara sleuthkit libesedb-utilspip install python-evtx volatility3 regipy
| Artifact | Tool | One-line usage |
|---|---|---|
| .evtx | python-evtx | evtx_dump.py Security.evtx > out.xml |
| Registry hive | hivex / regipy | hivexregedit --export SYSTEM '\' |
| BitLocker | dislocker + hashcat | dislocker -V img -u<pw> -- /tmp/dis |
| Memory | volatility3 | vol -f mem.raw windows.pslist |
| SMB | smbclient | smbclient -L //TARGET -N |
| PE binary | Ghidra | Import, analyse, remember rcx not rdi |
| Sample matching | yara | yara -s rule.yar sample.exe |
| NTFS image | sleuthkit | fls -r -o 2048 image.dd |
picoCTF challenges
| Challenge | Artifact | Difficulty |
|---|---|---|
| Event Viewing | Security.evtx. Three event IDs, three Base64 flag fragments | Medium |
| BitLocker-1 | BitLocker volume with a weak password. Extract, crack, mount | Medium |
| BitLocker-2 | Strong password, but RAM captured while mounted. Encryption bypassed | Medium |
| YaraRules0x100 | Windows PE sample. Collect indicators, write a rule, submit it | Medium |
| Printer Shares | Anonymous SMB. Enumerate shares, retrieve the misdirected document | Easy |
| Printer Shares 2 | The same surface with the easy path closed | Hard |
| Printer Shares 3 | Deepest of the three. SMB plus network forensics | Hard |
| PowerShelly | A PowerShell transformation to invert: XOR, shuffle, bit encoding | Hard |
| WinAntiDbg0x100 | PE binary with one IsDebuggerPresent check | Medium |
| WinAntiDbg0x200 | Three stacked anti-debug checks | Medium |
| WinAntiDbg0x300 | UPX packed plus a continuous check. Patch instead of debug | Medium |
Quick reference
# Identify before you tool upfile artifact && xxd artifact | head -5# Event log: parse, then histogram the event IDsevtx_dump.py Security.evtx > out.xmlgrep -oP '<EventID[^>]*>\K[0-9]+' out.xml | sort | uniq -c | sort -rn# Registry: always search BOTH encodingsstrings -e l HIVE | grep -i picostrings -e s HIVE | grep -i pico# BitLocker: what protects this volume?dislocker-metadata -V drive.ddhashcat -m 22100 target.hash rockyou.txt# Memory beats encryptionstrings -e l -n 8 mem.raw | grep -i 'picoCTF{'vol -f mem.raw windows.pslist# SMB null sessionsmbclient -L //TARGET -N
Next steps: the forensics roadmap for where this sits among the other categories, disk forensics for imaging and filesystem work, hash cracking for the hashcat side of the BitLocker workflow, and metadata forensics for the document artifacts that often accompany a Windows image.
Sources and further reading
Format documentation first, then the tools that parse it.
- Windows Event Log and Registry Hives for what these formats are and which fields Windows itself controls.
- python-evtx for the binary XML parser behind
evtx_dump.py, and Volatility 3 for the memory plugins used above. - dislocker for reading BitLocker volumes under Linux, and hashcat for mode 22100 and the rest of the cracking workflow.
- YARA documentation for rule syntax and string modifiers, and smbclient(1) for null-session enumeration options.