top of page

Performing DLL Hijacking

  • Mar 11
  • 10 min read

Updated: Mar 28

Introduction

 

In this blog, we'll learn about the concept of DLL hijacking, a technique that attackers and pentesters alike use to gain unauthorized access to the system. We will learn what DLLs are and exploit the way Windows OS loads the DLL (Dynamic Link Libraries), along with practical implications for how attackers can leverage them.

 

What is a DLL?

 

A Dynamic Link Library (DLL) is a library in Windows containing code and data that can be used by multiple programs simultaneously. In Windows operating systems, a significant portion of functionality comes from DLLs. When you run a program, many of its features—from 2D/3D graphics (via OpenGL32.dll) to network operations—are modularized and distributed in separate DLL files.

 

This modularity promotes code reuse and memory efficiency, as multiple applications can access the same functions collectively. However, this same convenience introduces potential security risks like DLL hijacking if not managed correctly.

 

There are two primary ways a DLL can be linked to a program:

 

  • Compile-time Linking: Also known as load-time dynamic linking.

  • Run-time Linking: Managed during execution via APIs like LoadLibrary.

 

Load-time Dynamic Linking

 

Load-time dynamic linking of a DLL occurs when the application is loaded into memory. The linking to the DLL is determined at the time of compilation, and the DLLs are loaded when the application starts. During the compilation of the executable, the linker creates an import table in the PE (Portable Executable) header. This import table lists the DLLs and the specific functions required by the executable.

 

Upon program launch, the linker utilizes this table to identify and locate the DLLs, adhering to a specific search order as per the "Dynamic-Link Library search order" to locate the names of the required DLLs. If a required DLL cannot be found, the system terminates the process and displays an error dialog to the user. However, if the DLLs are located, the system maps them into the virtual address space of the process and increments the DLL reference count. The DLL is mapped into the virtual address space of the process during initialization and is loaded into physical memory only when needed.

 

Example of above workflow:

 

#include <windows.h>  // Include Windows header
#include <stdio.h>    


// Define the function prototype for the function from the DLL

void __declspec(dllimport) displayMessage();

int main() {
    // Call the function from the DLL
    displayMessage();
    return 0;
}

 

Run-time Dynamic Linking

 

Run-time dynamic linking refers to the process of loading a DLL into an application’s address space and accessing its functions while the application is running. This method offers greater flexibility but requires additional code to manage the loading and resolution processes.

 

During execution, the application loads the DLL using functions such as LoadLibrary() or LoadLibraryEx(). Once the DLL is loaded, the application retrieves the addresses of the needed functions using GetProcAddress(). When the DLL is no longer needed, the application can unload it using the FreeLibrary() function. This approach allows applications to load and use DLLs as needed, conserving resources and enabling modular design. Run-time dynamic linking potentially enables the process to continue execution even when the requested DLL is not available. The procedure can then utilize a different method to attain its goal.

 

So What is DLL hijacking?

 

DLL hijacking is a technique that takes advantage of the Windows Search Order to load a malicious DLL into an application's process space. It can lead to arbitrary code execution, privilege escalation (EOP), or persistence.

 

Advanced Concept: DLL Proxying

 

A simple hijacking attack (replacing a DLL) usually causes the application to crash because the application expects specific exported functions that your malicious DLL doesn't have. Professional attackers use DLL Proxying. In this scenario, the malicious DLL executes its payload and then forwards all legitimate function calls to the original DLL. This ensures the application continues to run normally, hiding the attack from the user.

 

At a high level, DLL hijacking involves the following steps:

 

  1. Identification: Identifying a DLL that an application loads using a relative path.

  2. Creation: Creating a malicious DLL (often using proxying to maintain stability).

  3. Placement: Planting the DLL in a directory that is prioritized in the Windows Search Order.

  4. Execution: The application inadvertently loads the malicious DLL with the application's privileges.

 

Types of DLL Hijacking Techniques

 

