Detecting UMCI Bypasses and PowerShell Constrained Language Mode Escapes
When a WDAC policy includes UMCI (User-Mode Code Integrity), PowerShell is forced into Constrained Language Mode (CLM). CLM strips the dangerous surface — no arbitrary COM, no Add-Type, no Win32 API calls, no script-based .NET invocation — precisely the surface an attacker needs. Unsurprisingly, there is a long line of public research on escaping CLM and the surrounding UMCI surface: PSWorkflowUtility (CVE-2017-0215), the CVE-2017-0007 Device Guard defeat, CVE-2017-8715, CVE-2018-8212 (MSFT_SCRIPTRESOURCE), Invoke-History, and code injection into an already-running full-language host.
Part 5 deferred this family to its own post; this is that post. The good news for detection: CLM and UMCI produce rich, structured PowerShell telemetry, and the escapes have distinctive shapes.
Table of Contents
- What UMCI and CLM Actually Restrict
- The Shape of an Escape
- Detection Signal 1 — Script-Block Logging
- Detection Signal 2 — Full-Language Mode in a CLM Fleet
- Detection Signal 3 — Known Escape Indicators
- Detection Signal 4 — Code Injection into a Full-Language Host
- A Sigma Rule for the Family
- Tuning and Operational Notes
- References
1. What UMCI and CLM Actually Restrict
UMCI extends code-integrity enforcement into user-mode script engines: only trusted (WDAC-approved) assemblies and scripts may execute. Because PowerShell is a trusted, signed host, WDAC cannot simply block it — instead it forces PowerShell into Constrained Language Mode, in which the dangerous .NET/COM/Win32 surface is unavailable to untrusted scripts. An attacker who can either (a) escape CLM back to Full Language Mode, or (b) inject their code into a process that is already in Full Language Mode, recovers the full surface.
That is the entire shape of the escape problem, and every technique below is one of those two paths.
2. The Shape of an Escape
Two recurring patterns:
- CLM → Full Language Mode — abuse a trusted component the policy permits to run full-trust code, or exploit a logic flaw in the policy enforcement itself (the CVE family). Examples:
PSWorkflowUtilityhonouring a workflow in full trust;MSFT_SCRIPTRESOURCE(DSC) running outside CLM; module-manifest tricks. - Inject into a full-language host — find an already-running PowerShell process in Full Language Mode (e.g. a management agent, a signed module’s host) and inject code into its memory, bypassing the language-mode check entirely.
Both leave traces — the first in PowerShell’s own event logs, the second in process/thread telemetry.
3. Detection Signal 1 — Script-Block Logging
Enable PowerShell Script Block Logging (Event ID 4104 in Microsoft-Windows-PowerShell/Operational) across the fleet. It captures the de-obfuscated script block content at execution time, including the bodies of Invoke-History, workflow, and DSC resource calls. This is the single most important telemetry source for this family.
Pair it with Module Logging (EID 4103) and Transcription for defence-in-depth. Script-block logging defeats most obfuscation because the engine logs the script after de-obfuscation, at compile time.
4. Detection Signal 2 — Full-Language Mode in a CLM Fleet
If your fleet enforces UMCI, the steady state is Constrained Language Mode. A PowerShell process reporting Full Language (or the engine emitting the EID 4104 with a script block that would be illegal under CLM, such as Add-Type, [Activator]::CreateInstance, or direct Win32 via Add-Type) is the anomaly.
You can observe language mode two ways:
- The
EngineState/Host Applicationfields in PowerShell event logs. - ETW from
Microsoft-Windows-PowerShellreflecting a transition out of constrained mode.
On a CLM-enforcing fleet, alert on any Full Language context outside the small set of management agents that legitimately run that way.
5. Detection Signal 3 — Known Escape Indicators
Per-technique fingerprints visible in script-block logs and command lines:
- CVE-2017-0215 / PSWorkflowUtility —
Invoke-AsWorkflowor workflow invocation carrying inline script;New-PSWorkflowExecutionOption. - CVE-2017-8715 — PowerShell module manifest (
.psd1) abuse; watch forNestedModules/RootModulepointing at unusual assemblies. - CVE-2018-8212 —
MSFT_SCRIPTResource(DSC) invocation carrying script outside a legitimate DSC configuration pipeline. - Invoke-History —
Get-History/Invoke-Historyreplaying a crafted history entry; the replayed content is the payload. - CVE-2017-0007 — the named-pipe/RPC surface against the Device Guard component; correlate with unusual local RPC/Pipe activity.
The presence of these atoms in a script-block log on a CLM-enforcing host is the detection.
6. Detection Signal 4 — Code Injection into a Full-Language Host
The injection path is detectable at the process/thread level rather than the PowerShell log:
CreateRemoteThread/SetThreadContextinto apowershell.exe/pwsh.exeprocess from an external, non-parent process — the classic injection signature against a full-language host.- Memory write + execute into a PowerShell process from an unsigned or non-standard source.
- A PowerShell process whose loaded module set suddenly grows (Sysmon EID 7) with a non-default assembly after the process has been running.
This is the same behavioural shape as the debugger-as-injector from part 2 and reuses the same thread-context-manipulation detection logic, retargeted at a PowerShell host.
7. A Sigma Rule for the Family
A Sigma rule keyed on the script-block log (EID 4104) for the known escape indicators, plus a process-creation branch for the injection path.
title: UMCI / PowerShell CLM Escape Indicatorsid: 1f4a8b2c-6d3e-4a9f-b5c7-8e1d2a3b4c5dstatus: experimentaldescription: > Detects indicators of Windows Defender Application Control User-Mode Code Integrity (UMCI) bypass and PowerShell Constrained Language Mode (CLM) escape — known escape atoms in script-block logs, and injection into a running full-language PowerShell host.references: - https://www.100daysofredteam.com/ - https://enigma0x3.net/2017/10/19/umci-bypass-using-psworkflowutility-cve-2017-0215/ - /posts/wdac-bypass-techniques-reference-catalog/author: Anubhav Gaindate: 2026/07/13tags: - attack.defense_evasion - attack.t1059.001 - attack.t1127.001logsource: product: windows service: microsoft-windows-powershelldetection: selection_scriptblock: EventID: 4104 ScriptBlockText|contains: - 'Invoke-AsWorkflow' - 'New-PSWorkflowExecutionOption' - 'MSFT_SCRIPTResource' - 'Invoke-History' - 'Get-History' - '[Activator]::CreateInstance' - 'Add-Type' - '[Microsoft.VisualStudio.Text.Temporation]' selection_full_language: EventID: 400 Data|contains: - 'EngineState=FullLanguage' condition: selection_scriptblock or selection_full_languagefields: - ScriptBlockText - ComputerName - Userfalsepositives: - Legitimate DSC / workflow use on management servers (constrain by host) - Legitimate Add-Type use by signed modules on non-CLM hostslevel: mediumA companion process-creation rule for the injection path:
title: Injection Into a Full-Language PowerShell Hostid: 2a5b9c1d-7e4f-4b1a-9c8d-3f2e1a0b9c8dstatus: experimentaldescription: > Detects a non-parent process writing code into / hijacking threads of a running powershell.exe/pwsh.exe host, consistent with injecting into a Full-Language PowerShell process to escape Constrained Language Mode.references: - http://www.exploit-monday.com/2017/08/exploiting-powershell-code-injection.htmlauthor: Anubhav Gaindate: 2026/07/13tags: - attack.defense_evasion - attack.t1055 - attack.t1059.001logsource: product: windows category: process_creationdetection: target_host: Image|endswith: - '\powershell.exe' - '\pwsh.exe' injector_parent: ParentImage|endswith: - '\rundll32.exe' - '\regsvcs.exe' - '\regasm.exe' - '\msbuild.exe' - '\installutil.exe' condition: target_host and injector_parentfields: - Image - ParentImage - CommandLinefalsepositives: - Legitimate tooling that hosts PowerShell (constrain to known service accounts)level: high8. Tuning and Operational Notes
- Script-block logging is mandatory for this family. Without EID 4104 you are blind to the de-obfuscated content. Turn it on centrally and forward to the SIEM.
- Baseline language mode. On a UMCI fleet the expected state is CLM. Deviation to
FullLanguageis the alert, not the routine. - Correlate CVE indicators with patch level. CVE-2017-0215, CVE-2017-8715, CVE-2018-8212 are old; their appearance on a patched fleet implies either a missed host or a deliberate bypass attempt — both worth investigating.
- Watch the module manifest path.
.psd1files withNestedModules/RootModulepointing outsideProgram Filesare a common shape across several of these escapes.
9. References
- UMCI Bypass using PSWorkflowUtility (CVE-2017-0215) — Matt Nelson
- Defeating Device Guard: CVE-2017-0007 — Matt Nelson
- CVE-2017-8715 — Matt Nelson
- CVE-2018-8212: Device Guard/CLM Bypass via MSFT_SCRIPTRESOURCE — Matt Nelson
- Exploiting PowerShell Code Injection to Bypass CLM — Matt Graeber
- PowerShell Script Block Logging — Microsoft Learn
- MITRE ATT&CK T1059.001 — PowerShell