Crash Dump Analysis

Category: Reversing

Ett till pajat fönster

This challenge is about reversing a Windows kernel crash dump instead of a normal binary. The goal is not just to scrape strings, but to understand why the machine crashed, what the custom driver was doing right before the bugcheck, and where the real flag survived in the saved execution state.

Final flag: undut{w0w_7h15_w45_4nn0y1n6}

What The Challenge Is About

Ett till pajat fönster gives us a small Windows crash dump and expects us to recover the flag from the wreckage. The interesting part is that the challenge is built around a fake-looking trail of readable strings and a real solution hidden in the crash-time CPU context.

In practice, that means the task sits between forensics and reversing: we inspect dump metadata, recover driver-related strings, identify the custom code path, and then reason about the saved registers to reconstruct the secret.

The core trick is simple: the dump contains believable plaintext such as another, year, and pajat fönster, but those are just arguments passed into the crash path. The actual flag is stored in transformed form.

What We Started With

The challenge file was a Windows crash dump named 030426-4281-01.dmp.

The first useful check was simply identifying the file type:

file 030426-4281-01.dmp

030426-4281-01.dmp: MS Windows 64bit crash dump, version 15.19041, 4 processors, small dump

That tells us this is not an application log or a memory snapshot in a custom format. It is a real Windows minidump / kernel crash dump artifact.

Even before deeper analysis, the dump already gives us some useful constraints:

  • It is a 64-bit Windows crash dump.
  • The OS build is 15.19041.
  • The crash happened on a system with 4 processors.
  • The dump is small, so we should expect selective state instead of full RAM.

High-Level Plan

  1. Extract obvious strings from the dump and look for challenge-specific artifacts.
  2. Identify whether the crash involved a custom driver or ordinary Windows code.
  3. Find any hardcoded strings, registry paths, or suspicious bugcheck arguments.
  4. Disassemble the relevant driver code path to understand what happens before the crash.
  5. Recover the saved CPU context from the dump and inspect registers at crash time.
  6. Decode the transformed bytes into the real flag.

Step 1: Pull Strings from the Dump

The fastest first move is always broad string extraction:

strings -el 030426-4281-01.dmp | head -n 200
strings -a 030426-4281-01.dmp | rg -i "flag|Driver1|fonster"

This immediately revealed several important artifacts:

  • \??\C:\Users\Administrator\Desktop\Driver1.sys
  • \Device\HarddiskVolume2\Users\Administrator\Desktop\Driver1.sys
  • \Registry\Machine\Software\fonster
  • flag
  • \REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\Driver1
  • \Driver\Driver1

That is already enough to say the crash involves a custom kernel driver named Driver1.sys, and that the driver is connected to a registry key HKLM\Software\fonster with a value named flag.

The combination of a desktop-loaded driver path, a service entry under ControlSet001\Services, and a dedicated driver object name is strong evidence that this challenge was intentionally staged around a custom kernel component rather than a random Windows crash.

Step 2: Check the Obvious Strings Near the Registry Key

Near that registry string block, the dump also contained these plain ASCII fragments:

another
year
pajat fönster

In the raw dump, the last string is not even stored as one nice clean token. It appears as split fragments like pajat f and nster, which is a good reminder that string output alone can be misleading when it crosses memory boundaries.

These looked promising at first, but they turned out to be bait. That is a common CTF pattern: put readable words near the real logic so people stop at the first plausible guess.

Lesson: if a challenge gives you readable text too early, do not assume it is the flag. Confirm how the program uses those bytes.

Step 3: Read the Crash Header

We parsed the dump header directly to recover the bugcheck code:

BugCheckCode = 0x29

0x29 is SECURITY_SYSTEM. That matters because the suspicious strings were not just passive leftovers in memory. The custom driver was deliberately causing a system bugcheck and passing controlled arguments into it.

Step 4: Find the Driver Strings and Code References

The key move was locating the actual code references to the suspicious strings instead of just reading them as raw text.

Searching for RIP-relative references showed this:

0x4b0da -> 0x4bacc  (\Registry\Machine\Software\fonster)
0x4b0ec -> 0x4bb1c  (flag)
0x4b188 -> 0x4bb2c  (another)
0x4b194 -> 0x4bb3c  (year)
0x4b1a0 -> 0x4bb4c  (pajat fönster)

