Hook Injection

2022-11-15

Hooking is a common technique in game cheat development: inserting a small piece of machine code (typically a jmp instruction) into the memory where a normal function resides, then we can alter the behavior of that function. This technique is not only useful in game cheat development but also in software testing, where it can be used to mock a non-virtual function. Implementing hooks is also easy and straightforward.

Consider the following code:

#include <stdio.h>

int func()
{
    printf("hello, beautiful world");
    return 0;
}

int main()
{
    return func();
}

Although modifying func here is possible, let's assume that func is actually located in a dynamic or static library for which we do not have the source code. In such cases, we can use hooks to modify func at runtime.

Taking x64 architecture on Linux as an example, code typically resides in the .text segment, where memory is not writable. Therefore, we use mprotect to modify the memory's attributes, allowing writing to this segment:

#include <unistd.h>
#include <sys/mman.h>

// Assume the hooked function is 'orig_func'
int page_size = getpagesize();
void *aligned_addr = (void *)(((uintptr_t)orig_func) & (~(page_size - 1)));
if (mprotect(aligned_addr,
             page_size * 2,
             PROT_EXEC | PROT_WRITE | PROT_READ ) != 0) {
    err_log("failed to modify mem props");
    exit(EXIT_FAILURE);
}

Then, we write the machine code for jump, with its assembly pseudocode as follows:

mov rax, &hook_func
jmp rax

Since the rax register is used to store return values in the C/C++ ABI, it can be modified without affecting the program's behavior.

The code in C is as follows:

uint8_t hook_mcode[] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00,
                        0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0};
*(uintptr_t*)(hook_code + 2) = (uintptr_t)hook_func;
memcpy(orig_func, hook_mcode, 12);

The complete program looks like this:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <unistd.h>
#include <sys/mman.h>

int func()
{
    printf("hello, beautiful world\n");
    return 0;
}

int hook_func()
{
    printf("farewell, cruel world\n");
    return 0;
}

void hook(void *orig_func, void *hook_func, uint8_t *mcode_backup)
{
    int page_size;
    void *aligned_addr;
    uint8_t hook_mcode[12] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00,
                              0x00, 0x00, 0x00, 0xFF, 0xE0};

    page_size = getpagesize();
    aligned_addr = (void *)(((uintptr_t)orig_func) & (~(page_size - 1)));
    if (mprotect(aligned_addr,
                 page_size * 2,
                 PROT_EXEC | PROT_WRITE | PROT_READ ) != 0) {
        fprintf(stderr, "failed to modify mem props\n");
        exit(EXIT_FAILURE);
    }
    *(uintptr_t*)(hook_mcode + 2) = (uintptr_t)hook_func;
    memcpy(mcode_backup, orig_func, 12);
    memcpy(orig_func, hook_mcode, 12);
}

void restore(void *func, uint8_t *mcode)
{
    memcpy(func, mcode, 12);
}

int main()
{
    uint8_t mcode_backup[12] = {0};
    func();
    hook(func, hook_func, mcode_backup);
    func();
    restore(func, mcode_backup);
    func();
    return 0;
}

The program's output is as follows:

hello, beautiful world
farewell, cruel world
hello, beautiful world

Here, the first line is the original function's output, the second line is the output after being hooked, and the last line is the output after restoration.



Email: i (at) mistivia (dot) com