HomeNewsletterCommunityMVP FeedToolsArchiveBlogToday's NewsAboutServicesQuick Links Subscribe free
← Back to Blog
Windows 11 Windows 11Exploit ProtectionMicrosoft DefenderAttack Surface ReductionIntuneGroup PolicyEndpoint SecurityPowerShell

Exploit Protection Mitigations: The Per-Process Hardening Nobody Configures, and the XML That Rewrites All of It

IA
Imran Awan
21 August 2026

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.

The short version

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.

MitigationScopePowerShell keyword
Control flow guard (CFG)System and appCFG, StrictCFG, SuppressExports
Data Execution Prevention (DEP)System and appDEP, EmulateAtlThunks
Force randomization for images (Mandatory ASLR)System and appForceRelocateImages
Randomize memory allocations (Bottom-up ASLR)System and appBottomUp, HighEntropy
Validate exception chains (SEHOP)System and appSEHOP, SEHOPTelemetry
Validate heap integritySystem and appTerminateOnError
Arbitrary code guard (ACG)App onlyDynamicCode
Block low integrity imagesApp onlyBlockLowLabel
Block remote imagesApp onlyBlockRemoteImages
Block untrusted fontsApp onlyDisableNonSystemFonts
Code integrity guardApp onlyBlockNonMicrosoftSigned, AllowStoreSigned
Disable extension pointsApp onlyExtensionPoint
Disable Win32k system callsApp onlyDisableWin32kSystemCalls
Do not allow child processesApp onlyDisallowChildProcessCreation
Export address filtering (EAF)App onlyEnableExportAddressFilter, EnableExportAddressFilterPlus
Import address filtering (IAF)App onlyEnableImportAddressFilter
Simulate execution (SimExec)App onlyEnableRopSimExec
Validate API invocation (CallerCheck)App onlyEnableRopCallerCheck
Validate handle usageApp onlyStrictHandle
Validate image dependency integrityApp onlyEnforceModuleDepencySigning
Validate stack integrity (StackPivot)App onlyEnableRopStackPivot
Hardware-enforced stack protectionApp onlyNo 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.

Context: why the mitigation count keeps moving. The reference page documents twenty-two mitigations. The deployment page tells you to test "which of the 21 mitigations" are incompatible with your apps. Hardware-enforced stack protection and Randomize memory allocations are newer than some of the tables and are documented in prose rather than in every list. If a checklist you inherited names exactly twenty mitigations, it predates hardware shadow stacks and you are missing rows.

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.

  1. You configure mitigations on one reference device, in the Windows Security app or with Set-ProcessMitigation.
  2. 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.
  3. You deploy that XML by Group Policy or Intune. The policy value records either the file location or the XML content itself.
  4. 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.
  5. A process starts. The loader reads that executable's Image File Execution Options entry. If there is no entry, the system default applies.
  6. 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.
  7. 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\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<exe>
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\kernel
HKLM\SOFTWARE\Policies\Microsoft\Windows Defender ExploitGuard\Exploit Protection
Value nameWhich keyWhat it means
MitigationOptionsImage File Execution Options, per exeThe enforce-mode mitigation bits for that executable name.
MitigationAuditOptionsImage File Execution Options, per exeThe audit-mode bits. Present without the enforce value means log only.
EAFModulesImage File Execution Options, per exeThe module list that Export address filtering plus monitors for this exe.
UseFilterImage File Execution Options, per exeSignals that full-path filter subkeys exist under this executable name.
FilterFullPathFilter subkey under an exeLimits the override to the executable at exactly this path.
MitigationOptionsSession Manager kernelSystem-wide enforce bits. Absent means the Windows defaults apply.
MitigationAuditOptionsSession Manager kernelSystem-wide audit bits. Absent means no system-wide audit.
ExploitProtectionSettingsWindows Defender ExploitGuard policy keyThe 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.

Gotcha: the bitmask is not documented, so do not read it. 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.

