Windows Local Privilege Escalation - DLL Hijacking for CTF Creators
DLL Hijacking
A dynamically compiled Win32 executable may use functions exported by built-in or third-party Dynamic Link Libraries (DLLs). There are two common ways to achieve this:
Import Address Table (IAT): When a program is compiled, an import address table is written into the headers of the Portable Executable (PE). The IAT keeps track of the functions that need to be imported from a DLL. As a result, every time the program runs, the linker knows which libraries to load and which functions to call.
Windows APIs: Libraries can be imported at runtime using functions such as
LoadLibrary()
orLoadLibraryEx()
from the Windows API.
DLL Hijacking vs Reflective DLL Hijacking vs DLL Sideloading
This article does not cover every aspect of DLL hijacking. However, at a high level, these are the main differences:
DLL Hijacking: Also known as
PATH DLL Hijacking
, this occurs when a DLL file is written to disk and the%PATH%
environment variable is exploited.Reflective DLL Hijacking: In this method, a DLL file is not written to disk. Instead, the DLL is loaded directly into memory and executed from there.
DLL Sideloading: This happens when the permissions of an application’s directory are not properly configured, often limiting the impact to that specific application.
Is PATH DLL Hijacking a Vulnerability?
According to Microsoft, they won’t address DLL hijacking scenarios involving %PATH%
directories as a vulnerability. As we can see in this article.
DLL Search Order
Understanding how Windows performs the Dynamic-Link Library (DLL) Search Order is crucial. According to the official documentation, the process is as follows:
Desktop applications can control the location from which a DLL is loaded by specifying a full path, using DLL redirection, or by using a manifest. If none of these methods are used, the system searches for the DLL at load time as described in this section.
Before the system searches for a DLL, it checks the following:
- If a DLL with the same module name is already loaded in memory, the system uses the loaded DLL, regardless of its directory. The system does not search for the DLL.
- If the DLL is on the list of known DLLs for the version of Windows on which the application is running, the system uses its copy of the known DLL (and the known DLL’s dependent DLLs, if any). The system does not search for the DLL. For a list of known DLLs on the current system, see the following registry key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs.
Based on this information, Windows checks if a DLL is loaded in primary memory before searching for it. If it isn’t, it checks the ‘KnownDLLs’ list under the registry entry ‘HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs’. Primary memory, however, cannot be managed at an integrity level that is not a system-integrity level.
Safe DLL Search Mode is enabled by default. To disable this feature, create the HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode
registry value and set it to 0.
According to the official documentation, the search order is as follows:
If SafeDllSearchMode is enabled, the search order is:
- The directory from which the application loaded.
- The system directory. Use the GetSystemDirectory function to get the path of this directory.
- The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
- The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
- The current directory.
- The directories listed in the PATH environment variable. Note that this does not include the per-application path specified by the App Paths registry key. The App Paths key is not used when computing the DLL search path.
If SafeDllSearchMode is disabled, the search order is:
- The directory from which the application loaded.
- The current directory.
- The system directory. Use the GetSystemDirectory function to get the path of this directory.
- The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
- The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
- The directories listed in the PATH environment variable. Note that this does not include the per-application path specified by the App Paths registry key. The App Paths key is not used when computing the DLL search path.
Here is a diagram of how the DLL search order looks when SafeDllSearchMode is enabled:

The potential issues are marked: if an attacker can write to the application folder, they can perform DLL Sideloading
, or if they can write to the directories listed in the %PATH%
environment variable, they can perform PATH DLL Hijacking
.
Privilege Escalation via DLL Hijacking Sideloading & PATH DLL Hijacking
The problem with DLL Hijacking is in the fact that if a DLL can be loaded from another directory (PATH DLL Hijacking) or simply replaced in the applications folder (DLL Sideloading), then we as attackers can create our own DLL payload. If a service or an application is vulnerable to DLL Hijacking
we can escalate our privileges as long as the service or application is executed with higher privileges
or with a higher integrity level
.
DLL Hijacking is especially effective when it is implemented in services that are running as the LocalSystem (NT AUTHORITY\SYSTEM
) service account since it will allow us to escalate our privileges to System-integrity level
.
Configuration & Privilege Escalation
We’re gonna create an empty Empty Project (.NET Framework)
for C#:

Then we’re gonna set the directory in which we will save our project:

Now we’ll need to add a new item (Code File):

We’ll name this item as DLL-Hijack.cs
which it’ll be a Code File:

In the DLL-Hijack.cs
we’ll write this DLL hijackable vulnerable code:
|
|
Next, we’ll add a C++ item (Code File) named unknown.cpp
:

We’ll create a function named UnknownMethod
with the following code:
|
|
Then we’ll create an unknown.def
file which defines which function to export:
|
|
The Solution Explorer looks like this:

Build the solution:

Add the details:

We’re going to create the DLL with the Developer Command Prompt for VS 2022:
|
|
Now we have to copy the DLL into the directory of the executable:
|
|
Then we’re going to add the following procmon filter to filter the executable:

Now we’ll filter the DLL:

Finally, we’ll add the NAME NOT FOUND
string:

Hit the capture button (Ctrl+E) to start monitoring:

Execute the executable:
|
|
In the output we can see the DLL that the DLL file was found:

However, if we delete or rename the DLL file in our directory:
|
|
Then we execute our executable again while this time enabling the name not found and the capture button in procmon
:
|
|
We can already see that the DLL file was not found based on the output above, if we take a look at procmon, we can see the DLL search order:

Note: The Procmon operations order is from OLD on top and NEW on bottom. We can see this in the Time of Day column.
On our Visual Studio project we’ll add our evil DLL payload file named unknown-evil.cpp
which will spawn notepad.exe
:
|
|
We’ll compile this code into a DLL and copy the same to the application directory, this technique is known as DLL Sideloading since we’re writing a DLL in the application’s folder and not in the %PATH% environment variable:
|
|
Let’s open a command prompt or a PowerShell console as an administrator. When we run the executable the DLL will be loaded and the exported functions will be executed:
|
|
As we can see the application notepad.exe
was launched:

Using Process Hacker as administrator we can view the access token:

Noticed that is running with a high-integrity level since the executable was executed from a high-integrity level console. This created a process tree which by default the child process (notepad.exe) inherits the access token of the parent process (DLL-Hijack.exe).
Now we could also try to write to one of the directories in the %PATH% environment variable to implement a DLL PATH Hijacking technique. We can replace notepad.exe
with our implant
or payload
to elevate our privileges.
Defense
DLL hijacking is an exploitation technique used to achieve code execution within the context of an application or service integrity level. To mitigate this risk, it is essential to address vulnerabilities such as misconfigured folder permissions or the abuse of privileged file operations:
Misconfigured Folder Permissions: This issue can arise from the installation of third-party applications. While it may not always occur, system administrators should regularly check and verify the permissions of folders within the system to ensure they are correctly configured.
Abuse of Privileged File Operations: This problem stems from weaknesses in the application’s architecture. Developers should review and update the code to prevent such operations on files and folders that can be managed by users.
Additional Defenses
Implement Application Whitelisting: Use application whitelisting to ensure that only approved applications and DLLs can be executed. This helps prevent unauthorized DLLs from being loaded.
Enable Windows Defender Application Control (WDAC): WDAC can help control the execution of applications and DLLs, providing an additional layer of security.
Regularly Update Software: Ensure that all software, including third-party applications, is kept up to date with the latest security patches and updates.
Use Digital Signatures: Require that all DLLs and executables be digitally signed. This helps verify the integrity and authenticity of the files before they are loaded.
Monitor and Audit System Activity: Implement monitoring and auditing tools to track system activity and detect any suspicious behavior related to DLL loading and execution.
By implementing these measures, you can enhance your defenses against DLL hijacking and reduce the risk of exploitation.