← Zurueck
windowsdefendervulnerabilitydetection-engineeringsentinellpe

BlueHammer: A Defender's Perspective on the Unpatched Windows LPE

2026.04.07

On April 3rd, a researcher operating as Chaotic Eclipse published a working proof of concept for an unpatched Windows local privilege escalation on GitHub. The flaw has been named BlueHammer. No CVE, no Microsoft advisory, no patch at the time of writing. The PoC is public, a clean reimplementation already exists, and the disclosure was driven by frustration with the MSRC handling process rather than coordinated release.

This is what I pieced together in the first hours of looking at the PoC: a short explanation of what the chain does, the detection anchors that drop out of reading the source, and a mitigation playbook that does not rely on a patch existing.

Two things up front. First, I have not run the exploit. Everything below is derived from the public PoC source, the Bleeping Computer reporting, and Will Dormann’s confirmation that the bug is a TOCTOU plus path confusion against the Defender signature update path. Second, the detection queries are hypotheses based on code indicators. They are not validated against live exploitation telemetry. The schema verification I did is documented at the end so you can check my work. Treat the queries as starting points for your own hunt, not as drop-in production analytics.

TL;DR

  • Class: Local privilege escalation. TOCTOU race plus path confusion against the Defender signature update RPC interface.
  • Exploit primitive: A Cloud Files API batch oplock is used to freeze the Defender service mid-operation, which makes an otherwise tight race window practical.
  • Impact: SYSTEM on Windows 11 client. Elevated administrator with UAC consent on Windows Server. The reliability of the public PoC is build-dependent.
  • Prerequisites: Local code execution as an unprivileged user, Cloud Files filter driver loaded, Defender real-time protection active, VSS available.
  • Patch status: None. May Patch Tuesday (May 12, 2026) is the realistic hard deadline, an out-of-band release is plausible given the public pressure.
  • Best near-term mitigation: Disable the Cloud Files filter driver (CldFlt) on workstations that do not need OneDrive Files-On-Demand. Based on the public PoC’s hard dependency on CldFlt for the oplock primitive, this appears to be the most impactful single control.
  • CVSS estimate (mine, no official score exists): AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H, roughly 7.0 High.

Reliability and Platform Notes

This is worth calling out because it directly affects how you prioritize. The original PoC reportedly contains bugs that may prevent it from working reliably; the researcher said as much on release. A community reimplementation has been confirmed working on Windows 11 25H2, and the same author reports it does not work on 22H2, 23H2, or 24H2.

Translation: the underlying flaw is real and platform-wide, but the publicly available exploit code has a narrow sweet spot. I would not count on that staying true. Reimplementations are appearing on GitHub already, and the underlying primitive does not care about the OS build, it cares about how Defender resolves paths during signature updates. Expect a more reliable variant within days of someone investing the time.

One important framing point: this bug lives inside Defender itself. EDR block mode, MDE Plan 2, attack surface reduction - none of these help against the root cause, because the privileged component being abused is the very thing meant to protect you. Detection and chain-breaking are the only options until a patch ships.

What the Chain Actually Does

The PoC abuses Defender’s own privileges. That is the whole trick. There is no kernel exploit, no memory corruption, no shellcode. It is a logic flaw in how the Defender signature update code path resolves and reads files, raced against an attacker-controlled symbolic link.

Reading through the published source, the chain appears to work like this:

  1. The attacker downloads a legitimate Defender signature update bundle from the Microsoft fwlink endpoint, parses the embedded cabinet, and stages it locally. This is the “looks like a normal update” decoy.
  2. The PoC drops an EICAR variant (reversed in the binary so it does not detect itself) and watches the VSS object directory via NtQueryDirectoryObject, waiting for Defender to create a shadow copy in response to the detection.
  3. The Cloud Files API is used to freeze Defender. The PoC registers as a sync root provider, and when Defender opens a file inside that sync root, the attacker’s callback takes a batch oplock that suspends the Defender thread mid-operation. This is the load-bearing primitive.
  4. With Defender frozen, the PoC takes a second batch oplock on the signature base file (mpasbase.vdm), swaps a junction in the object manager namespace, and creates a symbolic link pointing into the VSS snapshot path that contains the SAM hive.
  5. The race is then triggered by an RPC call against the Defender service signature update interface. Defender, running as SYSTEM, follows the symlink and reads the SAM out of the VSS snapshot. From the attacker’s perspective, this is an arbitrary file read as SYSTEM.
  6. The SAM hive is parsed offline using offreg.dll. The boot key is reconstructed from the SYSTEM hive class names, the SAM key is decrypted, and NT hashes are recovered.
  7. From there, the PoC uses standard Windows APIs to either reset a local administrator password or register a service that runs as SYSTEM.