That told us one function was actively using all five strings together. So the next step was to disassemble that function.

Step 5: Disassemble the Critical Function

The important part of the function looked like this:

lea rdx, [rip+0x9eb]   ; "\Registry\Machine\Software\fonster"
lea rcx, [rsp+0x60]
call ...

lea rdx, [rip+0xa29]   ; "flag"
lea rcx, [rsp+0x70]
call ...

...

lea r8, [rip+0x99d]    ; "another"
mov ecx, 0x29
lea r9, [rip+0x9a1]    ; "year"
mov [rsp+0x20], r8
lea rdx, [rip+0x9a5]   ; "pajat fönster"
call ...

The structure is important:

  • First it resolves the registry path and the value name flag.
  • Then it reads registry data into a stack buffer.
  • After that it loads another, year, and pajat fönster.
  • Finally it calls into the bugcheck path using those strings as arguments.

This means the readable words were bugcheck arguments, not the flag itself.

Step 6: Recover the Saved Crash Context

A minidump often stores a Windows CONTEXT record. We searched for a valid x64 context block and found one whose stack pointer matched the active crash frame.

Important recovered registers:

ContextFlags = 0x10000f
Rcx = 0x29
Rdx = 0xfffff80219131ab0
R8  = 0xfffff80219131a90
R9  = 0xfffff80219131aa0
Rsp = 0xffffb183ccd34758
Rbp = 0xffffb183ccd34860
Rip = 0xfffff802154071b0

The values in RCX, RDX, R8, and R9 align with the bugcheck call arguments we had already identified. That confirmed we were looking at the real crash-time register state.

Step 7: Inspect the Saved XMM Registers

The driver contained vector instructions just before the string table. That suggested the flag data might not stay in ordinary general-purpose registers.

From the saved context we extracted:

XMM14 = 8a91043d8b84177888a05720ceca3f3f
XMM15 = cbca3f7c91915031ce915635ffff6048

Concatenated together, that gives a 32-byte blob:

8a91043d8b84177888a05720ceca3f3fcbca3f7c91915031ce915635ffff6048

That is exactly the right size for a short transformed flag string.

Step 8: Infer the Decode Method

Since the known flag format starts with undut{, we can use that as a crib. XOR the first six bytes of the blob with undut{:

cipher: 8a 91 04 3d 8b 84
plain : 75 6e 64 75 74 7b
key   : ff ff 60 48 ff ff

That partial key immediately suggests a repeating XOR key. Testing the repeating 8-byte key pattern ff ff 60 48 ff ff 60 48 produces a coherent plaintext.

Step 9: Decode the Blob

Applying the repeating XOR key gives:

undut{w0w_7h15_w45_4nn0y1n6}

This is the real flag. It also explains why the plain strings were wrong: the challenge was designed so that a shallow strings-only analysis would produce believable but incorrect answers.

Why the Earlier Guesses Failed

What looked tempting

  • another
  • year
  • pajat fönster

Those are human-readable and close to flag in memory.

Why they were wrong

  • They were passed into the bugcheck path as arguments.
  • They were not read back as one contiguous flag string.
  • The actual secret survived in vector register state, not as plain text.

Takeaways for Learners

  • Start with strings, but do not stop there.
  • Always ask: which code references this string?
  • If a dump involves a custom driver, inspect crash-time register state.
  • Saved CONTEXT structures can be more valuable than heap or stack strings.
  • Vector registers matter. Modern code may keep transformed secrets in XMM/YMM.
  • Known flag prefixes are powerful when testing XOR or simple substitution schemes.

Minimal Reproduction Workflow

1. file 030426-4281-01.dmp
2. strings -el 030426-4281-01.dmp | rg "Driver1|fonster|flag"
3. locate code references to those strings
4. disassemble the referencing function
5. recover the saved x64 CONTEXT from the dump
6. extract XMM14 and XMM15
7. concatenate the 32-byte blob
8. derive repeating XOR key from "undut{"
9. decode -> undut{w0w_7h15_w45_4nn0y1n6}