MitigationAttack class it stopsAudit mode
Arbitrary code guardMarking memory executable, so attacker-supplied code cannot run. Returns STATUS_DYNAMIC_CODE_BLOCKED.Cmdlet: AuditDynamicCode
Block low integrity imagesLoading files downloaded by a sandboxed browser. Returns STATUS_ACCESS_DENIED.Cmdlet: AuditImageLoad
Block remote imagesLoading a binary from a remote device such as a UNC share the attacker controls.Documented option; no cmdlet keyword
Block untrusted fontsFont-parsing bugs reached through fonts outside the system fonts directory.Cmdlet: AuditFont, FontAuditOnly
Code integrity guardLoading or injecting any binary not signed by Microsoft. Returns STATUS_INVALID_IMAGE_HASH.Cmdlet: AuditMicrosoftSigned, AuditStoreSigned
Control flow guardOverwriting a function pointer to redirect an indirect call.None. Compiled into the binary.
Data Execution PreventionExecuting injected code from a data page such as the heap or the stack.None
Disable extension pointsPersistence and injection via AppInit DLLs, legacy IMEs, and Windows event hooks.None, explicitly
Disable Win32k system callsSandbox escape through the win32k.sys kernel attack surface.Cmdlet: AuditSystemCall
Do not allow child processesLiving-off-the-land chains that launch a second process. Returns STATUS_CHILD_PROCESS_BLOCKED.Cmdlet: AuditChildProcess
Export address filteringShellcode reading export tables in ntdll.dll, kernelbase.dll and kernel32.dll to find useful APIs.Documented option; not via cmdlet
Force randomization for imagesReturn-to-libc style reuse of code sitting at a known base address.None, explicitly
Hardware-enforced stack protectionReturn-oriented programming, using a hardware shadow stack (Intel CET or AMD shadow stacks).Documented audit-only option
Import address filteringRewriting the import address table to hijack calls to sensitive APIs.Documented option; not via cmdlet
Randomize memory allocationsPredictable allocation addresses. Adds entropy on top of Mandatory ASLR.None, explicitly
Simulate executionROP gadgets, on 32-bit processes only, by walking the assembly to find the RET.Documented option; not via cmdlet
Validate API invocationROP gadgets, by checking that a sensitive API was called from a valid caller.Documented option; not via cmdlet
Validate exception chainsSEH overwrite, by validating the exception handler chain on dispatch.None, explicitly
Validate handle usageReuse of a recorded handle to reach a protected object. Raises STATUS_INVALID_HANDLE.None, explicitly
Validate heap integrityHeap corruption, by terminating the process instead of continuing.None, explicitly
Validate image dependency integrityDLL planting against statically linked Windows binaries. Returns STATUS_INVALID_IMAGE_HASH.Documented option; not via cmdlet
Validate stack integrityStack pivot, where a fake stack in heap memory drives execution.Documented option; not via cmdlet
Gotcha: "audit not available" means "not available from PowerShell". Microsoft's cmdlet table marks EAF, IAF, SimExec, CallerCheck and StackPivot as audit not available, with a footnote saying audit is not available via PowerShell cmdlets. The XML schema clearly supports it: Microsoft's own CSP example sets 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.

Destructive risk: removing the policy does not remove the settings. Microsoft states that when the Group Policy or MDM policy that deploys the XML is no longer enforced, the settings deployed by that XML are not automatically removed. Unassigning the Intune profile leaves every mitigation in place on every device, indefinitely. The documented rollback is to export an XML from a clean device, or take 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%4KernelMode.evtx
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.

Tip: for Office, Microsoft now tells you to use something else. The evaluation guidance says to prefer attack surface reduction rules over exploit protection for Outlook, Word, Excel, PowerPoint and OneNote, and to use Block Adobe Reader from creating child processes for Reader. It also lists five Program settings mitigations as deprecated: EAF and IAF for application compatibility, and SimExec, CallerCheck and StackPivot as superseded by arbitrary code guard. Building a new per-app policy around those five means building on a deprecation notice.

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.

PowerShell - run elevated
PS C:\> Get-ProcessMitigation -System # Reads the system-level defaults from the Session Manager kernel key. ProcessName : System Source : System Defaults Id : 0 DEP: Enable : NOTSET EmulateAtlThunks : NOTSET Override DEP : False ASLR: BottomUp : NOTSET ForceRelocateImages : NOTSET HighEntropy : NOTSET # HEALTHY on an unconfigured device: every field NOTSET, Source = System Defaults. # NOTSET does NOT mean off. At system level it means the Windows default applies. # Microsoft documents DEP, SEHOP and Validate heap integrity as on by default, # and Mandatory ASLR as off by default. # DIFFERENT, not broken: Source = Registry means someone wrote real values here.

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.

PowerShell - run elevated
PS C:\> Get-ProcessMitigation -Name iexplore.exe # Reads the REGISTRY entry for that exe name. # Add -RunningProcesses to read a live process instead of the registry. ProcessName : iexplore.exe Source : Registry Id : 0 DEP: Enable : NOTSET EmulateAtlThunks : OFF Override DEP : False ASLR: ForceRelocateImages : ON RequireInfo : OFF # Source = Registry proves an app-level override exists for this exe name. # ForceRelocateImages = ON means Mandatory ASLR is enforced here even though # the system default for it is off. The app-level value wins, always. # This particular entry ships with Windows. Nobody in your org typed it.

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.