DLL Search Order Hijacking

 

DLL search order hijacking occurs when a malicious DLL with the same name as a legitimate one is placed in a directory that is searched before the directory containing the legitimate DLL (if it exists at all). This causes the application to load the malicious DLL instead of the legitimate one.

 

DLL search order hijacking typically occurs with load-time dynamic linking. During load-time dynamic linking, the application searches for the required DLLs based on the predefined Windows search order, and there is no option to modify this search order.

 

In run-time dynamic linking, the application loads DLLs using functions like LoadLibrary() and LoadLibraryExW(). In such cases, DLL hijacking is possible only if the application does not explicitly specify the full path of the DLL and instead relies on the Windows search order to locate the DLL. By specifying the full path, the application can avoid unintended DLLs being loaded from untrusted directories.

 

Let’s understand the LoadLibrary() and LoadLibraryExW() functions in depth, which are responsible for loading the DLL dynamically.

 

Syntax:

 

HMODULE LoadLibraryA(
  [in] LPCSTR lpLibFileName
);


HMODULE LoadLibraryExA(
  [in] LPCSTR lpLibFileName,
       HANDLE hFile,
  [in] DWORD  dwFlags
);

 

lpLibFileName: represents a string representing the file name of the module to be loaded. If the string contains a full path (e.g., "C:\Windows\system32\lib.dll"), the function searches only that path for the module. However, if the string contains a relative path or a module name (for example, "lib.dll") without a path, the function searches for the DLL in several locations.

 

dwFlags: The action that will be performed upon loading the module in question. If no flags are given, the function behaves identically to the LoadLibrary function.

 

The main difference between the two functions is that LoadLibrary() does not allow the use of flags and follows the Windows search order. In contrast, LoadLibraryExW() allows the use of flags, offering the option to modify the search path, which enhances security and control.

 

Understanding Windows DLL Search Order

 

If the full path of a DLL is not specified (e.g., LoadLibrary("lib.dll")), the Windows OS uses a predefined sequence of locations to find it. This is where the risk lies.

 

Windows search modes can be classified into:

 

1. Safe DLL Search Mode (Default)

 

Enabled by default on modern Windows, SafeDllSearchMode moves the Current Working Directory (CWD) down the priority list. This prevents common attacks where an attacker tricks a user into opening a document from a folder containing a malicious DLL.

 

The Priority List (Safe Mode):

 

  1. Application Directory: The directory the .exe was launched from.

  2. System Directory: C:\Windows\System32

  3. 16-bit System Directory: C:\Windows\System

  4. Windows Directory: C:\Windows

  5. Current Working Directory (CWD): The folder the user is currently "in."

  6. Directories in %PATH% Variable.

 

2. Unsafe Search Mode (Legacy)

 

If SafeDllSearchMode is disabled (rare today), the Current Working Directory is moved to position #2, immediately after the application directory. This is significantly more dangerous.

 

3. The Ultimate Defense: KnownDLLs

 

Before Windows even starts the search order, it checks the KnownDLLs registry key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

 

Any DLL listed here (like user32.dll or kernel32.dll) is always loaded from the System32 directory, effectively making them immune to standard search-order hijacking.

 

Working of the LoadLibrary() function

 

Scenario A: The full path of the DLL is specified in the application

 

When the full path of the DLL is specified in the application, the application is restricted to searching for the DLL in the other directories and following the Windows search order, potentially mitigating the risk of DLL hijacking.

 

Below is the code for "TheApplication.exe", showing the application loading the DLL lib.dll from the specified path.

 

#include <windows.h>
#include <stdio.h>

int main() {
    // Load the DLL with a full path
    HMODULE hModule = LoadLibrary("C:\\Windows\\System\\lib.dll");

    if (hModule == NULL) {
        printf("Failed to load the DLL\n");
        return 1;
    }

    printf("DLL loaded successfully\n");
    FreeLibrary(hModule);
    return 0;
}

 

