Breaking into Libraries – DLL Hijacking
Many of us have likely encountered DLL errors when trying to open Windows applications. If you were like my younger self, you might have naively downloaded a missing DLL from sites like dll-files.com and placed it in the application directory without giving it a second thought. This blog post will examine the risks associated with this approach.
Dynamic Link Library (DLL) hijacking is a common occurrence that impacts Windows-based applications. This attack isn’t new; Microsoft acknowledged it in an advisory back in 2010 (Dark Reading). It takes advantage of the way Windows loads DLL files, allowing malicious actors to inject harmful code into legitimate software processes. By manipulating the search order for DLL files, attackers can deceive an application into loading a malicious DLL, leading to unauthorised code execution, data theft, or even complete system compromise.
A prime real-world example of this attack vector is the SolarWinds hack, one of the most sophisticated and far-reaching cyber attacks in recent history, which was uncovered in December 2020. It affected thousands of organisations, including major government agencies and Fortune 500 companies. The attack exploited a vulnerability in SolarWinds’ Orion platform, a widely used network management software. In this supply chain attack, the attackers managed to infiltrate SolarWinds build environment, inserting a malicious DLL into the Orion software updates between March and June 2020. This backdoor, later dubbed “Sunburst,” allowed the attackers to gain access to the networks of SolarWinds clients. Once installed, Sunburst enabled remote access, data exfiltration, and further lateral movement within compromised networks.
What made this attack particularly effective was its stealth and scope. By leveraging a trusted software update, attackers bypassed traditional security mechanisms. The hack went undetected for months, affecting organisations like the U.S. Department of Homeland Security, the Treasury Department, and companies such as Microsoft and Intel.
The SolarWinds hack is a prime example of how DLL hijacking, and more broadly, software supply chain vulnerabilities, can lead to severe consequences. In this attack, a compromised DLL was used to manipulate system processes, establish persistence, and enable lateral movement within victim networks, demonstrating the significant threat posed by such vulnerabilities.
In the following sections, we’ll explore the mechanics of DLL hijacking, the potential risks it presents, and provide an example to illustrate its effectiveness in real-world attacks.
What in DLL’s is going on?
To begin, it’s essential to understand what Dynamic Link Libraries (DLLs) are. Fortunately, we can obtain this information directly from the source: Microsoft Documentation.
For the Windows operating systems, much of the functionality of the operating system is provided by DLL. Additionally, when you run a program on one of these Windows operating systems, much of the functionality of the program may be provided by DLLs. For example, some programs may contain many different modules, and each module of the program is contained and distributed in DLLs.
The use of DLLs helps promote modularization of code, code reuse, efficient memory usage, and reduced disk space. So, the operating system and the programs load faster, run faster, and take less disk space on the computer.
In summary, Windows executables depend on DLLs based on their implementation, and there are various methods for including these libraries. Many of us have likely encountered error messages indicating that a DLL is missing when executing a binary. This occurs during the evaluation of the file’s full path, leading to potential issues. Typically, if the DLL’s location is hard-coded, the executable will search for the file in the specified directory. If not, it will follow a search order that generally appears as follows:
- HKEYLOCALMACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
- The folder from which the application loaded.
- C:\Windows\System32
- C:\Windows\System
- C:\Windows
- The current working directory
- Directories in the system PATH environment variable
- Directories in the user PATH environment variable
The list above outlines the standard search order locations that are evaluated during the execution of a typical application. Additionally, when Safe DLL search mode is disabled, a different search order sequence is followed, which is detailed below:
- DLL Redirection
- API sets
- SxS manifest redirection
- Loaded-module list
- Known DLLs
- The folder from which the application loaded.
As we will observe below, adversaries exploit this by loading a malicious DLL in place of the legitimate one, thereby performing DLL hijacking within the context of a legitimate application to achieve stealthy execution.
Hijacking Process
In this blog post, we will explore DLL exploitation through hijacking and proxying, allowing our malicious DLL to be loaded alongside the legitimate one without causing any runtime errors in the executable. For those purposes we are going to use a vulnerable KeePassXC application.
Before proceeding, it’s crucial to identify which DLLs are being loaded with our executable. To accomplish this, we’ll use Process Monitor (we can find that in Microsoft’s Sysinternals suite) to monitor the linked libraries. After running the KeePass binary, we can utilise the filtering options to search for events associated with the KeePass process.
The screenshot above displays all the events generated by the KeePass process. However, we are specifically interested in those related to .dll files. To narrow our focus, we will filter the results by using the Path. Since we are focusing solely on .dll files, we will apply a filter to narrow down the output accordingly.
In the screenshot above, the program attempts to load DLL files from the specified directory, leading to a NAME NOT FOUND error, as outlined in the search order mentioned earlier. To identify a DLL file suitable for hijacking, we need to find a library that is missing from our environment. We will focus on results logged as NAME NOT FOUND. In this case, we see that the executable is searching for opengl32.dll in its current working directory which makes it a perfect candidate for our proceedings.
To successfully achieve DLL hijacking, two requirements must be fulfilled:
- The malicious DLL must export the functions loaded by the target executable; otherwise, the application will fail to load the intended DLL.
- Any functions that the application calls from the legitimate DLL must also be available (exported) in the malicious DLL. If these functions are missing, the application may crash, making it essential to retain the legitimate implementation.
This is where DLL Proxying becomes useful. By creating a malicious DLL that includes all the exports and functions from the legitimate DLL and forwards calls to it, we can ensure that the application won’t crash during execution, allowing our malicious code to run undetected in the background. To facilitate this, we will use a straightforward Python script that utilises the pefile
library to export the necessary functions and save them into a .def
file, which we will then pass to our compiler.
import pefile import sys def generate_def_file(dll_path): try: # Load the DLL file dll = pefile.PE(dll_path) # Print the header for the DEF file print("EXPORTS") # Iterate through the exported symbols for export in dll.DIRECTORY_ENTRY_EXPORT.symbols: if export.name: # Decode the exported function name export_name = export.name.decode() ordinal = export.ordinal # Print in the format for DEF file print(f"{export_name}\t@{ordinal}") except Exception as e: print(f"Error: {e}") if __name__ == "__main__": # Check if the script is called with the correct number of arguments if len(sys.argv) != 2: print("Usage: python gen_def.py ") sys.exit(1) # Get the DLL file path from the command-line arguments dll_file_path = sys.argv[1] generate_def_file(dll_file_path)
Now, with the script in hand, we can retrieve the original dll file from C:\Windows\SysWOW64
and export its functions into a .def
file format using the following command:
python exporter.py opengl32_orig.dll > opengl32.def
For this demonstration, we’ll use a simple C++ WinAPI code that displays a message box. While this is an obvious indicator of the DLL’s execution, it’s important to highlight that in real-world scenarios, adversaries could load anything arbitrarily, stealthily concealing their activities.
#include void Payload() { MessageBox(NULL, "JUMPSEC DEMO", "Message", MB_OK | MB_ICONINFORMATION); } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: Payload(); break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: break; } return TRUE; }
Next, we’ll compile the malicious DLL using the mingw32 compiler with the following command:
i686-w64-mingw32-gcc -shared -o opengl32.dll opengl32.c opengl32.def -s
Once compiled, both legitimate and malicious DLL files should be copied into the directory where the KeePassXC binary resides. In this case, that directory is C:\Users\user\Desktop\KeePassXC-2.6.0-Win32-portable\KeePassXC-2.6.0-Win32
.
After launching the application, the appearance of our message box confirms that the DLL hijacking was successful, and the malicious DLL executed alongside the legitimate one. The key takeaway here is that DLL hijacking itself is not inherently a vulnerability—it’s a technique exploited by adversaries to achieve code execution or escalate privileges. The real concern lies in writable directories within the %PATH%
, as this allows malicious DLLs to be placed and executed.
A practical scenario would involve discovering a misconfigured service (identified using tools like icacls
) running under the NT AUTHORITY\SYSTEM
context. In such cases, DLL hijacking can be leveraged for arbitrary code execution with elevated privileges. However, it’s crucial to carefully select the target DLL for hijacking, as attempting to hijack the wrong one could disrupt the runtime and cause issues in the environment.
How can we prevent DLL hijacking ?
- One of the most effective ways to prevent DLL hijacking is to always use absolute, fully qualified paths when loading DLLs, rather than relying on relative paths or just filenames. This ensures that the application loads the intended, legitimate DLL from a trusted location, avoiding any potentially malicious or hijacked versions.
- Developers can further protect their applications by restricting the directories from which DLLs can be loaded. By using
SetDefaultDllDirectories
andAddDllDirectory
, one can control which folders the system searches for DLLs, thereby reducing the risk of loading malicious libraries from untrusted sources. - In cases where absolute paths aren’t possible, usage of the
LOAD_LIBRARY_SEARCH_*
flags when callingLoadLibrary
. These flags allow developers to specify safe directories, such as system directories or the application’s own folder, from which the DLLs will be loaded. - Another security measure would be to ensure that all DLLs used by the application are digitally signed with a trusted certificate. By signing DLLs, organisations can verify the authenticity and integrity of the library before loading it, significantly reducing the risk of loading malicious DLLs.
- By default, Windows can load DLLs from the current working directory, which may pose a security risk. Safe DLL Search Mode modifies this behaviour by ensuring that system directories (like
C:\Windows\System32
) are searched before the current working directory.
End note
In this hands-on lab, we explored the creation and deployment of a malicious DLL, demonstrating how small configuration mistakes can lead to major security vulnerabilities. DLL hijacking, a key technique in privilege escalation on Windows systems, was examined in detail. As we conclude, it’s clear that protecting Windows environments from these attacks requires a deep understanding of DLL inner workings and robust mitigation strategies. Hopefully you have found this blog post informative, and as always – stay informed, stay updated.
References
https://msrc.microsoft.com/blog/2018/04/triaging-a-dll-planting-vulnerability/