Exploit protection is the most thoroughly documented Windows security feature that almost nobody turns on. It is the in-box replacement for the Enhanced Mitigation Experience Toolkit, or EMET, which reached end of support on 31 July 2018. It ships in every Windows 10 build from 1709 onward, every Windows 11 build, and Windows Server from version 1803. It applies more than twenty separate memory-safety and code-integrity mitigations. And in most tenants the Intune profile that would deploy it has never been created.
That is a shame, because the feature is already running on your fleet. Windows configures a set of per-application mitigations at install time. Office writes more. Adobe Reader writes more. Your VPN client probably writes more. None of that shows up in a compliance report, and none of it is visible in one place. This post walks the whole surface: what each mitigation actually stops, where the state lives in the registry, which mitigations have an audit mode and on which configuration surface, and what happens to all of it the first time you deploy an XML.
Exploit protection applies mitigations at two levels: a system-wide default and a per-executable override, and the per-executable override always wins. Six mitigations can be set system-wide; the rest are per-app only. Deployment through Group Policy or Intune is a single XML file that describes the whole posture, so every executable named in that file has its mitigations rewritten to whatever the file says, and Microsoft documents that those settings are not removed when the policy stops applying. Audit mode is reachable from PowerShell for only six mitigations, even though the XML and the event log support audit for several more.
The problem: twenty-two mitigations and one blunt deployment channel
Start with what the feature is. A mitigation is a runtime rule that the kernel, the memory manager, or a user-mode shim applies to a process. It does not look for malware. It makes a class of exploitation technique fail outright.
Data Execution Prevention makes injected shellcode unexecutable. Arbitrary code guard makes just-in-time code generation impossible. Do not allow child processes makes a living-off-the-land chain break at the second hop. None of those rules care what the malware is called.
Microsoft documents twenty-two mitigations on the exploit protection reference page. Here is the complete list, with the scope Microsoft assigns each one and the PowerShell keyword you use to name it.
| Mitigation | Scope | PowerShell keyword |
|---|---|---|
| Control flow guard (CFG) | System and app | CFG, StrictCFG, SuppressExports |
| Data Execution Prevention (DEP) | System and app | DEP, EmulateAtlThunks |
| Force randomization for images (Mandatory ASLR) | System and app | ForceRelocateImages |
| Randomize memory allocations (Bottom-up ASLR) | System and app | BottomUp, HighEntropy |
| Validate exception chains (SEHOP) | System and app | SEHOP, SEHOPTelemetry |
| Validate heap integrity | System and app | TerminateOnError |
| Arbitrary code guard (ACG) | App only | DynamicCode |
| Block low integrity images | App only | BlockLowLabel |
| Block remote images | App only | BlockRemoteImages |
| Block untrusted fonts | App only | DisableNonSystemFonts |
| Code integrity guard | App only | BlockNonMicrosoftSigned, AllowStoreSigned |
| Disable extension points | App only | ExtensionPoint |
| Disable Win32k system calls | App only | DisableWin32kSystemCalls |
| Do not allow child processes | App only | DisallowChildProcessCreation |
| Export address filtering (EAF) | App only | EnableExportAddressFilter, EnableExportAddressFilterPlus |
| Import address filtering (IAF) | App only | EnableImportAddressFilter |
| Simulate execution (SimExec) | App only | EnableRopSimExec |
| Validate API invocation (CallerCheck) | App only | EnableRopCallerCheck |
| Validate handle usage | App only | StrictHandle |
| Validate image dependency integrity | App only | EnforceModuleDepencySigning |
| Validate stack integrity (StackPivot) | App only | EnableRopStackPivot |
| Hardware-enforced stack protection | App only | No keyword in the documented table |
That keyword spelling in the Validate image dependency integrity row is not a typo on my side. Microsoft's own table on the turn on exploit protection page spells it EnforceModuleDepencySigning. The matching property name in Get-ProcessMitigation output on a live Windows 11 device reads EnforceModuleDependencySigning, spelled correctly. I am reporting the observed difference rather than guessing which spelling the cmdlet parser accepts. Test it before you script against it.
Now the second half of the problem. There is exactly one supported way to push this configuration to a fleet: an XML file. Group Policy takes a path to the file. Intune takes the file itself. Both land in the same registry value.
That XML describes the whole posture in one blob. It is not a per-setting delta. Every executable named in the file has the mitigations named in that file written to its registry entry, whatever was there before.
Why it happens: two levels, one XML, and settings that do not roll back
Follow the chain from a policy assignment down to a blocked API call. It runs in this order.
- You configure mitigations on one reference device, in the Windows Security app or with
Set-ProcessMitigation. - You export the result. Microsoft documents that export as writing all settings, system-level and app-level, into one XML regardless of which pane you exported from.
- You deploy that XML by Group Policy or Intune. The policy value records either the file location or the XML content itself.
- Windows reads the XML and writes the individual registry values for you. System-level values land in the Session Manager kernel key. Per-app values land in an Image File Execution Options subkey named after the executable.
- A process starts. The loader reads that executable's Image File Execution Options entry. If there is no entry, the system default applies.
- The mitigation is enforced by whichever component owns it. The memory manager blocks image loads. The kernel validates indirect call targets for CFG. GDI validates font file locations. The exception dispatcher walks the SEH chain.
- A block or an audit is written to a Security-Mitigations event channel.
Step 4 is where the surprises live. The system-level and app-level values are stored separately, and the app-level value wins outright.
Microsoft publishes a truth table for that precedence. A mitigation set in Program settings is honoured above the same mitigation in System settings, in both directions. Turning DEP off system-wide and on for one executable gives you DEP on exactly one executable, and nowhere else.
The registry surface for the whole feature is small. Three parent keys hold everything:
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\kernel
HKLM\SOFTWARE\Policies\Microsoft\Windows Defender ExploitGuard\Exploit Protection
| Value name | Which key | What it means |
|---|---|---|
MitigationOptions | Image File Execution Options, per exe | The enforce-mode mitigation bits for that executable name. |
MitigationAuditOptions | Image File Execution Options, per exe | The audit-mode bits. Present without the enforce value means log only. |
EAFModules | Image File Execution Options, per exe | The module list that Export address filtering plus monitors for this exe. |
UseFilter | Image File Execution Options, per exe | Signals that full-path filter subkeys exist under this executable name. |
FilterFullPath | Filter subkey under an exe | Limits the override to the executable at exactly this path. |
MitigationOptions | Session Manager kernel | System-wide enforce bits. Absent means the Windows defaults apply. |
MitigationAuditOptions | Session Manager kernel | System-wide audit bits. Absent means no system-wide audit. |
ExploitProtectionSettings | Windows Defender ExploitGuard policy key | The deployed XML, or the path to it. Its presence means policy owns the posture. |
Those value names are not folklore. They are the exact names in the removal script Microsoft publishes on its own troubleshoot exploit protection mitigations page. The policy key and value name come straight out of ExploitGuard.admx in C:\Windows\PolicyDefinitions, which declares the key as Software\Policies\Microsoft\Windows Defender ExploitGuard\Exploit Protection and the value as ExploitProtectionSettings.
MitigationOptions is a numeric bitfield. Microsoft documents a handful of process-creation policy flags for the separate Process Mitigation Options Group Policy setting, but it does not publish a complete bit layout for the exploit protection value. Detection logic that compares that number against a constant will break the first time Microsoft adds a mitigation. Read the state through Get-ProcessMitigation, which returns named fields instead.
What each mitigation stops, and where audit mode really exists
This is the table people want and rarely get in one place. The audit column is the part worth reading twice, because audit availability depends on which surface you configure from.
| Mitigation | Attack class it stops | Audit mode |
|---|---|---|
| Arbitrary code guard | Marking memory executable, so attacker-supplied code cannot run. Returns STATUS_DYNAMIC_CODE_BLOCKED. | Cmdlet: AuditDynamicCode |
| Block low integrity images | Loading files downloaded by a sandboxed browser. Returns STATUS_ACCESS_DENIED. | Cmdlet: AuditImageLoad |
| Block remote images | Loading a binary from a remote device such as a UNC share the attacker controls. | Documented option; no cmdlet keyword |
| Block untrusted fonts | Font-parsing bugs reached through fonts outside the system fonts directory. | Cmdlet: AuditFont, FontAuditOnly |
| Code integrity guard | Loading or injecting any binary not signed by Microsoft. Returns STATUS_INVALID_IMAGE_HASH. | Cmdlet: AuditMicrosoftSigned, AuditStoreSigned |
| Control flow guard | Overwriting a function pointer to redirect an indirect call. | None. Compiled into the binary. |
| Data Execution Prevention | Executing injected code from a data page such as the heap or the stack. | None |
| Disable extension points | Persistence and injection via AppInit DLLs, legacy IMEs, and Windows event hooks. | None, explicitly |
| Disable Win32k system calls | Sandbox escape through the win32k.sys kernel attack surface. | Cmdlet: AuditSystemCall |
| Do not allow child processes | Living-off-the-land chains that launch a second process. Returns STATUS_CHILD_PROCESS_BLOCKED. | Cmdlet: AuditChildProcess |
| Export address filtering | Shellcode reading export tables in ntdll.dll, kernelbase.dll and kernel32.dll to find useful APIs. | Documented option; not via cmdlet |
| Force randomization for images | Return-to-libc style reuse of code sitting at a known base address. | None, explicitly |
| Hardware-enforced stack protection | Return-oriented programming, using a hardware shadow stack (Intel CET or AMD shadow stacks). | Documented audit-only option |
| Import address filtering | Rewriting the import address table to hijack calls to sensitive APIs. | Documented option; not via cmdlet |
| Randomize memory allocations | Predictable allocation addresses. Adds entropy on top of Mandatory ASLR. | None, explicitly |
| Simulate execution | ROP gadgets, on 32-bit processes only, by walking the assembly to find the RET. | Documented option; not via cmdlet |
| Validate API invocation | ROP gadgets, by checking that a sensitive API was called from a valid caller. | Documented option; not via cmdlet |
| Validate exception chains | SEH overwrite, by validating the exception handler chain on dispatch. | None, explicitly |
| Validate handle usage | Reuse of a recorded handle to reach a protected object. Raises STATUS_INVALID_HANDLE. | None, explicitly |
| Validate heap integrity | Heap corruption, by terminating the process instead of continuing. | None, explicitly |
| Validate image dependency integrity | DLL planting against statically linked Windows binaries. Returns STATUS_INVALID_IMAGE_HASH. | Documented option; not via cmdlet |
| Validate stack integrity | Stack pivot, where a fake stack in heap memory drives execution. | Documented option; not via cmdlet |
AuditEnableExportAddressFilter, AuditEnableImportAddressFilter, AuditEnableRopStackPivot, AuditEnableRopCallerCheck and AuditEnableRopSimExec. The event log agrees, with separate audit and enforce IDs for each. The audit path exists. The cmdlet just does not expose it, so build audit rings in XML, not in script.
The replace-and-tattoo behaviour, stated precisely
You will read in a lot of places that deploying an exploit protection XML wipes everything not in the file. That is not quite what Microsoft documents, and the nuance changes how you build the file. Three documented behaviours combine into the effect people describe.
First, export is total. The docs state plainly that exporting saves all settings for both app-level and system-level mitigations, from either pane. An XML produced by exporting a reference device therefore carries that device's entire posture, including things you never deliberately set.
Second, import is not a clean slate. Microsoft's troubleshooting page opens by saying the configuration export and import process "does not remove all unwanted mitigations", then supplies a script that deletes the registry values directly. Mitigations for an executable your XML never mentions survive the import.
Third, and this is the sharp edge, a reset is expressed as explicit false overrides. Microsoft's own reset file, EP-reset.xml from the Windows Security Baselines, works by naming each application and setting attributes such as OverrideDEP="false" and OverrideEnableExportAddressFilter="false". That is how you clear a mitigation: name it and override it back to default.
Put those together and the practical rule is simple. An exported XML from a clean reference machine is full of false override attributes, which is exactly why importing one appears to erase configuration you wanted to keep. It is not a wipe. It is a very long list of explicit instructions to go back to default, aimed at every app the reference machine happened to know about.
EP-reset.xml from the Windows Security Baselines, and deploy that. Do not plan a rollback you have not built the file for.
The rest of the surface, including the parts that do not exist
Working a surface checklist honestly means saying where there is nothing to find.
System files and binaries. There is no exploit protection service binary, because enforcement lives inside components you already have. C:\Windows\System32\win32k.sys and win32kfull.sys are the kernel-mode graphics components that Disable Win32k system calls exists to fence off. ntdll.dll, kernelbase.dll and kernel32.dll are the three modules Export address filtering protects by default, and EAF plus extends that to mshtml.dll, vbscript.dll, vgx.dll, mozjs.dll, xul.dll, acrord32.dll, acrofx32.dll, acroform.api and the Flash and JScript OCX families. The PowerShell surface is Microsoft.ProcessMitigations.Commands.dll, which sits in C:\Windows\System32\WindowsPowerShell\v1.0\Modules\ProcessMitigations on a stock Windows 11 install. The Group Policy template is C:\Windows\PolicyDefinitions\ExploitGuard.admx.
Log files. There is no text log. Nothing writes a .log file you can grep for a mitigation block. What exists is two event channels, which back onto these two files on disk:
C:\Windows\System32\winevt\Logs\Microsoft-Windows-Security-Mitigations%4UserMode.evtx
The string to search for is the process path in the event message. A healthy audit line reads "would have been blocked". An enforce line reads "was blocked". More on that below.
Services and scheduled tasks. No service drives exploit protection, and no scheduled task under \Microsoft\Windows\ applies it. Enforcement is in the kernel and the loader, and it happens at process start. Three services are adjacent but not load-bearing: SecurityHealthService (Windows Security Service) renders the settings page, WinDefend (Microsoft Defender Antivirus Service) owns the wider Defender stack, and Sense (Windows Defender Advanced Threat Protection Service) forwards mitigation events to Defender for Endpoint. Stopping all three would not disable a single mitigation.
Defender relevance. Exploit protection is one of the attack surface reduction capabilities in Defender for Endpoint. Its events surface in advanced hunting as DeviceEvents rows whose ActionType starts with ExploitGuard, in audited and blocked pairs such as ExploitGuardAcgAudited and ExploitGuardAcgEnforced. The Intune profile lives in the Endpoint security Attack surface reduction node, not in a Settings Catalog configuration profile.
How to verify: read the whole surface before you touch it
Verification comes first here for a reason. On a device you have never configured, the per-app override list is not empty, and you need to know what is already there before an XML rewrites it.
The first command reads the system-wide defaults. Run it elevated. It returns a named field for every mitigation rather than a bitmask.
Read that output as three states, not two. ON and OFF mean somebody wrote a value. NOTSET means nobody did. The distinction matters for rollback, because only the absent case returns to a true default when you reset.
The second command reads one executable's override. Swap in the process you care about.
Third, look at the registry directly, because the cmdlet will not hand you a countable list of every configured executable. Open Registry Editor and go to the Image File Execution Options key.
Fourth, check whether a policy XML is deployed at all. This single test tells you whether the device or the policy is authoritative.
Finally, read the event channels. This is where audit mode pays for itself, and where you will discover that mitigations are already firing on your fleet.
That wording difference is the fastest audit-versus-enforce test there is. Enforce events say was blocked. Audit events say would have been blocked. Here is the documented event ID catalogue for the channel.
| Event ID | Mitigation | Audit or enforce |
|---|---|---|
| 1 / 2 | Arbitrary code guard | Audit / enforce |
| 3 / 4 | Do not allow child processes | Audit / block |
| 5 / 6 | Block low integrity images | Audit / block |
| 7 / 8 | Block remote images | Audit / block |
| 9 / 10 | Disable Win32k system calls | Audit / block |
| 11 / 12 | Code integrity guard | Audit / block |
| 13 / 14 | Export address filtering | Audit / enforce |
| 15 / 16 | Export address filtering plus | Audit / enforce |
| 17 / 18 | Import address filtering | Audit / enforce |
| 19 / 20 | Validate stack integrity (StackPivot) | Audit / enforce |
| 21 / 22 | Validate API invocation (CallerCheck) | Audit / enforce |
| 23 / 24 | Simulate execution (SimExec) | Audit / enforce |
Two more channels carry exploit protection evidence, and neither is a Security-Mitigations channel. Control flow guard blocks are documented against provider WER-Diagnostics, event ID 5. Untrusted font blocks are documented against provider Win32K, event ID 260.
On the Windows 11 build 26200 device I tested, the WER provider enumerates as Microsoft-Windows-WER-Diag writing to the channel Microsoft-Windows-WER-Diag/Operational, with werfault.exe as its resource file. I am reporting that as observed on a live device, not as a documented rename. Check the name on your own build before you write a query against the short form.
The fix: audit first, one versioned XML, a reset file on the shelf
The workflow Microsoft documents has four stages, and every one of them exists because this feature breaks applications when you skip it.
Stage 1: build the configuration on one reference device
Configure on a single dedicated device, then export. Both the UI path and the cmdlet are documented.
- Open the Windows Security app from the shield icon in the taskbar, or search Start for Security.
- Select the App & browser control tile, then Exploit protection settings.
- Under System settings, set each mitigation to On by default, Off by default, or Use default.
- Under Program settings, choose Add program to customize. Use Add by program name to match any process with that name, or Choose exact file path to scope the override to one path.
- For each mitigation on that app, pick Audit rather than on, for the first round.
- Select Apply. Restart the app, or Windows, if you are prompted to.
- Scroll to the bottom of the Exploit protection section and select Export settings.
The cmdlet route writes the same file. Get-ProcessMitigation -RegistryConfigFilePath C:\ExploitConfigfile.xml is documented as reading the registry configuration and saving all of it to XML. If you are migrating from EMET, ConvertTo-ProcessMitigationPolicy -EMETFilePath policy.xml -OutputFilePath result.xml converts an old EMET policy file, and may also emit a companion code integrity file named CI-result.xml.
Stage 2: deploy it with Intune
- Sign in to the Microsoft Intune admin center at
intune.microsoft.com. - Go to Endpoint security, then Attack surface reduction, then Create policy.
- Set Platform to Windows 10, Windows 11, and Windows Server.
- Set Profile to Exploit Protection, then select Create.
- Give it a name that carries a version, for example
ASR-ExploitProtection-v3-audit, then select Next. - On Configuration settings, set Exploit Protection Settings to Configured, then browse to and select your XML file.
- Add scope tags if you use them, then assign to a pilot group of 10 to 50 devices. Do not assign to All devices.
- Review and create.
Under the covers this is the ExploitGuard configuration service provider. The OMA-URI is ./Device/Vendor/MSFT/Policy/Config/ExploitGuard/ExploitProtectionSettings, the format is a string, and the value is the XML itself. Microsoft notes that the system settings in that XML require a reboot while the application settings do not.
If you use Configuration Manager instead, the same profile exists under Endpoint Security then Attack surface reduction, and the older path is Assets and Compliance then Endpoint Protection then Windows Defender Exploit Guard.
Stage 3: or deploy it with Group Policy
- Open the Group Policy Management Console on your management workstation, or
gpedit.mscfor a single device. - Right-click the target Group Policy Object and select Edit.
- Navigate to Computer Configuration > Administrative Templates > Windows Components > Microsoft Defender Exploit Guard > Exploit Protection.
- Open Use a common set of exploit protection settings.
- Select Enabled.
- In Options, type the location of the XML file. A local path, a UNC path, or a URL are all accepted, for example
\\contoso\share\Config.xml. - Select OK, then deploy the GPO as you normally would.
Note the naming drift on that Group Policy path. Before Windows 10 version 2004, the node reads Windows Defender Exploit Guard rather than Microsoft Defender Exploit Guard. Both point at the same policy. The registry key underneath still says Windows Defender ExploitGuard on current Windows 11, which matters when you write detection logic.
There is also a second, older Group Policy that people confuse with this one. Computer Configuration > Administrative Templates > System > Mitigation Options > Process Mitigation Options takes a per-executable bit field written as a string of 0, 1 and ? characters, read right to left. Microsoft documents six flags for it, covering DEP, DEP ATL thunk emulation, SEHOP, force ASLR, and bottom-up ASLR on and off. It is a different, narrower mechanism from the exploit protection XML, and using both at once is a good way to confuse yourself.
Stage 4: keep the reset file, and roll out slowly
Microsoft's own deployment guidance is a ring model. Start with 10 to 50 devices as a test group. Then user acceptance testing with IT, security and helpdesk staff. Then 1, 5, 10, 25, 50, 75 and finally 100 percent of the estate.
It also names the software classes you should never protect this way: anti-malware and intrusion detection or prevention software, debuggers, DRM-handling software such as games, and anything using anti-debugging, obfuscation or hooking technologies. Services, both system and network, are documented as out of scope. The applications worth protecting are the ones that receive or handle untrusted data.
Before the first ring, put the reset file somewhere you will find it in a hurry. Microsoft distributes EP-reset.xml with the Windows Security Baselines, and documents the import as one command: Set-ProcessMitigation -PolicyFilePath EP-reset.xml.
Proof it worked: a real read from a managed Windows 11 device
I wrote a read-only companion script, Get-ExploitProtectionState.ps1, that walks the whole surface in one pass: policy XML presence, system-level state, raw kernel values, every per-application override in Image File Execution Options, and the health of both event channels. It calls no write cmdlet and imports no XML. It lives in the Windows-11-Scripts repository.
Here is a genuine run on a managed Windows 11 Enterprise build 26200 device, with the hostname replaced. This is not illustrative output. It is what the script printed.
Read that as follows. Zero system-level settings, with both kernel values absent, means the machine is on pure Windows defaults, which is the normal and correct starting point. Sixty-nine per-application overrides means the feature is very much alive at app level without anyone deploying anything. Five hundred and twenty-four events on the kernel-mode channel means mitigations are firing daily.
Grouping those 524 events by ID on the same device produced 322 of ID 12, 95 of ID 25, 52 of ID 3, 47 of ID 10, 7 of ID 1, and 1 of ID 36. Four of those are in the documented table: 12 is a code integrity guard block, 3 is a child process audit, 10 is a win32k call block, and 1 is an arbitrary code guard audit.
IDs 25 and 36 are not in the table. Their message text identifies them as a shadow stack return address mismatch and a blocked NtFsControlFile system call, which map to Hardware-enforced stack protection and to the DisableFsctlSystemCalls field visible in Get-ProcessMitigation output.
DisableFsctlSystemCalls and SetContextIpValidation fields that appear in cmdlet output but not in Microsoft's documented keyword table.
The practical takeaway from that run is the one nobody expects. This device has no exploit protection policy, and it is still blocking things every day, including a code integrity guard block that fires 322 times a week against PowerShell loading a native image. If you deploy an XML that names powershell.exe, you inherit responsibility for that behaviour whether you meant to or not.
Two community deep-dives corroborate the deployment side of this and are worth reading alongside the Microsoft pages. I fetched both and confirmed they load and are on topic.
| Author | Post | Why it is useful |
|---|---|---|
| Rudy Ooms | The Exploit Protection Between us | Walks the Intune Attack surface reduction path, the XML requirement, and troubleshooting through Get-ProcessMitigation and the Security-Mitigations log. |
| Peter van der Woude | Working with Exploit Protection to protect devices from being exploited | Covers the configure, export and distribute sequence, and how to verify the policy actually landed on a device. |
References
- Exploit protection reference - every mitigation, its compatibility notes and configuration options, the Image File Execution Options MitigationOptions key, and the EP-reset.xml contents.
- Turn on exploit protection to help mitigate against attacks - the system-versus-app scope table, the PowerShell keyword table, the Group Policy path, the Intune profile, and the safe deployment rings.
- Apply mitigations to help prevent attacks through vulnerabilities - the event ID table, the advanced hunting ActionTypes, and the EMET comparison.
- Import, export, and deploy exploit protection configurations - export and import behaviour, and the Group Policy distribution steps.
- See how exploit protection works in a demo - audit mode, the mitigations on by default, the deprecated Program settings mitigations, and the application compatibility list.
- Troubleshoot exploit protection mitigations - the registry value names, the Session Manager kernel values, and the removal script.
- ExploitGuard Policy CSP - the ExploitProtectionSettings OMA-URI, the Group Policy mapping, the registry key name, and an audit-mode XML example.
- Override Process Mitigation Options - the separate System, Mitigation Options Group Policy and its documented bit flags.
- ProcessMitigations module - Get-ProcessMitigation, Set-ProcessMitigation and ConvertTo-ProcessMitigationPolicy.
Download it from Imran76Awan/Windows-11-Scripts — no sign-in required. It is read-only: it reports and never changes a device or anything in Intune. Validate it in your own environment before relying on the output.