RCE Endeavors 😅

May 25, 2023

Function Hooking: Trampolines & Detours (2/7)

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

Table of Contents:

The previous post showed a valid technique for installing an inline hook. However, overwriting the original instruction bytes of the target function created a problem: these instructions were lost, leaving no way for the original function to be called. The way to remedy this problem is to create a new dynamically generated function — the trampoline function — that holds the original instructions in another part of the program’s executable memory. At the end of this function, there will be an unconditional jump back to the rest of the original function. Visually, the diagram below describes what will happen:

The control flow after an inline hook as been installed. The calling code will call DisplayMessageOnInterval, which will pass control flow to DisplayMessageOnIntervalHook. The hook will then call the trampoline function to pass control back to DisplayMessageOnInterval.

Creating the trampoline

To implement this will require allocating an executable block of memory, copying in the original instructions, and then adding the unconditional jump at the end. You can write a CreateTrampoline function to do this:

void* CreateTrampoline(void* const targetAddress, const size_t size) {

    const auto jumpBack{ CreateJumpBytes(
        reinterpret_cast<unsigned char*>(targetAddress) + size) };
    
    const auto trampolineStub{ VirtualAlloc(nullptr, size + jumpBack.size(),
        MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE) };
    if (trampolineStub == nullptr) {
        PrintErrorAndExit("VirtualAlloc");
    }

    std::memcpy(trampolineStub, targetAddress, size);
    std::memcpy(&reinterpret_cast<unsigned char *>(trampolineStub)[size],
        jumpBack.data(), jumpBack.size());

    return trampolineStub;
}

The CreateTrampoline function allocates executable memory for the relocated instructions, copies them in, then adds an unconditional jump back to the original code.

Here you allocate a block of executable memory by calling the VirtualAlloc function with the PAGE_EXECUTE_READWRITE flag. After successfully allocating the memory, it is simply a matter of copying in the instructions with memcpy and appending the unconditional jump at the end. The memory block will have instructions similar to the ones in the table below, though with different runtime and destination addresses.

Runtime AddressInstruction BytesInstruction
0x000002C4F37E000048 89 4C 24 08mov qword ptr [rsp+0x8], rcx
0x000002C4F37E000556push rsi
0x000002C4F37E000657push rdi
0x000002C4F37E000748 81 EC D8 00 00 00sub rsp, 0x0D8
0x000002C4F37E000E48 8D 7C 24 20lea rdi, [rsp+0x20]
0x000002C4F37E001348 B8 F3 11 67 09 F6 7F 00 00mov rax, 0x7FF6096711F3
0x000002C4F37E001DFF E0jmp rax
The instructions of the trampoline.

Installing the hook

Now all that is left is to define the trampoline signature and function pointer, modify the HookDisplayMessageOnInterval function to call the trampoline, and add functionality in the InstallInlineHook function to set the trampoline pointer. This is achieved with the code shown below:

// Trampoline pointer declaration
using HookDisplayMessageOnIntervalTrampolinePtr = void (__stdcall*)(
    const std::string& message);
static HookDisplayMessageOnIntervalTrampolinePtr HookDisplayMessageOnIntervalTrampoline{};

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

    std::cout << "HookDisplayMessageOnInterval function called!"
        << std::endl;
    HookDisplayMessageOnIntervalTrampoline("Hooked Hello World");
}

template <typename Trampoline>
void InstallInlineHook(void* const targetAddress, const void* const hookAddress,
    const Trampoline*& trampolineAddress) {

    trampolineAddress = reinterpret_cast<Trampoline*>(
        CreateTrampoline(targetAddress, 19));

    // Rest of code is the same
}

Additional code showing declarations of the trampoline and the modified HookDisplayMessageOnInterval and InstallInlineHook functions.

Calculating overwrite size

You may notice that there is a magic number (19) that is being passed to the CreateTrampoline function. This is the size, in bytes, of the instructions that will be relocated to the trampoline. Since the mov and jmp overwrite size takes up 12 bytes, and entire instructions need to be relocated (just copying 12 bytes will result in a partial copy of an instruction), the size needs to be rounded up to capture the full instruction. This is better illustrated in the table below; count the instruction bytes for the five instructions that are relocated to the trampoline.

Instruction BytesInstruction
48 89 4C 24 08mov qword ptr [rsp+0x8], rcx
56push rsi
57push rdi
48 81 EC D8 00 00 00sub rsp, 0x210
48 8D 7C 24 20lea rdi, [rsp+0x20]
B9 2E 00 00 00mov ecx, 0x2Eh
B8 CC CC CC CCmov eax, 0xCCCCCCCC
The original prologue instructions of DisplayMessageOnInterval. The highlighted rows denote the instructions that are overwritten.

Verifying the hook

At this point, the new version of the inline hook is ready to test. After re-running the code with the new changes, the console output should display “Hooked Hello World” on a five second interval, demonstrating that the hook function was called, modified a parameter, and forwarded the rest of the call to the original DisplayMessageOnInterval function.

This works great for the example program, but unfortunately it is not applicable in every situation. While relocating the instructions that were compiled for this example worked perfectly fine, that is not guaranteed to be the case all of the time. Imagine if you had relocated an instruction that references any sort of relative location, i.e., an instruction that does RIP-relative addressing, or relative branch instructions. When you relocate those types of instructions to another part of memory, the addresses that they were referencing would not match up, likely leading to a crash at runtime at some point in the program’s execution.

The fix for this is not quite so simple; it boils down to needing a disassembler to identify whether the instruction performs any sort operation on a relative address. Fortunately, there is an inline hooking library that takes care of this and provides an easy-to-use API, which will be covered in the second half of this post.

Running the demo

