Windows API Hooking and DLL Injection
This article is devoted to an approach for setting up local Windows hooks in C/C++ using native API calls.
Join the DZone community and get the full member experience.Join For Free
This article is devoted to an approach for setting up local Windows API hooks. This article will also provide you with a DLL (dynamic link library) injection example: we will demonstrate how you can easily hook the system network adapter enumerator API call to manipulate the returned network adapter info.
Hooking covers a range of techniques for altering or augmenting the behavior of an operating system, application, or other software components by intercepting API function calls, messages, or events passed between software components. Code that handles such interception is called a hook.
At Plexteq, we develop complex networking and security applications for which we use low-level techniques such as hooking and injection. We would like to share our experience in this domain.
Related Tutorial: Implementing Spring Boot Basic Security with Swagger 3 (OpenAPI 3).
Some of the software applications that utilize hooks are tools for programming (e.g. debugging), antimalware, application security solutions, and monitoring tools. Malicious software often uses hooks as well; for example, to hide from the list of running processes or to intercept keypress events in order to steal sensitive inputs such as passwords, credit card data, etc. Further Reading: How to Generate Keystore and CSR using keytool commands.
There are two main ways to modify the behavior of an executable:
- through a source modification approach, which involves modifying an executable binary prior to application start through reverse engineering and patching. Executable signing is utilized to defend against this, preventing code that isn’t properly signed from being loaded.
- through runtime modification, which is implemented by the operating system’s APIs. Microsoft Windows provides appropriate harnesses for hooking the dialogs, buttons, menus, keyboard, mouse events, and various system calls.
API hooks can be divided into the following types:
- Local hooks: these influence only specific applications.
- Global hooks: these affect all system processes.
In this article, we'll go over the hook technique for Windows that belongs to the local type done through a runtime modification using C/C++ and native APIs.
Local hooks implemented with the runtime modification approach have to be executed within the address space of the target program. A program that manipulates a target process and makes it load hook is called an injector. In our example, we imply that the hook setup code is contained within an external DLL resource that is an injection object.
The overall flow for preparing the hook to be loaded and executed requires the injector to follow these steps:
- Obtain the target process handle.
- Allocate memory within a target process and write the external DLL path into it (here we mean writing the dynamic library path that contains the hook).
- Create a thread inside the target process that would load the library and set up the hook.
In our example, we imply the hook setup code is located in DllMain function of the external DLL so it will be automatically executed upon a successful library load.
Microsoft Windows API provides several system calls that are suitable for implementing the injector. Let’s go through the steps and figure out the best way to implement them.
Note that the approach below won’t work for processes that don’t use kernel32.dll as in the samples below we heavily rely on API functions exported by it.
Suppose the target process is not running yet, and we would like to inject our hook right after the target program starts. To make this happen, the injector should first run the target process by making an API call to CreateProcess.
To make our hook set right after our target process starts, the injector has to suspend the target by passing a
CREATE_SUSPENDED flag (dwCreationFlags) and then, after injecting the hook, resume the target process by calling the ResumeThread API function.
Here’s an example of how to start a process in a suspended state:
The injector should first allocate memory with VirtualAllocEx.
And then write data to it with WriteProcessMemory.
Here’s an example of allocating and writing memory to a target process:
So CreateRemoteThread creates a new thread with state parameters dwCreationFlags in the target remote process specified by a hProcess handle. The newly created thread will execute a function pointed by lpStartAddress and pass lpParameter to it as a first argument.
Our plan now is to use this API function to start a thread and make it load our DLL, which we will accomplish by:
- Passing a pointer to the Windows API function LoadLibrary as a lpStartAddress.
- Passing a pointer to the DLL hook (the one we initialized using VirtualAllocEx and WriteProcessMemory) as a lpParameter.
To find a pointer to the LoadLibrary function in a target process, we will use GetProcAddress.
where hModule is a reference to a DLL that exports the LoadLibrary.
HMODULE pointer could be obtained with the help of GetModuleHandle.
Here’s an example of loading a DLL with the hook into the target process:
These are all the necessary steps for injecting the hook library. If the injection went well, the hook library is loaded in the target process, and the
DllMain function is executed so that we can set any hooks we want.
To implement the hooking itself, we recommend using one of the many already existing solutions. There are a lot of them available as open-source, free, or partially free solutions. For example, Microsoft Detour, a powerful hooking engine, has support for the x86 architecture in a free version (it requires a paid subscription for hooking on x64). Another popular engine is NtHookEngine, which supports both x86 and x64 and has a well-designed and very straightforward API. Actually, this engine exports just three simple-to-use functions:
HookFunction - sets a hook. This one may be used to hook any function that exists in the current process's virtual address space.
UnhookFunction - removes a specific hook.
GetOriginalFunction - returns a pointer to the original function. This is very useful when the original function needs to be called inside a hook function.
Further, in the sample implementation, we’ll be using NtHookEngine.
Our library hooks the GetAdaptersInfo method and fakes the network adaptor name and its MAC-address values.
So eventually we hook GetAdaptersInfo with FakeGetAdaptersInfo. Inside the FakeGetAdaptersInfo, we use GetOriginalFunction to get the actual adapter info. Next, we replace the first adapter info with fake values.
The injector in this example can be built for either x86 or x64 architectures; however, keep in mind that an injector built for x86 won’t run in an x64 environment.
Because hooks run in the context of an application, they must match the "bitness" of the application.
Our injector runs the target application by itself, and that’s why it doesn’t search for the target process ID in the active process lists.
Finally, the injector code:
To check if the hook library is working, we compare the output of the target application, first without the hooking and then with it.
First, we run a target app without the hook applied:
Now let’s check the output with the hook loaded. Please note the changed “adapter name” and “adapter addr” fields for the first adapter in the list.
Opinions expressed by DZone contributors are their own.