Being able to control and manipulate system behavior and API calls is a useful skill for any Windows developer. It allows you to investigate internal processes and detect suspicious and malicious code. Previously, we described an easy way to set a global API hook by manipulating the AppInit_DLLs registry key and make the calc.exe process invisible in the list of running processes.
This time, we dive even deeper into dynamic-link library (DLL) injection techniques. We demonstrate how to make any Windows process immortal so that no other process can terminate it. This DLL injection tutorial will be useful for Windows developers who want to know more about different ways of modifying the flow and behavior of API calls in Windows applications.
Before we dive into the depths of code manipulations, let’s go over some of the basics of API hooking.
What is API hooking? API hooking is a technique that developers use for manipulating the behavior of a system or an application. With the help of API hooking, you can intercept calls in a Windows application or capture information related to API calls. Additionally, API hooking is one of the techniques that antivirus and Endpoint Detection and Response solutions use for identifying malicious code.
There are many ways you can implement API hooking. The three most popular methods are:
- DLL injection — Allows you to run your code inside a Windows process to perform different tasks
- Code injection — Implemented via the WriteProcessMemory API used for pasting custom code into another process
- Win32 Debug API toolset — Provides you with full control over a debugged application, making it easy to manipulate the memory of a debugged process
In this article, we focus on the DLL injection method as it’s the most flexible, best-known, and most studied approach to manipulating system behavior through API calls. But what is DLL injection to begin with? In short, it’s the process of running custom code within the address space of a different process. DLL injection is also the most universal API hooking method and has fewer limitations than other API hooking techniques.
There are three widely used DLL injection methods based on the use of:
- the SetWindowsHookEx function. This method is only applicable to applications that use a graphical user interface (GUI).
- the CreateRemoteThread function. This method can be used for hooking any process but requires a lot of coding.
- remote thread context patching. This method is efficient but rather complex, so it’s better to use it only if the other two methods don’t work out for some reason.
Further in this article, we explain how to implement each of these methods and provide a practical example of setting API hooks with one of them. Our journey begins with overviewing the first technique on this list — using the SetWindowsHookEx function.
The first DLL injection technique we overview in this post is based on the SetWindowsHookEx function. Using the WH_GETMESSAGE hook, we set a process that will watch for messages processed by system windows. To set the hook, we call the SetWindowsHookEx function:
The WH_GETMESSAGE argument determines the type of hook, and the functionAddress parameter determines the address of the function (in the address space of your process) that the system should call whenever a window is about to process a message.
The dllToBeInjected parameter identifies the DLL containing the functionAddress function. The last argument, 0, indicates the thread for which the hook is intended. Passing 0, we tell the system that we’re setting a hook for all GUI threads that exist in it. So this method can be applied to hook a specific process or all processes in the system.
Let’s see how all this works:
- The Some_application.exe thread is about to send a message to some window.
- The system checks if the WH_GETMESSAGE hook is set for this thread.
- Then the system finds out whether Inject.dll, the DLL containing the callback for the message, is mapped to the address space of the Some_application.exe process.
- If Inject.dll isn’t mapped yet, the system maps it to the address space of the Some_application.exe process and increments the lock count of the DLL in that process.
- The DllMain function of Inject.dll is called with the DLL_PROCESS_ATTACH parameter.
- Then a callback is called in the address space of the Some_application.exe process.
- After returning from the callback, the DLL lock counter in the address space of the process is reduced by 1.
Now let’s see how we can inject DLL with a second method — using the CreateRemoteThread function.
Now we’re going to look at the most flexible way of injecting DLL — using the CreateRemoteThread function. The overall flow looks like this:
Injecting a DLL involves invoking the LoadLibrary function within the thread of the target process to load the desired DLL. Since managing threads of another process is extremely complicated, it’s better to create your own thread in it. Fortunately, the CreateRemoteThread function makes this easy:
This function is very similar to the CreateThread function but has an additional hProcess parameter that identifies the process to which the new thread will belong.
We start with getting the handle of the process we’re going to hook:
Then, we should allocate some memory in the target process in order to pass the DLL path, as the target process can access only its private memory:
Using the WriteProcessMemory function, we can place the DLL path into the address space of our target process:
Then we can start a new thread. With the help of this thread, our DLL will be loaded into the target process.
Finally, we can move to the third DLL injection method that’s based on thread context patching.
This method of DLL injection isn’t easy to detect, as it mostly looks like a regular thread activity. To succeed, we need to manipulate the context of an existing remote thread and make sure the thread doesn’t know about these manipulations. The instruction pointer of the target thread is first set to a custom piece of code. When the code is executed, the pointer is redirected to its original location.
This is what the whole process looks like:
Let’s see how we can implement this DLL injection method in an x64 system.
First, we need to locate the target process and pick a thread within it. It’s better to choose a thread that’s already running or is likely to run so that our DLL can be loaded as early as possible. Selecting a waiting thread isn’t the best idea, as such a thread won’t run the code unless it’s ready to run.
First, we use the OpenThread function to open the handle of the remote thread:
Then we need to allocate memory in the remote process to store our injected code and the DLL path in it:
Next we write the DLL path in the middle of the remote allocated buffer:
Then we suspend the remote thread and retrieve its context:
Now we compile assembly code and save it in the buffer:
We set the remote IP (RIP) register of our remote thread to the buffer:
Finally, we set a new context and resume the thread:
Now that you’ve got a better understanding of different DLL injection techniques, it’s time to see how these techniques work in practice.
While using the CreateRemoteThread function is the most universal way of setting API hooks with DLL injection, this method requires an extensive amount of preliminary coding. That’s why we’ll illustrate how to set API hooks with DLL injection using the SetWindowsHookEx function, which is a less time-consuming method.
This example is based on a basic user-mode DLL written in C++. To be able to follow your trail, make sure to add the latest version of the Mhook sources to your project.
Our main goal here is to create an immortal process that’s impossible for any other process in the system to terminate. We begin with setting a global API hook.
- We inject our DLL with the SetWindowsHookEx function:
- To make sure we can restore the original function after removing our hook, we need to store its address.
To terminate a process, we need to call the TerminateProcess function from kernel32.dll. Thanks to the creation and initialization of a global variable, we can now store the original function’s address:
- We’ve hooked the HookedTerminateProcess function instead of the original TerminateProcess function. The hooked function first calls the QueryFullProcessImageNameW function from kernel32.dll and gets the full name of the executable image for the process.
Now we need to check the process name. If it has the “_immortal” suffix, it’s the process we should not allow to be terminated.
Note: Both functions, the original and the hooked, must have identical signatures.
- Here, we can finally inject our DLL into the code of the target process to set our hook.
Once loaded in the target process, the DllMain function will receive the DLL_PROCESS_ATTACH parameter. Now we can manipulate this process and hook the chosen function with the help of the Mhook library:
- Once the DLL is unloaded from the target process’s address space, the DllMain function receives the DLL_PROCESS_DETACH parameter. After that, we remove the hook and restore the original function.
We now have all the code needed for setting API hooks with Windows DLL injection. It’s time to check if this code is actually working.
For a practical illustration, we used the Structured Storage Viewer utility and turned it into an immortal process by injecting a DLL with the SetWindowsHookEx function. As a result of this process, we got an executable with the name SSView_immortal.exe. Let’s launch this executable and look at it in Task Manager. We’ll also need the Process Explorer utility installed to check if our DLL is, in fact, injected in the Taskmgr.exe process:
In Task Manager, we can see the SSView_immortal.exe process. Let’s try to terminate it:
When we click End task, we get a message box with an error (the same error we show in our hooked function):
Then we also receive a message saying “Access is denied.” This is the ERROR_ACCESS_DENIED response we set earlier with the help of the SetLastError function when implementing our hooked function:
As you can see, we successfully hooked a system process and made it impossible for any other Windows process to terminate it, which is exactly what we intended to do.
There are many methods to hook an API call. DLL injection is one of the most flexible, effective, and well-studied methods for injecting custom code into a system process. When performing DLL injection, it’s important to insert code into a running process, as DLLs are meant to be loaded as needed at runtime.
There are many ways you can hook a function with DLL injection — by setting hooks in specific functions or manipulating the context of a remote thread. From our experience, we can say that setting hooks with the CreateRemoteThread function is the most effective approach. As this function is supported by the Windows operating system, there’s no need to use any additional tricks, complicated executable file structures, or operating system internals when working with it. However, if you’re working with a GUI application, you can use the most effortless option — the SetWindowsHookEx function.
At Apriorit, we’ve already set thousands of hooks and know how to find our way around different operating systems and processes. Get a step closer to realizing your dream project — contact us and tell us all about it!