On compiling and running the application and then analyzing it through the Process Monitor (as shown below), it is confirmed that the application is restricting the use of search orders.

 

 

Scenario B: The full path of DLL is NOT specified in the application

 

The code example shown below clearly shows that the full path of the DLL is not specified by default. The application has no choice but to utilize search order in order to locate required libraries, allowing attackers to leverage this functionality.

 

#include <windows.h>
#include <stdio.h>


int main() {
    // Load the DLL without a full path
    HMODULE hModule = LoadLibrary("example.dll");
    if (hModule == NULL) {
        printf("Failed to load the DLL\n");
        return 1;
    }
    printf("DLL loaded successfully\n");
    FreeLibrary(hModule);
    return 0;
}

 

The application.exe in the process monitor below demonstrates that by not specifying the full path of the DLL, the application utilizes search order for the purpose of locating the required DLL, thus allowing attackers to manipulate it.

 

 

Working of the LoadLibraryExW() function

 

Scenario A: The full path of the DLL is specified and NO flags are used.

 

When the LoadLibraryExW() function is used without any specific flag that allows the application to search for the required DLL through the search order and the full path of the DLL is specified, such implementation mitigates the risk of DLL hijacking as the results from the Process monitor convey that the application is looking for DLL in a path that is not writable by low-privileged users.

 

 

The application given below is an example where the code restricts the DLL search order and the full path of the DLL is specified in the application. Such an implementation mitigates the risk of DLL hijacking.

 

#include <windows.h>
#include <stdio.h>


int main() {
    // Specify the full path to the DLL on the Desktop
    const wchar_t *dllPath = L"C:\\Windows\\System\\lib.dll";
    // Load the DLL from the specified path without any additional flags
    HMODULE hModule = LoadLibraryExW(dllPath, NULL, 0);
    if (hModule == NULL) {
        printf("Failed to load the DLL. Error code: %lu\n", GetLastError());
        return 1;
    }
    printf("DLL loaded successfully\n");
    // Retrieve the address of a function from the DLL (assuming the function name is "MyFunction")
    FARPROC pFunc = GetProcAddress(hModule, "MyFunction");
    if (pFunc == NULL) {
        printf("Failed to find the function. Error code: %lu\n", GetLastError());
        FreeLibrary(hModule);
        return 1;
    }
    printf("Function address retrieved successfully\n");

    // Call the function (assuming it takes no parameters and returns an int)
    int result = ((int(*)())pFunc)();
    printf("Function executed with result: %d\n", result);
    // Free the DLL module
    FreeLibrary(hModule);
    return 0;
}

 

The results in the process monitor shown below confirm that the application is restricting the search order, which essentially implies that it might not be possible to perform search order DLL hijacking under this scenario.

 

 

Scenario B: The full path of the DLL is NOT specified and flags are used

 

When the LoadLibraryExW() function is used with a specific flag that makes the application vulnerable to search order hijacking, the application uses the Windows search order to search for the DLL.

 

#include <windows.h>
#include <stdio.h>


