$ cat writeup.md…
$ cat writeup.md…
Securinets CTF 2026
Task: ~2 GB Windows 11 kernel crash dump hiding a BYOVD exploit and an obfuscated flag. Solution: parse the RDMP dump wrapper so Volatility 3 runs, extract exploit tool + vulnerable driver, reverse IOCTL 0x222014's fake-object primitive, then XOR-decode a 32-byte kernel blob with the exploit's embedded 16-byte key.
Caught mid air. Figure out what's going on here: https://github.com/youssefnoob003/CTFTASK/releases/tag/v1.0
English summary: the GitHub release hosts ctf.zip (685,076,923 bytes, SHA-256
bb6ec02949228343a8c56888b02cc6a9ffaf8b8b561e502a6289a6ddb73d59be) which extracts
to ctf.dmp, a ~2 GB Windows kernel crash dump. The goal is to reconstruct what
happened at the moment of the crash and recover the flag (format Securinets{...}).
The title "DumPwn" = Dump + Pwn: a kernel exploit was caught mid-execution in a
crash dump.
file identifies a Windows kernel crash dump, but this is not a classic full dump:
The file starts with a normal _DMP_HEADER64 (PAGEDU64 signature) header, but
DumpType at header offset 0xF98 is 0xA — the new complete memory dump
type that stock Volatility 3 does not support (it crashes / refuses the layer).
After the crash-dump header pages sits a small RDMP wrapper (different signature/attributes from a classic PAGEDU64 full dump):
+0x10 QWORD — size of the metadata block+0x18 QWORD — file page index of the first data page+0x28 QWORD — total number of data pages+0x30 — stream directory: array of (PFN, run_length) QWORD pairsDecoding the directory gives the physical memory runs (base PFN, page count):
(0x1, 0x9f), (0x100, 0x24e), (0x457, 0x3bb),
(0x813, 0x7e46a), (0x7ed3e, 0xf1e), (0x7ff6e, 0xe)
First data page at file offset 0x2090; file size 2,141,454,480 bytes.
System snapshot: Windows 11 build 26100, kernel base 0xfffff801e75b0000,
KPCR/DTB 0x47425000.
Key lesson: 2024+ Windows 11 complete dumps may use this RDMP wrapper; handle the layered header before running Volatility.
With Volatility working (pslist, modules, driverirp, filescan) plus a
custom physical/virtual reader (below), three interesting artifacts surface:
flaws.exe — troll binary that prints 10 different plaintext
Securinets{...} strings. All decoys. The same strings are also planted inside
a tampered Windows Defender signature database in the dump — AI prompt-injection
bait. Do not trust any plaintext flag-format string.exploit.exe / taskhostw.exe — one and the same user-mode binary
(masquerading as the legitimate taskhostw.exe). It opens
\\.\Global\VaultKMControl and fires crafted IOCTLs.bomb.sys — a third-party vulnerable driver loaded as \Driver\ctf with
device \Device\Global\VaultKMControl (BYOVD pattern).IOCTL 0x222014 (METHOD_BUFFERED) input structure in IRP->SystemBuffer:
+0x00 u16 len ; only check: must be >= 0x14
+0x02 u16 op ; 1 = destroy, 2 = register, 3 = leak
+0x08 char[8] tag ; must equal "VAULTKM" (RtlCompareMemory == 8)
+0x18 u64 obj ; attacker-controlled kernel pointer ("fake object")
Operation 2 ("register vault") validates the "vault object" using only
attacker-writable data — [obj+0x10] == 0x60, [obj+0x14] == 'CVlt',
[obj+0x18] >= 1 — then:
[obj+0x28] — arbitrary kernel write,obj->[+0x20] — arbitrary kernel call.Operation 1 calls obj->[+0x30]; IRP_MJ_CLOSE paths call obj->[+0x40] and
obj->[+0x58]; operation 3 leaks the DeviceObject pointer. There is no
ProbeForRead/Write, no object-type check — the magic/size fields are trivially
forgeable because they live in attacker memory. Net result: LPE via arbitrary
kernel-mode call + write + pointer leak.
0x7ff6b0d60000, entry 0x2d50, main 0x1ac0.--ntbase 0xfffff801e75b0000, --src 0xffffe28eb2d1f000 (kernel VA
of a planted page), --memcpy 0xfffff801e7c6dd40, --delta 0x698..rdata, e.g. 48 29 c8 c3 and 48 8d 20 c3
(lea rsp,[rax]; ret — stack pivot).0x60070, forges a 0x60-byte
CVlt object with [obj+0x20] = pivot, opens the device, fires IOCTL
0x222014 op=2. The driver's arbitrary call pivots into the ROP chain, which
memcpys the planted kernel page into a user buffer.The story: the exploit popped the kernel and crashed the box — the dump caught it mid-air. The flag is what the exploit was trying to print.
Unzip ctf.zip → ctf.dmp. Parse the PAGEDU64 header (DumpType @0xF98 = 0xA,
physical runs from PhysicalMemoryBlockBuffer @0x88) and then the RDMP wrapper
after the header pages to build the physical-run map:
import struct FS = 2141454480 FIRSTPAGE = 0x2090 # (base_pfn, page_count) pairs decoded from the RDMP stream directory at +0x30 runs = [(0x1,0x9f),(0x100,0x24e),(0x457,0x3bb),(0x813,0x7e46a),(0x7ed3e,0xf1e),(0x7ff6e,0xe)] pagoff = {} off = FIRSTPAGE for pfn, cnt in runs: for i in range(cnt): pagoff[pfn + i] = off off += 0x1000 f = open("ctf.dmp", "rb") def rp(pa, n): # physical read pg, o = pa >> 12, pa & 0xFFF if pg not in pagoff: return None f.seek(pagoff[pg] + o); return f.read(n) def rq(pa): d = rp(pa, 8) return struct.unpack("<Q", d)[0] if d else None DTB = 0x47425000 # from KPCR def v2p(va): # 4-level x64 walk e = rq(DTB + ((va >> 39) & 0x1FF) * 8) if not e or not (e & 1): return None e = rq((e & 0x000FFFFFFFFFF000) + ((va >> 30) & 0x1FF) * 8) if not e or not (e & 1): return None if e & 0x80: return (e & 0x000FFFFFC0000000) | (va & 0x3FFFFFFF) e = rq((e & 0x000FFFFFFFFFF000) + ((va >> 21) & 0x1FF) * 8) if not e or not (e & 1): return None if e & 0x80: return (e & 0x000FFFFFFFE00000) | (va & 0x1FFFFF) e = rq((e & 0x000FFFFFFFFFF000) + ((va >> 12) & 0x1FF) * 8) if not e or not (e & 1): return None return (e & 0x000FFFFFFFFFF000) | (va & 0xFFF)
Recent Volatility 3 understands RDMP conceptually but crashes on this file. A small wrapper monkeypatches the crash-dump layer to build its segments from the RDMP stream directory:
#!/usr/bin/env python3 # vol_rdmp.py — run as: python3 vol_rdmp.py -f ctf.dmp windows.info import sys, struct import volatility3.cli from volatility3.framework.layers import crash _orig_load = crash.WindowsCrashDump32Layer._load_segments def _load_segments(self): if getattr(self, "dump_type", None) == 0xA: base = self._context.layers[self._base_layer] psize = self._page_size hdr_off = self.headerpages * psize rdmp = base.read(hdr_off, 0x40) metasize = struct.unpack_from("<Q", rdmp, 0x10)[0] firstpage = struct.unpack_from("<Q", rdmp, 0x18)[0] total = struct.unpack_from("<Q", rdmp, 0x28)[0] meta = base.read(hdr_off + 0x30, metasize) segments, off, cur, idx = [], firstpage, 0, 0 while idx * 16 + 16 <= len(meta) and cur < total: pfn, cnt = struct.unpack_from("<QQ", meta, idx * 16); idx += 1 if pfn == 0: break if cnt == 0: continue if cur + cnt > total: cnt = total - cur segments.append((pfn * psize, off, cnt * psize, cnt * psize)) off += cnt * psize; cur += cnt if not segments: raise crash.WindowsCrashDumpFormatException(self.name, "no RDMP segments") segments.sort(key=lambda x: x[0]) self._segments = segments return return _orig_load(self) crash.WindowsCrashDump32Layer._load_segments = _load_segments crash.WindowsCrashDump32Layer.supported_dumptypes = [0x1, 0x5, 0xA] crash.WindowsCrashDump64Layer.supported_dumptypes = [0x1, 0x5, 0xA] if __name__ == "__main__": sys.exit(volatility3.cli.main())
After this, windows.info, pslist, modules, driverirp and filescan all
work normally on the dump.
Dump the interesting images/data with the custom reader (or Volatility's
windows.dumpfiles): flaws.exe (decoy spinner), exploit.exe (whose DataSection
reveals it is byte-identical in purpose to the running taskhostw.exe), and the
kernel driver bomb.sys. Rebuild a clean copy of the driver (bomb_clean.sys)
and disassemble it with Capstone; reconstruct taskhostw.exe as a memory-aligned
image (taskhostw_img.bin, where file offset = VA − image base).
Static RE (Capstone) of bomb.sys and taskhostw.exe establishes the full
chain described in Analysis: user-mode tool forges a CVlt object → IOCTL
0x222014 op=2 → driver calls obj->[+0x20] (stack pivot lea rsp,[rax]; ret)
→ ROP chain at user stack 0x60070 → kernel memcpy of the planted page at
--src 0xffffe28eb2d1f000 into user memory → tool XOR-decodes 32 bytes and
printf("%s\n").
Two independent finds combine:
taskhostw.exe embeds a 16-byte XOR constant at file offset
0x18870 (VA 0x7ff6b0d78870), used by its decode loop
out[i] = buf[i] ^ key[i & 0xf] for i = 0..31.0x70c56000 (file offset 0x70aed090) consists of 128 identical copies of
one 32-byte block — a deliberately planted payload page belonging to the
exploit process memory.Repeating-key XOR of the 32-byte blob with the 16-byte key yields
Securinets{...} followed by NUL padding:
#!/usr/bin/env python3 # Flag recovery for DumPwn — models taskhostw.exe's output routine: # for i in range(32): out[i] = buf[i] ^ key[i & 0xf] then printf("%s\n", out) # key = 16-byte constant embedded in taskhostw.exe at file offset 0x18870 # buffer = 32-byte blob planted in kernel memory, physical page 0x70c56000 # (= ctf.dmp file offset 0x70aed090); page holds 128 identical copies. taskhostw = open("taskhostw_img.bin", "rb").read() # memory-aligned image dmp = open("ctf.dmp", "rb") key = taskhostw[0x18870:0x18870 + 16] # XOR key (value withheld) dmp.seek(0x70aed090) blob = dmp.read(32) # planted ciphertext out = bytes(b ^ key[i & 0xF] for i, b in enumerate(blob)) print(out.split(b"\x00")[0].decode())
Sanity check on the result: it matches the format, it is exactly what the exploit's decode loop produces, and unlike the 10 plaintext strings it is tied to the actual protection mechanism (XOR key + planted kernel page), so it is the real flag, not bait.
The 10 plaintext Securinets{...} strings appear in flaws.exe's output table
and again inside the Windows Defender signature database in the dump — a
prompt-injection trap aimed at AI-assisted solving. The real flag is only
reachable through the exploit's own key material: it never exists in plaintext
anywhere in the dump.
Use this technique when:
DumpType = 0xA and stock Volatility 3
crashes/refuses → look for the RDMP wrapper (metadata size / first data
page / total pages / (PFN, count) stream directory) after the header pages.\\.\Global\VaultKMControl) and IOCTLs that validate a "kernel object" using
fields readable/writable by the attacker → fake-object / arbitrary-call bug:
magic (CVlt), size (0x60), and count checks are forgeable data..rdata
(e.g. 48 8d 20 c3 = lea rsp,[rax]; ret) → ROP via ntoskrnl byte-pattern
scan, classic BYOVD-style kernel exploitation.Lessons learned: (a) modern Windows 11 complete dumps may be RDMP-wrapped — patch the crash-dump layer's segment loader before anything else; (b) plaintext flag strings are not evidence — follow the code path that would actually produce the output; (c) the vulnerable-driver → kernel ROP → XOR-obfuscated blob chain is a realistic BYOVD forensic scenario worth memorizing.
$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar