---
name: verify-exploit-mitigations
description: "Verification methodology to prove exploit mitigations are both PRESENT (compiled in and policy-configured) and EFFECTIVE (enforced at runtime, on every loaded module, not silently disabled). Answers the gap attackers rely on: a mitigation a baseline claims is on that is off on the live process. Covers ASLR, DEP/NX, CFG, CET shadow stack, stack canaries, and RELRO. Maps to MITRE ATT&CK T1203, T1190, T1068, T1211, T1210, mitigation M1050, and the tampering it detects (T1562.001, T1112). Signals: Windows Get-ProcessMitigation configured-vs-running delta, bcdedit nx, MitigationOptions registry, Sysmon 13, Security 4657; Linux randomize_va_space, proc maps, readelf, checksec, systemd-analyze security, auditd. Use when you need to attest ASLR DEP CFG or CET are truly enforced and not just configured, verify every loaded DLL is mitigated not just the main EXE, gate a CI build on hardening flags, or detect a mitigation turned off via IFEO."
---

# Verify Exploit Mitigations (present AND effective) - Defensive Methodology

> **Tier 2 - methodology skill.** No bespoke UMBRASEC detection rule ships with this skill yet. It gives the defender's verification 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.

This skill is the verification loop that sits beside [defend-exploit-mitigations](../defend-exploit-mitigations/SKILL.md). That sibling owns coverage-gap scanning and crash-cluster hunting for ASLR, DEP, CFG, and CET; do not duplicate it. This skill answers a narrower, sharper question: is the mitigation that your baseline claims is on actually enforced on the running process, on every module it loaded, and has nobody quietly switched it off since. A mitigation that is configured but not effective is worth nothing, and the delta between the two states is where this skill lives.

## TL;DR

Treat every mitigation claim as unverified until you have reconciled three states: what the image was compiled with, what policy is configured for it, and what the live process actually enforces. On Windows the single highest-value check is the delta between `Get-ProcessMitigation -Name app.exe` (the configured policy for that image name) and `Get-ProcessMitigation -RunningProcesses` (what the live processes report); a gap there means policy is not reaching the process. ASLR is only effective if every loaded module is dynamic-base, so verify the module set, not just the main EXE: one fixed-base DLL hands an attacker a stable address and defeats ASLR for the whole process. CFG is compiled per image, so a CFG-enabled EXE that loads a non-CFG DLL leaves that DLL's indirect calls unchecked. Then watch the tamper surface: per-image and system-wide `MitigationOptions` registry values, `Set-ProcessMitigation` runs, and on Linux any write to `randomize_va_space` or a `personality(ADDR_NO_RANDOMIZE)` call. Gate builds on the hardening flags, attest the fleet on a schedule, and state every result as a point-in-time attestation, not a standing guarantee.

## What it looks like, defender view

There is no single alert for "a mitigation is not effective." The defender view is a set of reconciliations where two sources that should agree do not. A hardening baseline or GPO says mandatory ASLR and CFG are on for the internet-facing app set, but `Get-ProcessMitigation -RunningProcesses` shows a live worker process with CFG off, because the image shipped without `/GUARD:CF` and policy cannot add CFG to a binary that was never compiled for it. Or `Get-ProcessMitigation -System` reports DEP on, while `bcdedit /enum {current}` shows the boot `nx` value as `OptIn` rather than `AlwaysOn`, so DEP applies only to a subset of processes. Or the main EXE audits clean but a plugin DLL loaded at runtime is fixed-base, so the process has a stable, predictable module and ASLR is cosmetic. The tamper case looks different again: a per-image `MitigationOptions` value appears under Image File Execution Options for a security-relevant process, written outside your change process, disabling a mitigation the baseline swears is on. On Linux the tell is a service whose binary is PIE and RELRO-clean, yet two launches land its code segment at the same base in `/proc/<pid>/maps` because the unit was started with `ADDR_NO_RANDOMIZE` or the host has `randomize_va_space` set to 0. In every case the exploitation telemetry (the crash hunt the sibling skill covers) is silent, because nothing fired; the mitigation was simply never in force.

## Log sources and signals

Configured-vs-effective reconciliation (read on demand, Windows):

- **`Get-ProcessMitigation -Name app.exe`** reports the configured policy for that image name. **`Get-ProcessMitigation -RunningProcesses`** (or `-Id <pid>`) reports what live processes actually have in force. The delta between the two is the core signal of this skill. **`Get-ProcessMitigation -System`** gives the system-wide defaults, and **`Get-ProcessMitigation -RegistryConfigFilePath policy.xml`** exports the policy so it can be diffed across the fleet.
- **`bcdedit /enum {current}`** `nx` value: `AlwaysOn`, `OptOut`, `OptIn`, or `AlwaysOff` tells you how broadly DEP is actually enforced at boot, independent of what a GUI claims. **`Get-CimInstance Win32_OperatingSystem | Select DataExecutionPrevention_SupportPolicy`** returns 0 (AlwaysOff), 1 (AlwaysOn), 2 (OptIn), or 3 (OptOut) for the same fact from WMI.
- **Per-module truth:** enumerate the loaded module set with `(Get-Process app).Modules` and run winchecksec ([github.com/trailofbits/winchecksec](https://github.com/trailofbits/winchecksec)) or BinSkim ([github.com/microsoft/binskim](https://github.com/microsoft/binskim)) over each path, or read the mitigation columns in Process Explorer / System Informer. A process is only as hardened as its weakest loaded DLL.
- **`dumpbin /headers`** and **`dumpbin /loadconfig`** confirm what a PE was actually compiled with: Dynamic base (ASLR), High Entropy VA, NX compatible (DEP), Control Flow Guard, and the CET compatible flag (shown by dumpbin /headers under the extended DLL characteristics debug directory entry). Compiled-in state is the ceiling; policy can never exceed it.

Tamper and change signals (logged over time, Windows):

- **Per-image mitigation config** lives under `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<image.exe>` in the `MitigationOptions` and `MitigationAuditOptions` values; **system-wide** defaults live in `HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Kernel\MitigationOptions`. A write to either outside your change process is the tamper signal (T1112, and T1562.001 when it disables protection).
- **Sysmon Event ID 13** (RegistryEvent, Value Set) records writes to those keys if your Sysmon config includes them. Native **Security Event ID 4657** (a registry value was modified) covers the same writes but requires Audit Registry under Object Access plus a SACL placed on the keys; without the SACL there is no 4657.
- **PowerShell Event ID 4104** (script block logging) captures `Set-ProcessMitigation`, `bcdedit`, and registry writes performed through PowerShell, including the ones that disable a mitigation.
- **Microsoft Defender for Endpoint advanced hunting**, table **DeviceRegistryEvents**, where `ActionType == "RegistryValueSet"`, `RegistryKey` contains `Image File Execution Options` and `RegistryValueName == "MitigationOptions"`, surfaces the same tamper fleet-wide without per-host Event Viewer.

Configured-vs-effective and tamper (Linux):

- **`cat /proc/sys/kernel/randomize_va_space`** must read 2 for full ASLR; 0 means disabled host-wide.
- **Per-process effectiveness:** `cat /proc/<pid>/personality` with the `ADDR_NO_RANDOMIZE` bit (0x0040000) set means that process opted out of ASLR regardless of the host setting. Compare `/proc/<pid>/maps` across two launches of the same service; if the code segment base does not move, ASLR is not effective for it even when the binary is PIE.
- **`readelf -lW <binary>`** shows `GNU_STACK` (an `RWE` flag means an executable stack, so NX is defeated for the stack) and `GNU_RELRO`; **`checksec --file=`** and **`checksec --proc-all`**, and Debian **`hardening-check`**, report PIE (ASLR), NX, RELRO, stack canary, and FORTIFY for files and live processes.
- **`systemd-analyze security <unit>`** scores a service's own sandbox and hardening directives, exposing units that run without `NoNewPrivileges`, `MemoryDenyWriteExecute`, or a seccomp filter.
- **auditd** watches make tamper durable: a watch on `randomize_va_space` for write and attribute change, and a syscall rule on `personality`, record attempts to turn ASLR off; **ANOM_ABEND** remains the crash signal the sibling skill uses.

What normal looks like: configured policy, compiled-in flags, and live process state all agree for the internet-facing and privileged app set; module scans show no fixed-base DLLs in those processes; the mitigation registry keys change only through your deployment tooling; and on Linux services relaunch at fresh bases every time. The anomaly is any disagreement among those states, or any out-of-band write to the config.

## Detection approach

Verification produces two kinds of finding, and they need different handling.

1. **Reconciliation findings (point-in-time attestation).** On a schedule, collect compiled-in state (BinSkim or winchecksec, or `readelf`/`checksec` on Linux), configured policy (`Get-ProcessMitigation -RegistryConfigFilePath`, or the GPO baseline), and live runtime state (`Get-ProcessMitigation -RunningProcesses`, or `/proc/<pid>/maps` and `/proc/<pid>/personality`). Raise a finding for every disagreement: a running process missing a mitigation the baseline requires, an image name with no compiled support for a mitigation policy claims to enforce, or any loaded module that is fixed-base inside an ASLR-required process. This is a configuration attestation, not an alert stream; its output is a signed per-host report, and its value is proving a negative (the mitigation really is in force) rather than catching an event.

2. **Tamper detection (real alerting).** Alert when the per-image or system-wide `MitigationOptions` registry values change outside your change window, when `Set-ProcessMitigation` or `bcdedit /set nx` runs interactively, or when `randomize_va_space` is written or a process sets `ADDR_NO_RANDOMIZE`. These are the events that make effectiveness verifiable continuously rather than only at scan time, and a disable on a security-relevant image is T1562.001.

Use public, maintained rules rather than inventing one; a tuned UMBRASEC rule is not yet shipped for this threat. Real starting points, cited at directory level:

- **SigmaHQ** `rules/windows/registry/registry_set/` at [github.com/SigmaHQ/sigma](https://github.com/SigmaHQ/sigma/tree/master/rules/windows/registry/registry_set) carries exploit-protection and code-integrity tamper rules (for example `registry_set_disabled_exploit_guard_net_protection_on_ms_defender.yml`, `registry_set_hvci_disallowed_images.yml`, and `registry_set_credential_guard_disabled.yml`); start from these patterns and add the `MitigationOptions` value paths above.
- **SigmaHQ** `rules/windows/powershell/powershell_script/` for `Set-ProcessMitigation` and `bcdedit` script-block detections, and `rules/linux/auditd/` ([github.com/SigmaHQ/sigma](https://github.com/SigmaHQ/sigma/tree/master/rules/linux/auditd)) for the ASLR-disable syscall patterns.
- **Microsoft Learn - Customize exploit protection** and the exploit protection reference for the per-mitigation audit-vs-enforce behavior and the policy export format ([learn.microsoft.com/en-us/defender-endpoint/customize-exploit-protection](https://learn.microsoft.com/en-us/defender-endpoint/customize-exploit-protection)).
- **MITRE D3FEND** ([d3fend.mitre.org](https://d3fend.mitre.org/)) for the countermeasure mapping below.

## Threat hunt

Hypothesis: a mitigation the baseline claims is enforced is not actually in force somewhere, by compilation, by policy reach, by a weak loaded module, or by tamper.

1. **Configured-vs-effective delta (Windows).** For each monitored image, pull `Get-ProcessMitigation -Name app.exe` and `Get-ProcessMitigation -RunningProcesses` and diff the ASLR (BottomUp, ForceRelocateImages), DEP, CFG, and UserShadowStack fields. Any live process weaker than its configured policy is the lead. Join to `Get-ProcessMitigation -System` and `bcdedit` `nx` to decide whether the gap is host-wide or per-image.
2. **Weakest-module hunt.** For each high-value process, enumerate `(Get-Process app).Modules` and run winchecksec over every module path. Rank processes by their least-mitigated loaded module. A single fixed-base or non-CFG DLL in an otherwise hardened, internet-facing process is a prioritized finding, because it quietly defeats ASLR or leaves indirect calls unguarded for the whole address space.
3. **Tamper hunt.** Over 30 to 90 days, list every write to the per-image Image File Execution Options `MitigationOptions`/`MitigationAuditOptions` values and the Session Manager Kernel `MitigationOptions` value (Sysmon 13, Security 4657, or MDE `DeviceRegistryEvents`), plus every `Set-ProcessMitigation` and `bcdedit /set nx` in PowerShell 4104. Reconcile each against your change tickets. Any unexplained write, especially one that clears a bit, is a T1562.001 candidate.
4. **Linux effectiveness hunt.** Confirm `randomize_va_space` is 2 on every host; for each hardened service, check `/proc/<pid>/personality` for `ADDR_NO_RANDOMIZE`, relaunch and diff `/proc/<pid>/maps` bases, and run `checksec --proc-all` to flag any live process without NX, RELRO, PIE, or a canary. Pair with `systemd-analyze security` to find units running without the sandbox directives that keep those properties intact.

## Hardening

Make verification cheap, continuous, and hard to undo. The controls below ground in the UMBRASEC [foundation guide](https://umbrasec.dev/guide/foundation.html), which maps the 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 |
|---|---|---|---|---|
| Maintain a binary, module, and host mitigation inventory | You cannot attest coverage without knowing every image and every host in scope, so inventory is the precondition for verification | User application hardening | A.5.9 Inventory of assets | ID.AM Asset Management |
| Gate builds and deployments on hardening flags | Fail CI when BinSkim, winchecksec, or hardening-check reports a missing ASLR, DEP, CFG, CET, RELRO, or canary flag, so an unhardened binary never ships to verify later | User application hardening | A.8.7 Malware; A.8.19 Software on systems | PR.PS Platform Security |
| Audit-then-enforce rollout, then attest runtime state matches policy | Turn mitigations on in audit mode, confirm compatibility, enforce, then prove the live process state equals the configured policy on a schedule | User application hardening | A.8.7 Malware; A.8.19 Software on systems | PR.PS Platform Security |
| Lock the mitigation config against tampering | Put a SACL on the IFEO and Session Manager Kernel keys so changes are audited, restrict who can call Set-ProcessMitigation or write randomize_va_space, and alert on out-of-band writes | Restrict administrative privileges | A.8.2 Privileged access rights | PR.AA-05 Least privilege |
| Patch applications and operating systems | Pulls in recompiled binaries that ship CFG, CETCompat, and FORTIFY, raising the compiled-in ceiling that policy verification measures against | Patch applications; Patch operating systems | A.8.8 Technical vulnerabilities | ID.RA; PR.PS Platform Security |

## Honest limitations

Verification is a point-in-time attestation, not a standing guarantee: a process can be re-launched, a DLL hot-loaded, or a registry value flipped the moment after your scan, which is why the tamper detection above matters as much as the scan. `Get-ProcessMitigation -RunningProcesses` reports what the process declares to the API; a sufficiently privileged attacker who can write kernel memory or disable your agent can also make that report lie, so the tool is trustworthy only on a host whose integrity you trust by other means. The per-module check depends on catching the process while the module is loaded; a DLL loaded transiently for an exploit and unloaded will not appear in a periodic scan. On Linux, comparing two `/proc/<pid>/maps` bases proves randomization is happening but says nothing about its entropy, so a weak-entropy ASLR still reads as effective. Compiled-in flags are the ceiling and nothing in policy can raise them, so an image built without CFG is simply unverifiable as CFG-enabled no matter what the baseline says. And verification proves enforcement, not sufficiency: an information leak that discloses a module base defeats verified ASLR, and data-only attacks operate within verified CFG and CET, so a clean attestation lowers but never eliminates exploitation risk. State the result as evidence, name the scan time, and leave the final risk call to the defender.

## ATT&CK and D3FEND

- MITRE ATT&CK (what the mitigations defend against): [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 (what this skill detects when a mitigation is turned off): [T1562.001 Impair Defenses: Disable or Modify Tools](https://attack.mitre.org/techniques/T1562/001/) and [T1112 Modify Registry](https://attack.mitre.org/techniques/T1112/).
- MITRE ATT&CK mitigation: [M1050 Exploit Protection](https://attack.mitre.org/mitigations/M1050/), the canonical mitigation for this family.
- MITRE D3FEND: [D3-ACH Application Configuration Hardening](https://d3fend.mitre.org/technique/d3f:ApplicationConfigurationHardening/) and [D3-SCP System Configuration Permissions](https://d3fend.mitre.org/technique/d3f:SystemConfigurationPermissions/) for verifying and protecting the mitigation configuration itself; [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) name the countermeasures whose enforcement this skill attests.
- Public rule sources: SigmaHQ `rules/windows/registry/registry_set/`, `rules/windows/powershell/powershell_script/`, and `rules/linux/auditd/` at [github.com/SigmaHQ/sigma](https://github.com/SigmaHQ/sigma); Microsoft Learn exploit protection documentation; MITRE D3FEND.

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