Manually Implementing Inline Function Hooking
This post covers the general process of implementing a simple inline function hook for an x86 Win32 API.
Last updated
This post covers the general process of implementing a simple inline function hook for an x86 Win32 API.
Last updated
Malware and EDR agents commonly hook Win32 API functions so that they can alter and/or monitor calls made to these APIs. In the case of malware, it may be beneficial for the attacker to hook functions that handle credential information (or other sensitive information) so that they can extract credentials entered by a user. Equally, EDRs will often hook APIs that are commonly abused by attackers (an example API would be VirtualAllocEx) so that they gain visibility into when a process uses the API, and what content is passed to the API with the goal of being able to detect malicious activity.
Although there are more stable and standardised methods of implementing function hooking (such as Microsoft Detours) it's still a valuable learning experience to look at how we can implement a hook without relying on libraries that do a lot of the heavy lifting. This blog post will cover the process of implementing an inline function hook for the MessageBoxA function.
This blog post only covers x86 inline hooks, the process for hooking x64 functions requires a different approach
As you can see from the images above, an inline function is the process of redirecting the execution flow away from the legitimate function that was called, executing some "hook" code, and then passing execution back to the legitimate function so that execution continues as normal. The interesting stuff happens in the "hook" code, where an attacker can change the information being sent to the function, or an EDR agent can inspect and log the data being passed to the function.
Specifically with an inline function hook, you can see from the image above that the high level process for implementing an inline function hook is:
The first n bytes of the target function (the function we want to hook) are copied to a memory location often referred to as a "trampoline".
n bytes of the target function are overwritten by a relative JMP command which will pass execution to our user defined code.
After executing our "hook" code, execution needs to return back to the original function. To do this we pass execution to our trampoline which contains the bytes that we overwrote in the original function.
After executing the overwritten bytes, a relative JMP takes the execution back to the original function at an offset after our added JMP command to prevent recursion.
The first step in the process is to set up our trampoline. This is a section of memory that will store a copy of the bytes that overwrite in the MessageBoxA function and will also contain the JMP back to an offset in the original MessageBoxA function:
A high level overview of the code snippet above does the following:
Gets the address of the MessageBoxA
function from the user32.dll
DLL.
Allocates some memory using VirtualAlloc
to store our trampoline code.
Uses memcpy_s
to copy 5 bytes from the start of the MessageBoxA
function to the start of the trampoline.
At the end of the trampoline (which is now filled with the copied bytes), add a JMP opcode (0xE9) which will take a relative address to JMP to the original MessageBoxA
function + an offset.
Calculates the size of the JMP that needs to be made to go from the trampoline memory region, to the MessageBoxA
function + offset. This is a relative JMP so to get the size we can just subtract the trampoline memory address from the address of the messageBoxA function.
When we add our hook code to the original MessageBoxA
function in the next step, our hook will be in the form of a JMP (0xE9) opcode (1 byte) and a relative address (4 bytes) which will take us to our area of user-defined code. This means that we will overwrite a total of 5 bytes at the start of the MessageBoxA
function.
Inspecting the 5 bytes at the start of the MessageBoxA
function shows us that this forms 3 complete operations:
mov edi,edi
push ebp
move ebp,esp
This means that we are safe to overwrite exactly 5 bytes with out JMP command in the case of MessageBoxA
, but if we were to target other functions we may have to pad out our overwrite with Nops
(0x90) to make sure the we overwrite complete instructions.
Now that we've created our trampoline to get back to the original MessageBoxA
function we can add our "hook" code that we want to execute when the MessageBoxA
function is called. This is where we can do our evil activity if we wanted to:
The code above is a fairly simple example of how to handle the function that you want to hook. Lines 3 & 4 create a simple typedef with the same return type (int), calling convention (stdcall) and parameters as the original MessageBoxA
function. This function - messageBoxATrampoline
- will just point to the memory location of our trampoline, this allows us to "return" to this address after we run our hooked code (as seen on line 15).
Lines 9 - 16 are our "hook" code, this is the code that will be executed before the real MessageBoxA
function is executed. The example here is fairly boring, we just change the text that will be displayed in the message box to "Hooked".
On line 15 we "return" to the messageBoxATrampoline
function which is our trampoline. If you remember from the previous step, the trampoline contains the 5 bytes that we copied from the original MessageBoxA
function, and a relative JMP to the original MessageBoxA
function.
The next step is to patch the original MessageBoxA
function so that execution is redirected to our 'hook' code in the HookedMessageBox
function. This is relatively straight forward to do, we just overwrite the first 5 bytes of the MessageBoxA
function with a relative JMP to the HookedMessageBox
function:
Now that we have most of the pieces of the puzzle together, the final step is to map our trampoline code to the messageBoxATrampoline
function:
At this point our hook should be fully working except for some other bits of generic code like includes, error handling and general structure. Adding these in and combining the steps above gives us the code shown below.
Note that we call the MessageBoxA function twice, first before we add the hook to show how it should work and then again after we've added the hook.