Manually Implementing Inline Function Hooking

This post covers the general process of implementing a simple inline function hook for an x86 Win32 API.

Introduction

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

What is an Inline Function Hook?

Normal Operation Flow (No Hook)

Hooked Operation Flow

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.

Programming The Function Hook

Step 1 - Build The Trampoline

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:

  1. Gets the address of the MessageBoxA function from the user32.dll DLL.

  2. Allocates some memory using VirtualAlloc to store our trampoline code.

  3. Uses memcpy_s to copy 5 bytes from the start of the MessageBoxA function to the start of the trampoline.

  4. 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.

  5. 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.

Why do we copy 5 bytes?

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:

  1. mov edi,edi

  2. push ebp

  3. move ebp,esp

Disassembly of the start of the MessageBoxA function

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.

Step 2 - Add Our Hook Code

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.

Step 3 - Hook The Original 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:

Step 4 - Map The Trampoline To A 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:

Step 5 - Putting It All Together

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.

Demo

References

Last updated

Was this helpful?