The TOCTOU is between Defender checking the path and Defender opening the file. The path confusion is the symlink swap inside that window. The oplock is what makes the otherwise tiny race window practically exploitable, because it stops Defender at exactly the right moment.

This matters for defenders because every step in that chain leaves a fingerprint somewhere, and several of them require capabilities that you can take away.

MITRE ATT&CK Mapping

For the chain as a whole:

  • T1068 Exploitation for Privilege Escalation (the core of the issue)
  • T1003.002 OS Credential Dumping: Security Account Manager (the SAM hive read)
  • T1574 Hijack Execution Flow (the symlink swap)
  • T1055 Process Injection adjacent: not classical injection, but the oplock primitive achieves a similar “make a privileged process do my bidding” effect

For post-exploitation:

  • T1136.001 Create Account: Local Account, or T1098 Account Manipulation, depending on which escalate.cpp branch the attacker uses
  • T1543.003 Create or Modify System Process: Windows Service

Detection

Six hunt anchors, ordered by signal quality. The first one is the cleanest. The schema verification I did against Microsoft’s public docs and community sources is in the appendix at the end of this post.

Anchor A: Non-Defender Process Touching the Signature Base Files

The Defender signature base files live under C:\ProgramData\Microsoft\Windows Defender\Definition Updates\<RandomGUID>\. The file family is mpasbase.vdm, mpasdlta.vdm, mpavbase.vdm, mpavdlta.vdm. Normally these are only opened by MsMpEng.exe, MpCmdRun.exe, the update plumbing under svchost, or TrustedInstaller. Anything else opening one of them stands out clearly in telemetry.

DeviceFileEvents
| where Timestamp > ago(7d)
| where FileName in~ ("mpasbase.vdm", "mpasdlta.vdm", "mpavbase.vdm", "mpavdlta.vdm")
| where FolderPath has @"\Windows Defender\Definition Updates\"
| where InitiatingProcessFileName !in~ (
    "MsMpEng.exe", "MpCmdRun.exe", "TrustedInstaller.exe",
    "svchost.exe", "mpsigstub.exe"
  )
| where InitiatingProcessAccountName != "SYSTEM"
| project Timestamp, DeviceName, ActionType, FolderPath, FileName,
          InitiatingProcessFileName, InitiatingProcessAccountName,
          InitiatingProcessCommandLine, InitiatingProcessParentFileName

This is the cleanest signal in the chain. The folder path filter prevents matches against unrelated files that happen to share the name. Worth deploying as a custom detection rule, not just a scheduled hunt.

Anchor B: Defender Service Instability Correlated with Unprivileged File Access

When the oplock holds Defender frozen, the signature update operation can fail and the MsMpEng worker can exhibit instability. I am deliberately not building an analytic against an “AntivirusDefinitionsUpdateFailed” ActionType here because I cannot verify that string exists in the DeviceEvents schema; Microsoft does not publish the full ActionType list publicly. What I can build cleanly is a process-side signal: an MsMpEng.exe or MpCmdRun.exe process termination that correlates in time with an unprivileged process taking unusual file actions.

let WindowStart = ago(7d);
let DefenderProcessEvents =
    DeviceProcessEvents
    | where Timestamp > WindowStart
    | where FileName in~ ("MsMpEng.exe", "MpCmdRun.exe")
    | where ActionType == "ProcessCreated"
    | project DefenderTime = Timestamp, DeviceId, DefenderName = FileName;
DeviceFileEvents
| where Timestamp > WindowStart
| where InitiatingProcessAccountName != "SYSTEM"
| where FolderPath has @"\Windows Defender\Definition Updates\"
| join kind=inner DefenderProcessEvents on DeviceId
| where DefenderTime between (Timestamp .. (Timestamp + 2m))
| project Timestamp, DeviceName, FileName, InitiatingProcessFileName,
          InitiatingProcessAccountName, DefenderName, DefenderTime

