---
name: verify-windows-mitigations
description: "Verification-and-drift methodology for confirming Windows exploit mitigations are enforced per host and per process, not merely named in policy: DEP, ASLR, CFG, CET shadow stack, SEHOP, heap integrity, ACG, Code Integrity Guard, and image-load and child-process blocking (MITRE ATT&CK T1203, T1190, T1068, T1211, T1210; mitigation M1050). Signals: Get-ProcessMitigation and NOTSET inheritance, MitigationOptions registry values, the Security-Mitigations event channels, Application log 1000 and 1001, and Defender for Endpoint DeviceEvents. Gives how to read live enforcement, find NOTSET and precedence gaps, detect baseline drift, hunt mitigations firing, and harden with a GPO or Intune baseline. Use when confirming exploit mitigations are enforced, verifying ASLR DEP CFG CET SEHOP coverage on a Windows fleet, auditing per-process mitigation state, reconciling an Exploit Protection XML baseline, or triaging a fast-fail crash cluster."
---

# Verify Windows Exploit Mitigations Are Enforced - Defensive Methodology

> **Tier 2 - methodology skill.** No bespoke UMBRASEC detection rule ships with this skill yet. It gives the defender's method, the log sources and signals, the hunt, and the hardening, and points to public, maintained rule sources by URL. Promotion to Tier 1 (a sourced, tuned UMBRASEC rule) is tracked in [COVERAGE.md](https://github.com/umbrasec-labs/umbra-blue/blob/main/COVERAGE.md). Do not treat the content below as citing a UMBRASEC rule; there is none for this threat yet.

## TL;DR

A mitigation named in a policy document is not a mitigation that is enforced on the process in front of you. This skill is about closing that gap: proving which of DEP, ASLR (bottom-up and mandatory), CFG, CET shadow stack, SEHOP, heap-integrity, ACG, Code Integrity Guard, remote and low-integrity image blocking, and child-process blocking is live on each host and each running image, and finding where the answer is silently "none". The verification tool is `Get-ProcessMitigation`; the two traps are `NOTSET` (which means "inherit", not "on") and the rule that per-app Program settings override System settings. Build one Exploit Protection XML baseline, deploy it by GPO or Intune, and then treat any divergence between that XML and live `Get-ProcessMitigation` output as drift to investigate. The detection value is secondary: when a mitigation fires or a mitigated exploit fails, you see audit events in the Security-Mitigations channels (for the mitigations that have an audit mode), the same events in Microsoft Defender for Endpoint `DeviceEvents` via Advanced Hunting, or a crash cluster of fast-fail (`0xc0000409`) and access-violation (`0xc0000005`) exceptions in the Application log. This skill is Windows-specific and runtime-and-policy focused; for the cross-platform binary-level coverage audit (dumpbin, BinSkim, winchecksec, checksec, CETCOMPAT linkage, crash-cluster hunting) see the sibling [defend-exploit-mitigations](../../exploit-dev/defend-exploit-mitigations/SKILL.md).

## What it looks like, defender view

Verification failure has no alert; it is an absence you have to go looking for. A host where the Exploit Protection GPO "applied" can still run a high-value process with CFG at `NOTSET`, because the image was never added to Program settings and CFG cannot protect an image that was not compiled with /guard:cf even though the System default for CFG is on, or because a locally cached configuration or a later PowerShell `Set-ProcessMitigation` quietly diverged from the deployed XML. The defender view is therefore two layered pictures. First, state: the output of `Get-ProcessMitigation` for a process shows each mitigation as `ON`, `OFF`, or `NOTSET`, and the honest reading is that `NOTSET` at app level means "whatever the system setting is" and `NOTSET` at system level means "the Windows install default", which may be off for CFG, SEHOP, ACG, and the image-load guards even though they are on in your baseline document. Second, firing: when an exploit actually hits an enforced mitigation, the process usually dies rather than raising a tidy security log entry. A CFG indirect-call failure, a CET return-address mismatch, and a `/GS` stack cookie failure all surface through the same fast-fail path and terminate the process with exception code `0xc0000409` (STATUS_STACK_BUFFER_OVERRUN, reused for the whole security fast-fail family); a heap-integrity (`TerminateOnError`) kill surfaces as heap corruption `0xc0000374` or a fast-fail, and a mitigated-but-not-fatal exploit attempt instead shows as an access violation `0xc0000005`. The mitigations that carry an audit mode (ACG, Code Integrity Guard, block-low-integrity-images, block-untrusted-fonts, disable-Win32k-calls, child-process, EAF/IAF, the ROP checks) record the attempt without the kill, so those leave a trail even when the process survives. CFG, DEP, mandatory ASLR, SEHOP, and heap-integrity have no audit mode, so for those the only runtime evidence is the crash.

