RCE Endeavors 😅

May 25, 2023

Function Hooking: Inline Hooks (1/7)

Filed under: Programming,Reverse Engineering — admin @ 11:39 PM

Table of Contents:

Function hooking is a technique that lets you intercept function calls and redirect them to another location. The true power of function hooking is the ability to take full control over the program’s runtime behavior. With function hooking, you define a hook function that carries out your desired logic; for example, adding functionality to log function calls, inspecting or overwriting function parameters, changing return values, or redefining functions in their entirety. After defining the hook function, you change the program to redirect control flow to it. There are many techniques to do this, which this series of posts will cover.

The best way to really understand function hooking is to try it out for yourself. Below is a demo program that will be used as the demonstration for the next several function hooking techniques.

// Disable warning for usage of ctime
#define _CRT_SECURE_NO_WARNINGS

#include <chrono>
#include <ctime>
#include <format>
#include <iostream>
#include <string>
#include <thread>

void DisplayMessageOnInterval(const std::string& message) {

    const auto currentTime{ std::chrono::system_clock::to_time_t(
        std::chrono::system_clock::now()) };

    std::cout << std::format("{} @ {}",
        message, std::ctime(&currentTime)) << std::endl;
}

int main(int argc, char* argv[]) {

    while (true) {
        DisplayMessageOnInterval("Hello World!");
        std::this_thread::sleep_for(std::chrono::seconds(5));
    }

    return 0;
}

The program is rather simple; there is a DisplayMessageOnInterval function that takes in a message to print out. This function gets called in a loop where it will print out the message and the current time, then execution will pause for five seconds before looping.

Inline Hooks

Inline hooks are a type of function hook that involve changing a program’s control flow by directly patching its instruction bytes. When a function gets called, instead of executing the expected prologue instructions, it will execute an unconditional jump that was patched in at runtime. This unconditional jump will redirect control flow over to a function that you have written – the hook function. From this hook function, you have full control: you can change the values of the arguments and call the original function, you can call the original function and modify the return value, or you can choose to not call the original function at all and execute something entirely different!

Inspecting the target function

To see the assembly instructions of the DisplayMessageOnInterval function, set a breakpoint on the function by highlighting the line with the function’s name and pressing F9 in Visual Studio. After setting the breakpoint, execute the program with the debugger attached by pressing F5. Your breakpoint should be hit almost immediately after the program begins execution. At this point, the debugger is in a broken state and you can view the assembly instructions. In Visual Studio, navigate to the Disassembly window (select Debug -> Windows -> Disassembly from the menu bar at the top). This will bring you to the disassembly listing, which will show a mapping of the source code to the runtime addresses and assembly instructions (see the Running the demo below section for screenshots). The instructions that are displayed in that window should be similar to the ones shown below, though with different runtime addresses.

Runtime AddressInstruction BytesInstruction
0x00007FF7FEF7A8A048 89 4C 24 08mov qword ptr [rsp+0x8], rcx 
0x00007FF7FEF7A8A555push rbp 
0x00007FF7FEF7A8A656push rsi 
0x00007FF7FEF7A8A757push rdi 
0x00007FF7FEF7A8A848 81 EC 10 02 00 00sub rsp, 0x210 
0x00007FF7FEF7A8AF48 8D 6C 24 20lea rbp, [rsp+0x20] 
The instruction bytes that are present in the prologue of the DisplayMessageOnInterval function.

In the Disassembly window, you can perform single stepping of the instructions by pressing F10 to Step Into and F11 to Step Over an instruction. From here you can observe the state of the program as the function executes, starting at the prologue, moving to the actual function logic, and wrapping up with the epilogue instructions.

Creating the jump stub

Installing an inline hook is just a matter of overwriting these instructions with an unconditional jump to your hook function. Setting the hook can be accomplished by writing in the instructions below at the start of the function, though the specific use of unconditional jumps is just one technique of many.

Instruction BytesInstruction
48 B8 CC CC CC CC CC CC CC CCmov rax, 0xCCCCCCCC 
FF E0jmp rax 
An unconditional jump to a placeholder address. This placeholder address will be replaced with the hook address and these bytes will overwrite the prologue instructions of the DisplayMessageOnInterval function.

Here the value address at 0xCCCCCCCC is a placeholder and will be overwritten with the runtime address of the hook function. You can define a function to generate these instructions and substitute in the desired address, as is shown below:

