https://github.com/cursey/safetyhook



Inline hooks

Inline hooks are the most standard kind of function hook available. It replaces the instructions at the start of a function with a jump to a trampoline. The trampoline then jumps to the hook destination. It then provides a mechanism to call the original function along with the instructions that were replaced.

The easiest way to create and use inline hooks is via the easy API safetyhook::create_inline.

#include <iostream>

#include <safetyhook.hpp>

__declspec(noinline) int add(int x, int y) {
    return x + y;
}

SafetyHookInline g_add_hook{};

int hook_add(int x, int y) {
    return g_add_hook.call<int>(x * 2, y * 2);
}

int main() {
    std::cout << "unhooked add(2, 3) = " << add(2, 3) << "\\n";

    // Create a hook on add.
    g_add_hook = safetyhook::create_inline(add, hook_add);

    std::cout << "hooked add(3, 4) = " << add(3, 4) << "\\n";

    g_add_hook = {};

    std::cout << "unhooked add(5, 6) = " << add(5, 6) << "\\n";

    return 0;
}

Mid hooks

Mid hooks are a very flexible kind of hook that provides access to the CPU context at the point in which the hook was hit. This gives direct register access to nearly any point within a functions execution path.

The easiest way to create and use mid hooks is via the easy API safetyhook::create_mid.

// NOTE: This example is intended for 64-bit release mode only.
#include <iostream>

#include <Zydis.h>
#include <safetyhook.hpp>

__declspec(noinline) int add_42(int a) {
    return a + 42;
}

void hooked_add_42(SafetyHookContext& ctx) {
    ctx.rax = 1337;
}

SafetyHookMid g_hook{};

int main() {
    std::cout << "unhooked add_42(2) = " << add_42(2) << "\\n";

    // Let's disassemble add_42 and hook its RET.
    ZydisDecoder decoder;
		ZydisDecodedInstruction ix;

    ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);

    for (auto* ip = reinterpret_cast<uint8_t>(add_42); *ip != 0xC3; ip += ix.length) {
        ZydisDecoderDecodeInstruction(
            &decoder, nullptr, reinterpret_cast<void*>(ip), 15, &ix);
    }

    g_hook = safetyhook::create_mid(ip, hooked_add_42);

    std::cout << "hooked add_42(3) = " << add_42(3) << "\\n";

    g_hook.reset();

    std::cout << "unhooked add_42(4) = " << add_42(4) << "\\n";

    return 0;
}

VMT hooks

VMT hooks are a way to hook object methods without patching the method itself (no direct control flow redirection). Functionally they are the same as Inline hooks but can only be applied to objects with a virtual method table.

<aside> 💡 VMT hooks are generally setup in two distinct phases. First you create a safetyhook::VmtHook for a specific object in memory. Then you can hook individual methods of that object with safetyhook::VmHook.

</aside>

The easiest way to create and use VMT hooks is via the easy API safetyhook::create_vmt along with safetyhook::create_vm.

#include <memory>
#include <print>

#include <safetyhook.hpp>

class Interface {
public:
    virtual ~Interface() = default;
    virtual int add_42(int a) = 0;
};

class Target : public Interface {
public:
    int add_42(int a) override { return a + 42; }
};

SafetyHookVmt g_target_hook;
SafetyHookVm g_add_42_hook;

class Hook : public Target {
public:
    int hooked_add_42(int a) { return g_add_42_hook.thiscall<int>(this, a) + 1337; }
};

int main() {
    auto target = std::make_unique<Target>();

    std::println("unhooked target->add_42(1) = {}", target->add_42(1));

    g_target_hook = safetyhook::create_vmt(target.get());
    g_add_42_hook = safetyhook::create_vm(g_target_hook, 1, &Hook::hooked_add_42);

    std::println("hooked target->add_42(2) = {}", target->add_42(1));

    g_target_hook = {};

    std::println("unhooked target->add_42(3) = {}", target->add_42(1));

    return 0;
}