Table of Contents:
- DLL Injection: Background & DLL Proxying (1/5)
- DLL Injection: Windows Hooks (2/5)
- DLL Injection: Remote Threads (3/5)
- DLL Injection: Thread Context Hijacking (4/5)
- DLL Injection: Manual Mapping (5/5)
One of the most straightforward ways to perform DLL injection is with the use of the SetWindowsHookEx API. Hooks, in Windows terminology, are mechanisms that allow applications to intercept particular system events. Installing a hook is a two-part process. First, you must define and implement a hook procedure* for the hook type that you will be installing. This procedure must be in a DLL**, since the DLL will be injected into the target process(es). Next, you call the SetWindowsHookEx API to install the hook on a specific thread, or on all threads.
* The term “hook procedure” will be used throughout this section. A procedure is just another name for a function, but I will use the term “hook procedure” specifically when referring to the function that will handle hook events.
** Technically, if you are installing a global hook, or a hook procedure on a thread in your own process, it does not need to be in a DLL. This will not be the case for any examples presented in this series since we are interested in injecting DLLs into other processes.
HHOOK WINAPI SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId);
The first parameter corresponds to the type of hook that will be installed. There are many different types of hooks for system events; there are hooks to monitor messages being sent to window procedures, message queue hooks, keyboard hooks, mouse hooks, and more. These have predefined values such as WH_CALLWNDPROC, WH_GETMESSAGE, WH_KEYBOARD, and so on. Each hook type will have a corresponding hook procedure function associated with it. This is the function that will be invoked when the hook captures a system event. For example, a hook procedure for keyboard hooks (WH_KEYBOARD) must be implemented with the following prototype:
LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam);
The second and third parameters to SetWindowsHookEx are a pointer to the hook procedure, and the base address of the DLL that contains the hook procedure. At runtime, these can be obtained by loading the library with LoadLibraryA and calling GetProcAddress on the hook procedure. Since the pointer to the hook procedure is retrieved with GetProcAddress, the hook procedure must be explicitly exported from the DLL so that it can be found. The final parameter to SetWindowsHookEx is the thread ID that will be associated with the hook. The DLL containing the hook procedure will be injected into the process that owns this thread. The SetWindowsHookEx API allows you to hook all running threads if you pass in a value of zero (0) for this parameter.
Keyboard hooks
For the example, we will use a keyboard hook (WH_KEYBOARD). Let’s start with creating the DLL that will be injected. The code for the DLL is shown below:
extern "C" {
__declspec(dllexport) LRESULT CALLBACK KeyboardProc(
int nCode, WPARAM wParam, LPARAM lParam) {
if (nCode != HC_ACTION) {
return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
// Key is up
if ((lParam & 0x80000000) || (lParam & 0x40000000)) {
MessageBoxA(nullptr, "Hello World!",
nullptr, 0);
}
return CallNextHookEx(nullptr, nCode, wParam, lParam);
}
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,
LPVOID lpvReserved) {
return TRUE;
}
The code for the DLL that will be injected.
Since a keyboard hook is being installed, there needs to be a hook procedure that will handle the appropriate events. The KeyboardProc hook procedure does just that; it is the function that will be called when there is a keyboard event to be processed. The KeyboardProc function is exported via the __declspec(dllexport) keyword making it accessible for importing by other applications. You may notice that the KeyboardProc function is also inside of an extern “C” block. This forces the compiler to perform C linkage for functions inside the block, which effectively prevents the compiler from performing any name mangling to the exported function name. If your exported function is outside of this block, then it likely will not be found when you call GetProcAddress to get a pointer to it, due to name mangling.
The KeyboardProc function will display a “Hello World!” message box to the screen in the event of a key press. The function starts by checking whether the status indicates that there is information about the keystroke message. If not, then CallNextHookEx is called to pass the message down the hook chain. If there is keystroke information present, then the function checks whether the message indicates that the key is in an up or released position. If so, then the message box is displayed.
Getting the target thread id
After building the DLL, you can move on to creating the loader for it. The loader logic is pretty straightforward. As described earlier, setting a windows hook involves obtaining the relevant pointers and calling SetWindowsHookEx to perform the installation.
std::pair<DWORD, DWORD> GetTargetProcessAndThreadId(const std::string& windowTitle) {
DWORD processId{};
const auto threadId{ GetWindowThreadProcessId(
FindWindowA(nullptr, windowTitle.c_str()),
&processId) };
if (threadId == 0 || processId == 0) {
PrintErrorAndExit("GetWindowThreadProcessId");
}
return std::make_pair(processId, threadId);
}
int main(int argc, char* argv[]) {
const auto injectingLibrary{ LoadLibraryA("SetWindowsHookExDll.dll") };
if (injectingLibrary == nullptr) {
PrintErrorAndExit("LoadLibraryA");
}
const auto hookFunctionAddress{ reinterpret_cast<HOOKPROC>(
GetProcAddress(injectingLibrary, "KeyboardProc")) };
if (hookFunctionAddress == nullptr) {
std::cerr << "Could not find hook function" << std::endl;
return -1;
}
const auto threadId{ GetTargetProcessAndThreadId(
"Untitled - Notepad").second };
const auto hook{ SetWindowsHookEx(WH_KEYBOARD,
hookFunctionAddress, injectingLibrary, threadId) };
if (hook == nullptr) {
PrintErrorAndExit("SetWindowsHookEx");
}
std::cout << "Hook installed. Press enter to remove hook and exit."
<< std::endl;
std::cin.get();
UnhookWindowsHookEx(hook);
return 0;
}
A program that injects a DLL named SetWindowsHookExDll.dll into a process.
The loader above starts out by loading SetWindowsHookExDll.dll into memory with a call to LoadLibraryA. After loading the library, a pointer to an export named KeyboardProc is obtained by calling GetProcAddress. The loader continues on to get the thread ID of another process – Notepad in this case. The thread ID is obtained by searching for a window with the caption “Untitled – Notepad”, which is the default caption when you first open the application. Once the thread ID is obtained, the hook can be installed. The SetWindowsHookEx function is called, passing in WH_KEYBOARD to indicate that the hook type will be a keyboard hook, along with the pointer to the hook procedure, the base address of SetWindowsHookExDll.dll, and Notepad’s thread ID.
Running the demo
Note: If you are using the new UWP Notepad that is in the latest Windows version, you will need to downgrade to the classic version for the demo to work.
The SetWindowsHookExDll and SetWindowsHookExLoader projects provide the full implementation that was presented in this section. To test this locally, build both the DLL and the loader projects. After a successful build, launch Notepad and then the loader application. Switch to the Notepad window and press any key in the Notepad text box. Upon doing this, you should see a “Hello World!” message box pop up from inside the Notepad process.
If you minimize the Notepad window and type elsewhere, you will not see a message box. This shows that the hook was successfully installed on the target process; meaning that the DLL that you wrote was injected into the process and that your hook procedure was invoked in the context of the target processes address space. You can verify this by opening up Process Hacker, finding the notepad.exe process, and navigating to its Modules tab.
When you are finished, go back to the loader and press the Enter key to exit. Doing this will call UnhookWindowsHookEx to remove your hook from the Notepad process. If you are feeling particularly brave, you can pass in zero (0) as the thread ID parameter to SetWindowsHookEx and observe what happens.