Having fun with KeePass2: DLL Hijacking and hooking APIs
With the recent KeePass2 disputed CVE-2023-24055 and all the fuss around it, it motivated me to finish a little project I had started last year.
My goal was to see if I could find a way to intercept the Master Password of a KeePass2 database.
For fun and learning ofc.
In this article, we’ll cover:
- DLL Hijacking
- Hooking Windows APIs
Introduction
Picture this.
You are on an engagement, admin on a workstation and just now retrieved a KeePass2
Database.
Problem: KeeFarce/KeeThief don’t work anymore.
Let’s find another way to get that Master Password
.
Table of content
- DLL Hijacking
- Hooking Windows APIs
- Automatic code generation
- When everything comes together
- Conclusion
- Useful Links
DLL Hijacking
Was ist das?
DLL hijacking is a type of attack where you take advantage of an application’s search order for loading dynamic-link libraries.
When an application attempts to load a DLL file, it will search for the file in a specific order. The order is as follows:
- The directory from which the application loaded
- The system directory:
C:\Windows\System32
- The 16-bit system directory:
C:\Windows\System
- The Windows directory:
C:\Windows
- The current directory
- Directories specified in the
PATH
environment variable
If an attacker is able to place a malicious DLL file in one of these directories with the same name as a legitimate DLL file, the application will load the malicious DLL instead of the legitimate one, allowing the attacker to execute arbitrary code in the process.
Does this also apply to KeePass?
Target DLL
The easiest way to find potential hijackable DLL is to search with promon. Search for CreateFile
on a DLL that returns with the error NAME NOT FOUND
, such as this one here:
What is happening?
Here KeePass tries to a DLL called UxTheme.dll
, but tries to load it for its own installation folder in C:\Program Files\KeePass Password Safe 2
.
However, this is normally a system DLL and is present in C:\Windows\System32
:
Thus, this DLL might be a good candidate for hijacking.
Let’s compile a DLL and try to see if it gets loaded in KeePass2
if we rename it. We’ll use this code:
BOOL APIENTRY DllMain(
HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
WPP_INIT_TRACING(L"Test");
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
TraceEvents(TRACE_LEVEL_VERBOSE, GENERAL, "[+ dllmain] DLL_PROCESS_ATTACH\n");
}
return TRUE;
}
Notice the call to TraceEvents
instead of a printf
. This is because as we are working with a DLL, there is no easy way to get its output.
TraceEvents
uses the Windows software trace preprocessor (WPP), that is a component of ETW.
We’ll be able to see our events in TraceView.
Now that we have our DLL, let’s move it to C:\Program Files\KeePass Password Safe 2
and rename it to UxTheme.dll
:
And run KeePass:
Yeah!! It worked. We have a log in TraceView showing our DLL loaded in KeePass2
.
That was easy. Now what?
Hooking Windows APIs
Hooking allows you to intercept and/or modify the behavior of functions called by a given program. In our case, we want to be able to log parameters and return values of Windows API calls.
In the following diagram, you can see a normal API call (in green) versus a hooked API call (in red):
Fun fact, that is also how userland AV/EDRs monitor API calls.
I started working on a custom hooking engine, before stumbling upon Microsoft’s own library to do precisely that: Detours
I won’t go into details on the implementation of the hooks as documentation is available online.
MessageBoxW Example
Let’s start simple with a MessageBoxW
call.
MessageBoxW(
NULL,
TEXT("Hello Twitter!"),
TEXT("SimpleEXE"),
MB_OK
);
If we take a look at the documentation for MessageBoxW on MSDN, we get this function prototype:
int MessageBoxW(
[in, optional] HWND hWnd,
[in, optional] LPCWSTR lpText,
[in, optional] LPCWSTR lpCaption,
[in] UINT uType
);
Our hook function to log the lpText
and lpCaption
parameter would look something like this:
int (*real_MessageBoxW)(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) = MessageBoxW;
int hook_MessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)
{
TraceEvents(TRACE_LEVEL_VERBOSE, GENERAL, "[+ Hook] MessageBoxW(lpText=%ls, lpCaption=%ls, uType=%u)", lpText, lpCaption, uType);
return real_MessageBoxW(hWnd, lpText, lpCaption, uType);
}
This code is pretty simple. When MessageBoxW
is called, it will first log the arguments using TraceEvents
before resuming execution to the “real” MessageBoxW
.
Let’s try it out!
Hook the box
This was tested with a custom loader that injects the DLL when starting the process. And when the MessageBox
appears:
The function call gets logged in TraceView! With the arguments and all.
Now, imagine being able to run code in a sensitive process and being able to intercept functions that handle sensitive data.
Automatic code generation
But first, code generation.
Now that we have a first working example of hooking, we have to scale it up. There are a lot of functions in the Windows API. Remember, our objective is to intercept the Master Password of the database, when it is typed.
I’m clearly not writing the code for each Windows API function myself. Ain’t nobody got time for that. I went for a Python script and a Json file to generate the code for the hooking dll.
MSDN is your best friend for documentation, or you could also check header files in Visual Studio.
Here is a quick pick of the json file:
And the generation:
The default code generation invokes TraceEvents
to log function calls and arguments. Here is a sample of the generated code:
It looks a lot like the code in our first example but is generated automatically, as long as the function is added to the json file. I also added custom code snippets to extend functionalities if necessary. That way we can log return values, print arguments in a specific way, etc.
And just like that, we have working code generation. We can monitor any Windows API function that we want by adding it’s prototype to the json file!
When everything comes together
While testing, I narrowed my search to function that handle strings and clipboard activity. After a bit of research I found two interesting API calls for our use case:
- SetClipboardData: Places data on the clipboard in a specified clipboard format
HANDLE SetClipboardData( [in] UINT uFormat, [in, optional] HANDLE hMem );
- ToUnicodeEx: Translates the specified virtual-key code and keyboard state to the corresponding Unicode character or characters
int ToUnicodeEx( [in] UINT wVirtKey, [in] UINT wScanCode, [in] const BYTE *lpKeyState, [out] LPWSTR pwszBuff, [in] int cchBuff, [in] UINT wFlags, [in, optional] HKL dwhkl );
After generating hooks for those two functions, we copy the DLL to C:\Program Files\KeePass Password Safe 2
and rename it to UxTheme.dll
.
And now, let’s run KeePass2
again and type the password to unlock the database:
How cool is that? The password gets logged in TraceView!
Entries in the database can also be intercepted when CTRL+C
is pressed, thanks to the hook on SetClipboardData
:
Conclusion
I still have some work to do to write the password to a file but you got the idea. Hope you liked what you read and learned something new today.
I don’t have a comment section but send me a message over on Twitter before the platform disappears haha.
Useful Links
https://learn.microsoft.com/en-us/sysinternals/
https://github.com/microsoft/Detours
https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/traceview https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/wpp-software-tracing