If you want a stricter version using the local Windows Event Log path: Defender Operational log (Microsoft-Windows-Windows Defender/Operational), Event ID 2001 is “Definition update failed”. A burst of 2001 events on a single host within minutes, especially with an unusual error code, is the signal. In Defender XDR you would need Sysmon or Event Log forwarding to surface this directly; native DeviceEvents may or may not include it depending on the tenant configuration. Verify in your own environment before relying on it.

Anchor C: Low or Medium IL Parent Spawning a SYSTEM Child

Generic LPE detection, not BlueHammer-specific, but it catches the post-exploitation step where the chain pivots from “I have arbitrary read as SYSTEM” to “I have a SYSTEM shell.” Worth having permanently regardless of this issue.

let LookbackWindow = 7d;
DeviceProcessEvents
| where Timestamp > ago(LookbackWindow)
| where ProcessIntegrityLevel == "System"
| extend ParentPid = InitiatingProcessId,
         ParentCreationTime = InitiatingProcessCreationTime
| join kind=inner (
    DeviceProcessEvents
    | where Timestamp > ago(LookbackWindow)
    | where ProcessIntegrityLevel in ("Low", "Medium")
    | project DeviceId, ParentPid = ProcessId,
              ParentCreationTime = ProcessCreationTime,
              ParentName = FileName, ParentUser = AccountName,
              ParentCmdLine = ProcessCommandLine
  ) on DeviceId, ParentPid, ParentCreationTime
| where InitiatingProcessFileName !in~ ("consent.exe", "RuntimeBroker.exe")
| project Timestamp, DeviceName, ParentName, ParentUser, ParentCmdLine,
          FileName, ProcessCommandLine

The join uses both PID and process creation time, which avoids the PID-recycling false positive that a naive PID-only join produces. Expect false positives from legitimate UAC elevation and installer chains; the consent.exe exclusion handles the most common one. Tune further by adding known installers in your environment.

Anchor D: Unusual Cloud Files Sync Root Registration

The CldFlt API is normally only called by OneDrive and a small number of named cloud sync clients. Any other process registering as a sync root provider is interesting. Direct visibility on this requires either ETW (Microsoft-Windows-CloudFilters provider) or Sysmon registry monitoring on HKLM\SYSTEM\CurrentControlSet\Services\CldFlt\Instances. If you have neither, this is aspirational rather than deployable. If you do have Sysmon with the right registry filters, the analytic looks like this:

Sysmon Event ID 12 or 13
TargetObject contains "\Services\CldFlt\Instances\"
Image not in (OneDrive.exe, FileCoAuth.exe, known sync clients)

I am keeping this one prose rather than KQL because the right query depends entirely on whether you ingest Sysmon into Defender XDR or Sentinel, and how you have it configured.

Anchor E: Local Administrator Account Changes from Unusual Contexts

The post-exploitation step uses standard Windows account management APIs to either reset a local admin password or create a new privileged account. Specifically, the PoC calls NetUserSetInfo for the password reset and SamSetInformationUser to restore the original NT hash afterwards — that second call is anti-forensic and worth noting because it means the password change may be reverted before you see it. These actions surface in DeviceEvents and the Windows Security Event Log. The cleanest cross-tenant signal:

DeviceEvents
| where Timestamp > ago(7d)
| where ActionType in~ (
    "UserAccountCreated", "UserAccountModified",
    "PasswordChangeAttempt", "UserAccountAddedToLocalGroup"
  )
| where InitiatingProcessFileName !in~ (
    "lsass.exe", "services.exe", "wininit.exe",
    "MMC.exe", "SecurityHealthService.exe", "net.exe", "net1.exe"
  )
| where InitiatingProcessAccountName != "SYSTEM"
| project Timestamp, DeviceName, ActionType,
          InitiatingProcessFileName, InitiatingProcessAccountName,
          InitiatingProcessCommandLine

A note on the ActionType strings: the values used here (UserAccountCreated, UserAccountModified, PasswordChangeAttempt, UserAccountAddedToLocalGroup) are documented in the DeviceEvents schema reference. If they do not return rows in your tenant, fall back to Windows Security Event Log 4720, 4722, 4724, 4732, 4738.