// Function pointer type for the function in the DLL
typedef void (*FunctionType)();
int main() {
    // Load the DLL using the LOAD_WITH_ALTERED_SEARCH_PATH flag (vulnerable to search order hijacking)
    HMODULE hModule = LoadLibraryExW(L"lib.dll", NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
    if (hModule == NULL) {
        printf("Failed to load the DLL\n");
        return 1;
    }
    printf("DLL loaded successfully\n");
    // Retrieve the address of the function in the DLL
    FunctionType function = (FunctionType)GetProcAddress(hModule, "FunctionName");
    if (function == NULL) {
        printf("Failed to get the address of the function\n");
        FreeLibrary(hModule);
        return 1;
    }
    printf("Function address obtained successfully\n");
    function();
    printf("Function called successfully\n");

    FreeLibrary(hModule);
    printf("DLL freed successfully\n");
    return 0;
}

 

Here, the LOAD_WITH_ALTERED_SEARCH_PATH is called, which instructs the application to use "search order" instead of searching for the DLL in the specific path. Results in the process monitor confirm that the application is indeed using the search order, which tells us that it is possible to perform search order DLL hijacking.

 

 

Performing DLL Hijacking Through Search Order

 

The application (TheApplication.exe), loads the required legitimate DLL, lib.dll, from the C:\Windows directory and utilizes the search order to load the DLL, as previously described.

 

 

The DLL is loaded from the path in the search order where it originally existed. According to this research, a malicious DLL can be purposefully inserted ahead of a legitimate DLL by an attacker who gains knowledge of it.

 

Based on the analysis, the path "C:\Users\oogabooga\Desktop\DLL" is prioritized in the search order before "C:\Windows," which is the application's directory. Therefore, if a malicious DLL is placed in the application's directory, it is going to be executed.

 

 

In the above example, the malicious DLL is placed in the directory of the application, specifically the directory that is searched before to the legitimate DLL's path, which is C:\Windows. Consequently, the malicious DLL is executed.

 

DLL Phantom Loading

 

Phantom DLL hijacking is a security vulnerability in Windows where the operating system makes references to DLL files that do not actually exist. Attackers take advantage of this vulnerability by strategically inserting a malicious DLL into the designated location of one of these absent files. When the operating system tries to execute the code linked to the file, the malicious DLL is loaded instead, enabling the attacker's code to run.

 

In the event that the DLL does not exist, the Windows search order continues to look for it in Windows directories and then in the %PATH% directories, following the standard search order until the LoadLibraryExW() function is called with flags that do not change the search path.

 

As an example, we will utilize a sample application (searchDLL.exe) that is attempting to load a non-existent DLL called cool.dll.

 

 

DLL hijacking can occur in this situation if any of the directories shown in the screenshot above have write permissions. Malicious DLLs can be inserted into the writable paths, and when the application is executed, it will load these malicious DLLs.

 

Let's check the directories to see if they can be written to for demonstration reasons.

 

An unprivileged user can write to the directory (C:\Users\oogabooga\AppData\Local\Microsoft\WindowsApps) in the aforementioned situation. Thus, by leveraging this information, an attacker may insert a malicious DLL in the directory that has the same name as the legitimate DLL, forcing the program to load the malicious DLL.

 

 

As previously mentioned, the application loads cool.dll, which the attacker placed in the writable directory upon the execution of searchDLL.exe.

 

 

Modern Mitigations for Developers

 

To protect your applications from DLL hijacking, follow these industry best practices:

 

  1. Always use Absolute Paths: Specify the full path (e.g., C:\Program Files\App\lib.dll) when calling LoadLibrary.

  2. SetDefaultDllDirectories: Call this API at the start of your program to restrict DLL searches to the application directory and System32.

  3. SetDllDirectory: Use SetDllDirectory("") to remove the Current Working Directory from the search path entirely.

  4. Code Signing: Always verify the digital signature of a DLL before loading it.

  5. Secure Installation Directories: Ensure only administrators have write access to your application folders.

 

Conclusion

 

DLL hijacking remains a potent technique because it leverages the fundamental architecture of how Windows handles dependencies. By understanding search order priorities, KnownDLLs, and the role of SafeDllSearchMode, defenders can build more resilient applications while attackers can identify critical paths of execution to leverage for their operations.

 

References

 

 

 

Register for instructor-led online courses today! https://www.darkrelay.com/courses

 

Check out our self-paced learning paths!

 

Explore our bundled Pricing & Plans for cost-effective options! Buy a course subscription to learn more, hands-on labs and expert-led training included. https://www.darkrelay.com/plans-pricing

 

Contact us for custom pentesting needs at: info@darkrelay.com or WhatsApp.

 

Comments


bottom of page