← All posts

Auditing Windows Exploit Mitigations: SEHOP, ASLR, and DEP

The system-wide exploit mitigations attackers count on being off - SEHOP, mandatory ASLR, and DEP/NX - and how to audit them in one command.

Every Windows process runs inside a set of exploit mitigations - low-level defenses baked into the OS and the loader that make memory-corruption bugs far harder to weaponize. When they are on, a stack-overflow or a use-after-free that would have handed an attacker code execution instead just crashes the process. When they are off - or quietly disabled by a legacy app's installer - the same bug becomes a working exploit. These are the switches attackers count on being off, and almost nobody audits them.

This post walks the three system-wide mitigations WinSentinel's Exploit Mitigation module checks - SEHOP, mandatory ASLR, and DEP/NX - what each one does, how it gets turned off, and how to confirm your machine's posture in one command.

SEHOP - Structured Exception Handler Overwrite Protection

Before ASLR was universal, one of the most reliable Windows exploitation techniques was overwriting a thread's exception-handler chain (SEH) on the stack, then triggering an exception so the OS jumped to attacker-controlled code. SEHOP defeats this by validating the integrity of the SEH chain at dispatch time: if the chain has been tampered with, the process is terminated instead of jumping to the forged handler.

SEHOP is controlled machine-wide by the registry value DisableExceptionChainValidation under HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\kernel. The gotcha: the name is a double-negative. 0 means SEHOP is enabled (validation is not disabled); 1 means it has been switched off. Plenty of old "performance tuning" guides and legacy-app installers set it to 1 and never put it back.

reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\kernel" /v DisableExceptionChainValidation

If that value is 1, SEHOP is off system-wide. You want it absent or 0.

Mandatory ASLR - randomize where code lives

Address Space Layout Randomization (ASLR) randomizes the base addresses of executables, DLLs, the heap, and the stack so an attacker can't hard-code the address of a gadget or a function to jump to. Modern binaries opt into ASLR at compile time (the /DYNAMICBASE linker flag), but plenty of older third-party DLLs don't - and a single non-randomized module loaded into a process can be enough to build a reliable ROP chain.

Mandatory ASLR (a.k.a. "force relocation of images") closes that gap by relocating even images that didn't opt in. It's governed system-wide by the MoveImages value under HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management, and by the machine-wide process mitigation policy you can inspect with PowerShell:

Get-ProcessMitigation -System | Select-Object -ExpandProperty Aslr

You're looking for ForceRelocateImages and BottomUp to be ON. Note that turning mandatory ASLR on can break old apps that ship non-relocatable images - which is exactly why it sometimes gets disabled and forgotten.

DEP / NX - data pages should never be code

Data Execution Prevention (DEP), backed by the CPU's NX ("no-execute") bit, marks memory pages that hold data - the stack, the heap - as non-executable. If an attacker sprays shellcode onto the stack and jumps to it, the CPU faults instead of running it. DEP is the reason attackers pivoted to return-oriented programming in the first place; without it, exploitation is trivial.

DEP's system policy is a boot setting, not a registry toggle, so you read it from the boot configuration:

bcdedit /enum {current}

Look at the nx line. The healthy values are OptIn (the Windows default - DEP for system components and opted-in apps) or, better, OptOut / AlwaysOn. The value you do not want to see is AlwaysOff, which disables DEP machine-wide - occasionally set by ancient software that ran afoul of NX and never cleaned up after itself.

Why this belongs in a routine audit

None of these three settings changes by itself. They get flipped by:

Because they're system-wide and rarely reviewed, a disabled mitigation can sit undetected for years while quietly lowering the bar for every memory-corruption bug on the box. That's precisely the kind of slow, invisible drift a scheduled audit is built to catch.

Audit it in one command

WinSentinel's Exploit Mitigation module checks all three - SEHOP via DisableExceptionChainValidation, mandatory ASLR via the Memory Management MoveImages policy and the machine-wide process mitigation options, and the DEP/NX boot policy parsed straight from bcdedit - and rolls them into your security score with a severity and a plain-English explanation for each:

dotnet tool install --global WinSentinel.Cli
winsentinel --audit

Every check runs locally, reads real Windows state (no stubs), and is free on a single machine - along with all of WinSentinel's other audit modules, the real-time monitor, scheduled scans, and PDF reports. If a mitigation has been silently switched off, you'll see it in the report with a clear "here's what's wrong and here's why it matters" note instead of finding out the hard way when a bug that should have crashed turns into a shell.

The mitigations that matter most are the ones you assume are on. Audit them, don't assume them.