This anchor is the weakest of the five, because legitimate management tooling triggers it constantly and tuning is environment-specific. Pair it with Anchor A; the conjunction of A and E on the same host within minutes is very strong.

Anchor F: SAM Hive Access via VSS Shadow Copy

This is a strong BlueHammer-specific signal. The PoC redirects Defender’s file read into a VSS snapshot of the SAM hive via a symlink swap. Any process reading SAM, SYSTEM, or SECURITY hives from a shadow copy path that is not a known backup agent is highly suspicious.

DeviceFileEvents
| where Timestamp > ago(7d)
| where FolderPath has "HarddiskVolumeShadowCopy"
| where FolderPath has @"\Windows\System32\config\"
| where FileName in~ ("SAM", "SYSTEM", "SECURITY")
| where InitiatingProcessFileName !in~ (
    "svchost.exe", "lsass.exe", "TiWorker.exe", "vssvc.exe"
  )
| project Timestamp, DeviceName, ActionType, FolderPath, FileName,
          InitiatingProcessFileName, InitiatingProcessAccountName,
          InitiatingProcessCommandLine

If you see this fire, the host should be considered compromised. There is no legitimate reason for a non-backup process to open SAM from a shadow copy. This signal is independent of the Defender chain and would also catch manual SAM dumping via vssadmin and copy.

One-Hit Check: Correlated BlueHammer Detection

If you want a single query that combines multiple indicator stages and correlates them within a time window, use the following. It looks for suspicious DLL loading patterns (cldapi.dll, offreg.dll, samlib.dll loaded together by the same non-SYSTEM process), service creation by non-admin accounts, group membership changes indicating privilege escalation, and Defender-related process activity from unprivileged contexts. It then joins all four stages on device and user, requires at least two indicator types to fire, and applies a cumulative suspicion score. Only alerts scoring 10 or above within a two-hour window are surfaced.

This is a heavier query than any of the individual anchors. Run it as a scheduled hunt, not as a real-time custom detection. Expect it to take noticeably longer in large tenants. Set the time range via the UI picker next to Run Query rather than hardcoding it.

A note on portability: Stage 3 requires Microsoft Defender for Identity (MDI). If your tenant does not have MDI, remove the privilege_escalation stage and its join. Stage 1 requires MDE Plan 2 for DeviceImageLoadEvents. The admin account filter uses has "admin", which is a term-based match — it covers administrator, adm_admin, admin_svc and similar. If your environment uses a different naming convention for privileged accounts, adjust accordingly.

// ── ADJUST BEFORE USE ──────────────────────────────────────
// Stage 1: Requires MDE Plan 2 (DeviceImageLoadEvents).
// Stage 2: "has admin" is a term-based match. Adjust if your
//          privileged accounts use a different naming convention.
// Stage 3: Requires Defender for Identity (MDI). Remove the
//          privilege_escalation let-block and its join if unavailable.
// Time range: Set via the UI picker next to "Run query".
// ────────────────────────────────────────────────────────────
let suspicious_dll_loads = DeviceImageLoadEvents
| where FileName in~ ("cldapi.dll", "offreg.dll", "samlib.dll")
| where InitiatingProcessAccountName != "SYSTEM"
| summarize 
    LoadedDLLs = make_set(FileName),
    FirstLoad = min(Timestamp),
    LastLoad = max(Timestamp),
    ProcessCount = dcount(InitiatingProcessFileName)
    by DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessAccountName, InitiatingProcessId
| where array_length(LoadedDLLs) >= 2
| where datetime_diff('minute', LastLoad, FirstLoad) <= 10
| extend SuspiciousScore = case(
    array_length(LoadedDLLs) == 3, 10,
    array_length(LoadedDLLs) == 2, 7,
    5)
| project-rename 
    ProcessName = InitiatingProcessFileName,
    CommandLine = InitiatingProcessCommandLine,
    UserAccount = InitiatingProcessAccountName,
    ProcessId = InitiatingProcessId;