Registry Editor
Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\iexplore.exe
Name                 Type           Data
(Default)          REG_SZ       (value not set)
MitigationOptions   REG_BINARY    00 01 00 00 00 00 00 00 ...
Illustrative rendering of a key that really is present on a stock Windows 11 install. The binary data is truncated, and the bit layout is undocumented, so do not decode it. Read it with Get-ProcessMitigation.

Fourth, check whether a policy XML is deployed at all. This single test tells you whether the device or the policy is authoritative.

PowerShell - run elevated
PS C:\> $k = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender ExploitGuard\Exploit Protection' PS C:\> Get-ItemProperty -LiteralPath $k -Name ExploitProtectionSettings -ErrorAction SilentlyContinue # Nothing returned = no policy XML. Local state is authoritative today. # A long XML string returned = Intune or MDM pushed the file content inline. # A path or UNC returned = Group Policy pushed a file location. # If it is a path, confirm the computer account can still reach it.

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.

Event Viewer
Applications and Services Logs > Microsoft > Windows > Security-Mitigations > KernelMode
Level: Warning  |  Source: Microsoft-Windows-Security-Mitigations  |  Event ID: 10
Process '\Device\HarddiskVolume3\Program Files\...\example.exe' (PID 32624) was blocked from making system calls to Win32k.sys.
Level: Information  |  Event ID: 1
Process '\Device\HarddiskVolume3\Windows\System32\spoolsv.exe' (PID 8076) would have been blocked from generating dynamic code.
Real events from a managed Windows 11 Enterprise device, with the process path and PID redacted. Note the wording: "was blocked" is enforce, "would have been blocked" is audit.

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.

Channels: Microsoft-Windows-Security-Mitigations/KernelMode and Microsoft-Windows-Security-Mitigations/UserMode   (provider: Microsoft-Windows-Security-Mitigations)
Event IDMitigationAudit or enforce
1 / 2Arbitrary code guardAudit / enforce
3 / 4Do not allow child processesAudit / block
5 / 6Block low integrity imagesAudit / block
7 / 8Block remote imagesAudit / block
9 / 10Disable Win32k system callsAudit / block
11 / 12Code integrity guardAudit / block
13 / 14Export address filteringAudit / enforce
15 / 16Export address filtering plusAudit / enforce
17 / 18Import address filteringAudit / enforce
19 / 20Validate stack integrity (StackPivot)Audit / enforce
21 / 22Validate API invocation (CallerCheck)Audit / enforce
23 / 24Simulate 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.

Windows SecurityApp & browser controlExploit protection settingsExport settings
  1. Open the Windows Security app from the shield icon in the taskbar, or search Start for Security.
  2. Select the App & browser control tile, then Exploit protection settings.
  3. Under System settings, set each mitigation to On by default, Off by default, or Use default.
  4. 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.
  5. For each mitigation on that app, pick Audit rather than on, for the first round.
  6. Select Apply. Restart the app, or Windows, if you are prompted to.
  7. Scroll to the bottom of the Exploit protection section and select Export settings.
Gotcha: "Use default (On)" does not export as On. Microsoft's guidance is explicit about this. If you want a mitigation to be On in the exported XML, choose On by default, not Use default (On). The two look nearly identical in the UI and produce different files.

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

intune.microsoft.comEndpoint securityAttack surface reductionExploit Protection
  1. Sign in to the Microsoft Intune admin center at intune.microsoft.com.
  2. Go to Endpoint security, then Attack surface reduction, then Create policy.
  3. Set Platform to Windows 10, Windows 11, and Windows Server.
  4. Set Profile to Exploit Protection, then select Create.
  5. Give it a name that carries a version, for example ASR-ExploitProtection-v3-audit, then select Next.
  6. On Configuration settings, set Exploit Protection Settings to Configured, then browse to and select your XML file.
  7. Add scope tags if you use them, then assign to a pilot group of 10 to 50 devices. Do not assign to All devices.
  8. 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

Computer ConfigurationAdministrative TemplatesWindows ComponentsMicrosoft Defender Exploit GuardExploit Protection
  1. Open the Group Policy Management Console on your management workstation, or gpedit.msc for a single device.
  2. Right-click the target Group Policy Object and select Edit.
  3. Navigate to Computer Configuration > Administrative Templates > Windows Components > Microsoft Defender Exploit Guard > Exploit Protection.
  4. Open Use a common set of exploit protection settings.
  5. Select Enabled.
  6. 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.
  7. Select OK, then deploy the GPO as you normally would.
Destructive risk: an unreachable XML path is worse than no policy. Group Policy stores a location, not the file. Microsoft states that every device using the configuration must be able to access the file, so it belongs on a share the computer accounts can read. If the share moves, devices keep whatever mitigations they already have, from whatever version of the file they last read, and nothing tells you the policy stopped working. Intune sidesteps this because the CSP carries the XML content itself.

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.

Tip: version the XML in source control, not on a share. The XML is the whole posture, so a diff between two versions is a complete change record for the feature. Keep it in Git, carry the version in the Intune profile name, and treat the share copy or the Intune upload as a build artefact. When someone asks why an application started crashing after a Tuesday, the diff is the answer.

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.

PowerShell 5.1 - elevated - real run, identifiers replaced
PS C:\> .\Get-ExploitProtectionState.ps1 -MaxApps 4 -Quiet Device : CONTOSO-1234 OS : Microsoft Windows 11 Enterprise (build 26200) PS engine : 5.1.26100.9168 / Desktop Elevated : True Module : ProcessMitigations 1.0.12 1. Policy XML deployment Policy key : HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender ExploitGuard\Exploit Protection State : NOT deployed (no policy XML on this device) # So nothing below came from a management platform. That is the whole point. 2. System-level mitigations Settings explicitly configured at system level : 0 of 58 # HEALTHY baseline: every field NOTSET, so pure Windows defaults. 3. Raw kernel mitigation values MitigationOptions : absent (Windows default in effect) MitigationAuditOptions : absent (Windows default in effect) 4. Per-application overrides (Image File Execution Options) Per-application override entries found : 69 Audit versus enforce breakdown: enforce only : 65 audit only : 0 (logs, blocks nothing) enforce+audit : 0 # 69 per-app overrides on a device nobody deliberately configured. Windows, # Office, Adobe Reader and a VPN client all wrote entries here. # THIS is the state your first XML deployment rewrites. 5. Security-Mitigations event channels Channel : Microsoft-Windows-Security-Mitigations/KernelMode Enabled : True Records : 524 Channel : Microsoft-Windows-Security-Mitigations/UserMode Enabled : True Records : 0 # BROKEN would be Enabled : False, or a read failure. The script says # "read failed" rather than printing 0, because 0 reads as "no blocks". Verdict * No policy XML is deployed. Local state is authoritative today, and will be overwritten for every executable named in the first XML you deploy. * Warnings : 0 This report is read-only. Nothing on this device was changed.

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.

Gotcha: event IDs 25 and 36 are observed, not documented. Microsoft's published table stops at 24. I found 25 and 36 on a live Windows 11 build 26200 device and read their message text to identify them. Treat them as observed-and-undocumented: useful for understanding what is happening, unsafe as the basis for a detection rule or an alert, because an undocumented ID can change in any update. The same caution applies to the 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.

AuthorPostWhy it is useful
Rudy OomsThe Exploit Protection Between usWalks the Intune Attack surface reduction path, the XML requirement, and troubleshooting through Get-ProcessMitigation and the Security-Mitigations log.
Peter van der WoudeWorking with Exploit Protection to protect devices from being exploitedCovers the configure, export and distribute sequence, and how to verify the policy actually landed on a device.

References

  1. Exploit protection reference - every mitigation, its compatibility notes and configuration options, the Image File Execution Options MitigationOptions key, and the EP-reset.xml contents.
  2. 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.
  3. Apply mitigations to help prevent attacks through vulnerabilities - the event ID table, the advanced hunting ActionTypes, and the EMET comparison.
  4. Import, export, and deploy exploit protection configurations - export and import behaviour, and the Group Policy distribution steps.
  5. 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.
  6. Troubleshoot exploit protection mitigations - the registry value names, the Session Manager kernel values, and the removal script.
  7. ExploitGuard Policy CSP - the ExploitProtectionSettings OMA-URI, the Group Policy mapping, the registry key name, and an audit-mode XML example.
  8. Override Process Mitigation Options - the separate System, Mitigation Options Group Policy and its documented bit flags.
  9. ProcessMitigations module - Get-ProcessMitigation, Set-ProcessMitigation and ConvertTo-ProcessMitigationPolicy.
PowerShell — companion script

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.

Get-ExploitProtectionState.ps1 — Read-only report of the Windows exploit protection (Exploit Guard) mitigation state on
View all scripts on GitHub
Was this post helpful?
React below — no account needed
Share this post
LinkedIn X / Twitter Reddit Bluesky

More from EndpointWeekly

Windows 11
Controlled folder access blocked your backup software: reading…
Microsoft Defender's controlled folder access blocks untrusted processes writing to…
Windows 11
Attack Surface Reduction rules broke a line-of-business app:…
An ASR rule in Block mode kills an app and tells you nothing but a GUID. Here is the…
Windows 11
Dev Drive on Windows 11: a ReFS volume with a different…
Dev Drive is a ReFS volume where Filter Manager detaches every minifilter except…