A while back I was working on a add-in/plug-in for a software that would enhance it’s functionality. I found that the easiest and most pain free way of loading arbitrary code was via a method referred to as DLL ‘proxying‘ or ‘hijacking‘. This method is unfortunately abused and most commonly used in malware prompting Microsoft to outline ways to secure your applications when loading libraries to prevent such attacks. In this post I will run you through the process of how this method works, this will also help you gain a better understanding of the internal functionality of the Microsoft Windows Operating System.
First, you may not know or be wondering. What are DLLs? DLL stands for Dynamic Link Library and they are the core libraries that contain functional code or resources used in Windows applications. They share the same Portable Executable (PE) file format as .EXE files. For this post I will touch briefly on the structure in order to explain the DLL proxying process.
Microsoft’s implementation of the shared library concept allows the use of written code amongst multiple applications. For example ‘ApplicationA‘ can load a library titled Common.DLL and call a function in that library that does the addition of two integers. Subsequently ‘ApplicationB‘ can also load that library and call the same function. The function in the library is effectively shared between the two apps. For this reason libraries expose functions which get stored in a structure called the export table inside the DLL. You can read more about the PE format here.
The Windows DLL Loader or known as the PE Loader is responsible for loading these libraries on either startup of a program or at runtime when they are required. When attempting to load a library it adheres to a defined search order. Below you can see the default order of which the loader attempts to search for the requested library to be loaded. The order will differ when safe DLL search mode is disabled.
- Initially it will first attempt to look inside the process memory to determine if the library has been previously loaded.
- Next it will look inside the KnownDLLs registry entry located at:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLsto see if any match the name.
- The subsequent location is the application directory. This directory is of importance to us for this method to work.
You can find a more detailed explanation of each location by reading through the Microsoft documentation.
The DLL search order can be exploited to trick a target application to load an illegitimate DLL library. This can occur if there is no explicit path specified when loading a library. For example, if an application wishes to load a DLL called Example.dll without specifying it’s location the Library Loader will commence a search using the ordered list. Once there is a file located with the exact library name it will be loaded into the running application’s process memory. The library loaded will then be prepared to have one of it’s exported functions executed.
To highjack this flow we can place a DLL library that we have crafted in a location that is early on in the search order. This will result in our DLL being loaded for execution rather than the original intended library which may be located further down the search list.
It is important to note that for this to work in a stable manner we must create what is known as a DLL wrapper to expose the same functions that are exported by the original DLL library. If we fail to do so when our library is loaded the function call intended will fail as it doesn’t exist.
Below is a diagram to demonstrate this where a application process wishes to call a function titled “FunctionA” in the library Example.DLL.
Here is the process outlined:
- The Windows PE Loader will use the search order list and scan to locate a library called Example.DLL
- It will fail to locate the library in process memory and the KnownDLLs registry entry finding a matching file in the application directory instead.
- This file is not the original intended library but our wrapper DLL. The exported functions by the original DLL are mirrored in our wrapper using DLL Redirection which in layman’s terms tells the loader to look elsewhere for the origins of FunctionA.
- The loader will then load Example_Org.DLL which was the original library intended to be loaded.
- The loader will then locate the exported function FunctionA inside the export table of Example_Org.DLL and attempt to resolve it’s virtual memory address so it can be called and executed.
We can utilize a tool mentioned in one of my previous blog posts to locate libraries that are susceptible to such an attack. Process Monitor by Mark Russinovich has the ability to monitor library load calls and using a certain filter it is possible to discover DLLs that fall into that category.
In the example below I will explain how code execution can be acquired on Microsoft Teams by using DLL proxying/hijacking.
If we set the filter on Process Monitor to locate DLLs being loaded by Teams.exe with the Result of NAME NOT FOUND it can be observed that once the application is run we get quite a few matching results matching the criteria.
Following the example above with Microsoft Teams we can choose one of the DLLs present in that list to perform this. In this demonstration I have chosen to go with the library VERSION.DLL. This library provides helper functionality that deals with files and file attributes.
A C++ Native DLL project will then need to be created that exposes the same functions exported by this library. I have provided an example project on GitHub we can use.
Opening the original DLL library in PeStudio we can observe the functions it has exported. Usually you can locate the original file (if it exists) in the System32 directory.
Our next step is to mirror these functions exported in our wrapper DLL library without copying any implementation over but instead redirecting them back to the original.
This can be achieved by using a command line tool I’ve created called PEExportGen which you can download from GitHub.
PEExportGen will take an import library, parse it’s export table and create a C++ header that contains export definitions that the C++ Compile Linker can use to create the wrapper DLL.
After this we can build the library, ready to be deployed.
The final step is renaming the original DLL Library to VERSION_orig.DLL and our newly compiled wrapper as the original’s name VERSION.DLL.
Both files then can be dropped inside the application folder next to the Teams.exe executable. This directory is typically located at:
After the crafting and deployment of the wrapper DLL we can proceed to run the application Teams.exe. You should see a console window popup with our intended output. This demonstrates how arbitrary code can be executed inside a process with DLL proxying/hijacking.
To prevent this type of hijacking there are a few steps you can take to ensure this risk is mitigated in your application. When you wish to load a library providing a full path is the best approach.
HMODULE handle = LoadLibrary("someLibrary.dll");
The above call to LoadLibrary will result in the search order to locate the library. The right way to load the library would be to supply its full file path.
HMODULE handle = LoadLibrary("C:\\Windows\\System32\\someLibrary.dll");
In some situations where the file path can not be determined it could be possible to locate the file yourself by checking it’s hash or digital signature (if it is signed).
There are other ways to omit searching certain paths by using the Flags attribute in LoadLibraryEx. Another common mistake developers may make is using LoadLibrary to determine if a Library exists on a system, this is not good practice and other avenues should be pursued to accomplish this.
Finally it is important to have the appropriate directory permissions for your application. If the app directory is not writeable it will reduce the risk significantly.
This method of code execution has been around for quite some time in Windows, I hope through reading this post you have a better understanding of how libraries are loaded. It can be evident without proper care this method can open up your program or application to vulnerabilities. It also however can be used in a non malicious way to load/modify/enhance an old legacy application for instance that might not work due to incompatibility or lack of interoperability.