The InlineHookWithTrampoline project provides the full implementation that was presented in this section. As before, you can begin by setting breakpoints on the InstallInlineHook and the line immediately following, as is shown below:

Setting a breakpoint on InstallInlineHook and the next immediate line.

To inspect the generated trampoline, set another breakpoint on the return statement of the CreateTrampoline function.

A breakpoint being set on the return statement.

Launch the application in Visual Studio and verify that your first breakpoint on the InstallInlineHook function was hit immediately. Continue execution so that the next breakpoint gets hit. You should now be in a broken state on the return statement in the CreateTrampoline function. Hover your mouse over the trampolineStub variable. A context menu should pop up showing the variable name and address. Right click the address and select “Copy” from the menu.

The context menu of the trampolineStub variable.

Navigate to the Disassembly window in Visual Studio and paste in the address of trampolineStub in the Address text box. You should see instructions similar to what is shown in below:

The trampoline stub instructions.

In the Disassembly window, you can set breakpoints on individual instructions. As in the code editor, click to the left of the address to enable a breakpoint on it. Once your breakpoint is enabled, you will see a red circle.

A breakpoint set on the first instruction of the trampoline stub.

After setting this breakpoint, continue execution of the program. The breakpoint on the while loop should be hit, since it is the line of code that immediately follows the InstallInlineHook function. Continue execution once more. You should now be broken on the mov instruction in the trampoline stub. The call stack shows that the trampoline stub is being called from the HookDisplayMessageOnInterval function, as expected.

The call stack when the trampoline stub is executed.

In the Disassembly window, with press the F11 key to let the Visual Studio debugger perform a Step Into operation. Continue doing this until you have executed past the final jmp rax instruction of the trampoline stub. Notice that you are back in the original HookDisplayMessage function, but at the address past the overwritten bytes.

Jumping back into the HookDisplayMessage function.

This has shown the entire flow of events, starting with the generation of the trampoline stub, then its invocation by the hook function, and finally how the trampoline stub passes control back to the original function.

Detours

Detours is a library released by Microsoft that, among other useful features, provides the ability to install inline hooks on arbitrary functions. The library itself is open source, available for multiple architectures, and well documented. Detours has some nice abstractions; the library hides the need to change memory page protections, performs the trampoline function allocation and copy, and is able to handle fixing up relative instructions that were relocated. Internally, Detours is able to disassemble instructions at the target address, so as a user you do not need to calculate how many instructions to relocate and overwrite.

Installing the hook

To install an inline hook using Detours, the InstallInlineHook function can be modified as is shown below:

template <typename Trampoline>
void InstallInlineHook(void* targetAddress, void* const hookAddress,
    const Trampoline*& trampolineAddress) {

    DetourTransactionBegin();

    DetourUpdateThread(GetCurrentThread());
    PDETOUR_TRAMPOLINE detourTrampolineAddress{};
    DetourAttachEx(&(static_cast<void*&>(targetAddress)),
        static_cast<void *>(hookAddress),
        &detourTrampolineAddress, nullptr, nullptr);
    trampolineAddress = reinterpret_cast<Trampoline*>(detourTrampolineAddress);
    // Add any additional hooks here

    DetourTransactionCommit();
}

The DisplayMessageOnInterval function showing how to install an inline hook with the Detours API.

Detours treats installing inline hooks as part of a transaction, meaning you can perform multiple hook install and removal operations in one action. DetourTransactionBegin is called to begin a new transaction to install or remove an inline hook — a detour to use the library’s terminology. DetourUpdateThread is used to enlist a thread to perform the instruction overwriting. DetourAttach[Ex] is used to specify where the detour should be installed; which target address should be detoured to a hook address. Lastly, DetourTransactionCommit is called to commit the transaction and perform the installation or removal of detours.

Running the demo

The InlineHookDetours project provides the full implementation that was presented in this post. When opening this project, a Windows x64 package of Detours will be installed via the provided vcpkg.json file. Once installed, the project can be built and the demo application can be run. Detours will work in a very similar manner to the previous section where inline hooks with trampolines were covered. The best way to see this is to add breakpoints on the InstallInlineHook function and the subsequent line, as shown below.

Setting breakpoints on the InstallInlineHook in the Detours demo project.

Launch the application and let it hit the first breakpoint on InstallInlineHook. Use the Disassembly window to inspect the instructions at the address of DisplayMessageOnInterval. The instructions should look similar to the image below.

The original instructions of DisplayMessageOnInterval.

Continue execution to let the Detours library install the inline hook. After the second breakpoint following the InstallInlineHook function is hit, the hook will be installed. You can inspect the address of DisplayMessageOnInterval again and see that there has been an unconditional jump written in.

The instructions of DisplayMessageOnInterval after the Detours library has placed an inline hook.

One interesting thing about the jump is that Detours uses a relative jump, instead of a jump to an absolute address as was demonstrated in the previous section. This is useful because it takes up less bytes: five bytes for the relative jump, while the absolute requires twelve bytes. The only downside is that the destination address must reside within a 2 GB range, either forward of backwards, in memory from the source instruction. In practical terms this limitation is usually not an issue.

You can also inspect the address of HookDisplayMessageOnIntervalTrampoline to see the trampoline that Detours generated. You can find the address by highlighting the variable while in a broken state. A context menu containing the address will pop up.

Getting the address of HookDisplayMessageOnIntervalTrampoline.

Putting this address in the Disassembly window will show the instructions at the trampoline. The original instruction that was overwritten by the five byte relative conditional jump was itself five bytes long, the trampoline will only contain one instruction, followed by an unconditional jump back to the original function.

The trampoline generated by Detours.

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.

« Newer Posts

Powered by WordPress