let service_manipulation = DeviceEvents
| where ActionType == "ServiceInstalled"
| where InitiatingProcessAccountName != "SYSTEM"
| where not(InitiatingProcessAccountName has "admin")
| summarize 
    Actions = make_set(ActionType),
    FirstAction = min(Timestamp),
    LastAction = max(Timestamp),
    ServiceNames = make_set(FileName)
    by DeviceName, InitiatingProcessFileName, InitiatingProcessAccountName, InitiatingProcessId
| extend SuspiciousScore = 8
| project-rename 
    ProcessName = InitiatingProcessFileName,
    UserAccount = InitiatingProcessAccountName,
    ProcessId = InitiatingProcessId;
let privilege_escalation = IdentityDirectoryEvents
| where ActionType == "Group Membership changed"
| where DestinationDeviceName != ""
| where TargetAccountDisplayName != ""
| where AdditionalFields has_any ("Administrators", "Admin", "Power Users")
| extend SuspiciousScore = 8
| project-away DeviceName
| project-rename DeviceName = DestinationDeviceName;
let defender_activity = DeviceProcessEvents
| where ProcessCommandLine has_any ("Defender", "MpCmdRun", "mpsigstub", "Definition Updates")
| where AccountName != "SYSTEM"
| where ProcessCommandLine has_any ("ServerMpUpdateEngineSignature", "mpasbase", "Definition Updates")
| extend SuspiciousScore = 6
| project-rename
    ProcessName = FileName,
    CommandLine = ProcessCommandLine,
    UserAccount = AccountName;
suspicious_dll_loads
| join kind=fullouter (
    service_manipulation
) on DeviceName, UserAccount
| extend _dn = coalesce(DeviceName, DeviceName1), _ua = coalesce(UserAccount, UserAccount1)
| project-away DeviceName, DeviceName1, UserAccount, UserAccount1
| project-rename DeviceName = _dn, UserAccount = _ua
| join kind=fullouter (
    privilege_escalation
) on DeviceName
| extend _dn = coalesce(DeviceName, DeviceName1)
| project-away DeviceName, DeviceName1
| project-rename DeviceName = _dn
| join kind=fullouter (
    defender_activity
) on DeviceName, UserAccount
| extend _dn = coalesce(DeviceName, DeviceName1), _ua = coalesce(UserAccount, UserAccount1)
| project-away DeviceName, DeviceName1, UserAccount, UserAccount1
| project-rename DeviceName = _dn, UserAccount = _ua
| where 
    (isnotempty(LoadedDLLs) and (isnotempty(Actions) or isnotempty(TargetAccountDisplayName) or isnotempty(CommandLine))) or
    (isnotempty(Actions) and (isnotempty(TargetAccountDisplayName) or isnotempty(CommandLine))) or
    (isnotempty(TargetAccountDisplayName) and isnotempty(CommandLine))
| extend 
    TotalSuspiciousScore = coalesce(SuspiciousScore, 0) + coalesce(SuspiciousScore1, 0) + coalesce(SuspiciousScore2, 0) + coalesce(SuspiciousScore3, 0),
    MaxTimestamp = max_of(max_of(FirstLoad, LastLoad), max_of(FirstAction, LastAction)),
    MinTimestamp = min_of(min_of(FirstLoad, LastLoad), min_of(FirstAction, LastAction))
| where datetime_diff('hour', MaxTimestamp, MinTimestamp) <= 2
| project 
    DeviceName,
    UserAccount = coalesce(UserAccount, TargetAccountDisplayName),
    ProcessName = coalesce(ProcessName, ProcessName1, ProcessName2),
    TotalSuspiciousScore,
    LoadedDLLs,
    ServiceActivity = Actions,
    PrivilegeEscalation = iff(isnotempty(TargetAccountDisplayName), "Yes", "No"),
    DefenderActivity = iff(isnotempty(CommandLine), "Yes", "No"),
    TimeSpan = datetime_diff('minute', MaxTimestamp, MinTimestamp),
    FirstActivity = MinTimestamp,
    LastActivity = MaxTimestamp
| where TotalSuspiciousScore >= 10
| sort by TotalSuspiciousScore desc, TimeSpan asc

Mitigation

No patch exists. Everything below is defense-in-depth against the published chain, not against the underlying flaw. Document each control as a temporary compensating measure with a sunset tied to the eventual fix, otherwise you will find these settings six months from now and not remember why.

Break the Chain: Disable Cloud Files Filter Driver Where Not Needed