std::array<unsigned char, 12> CreateJumpBytes(const void* const destinationAddress) {

    std::array<unsigned char, 12> jumpBytes{ {
        /*mov rax, 0xCCCCCCCCCCCCCCCC*/
        0x48, 0xB8, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,

        /*jmp rax*/
        0xFF, 0xE0
    } };
    
    // Replace placeholder value with the actual hook address
    const auto address{ reinterpret_cast<size_t>(destinationAddress) };
    std::memcpy(&jumpBytes[2], &address, sizeof(void *));

    return jumpBytes;
}

The CreateJumpBytes function generates the unconditional jump stub and writes in the hook address over the placeholder address.

Installing the hook

After building this stub, you can write it in place of the original instructions. The code for doing this is shown below. One important thing to note is that the memory page protections of the target address must be changed to PAGE_EXECUTE_READWRITE to allow the new instructions to be written in. Failure to do so will result in an access violation at runtime.

void InstallInlineHook(void* const targetAddress, const void* const hookAddress) {

    const auto hookBytes{ CreateJumpBytes(hookAddress) };
    const auto oldProtections{ ChangeMemoryPermissions(
        targetAddress, hookBytes.size(), PAGE_EXECUTE_READWRITE) };
    std::memcpy(targetAddress, hookBytes.data(), hookBytes.size());
    ChangeMemoryPermissions(targetAddress, hookBytes.size(), oldProtections);

    FlushInstructionCache(GetCurrentProcess(), nullptr, 0);
}

The InstallInlineHook function overwrites the instructions at the target address with the generated unconditional jump stub.

At this point, all that is left is to write the hook function. To keep it simple, the hook function, defined as HookDisplayMessageOnInterval, does nothing more than print out a message to the console.

void HookDisplayMessageOnInterval(const std::string& message) {

    std::cout << "HookDisplayMessageOnInterval function called!"
        << std::endl;
}

The definition of the hook function that will be called.

After executing the InstallInlineHook function, with DisplayMessageOnInterval as the target address and HookDisplayMessageOnInterval as the hook address, the assembly instructions at the start of DisplayMessageOnInterval will look similar to those in the table below, although with different runtime addresses.

Runtime AddressInstruction BytesInstruction
0x00007FF6F0BEA8A048 B8 00 AA BE F0 F6 7F 00 00mov rax, 0x7FF7FEF7AA00 
0x00007FF6F0BEA8AAFF E0jmp rax 
0x00007FF6F0BEA8AC02 00add al, byte ptr [rax] 
0x00007FF6F0BEA8AE00 48 8Dadd byte ptr [rax-0x73], cl 
0x00007FF6F0BEA8B16Cins byte ptr [rdi], dx 
0x00007FF6F0BEA8B224 20and al, 0x20 
The prologue instructions of DisplayMessageOnInterval after the unconditional jump stub has been written.

Verifying the hook

Now when the program runs, the message should get printed out to the console from the hook function instead of the original. At this point, control flow of the original function has changed to the defined hook function, and as shown, any code that is present in the hook function will get executed. However, since the original instructions were overwritten, control cannot get passed back to the original function. To resolve this, the original instructions must be saved and relocated elsewhere in the program’s address space. This technique is called creating a trampoline function and is covered in the next post.

Running the demo

The InlineHook project provides the full implementation that was presented in this section. The best way to see what is happening is to set a breakpoint on the line that installs the hook, and the line immediately afterward as shown below.

Setting a breakpoint on InstallInlineHook and the next immediate line.

After setting the breakpoints, launch the executable in Visual Studio. The breakpoint should be hit immediately. Navigate to the Disassembly window and type in DisplayMessageOnInterval in the Address text box. After doing this, you should see the assembly instructions for the function.

The assembly instructions corresponding to DisplayMessageOnInterval.

Continue execution so that the next breakpoint gets hit. At this point, the hook has been installed and you can navigate back to the Disassembly window. When looking at DisplayMessageOnInterval in this window again, you should see different instructions that show the hook address being moved into the RAX register, followed by an unconditional jump to RAX.

The unconditional jump to the hook address is shown after the hook is installed.

Now the inline hook is in place and any call to DisplayMessageOnInterval will instead be re-routed to HookDisplayMessageOnInterval, as you can see for yourself by continuing execution of the program.

1 Comment »

  1. Thank You For Help.. very detailed documents. good for beginner.

    Comment by Htoo — June 23, 2023 @ 5:51 AM

RSS feed for comments on this post. TrackBack URL

Leave a comment

 

Powered by WordPress