## Log sources and signals

Verification signals (what is configured and enforced):

- **`Get-ProcessMitigation`** is the authoritative runtime read. `Get-ProcessMitigation -System` shows the system-wide defaults; `-Name <image>.exe` shows the configured policy for an image; `-Id <pid>` shows what is live on a specific running process, which is the only way to confirm enforcement rather than intent. Output fields include Dep, Aslr (BottomUp, ForceRelocateImages, HighEntropy), ControlFlowGuard, Sehop, Heap (TerminateOnError), DynamicCode (ACG), ImageLoad (BlockRemoteImageLoads, BlockLowLabelImageLoads), StrictHandle, ChildProcess, UserShadowStack (CET), and the ROP/EAF/IAF payload-guard families. Read `NOTSET` as "inherits, verify the parent default", never as "on".
- **Registry policy sources.** Per-image Exploit Protection lives under `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<image.exe>` in the `MitigationOptions` and `MitigationAuditOptions` REG_BINARY values. System-wide kernel mitigation state lives under `HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Kernel` (`MitigationOptions`, `MitigationAuditOptions`). SEHOP has its own legacy switch (`DisableExceptionChainValidation`) and DEP has a boot policy set by `bcdedit /enum` (`nx`). These are the on-disk truth that `Get-ProcessMitigation` decodes; comparing them across hosts catches machines that drifted from the baseline.
- **Exploit Protection XML.** `Get-ProcessMitigation -RegistryConfigFilePath <file>.xml` exports the live configuration. The deployed GPO or Intune baseline XML compared against this per-host export is your drift signal.

Enforcement-and-failure signals (something fired or an exploit failed):

- **Microsoft-Windows-Security-Mitigations (Kernel Mode / User Mode)** channels record Exploit Protection events, in audit and enforce variants, for the mitigations that carry an audit mode. Microsoft documents the audit event IDs in this provider as **1** (ACG audit), **3** (do-not-allow-child-processes audit), **5** (block-low-integrity-images audit), **7** (block-remote-images audit), **9** (disable-Win32k-system-calls audit), and **11** (Code Integrity Guard audit); the corresponding block events fire in the same provider when the mitigation is enforced rather than audited.
- **Application** log **Event ID 1000** (Application Error) and **Event ID 1001** (Windows Error Reporting / WerFault) carry the faulting module and the exception code for the fast-fail and access-violation crashes that a tripped no-audit mitigation (CFG, DEP, SEHOP, heap-integrity, CET) produces.
- **Microsoft Defender for Endpoint** surfaces the same Exploit Protection audit and block events through `DeviceEvents` in Advanced Hunting, which is the fleet-wide way to query firings across hosts without per-host Event Viewer collection; Microsoft notes audit events are viewable in Event Viewer or via Advanced Hunting on the exploit protection reference page.

## Detection approach

Alert on three things, in prose, and pull the actual rules from public maintained sources rather than inventing one, because no tuned UMBRASEC rule ships for this threat yet.