The exploit is hard-dependent on the Cloud Files API for the oplock primitive. On any workstation that does not actively use OneDrive Files-On-Demand or another Cloud Files-based sync provider, CldFlt is dead weight.

sc.exe config CldFlt start= disabled

Roll out via Intune Settings Catalog, GPO Computer Preferences, or Configuration Manager. Pilot it. The visible side effect for users of OneDrive Files-On-Demand is that files will sync fully to disk instead of staying as placeholders. For most engineering and office workstations in a typical corporate fleet that is acceptable. For users who genuinely depend on placeholder behavior, leave them on the unmitigated path and rely on detection.

This is the strongest single control I can identify from reading the public PoC. A future variant might find another oplock provider, but the public PoC and its known reimplementations all rely on CldFlt. If you can verify the mitigation against a test client where CldFlt is disabled and the public PoC fails, please publish your results - that would be the most valuable contribution anyone could make right now.

Tamper Protection Enforced

Should already be on. Verify with Get-MpComputerStatus across the fleet. Without Tamper Protection, the post-exploitation step can simply turn Defender off, which removes the primary detection surface for everything else you do.

ASR Rule: Block Credential Stealing from LSASS

GUID 9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2. Set to Block, not Audit. Does not stop the BlueHammer SAM read directly, since that goes through Defender rather than LSASS, but it raises the cost of the typical follow-on credential dumping that an attacker would do once they have SYSTEM.

VSS Hardening: Handle with Care

The chain depends on a VSS snapshot being available. On clients where you have no backup agent that requires VSS and System Restore is disabled anyway, setting the Volume Shadow Copy Service to Manual is technically possible. I would only do this in a small pilot and only after explicitly confirming nothing in your backup or imaging stack quietly relies on it. On servers, do not touch VSS.

What Not to Do

Do not disable Defender real-time protection. The exploit needs Defender as a privileged helper to actually do the file read. Disabling Defender does not help and removes your entire detection layer.

Do not push out emergency WDAC or AppLocker policies for this single issue. If those projects are already in flight, accelerate them. If they are not, this is not the week to start.

Do not mass-mail your users. The story is in security press but not yet in mainstream coverage. An internal advisory in your security channel is appropriate, a company-wide email is not.

What I Am Watching For

An out-of-band patch from Microsoft within the next one to two weeks is plausible given the public pressure. May Patch Tuesday (May 12, 2026) is the realistic hard deadline. Once a patch lands, the compensating controls above should be reverted in the same change window that deploys the fix, otherwise the temporary state becomes permanent and the next person to take over the environment will inherit a mystery.

I am also watching for variants. The current PoC has a narrow Windows 11 25H2 sweet spot. Reimplementations are already appearing on GitHub. Expect a reliable Windows Server variant within days of the first one being demonstrated, because the underlying primitive does not care about the platform, it cares about how Defender resolves paths during signature updates.

Appendix: Schema Verification Notes

A few of the queries above use ActionType strings or column references that deserve explicit honesty about how confident I am.

  • DeviceFileEvents columns (FileName, FolderPath, InitiatingProcess*) are documented in Microsoft Learn and stable across tenants. Anchor A is verified.
  • DeviceProcessEvents columns and the ProcessIntegrityLevel values “Low”, “Medium”, “System” are documented and verified.
  • The ActionType list for DeviceEvents is partially published. ServiceInstalled, UserAccountCreated, UserAccountModified, PasswordChangeAttempt, and UserAccountAddedToLocalGroup are documented in the schema reference. Note that ServiceDeleted does not exist as an ActionType in DeviceEvents — service deletion must be tracked via DeviceRegistryEvents or Windows Event Log. Test ActionType strings in your own Advanced Hunting console before deploying as custom detections.
  • Sysmon-based suggestions in Anchor D depend entirely on whether your environment ingests Sysmon and how it is configured.
  • Anchor F uses DeviceFileEvents with VSS shadow copy paths. The HarddiskVolumeShadowCopy path component is stable across Windows versions. This anchor has the lowest false positive rate of any query in this post.

References

Let me know if you find issues with the detection queries, or if you have telemetry from a real exploitation attempt that lets you validate or refine them. Reach me via the contact link on marcel@graewer.com.