Whether a memory corruption bug becomes a working exploit depends less on the bug than on which platform mitigations are switched on in the process it lands in. Data Execution Prevention, address space layout randomisation, stack canaries, control-flow integrity and hardware shadow stacks each break a specific step that attackers rely on, and each has known gaps. This guide explains what each mitigation stops, where it is enforced on Windows, Linux and Arm, how to tell whether your software and fleet actually have it, and how to roll out stronger settings with audit mode and rollback. It is written for vulnerability managers triaging exploitability, endpoint engineers hardening fleets, and developers shipping native code. It is defensive: it describes attacker steps only at the level needed to understand what each control blocks.
Key takeaways
- Each mitigation breaks one link in the chain. DEP stops injected code running, ASLR hides where useful code lives, canaries and shadow stacks protect return addresses, and CFI constrains indirect calls.
- Mitigations are per binary and per process. One module built without ASLR or CFG support can undo protection for the whole process, so measure at the binary level.
- Information leaks are the universal bypass. The Linux kernel self-protection documentation notes that randomisation and canaries are statistical defences that an information exposure can defeat.
- Hardware enforcement is the current frontier. Intel CET shadow stacks and IBT, and Arm Pointer Authentication, move return and branch checks into the processor.
- Mitigations do not stop data-only attacks. Memory disclosure bugs that leak secrets, such as session tokens, succeed even with every mitigation on.
How the attack works
A memory corruption exploit typically has to achieve four things, and the mitigation landscape maps onto those steps. See the Memory Corruption Vulnerabilities Guide for the bug classes themselves.
| Attacker need | Classic technique (conceptual) | Mitigation that blocks it | Known gap |
|---|---|---|---|
| Run attacker-supplied code | Place code in a buffer and jump to it | DEP / NX: memory not marked executable cannot run | Code reuse (return-oriented programming) uses existing code instead |
| Know where useful code and data are | Hard-coded addresses | ASLR (user mode), KASLR (kernel) | Information leaks, non-randomised modules, low entropy on 32-bit |
| Hijack a return address | Overwrite saved return address on the stack | Stack canaries; shadow stacks (Intel CET, Windows Hardware-enforced Stack Protection); Arm PAC on return addresses | Canary leaks; modules not shadow-stack compatible |
| Hijack an indirect call or jump | Overwrite a function pointer or virtual table pointer | CFI: Windows Control Flow Guard, Clang CFI, Intel IBT, Arm BTI | Coarse-grained CFI allows any valid target; data-only attacks |
| Generate new code at runtime | Make memory writable and executable | Windows Arbitrary Code Guard (ACG); W^X policies | Breaks JIT engines unless they are designed for it |
| Load attacker code as a module | Inject an unsigned DLL | Windows Code Integrity Guard (CIG) | Abuse of legitimately signed modules |
- Shadow stacks in hardware: the Linux kernel documentation for CET explains that on each CALL the processor pushes the return address to both the normal stack and a shadow stack that applications cannot modify directly; on return it compares the two and raises a control-protection fault if they differ.
- Indirect branch tracking: IBT verifies that indirect CALL and JMP targets are marked by the compiler with
ENDBRinstructions. Linux currently supports user-space shadow stack and kernel IBT on 64-bit x86. - Pointer authentication on Arm: the ARMv8.3 extension inserts a Pointer Authentication Code, derived from the pointer, a context value such as the stack pointer and a secret key, into unused high-order pointer bits and verifies it before use. The instructions behave as no-ops on hardware without the extension, so binaries remain compatible.
Where it shows up
| Platform | Mitigation controls | How to check |
|---|---|---|
| Windows (Exploit protection) | DEP (always on for x64 and ARM64 processes), Force randomization for images (Mandatory ASLR), Randomize memory allocations (Bottom-up ASLR, with high entropy), CFG, Validate exception chains (SEHOP), Validate heap integrity, Hardware-enforced stack protection, ACG, CIG, Block remote images, Don't allow child processes, Disable Win32k system calls | Windows Security app, Get-ProcessMitigation, per-image settings under Image File Execution Options\<image>\MitigationOptions |
| Linux user space | kernel.randomize_va_space (0 off, 1 mmap base, stack and VDSO, 2 adds heap), PIE executables, NX, stack protector, user shadow stack (X86_USER_SHADOW_STACK, disabled with nousershstk) | /proc/cpuinfo shows user_shstk; readelf -n shows the SHSTK property in the ELF note |
| Linux kernel | KASLR (CONFIG_RANDOMIZE_BASE), kernel IBT, hashed %p pointer printing since 4.15 | Kernel config and boot parameters. See the Linux Kernel Hardening and sysctl Guide |
| Arm64 | Pointer Authentication (five keys: APIAKey, APIBKey, APDAKey, APDBKey, APGAKey), Branch Target Identification | CPU feature reporting and compiler flags used for the build |
| Compilers | Clang CFI (-fsanitize=cfi, requires LTO and usually -fvisibility=hidden), MSVC /guard:cf, stack protectors, CET compatibility flags | Build logs and binary inspection. See the Compiler and Toolchain Hardening Guide |
Two points matter for triage. First, compiler-dependent mitigations (CFG, Clang CFI, shadow-stack compatibility) protect only code built with them, so a legacy plug-in or third-party DLL can be the weak module. Second, Microsoft notes that Mandatory ASLR can block images whose relocation data was stripped, which is why some vendors still ship non-relocatable binaries and why forced ASLR needs testing.
Impact
- On exploitability scoring: the same vulnerability can be trivially exploitable in a process without ASLR and DEP and require a separate information leak plus a code-reuse chain in a fully hardened one. Mitigation status is a legitimate input to prioritisation, but never a reason to skip a patch for a known exploited bug.
- On attacker cost: modern exploits for hardened browsers and kernels usually chain several bugs: one to leak addresses, one to corrupt memory, often another to escape a sandbox. Each mitigation adds a bug the attacker must find.
- On data-only attacks: none of these controls stop an out-of-bounds read that returns secrets, or a write that changes a permission flag without hijacking control flow.
- On availability: most mitigations terminate the process on violation. That is the intended fail-safe, but it turns an exploit attempt into a crash that you must detect and investigate.
Detection
Mitigations generate some of the highest-fidelity exploit signals available, because legitimate software rarely triggers them once compatibility issues are resolved.
- Windows Security-Mitigations log: Microsoft documents audit events in the Security-Mitigations (Kernel Mode and User Mode) logs, including Event ID 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 (CIG audit). Forward these to the SIEM; Microsoft Defender for Endpoint also exposes them in Advanced Hunting.
- Control-protection faults and fast-fail crashes: CET shadow stack violations raise a control-protection fault; CFG and Clang CFI terminate the process on an invalid indirect call. Collect crash reports (Windows Error Reporting, systemd-coredump) and alert on these crash types in internet-facing services and document handlers.
- Crash clustering: repeated crashes of one service from one source address, particularly with different faulting addresses, suggest an attacker probing a layout or brute-forcing a canary.
- Configuration drift: regularly report processes running with mitigations disabled, binaries without ASLR or CFG flags, and Linux hosts with
randomize_va_spacebelow 2.
Relevant ATT&CK techniques for detection content are T1203 Exploitation for Client Execution, T1068 Exploitation for Privilege Escalation, T1211 Exploitation for Stealth and T1055 Process Injection, which ACG and CIG make harder.
Metrics:
- Percentage of executables and DLLs in your software inventory built with ASLR, DEP-compatible, CFG or CFI, and CET compatibility.
- Percentage of high-risk processes (browsers, office suites, PDF readers, internet-facing services) running with the target mitigation profile enforced.
- Percentage of capable hardware with shadow stacks enabled.
- Number of mitigation audit events per week during rollout, trending to zero before enforcement.
- Mean time to triage a mitigation-triggered crash in a high-risk process.
Prevention and hardening
Decision guidance
- Baseline for everything: DEP, ASLR with high entropy, SEHOP and heap integrity validation are low risk on modern software. Treat any process without them as a finding.
- High-risk processes: browsers, mail clients, document viewers and internet-facing services justify stronger, per-application settings such as ACG, CIG, child process blocking and Hardware-enforced stack protection in strict mode, after audit testing.
- Software you build: enable CFG or Clang CFI and shadow-stack compatibility in release builds; it is cheaper than retrofitting process-level controls.
- Small organisations: rely on operating system defaults, keep them on, and buy software that ships with modern mitigations. Large organisations should run per-application profiles distributed as Exploit protection XML through Intune or Group Policy.
Rollout
- Inventory: list binaries and their mitigation flags, starting with high-risk applications and services.
- Audit mode: on a pilot ring, enable audit for stronger mitigations, for example
Set-ProcessMitigation -Name <app.exe> -Enable AuditDynamicCodefor ACG, and collect Security-Mitigations events for two to four weeks. - Resolve conflicts: identify plug-ins, injected security agents or JIT engines that trigger events; update, replace or exclude them per application.
- Enforce in rings: pilot, then broad deployment, with crash telemetry watched at each stage.
- Linux: confirm
kernel.randomize_va_space = 2, PIE and stack protector in distribution builds, and CET-capable glibc and kernel where hardware supports it. - Rollback: keep previous Exploit protection XML profiles versioned; reverting is a policy change, not a reinstall. Never roll back by disabling a mitigation system-wide when one application is the problem.
Framework mapping
| Framework | Reference |
|---|---|
| NIST SP 800-53 Rev. 5 | SI-16 Memory Protection; CM-6 Configuration Settings; SA-15 Development Process, Standards, and Tools |
| NIST CSF 2.0 | PR.PS-01 Configuration management practices are established and applied |
| MITRE D3FEND | D3-SAOR Segment Address Offset Randomization; D3-SFCV Stack Frame Canary Validation; D3-PAN Pointer Authentication |
| MITRE ATT&CK | T1203, T1068, T1211, T1055 |
Lessons from incidents
- Memory disclosure beats control-flow defences: in CitrixBleed (CVE-2023-4966), crafted requests made NetScaler appliances return memory containing valid session cookies, which attackers replayed to bypass passwords and MFA. No hijack of control flow was needed, so DEP, ASLR and CFI were irrelevant. Patch, then revoke sessions and secrets that may have been in memory.
- One weak module undoes the process: a single plug-in without ASLR provides fixed addresses for code reuse. Inventory mitigation flags per module, not per application.
- Disabled for compatibility, never re-enabled: mitigations switched off years ago for an old line-of-business application persist through rebuilds. Review exceptions on a schedule.
- Appliances lag desktops: embedded and edge devices often run older toolchains and kernels with weaker mitigations, which is one reason they are favoured targets. See the Edge Device Exploitation by Threat Actors Guide.
- Crashes treated as bugs, not attacks: mitigation-triggered terminations in internet-facing services are filed as stability issues instead of being investigated as possible exploitation.
Practitioner checklist
- Inventory binaries for ASLR, DEP, CFG or CFI and shadow-stack compatibility; flag non-relocatable modules.
- Enforce the baseline (DEP, high-entropy ASLR, SEHOP, heap integrity) everywhere and treat exceptions as findings.
- Apply per-application profiles (ACG, CIG, child process blocking, Hardware-enforced stack protection) to high-risk processes after audit testing.
- Confirm
kernel.randomize_va_space = 2, KASLR and CET support on Linux fleets. - Forward Security-Mitigations events and mitigation crash types to the SIEM and alert on high-risk processes.
- Build your own native code with CFG or Clang CFI and shadow-stack compatibility enabled.
- Version Exploit protection XML profiles for rollback and review exceptions quarterly.
- Do not lower patch priority for known exploited vulnerabilities because mitigations are present.
Standards and references
- Microsoft Learn: Exploit protection reference
- Microsoft Learn: Evaluate exploit protection
- Linux kernel documentation: Control-flow Enforcement Technology (CET) Shadow Stack
- Linux kernel documentation: Kernel Self-Protection
- Linux kernel documentation: sysctl kernel parameters (randomize_va_space)
- Linux kernel documentation: Pointer authentication in AArch64 Linux
- LLVM: Clang Control Flow Integrity
- MITRE D3FEND: Pointer Authentication
Related NHI Mgmt Group resources: Memory Corruption Vulnerabilities Guide · Memory-Safe Languages Guide · Windows Endpoint Hardening Guide · Compiler and Toolchain Hardening Guide