First, enforcement firings. The audit and block events in the Security-Mitigations channels (IDs 1, 3, 5, 7, 9, 11 for the audited mitigations) and the same events queried through Defender for Endpoint `DeviceEvents` are low-volume and high-signal on a tuned baseline: an ACG (ID 2) or Code Integrity Guard (ID 12) block, or the matching audit event (ID 1 or 11), on a hardened, internet-facing image is close to a binary indicator worth a ticket. SigmaHQ maintains built-in-log rules for these under `rules/windows/builtin/security_mitigations/` at [github.com/SigmaHQ/sigma](https://github.com/SigmaHQ/sigma/tree/master/rules/windows/builtin/security_mitigations).

Second, the crash tell for the no-audit mitigations (CFG, DEP, SEHOP, heap-integrity, CET). Watch Application log Event IDs 1000 and 1001 for a cluster of the same faulting image terminating with `0xc0000409`, `0xc0000005`, or `0xc0000374` in a short window, especially on a browser, Office component, PDF reader, or an internet-facing service. SigmaHQ carries application-error and WER rules under `rules/windows/builtin/application/` at [github.com/SigmaHQ/sigma](https://github.com/SigmaHQ/sigma/tree/master/rules/windows/builtin/application); tune the clustering threshold to your environment's normal crash rate.

Third, the verification gap itself, which is the point of this skill and has no shipped event. Run `Get-ProcessMitigation -Id` against the live process set of your high-value images on a schedule, diff each against the deployed Exploit Protection XML baseline, and alert on any image that should carry CFG, DEP, SEHOP, ACG, or the image-load guards but reads `NOTSET` or `OFF` at runtime. Microsoft documents the mitigation set, the PowerShell cmdlets, and the NOTSET semantics at [learn.microsoft.com/en-us/defender-endpoint/customize-exploit-protection](https://learn.microsoft.com/en-us/defender-endpoint/customize-exploit-protection); the D3FEND catalog frames the mitigations as countermeasures at [d3fend.mitre.org](https://d3fend.mitre.org/).

## Threat hunt

Hypothesis-driven, aimed at the enforcement gaps a policy report would hide:

1. **NOTSET-gap hunt.** Enumerate the internet-facing and privileged app set, then for each live process run `Get-ProcessMitigation -Id <pid>` and record ASLR (BottomUp, ForceRelocateImages), DEP, CFG, SEHOP, Heap TerminateOnError, ACG (DynamicCode), and UserShadowStack (CET). Every row where a mitigation your baseline claims reads `NOTSET` or `OFF` is a prioritized finding; join it to `Get-ProcessMitigation -System` to confirm whether the system default would have covered it.
2. **Drift hunt.** For each host, export live config with `Get-ProcessMitigation -RegistryConfigFilePath` and diff it against the authoritative deployed XML. A machine whose export no longer matches the baseline was changed locally (a later `Set-ProcessMitigation`, an app reinstall that rewrote its IFEO key, or a GPO that failed to apply); the IFEO and Session Manager `MitigationOptions` registry values pinpoint which image drifted.
3. **CET-readiness hunt.** Cross-reference hosts whose CPU and OS support user-mode shadow stacks against those where `Get-ProcessMitigation` shows UserShadowStack actually `ON` for the target images. The delta is CET coverage you are entitled to but not enforcing.
4. **Firing hunt.** Over a long window, pull the Security-Mitigations audit events (IDs 1, 3, 5, 7, 9, 11) and block events (IDs 2, 4, 6, 8, 10, 12), queried locally or through Defender for Endpoint `DeviceEvents`, grouped by image and host, and the 1000/1001 crash clusters for the no-audit mitigations. A reachable, hardened image that is both firing audit events and crashing with `0xc0000409` is an exploitation candidate, not a stability bug.

## Hardening

Make the mitigations mandatory from one authoritative baseline and prove it is live, rather than trusting that a policy name equals enforcement. The controls below ground in the UMBRASEC [foundation guide](https://umbrasec.dev/guide/foundation.html), which maps the ASD Essential Eight to ISO 27001:2022 and NIST CSF 2.0 directly.

| Control | What it does | Essential Eight | ISO 27001:2022 | NIST CSF 2.0 |
|---|---|---|---|---|
| Deploy one Exploit Protection XML baseline (mandatory DEP, ASLR, CFG, SEHOP, CET, ACG where supported) via GPO or Intune, audit mode first then enforce | Turns the mitigations on system-wide and per high-value image from a single source of truth, after audit mode confirms compatibility | User application hardening | A.8.7 Malware; A.8.19 Software on systems | PR.PS Platform Security |
| Verify enforcement with scheduled Get-ProcessMitigation and baseline diffing | Confirms the policy is live per process, catches NOTSET gaps and local drift that a policy report hides | User application hardening | A.8.19 Software on systems | PR.PS Platform Security |
| Patch applications and operating systems | Pulls in recompiled binaries that ship CFG and CET support and closes the memory-corruption bugs the mitigations only contain | Patch applications; Patch operating systems | A.8.8 Technical vulnerabilities | ID.RA; PR.PS Platform Security |
| Application control (allow only approved software) | Blocks unmitigated or attacker-supplied binaries from executing, so an image that would read NOTSET never runs | Application control (ML2+) | A.8.19 Software on systems | PR.PS Platform Security |
| Restrict administrative privileges | Limits the blast radius of an exploit that does land and stops local re-disabling of mitigations | Restrict administrative privileges | A.8.2 Privileged access rights | PR.AA-05 Least privilege |

## Honest limitations

Verification proves configuration, not invulnerability. `Get-ProcessMitigation -Id` reads what is enforced on a process at that moment; a process that later loads a plugin, is relaunched under a different policy, or is hit by a bug in the mitigation itself is outside that snapshot. `NOTSET` requires you to chase the inherited default correctly or you will record a gap as covered, or the reverse. The no-audit mitigations (CFG, DEP, mandatory ASLR, SEHOP, heap-integrity, CET) produce no event when they are merely on, so for those the only runtime proof is the configuration read plus the crash-cluster tripwire, and a reliable quiet exploit that works first try yields one clean crash or none. CFG and CET defend forward and backward control flow but not data-only or counterfeit-object attacks that stay inside the permitted graph; mandatory ASLR is defeated by an information leak that discloses a module base before any crash. CET needs supporting silicon and a CETCOMPAT-linked image, so unsupported hardware or a legacy binary leaves a gap you cannot configure away, which is the sibling skill's binary-audit job. Treat the audit events as a tripwire, the crash clusters as a lead, and the per-process state read as the load-bearing evidence; never assume a mitigation named in the GPO is live on the host in front of you until `Get-ProcessMitigation` on that host says so.

## ATT&CK and D3FEND

- MITRE ATT&CK: [T1203 Exploitation for Client Execution](https://attack.mitre.org/techniques/T1203/), [T1190 Exploit Public-Facing Application](https://attack.mitre.org/techniques/T1190/), [T1068 Exploitation for Privilege Escalation](https://attack.mitre.org/techniques/T1068/), [T1211 Exploitation for Defense Evasion](https://attack.mitre.org/techniques/T1211/), [T1210 Exploitation of Remote Services](https://attack.mitre.org/techniques/T1210/).
- MITRE ATT&CK mitigation: [M1050 Exploit Protection](https://attack.mitre.org/mitigations/M1050/), the canonical mitigation this whole skill verifies.
- MITRE D3FEND: [D3-SAOR Segment Address Offset Randomization](https://d3fend.mitre.org/technique/d3f:SegmentAddressOffsetRandomization/) (ASLR) and [D3-PSEP Process Segment Execution Prevention](https://d3fend.mitre.org/technique/d3f:ProcessSegmentExecutionPrevention/) (DEP/NX). D3FEND maps SEHOP to [D3-EHPV Exception Handler Pointer Validation](https://d3fend.mitre.org/technique/d3f:ExceptionHandlerPointerValidation/) and the CET shadow stack to [D3-SSC Shadow Stack Comparisons](https://d3fend.mitre.org/technique/d3f:ShadowStackComparisons/); CFG has no single clean D3FEND technique and is best described in prose under the application-hardening branch.
- Public rule sources: SigmaHQ `rules/windows/builtin/security_mitigations/` and `rules/windows/builtin/application/` at [github.com/SigmaHQ/sigma](https://github.com/SigmaHQ/sigma); Microsoft Learn [Customize exploit protection](https://learn.microsoft.com/en-us/defender-endpoint/customize-exploit-protection) for the mitigation set, cmdlets, and NOTSET semantics; MITRE D3FEND.

**Scope note.** This is a defensive skill. It explains exploitation only to the depth needed to verify, detect, and harden the Windows mitigations against it, and deliberately ships no exploit code, bypass techniques, or offensive tooling.
