Library / SDK
cursey/safetyhook avatar
cursey/safetyhook

SafetyHook, a C++23 procedure hooking library that tries not to crash

C++23 procedure hooking library.

768 stars89 forksC++BSL-1.0

At a glance

What is it?
cursey/safetyhook hooks Windows x86 and x86_64 procedures at runtime, and the interesting part is what it does around the hook: it stops other threads while installing or removing one, repairs instruction pointers that were mid-execution, relocates instructions with RIP-relative displacements correctly, and widens short branches. It depends on Zydis and ships under the Boost Software Licence.
Who is it for?
Adopt SafetyHook if you need runtime procedure hooking on Windows and want a library that handles thread suspension and instruction relocation for you rather than one you assemble from inline assembly patches. Skip it if your target is Linux or macOS, since it is Windows only, or if you need an interface-level interception layer, since this operates on procedures rather than on API boundaries.
Can I use it commercially?
Yes. BSL-1.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 126 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the library is trying to do differently

SafetyHook is a C++23 procedure hooking library for Windows x86 and x86_64 systems. The README says it aims to make runtime procedure hooking as safe as possible while maintaining simplicity of its implementation, and then lists what it currently does.

The list is the whole argument, and it is worth reading item by item because each one is a specific way that hooking libraries traditionally go wrong.

It stops all other threads when creating or deleting hooks. This is the fundamental safety property: if another thread is executing the five bytes you are about to overwrite, it crashes or executes nonsense. Most hooking libraries leave you to handle that, and the handling is where the subtle bugs live.

It fixes the instruction pointer of threads that may be affected by the creation or deletion of a hook. Suspending a thread does not reset where it will resume, and if that address is inside the bytes you just overwrote, the thread has to be moved somewhere safe before it runs again. Doing that correctly requires inspecting the thread's instruction pointer and, where necessary, rewriting it.

It fixes IP relative displacements of relocated instructions, such as a lea rax, [rip + 0x1234] style addressing, and it fixes the relative offsets of relocated instructions such as a jump to an absolute address. When a function prologue is moved into a trampoline, any instruction that computes an address relative to itself has to be adjusted, and getting the adjustment wrong produces code that reads the wrong memory.

It widens short branches into near branches, and it handles short branches that land within the trampoline. A two byte short jump has a range of about 128 bytes, and once a prologue has been relocated that range may no longer reach its target, so the jump has to be rewritten as a longer form.

Finally it uses a modern disassembler engine that supports the latest instructions, which is Zydis, and it has a carefully designed API that it claims is hard to misuse.

Installation paths and the Zydis dependency

There are three ways to add SafetyHook to a project, and the README recommends the amalgamated build for most people.

The amalgamated builds are on the releases page. You download the ZIP that contains Zydis, or one without Zydis if your project already has it, and copy the files into your project. The README notes you may need to define ZYDIS_STATIC_BUILD when using the build with Zydis included, because the amalgamated Zydis is built statically.

The CMake FetchContent route is:

CMake
include(FetchContent)

# Safetyhook
FetchContent_Declare(
    safetyhook
    GIT_REPOSITORY "https://github.com/cursey/safetyhook.git"
    GIT_TAG "origin/main"
)
FetchContent_MakeAvailable(safetyhook)

If you want SafetyHook to fetch Zydis for you, you enable the CMake option -DSAFETYHOOK_FETCH_ZYDIS=ON. The three routes are CMake FetchContent, git submodules, and copying the amalgamated files in directly.

The Zydis dependency is not incidental. A hooking library has to know where the instructions in a function begin and end, and where a branch lands, in order to decide what can be relocated and how. Zydis is a modern disassembler that handles current instruction sets, which matters more every year as new encodings appear.

The repository layout reflects a modern C++ project. There is an amalgamate.py script at the root, which is what produces those single-file builds, a cmake/ directory, a module/ directory for the CMake module packaging, include/ and src/ for the library, test/ and example/ directories, a docs/ directory, a vcpkg.json for the Visual Studio package manager, and a .clang-format file.

The example directory is worth a look for scope. It holds minimal.cpp, dll.cpp, midhook.cpp, module.cpp, multiple.cpp, threadsafe.cpp and vmthook.cpp, which together suggest inline hooks, hooks into DLLs, mid-function hooking, multiple simultaneous hooks, thread safety and hooking through the virtual method table.

What the API looks like in practice

The usage example in the README is small enough to read in full, and it shows the design decision about how a hook holds its state.

C++
#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 (This uses SafetyHook's easy API).
    g_add_hook = safetyhook::create_inline(reinterpret_cast<void*>(add), reinterpret_cast<void*>(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;
}

Three details carry the design.

The hook object is a global, and the hook function calls through it with g_add_hook.call<int>(x * 2, y * 2). The template argument gives the return type, and the call dispatches into the original function. A global hook object is the usual arrangement because the detour needs a stable address to point at, and having it in static storage means the address does not change between the moment the hook is created and the moment it is called.

Creation is a factory function, safetyhook::create_inline, taking the target address and the detour address, and it returns an object assigned to the hook. That means there is no separate init step and no separate enable flag.

Removal is assignment. g_add_hook = {} resets the object, and the next line shows the unhooked function behaving normally again. In a library where creating and deleting a hook both involve suspending threads and repairing instruction pointers, expressing removal as destruction of the handle is the API that makes it hard to leak a hook.

The noinline attribute on the target function is not decoration either. A hooked function must have a real prologue to relocate, and an inlined function has no address to hook.

The example directory and what the scope covers

The example directory is the clearest statement of what the library supports, since the file names map to the different ways a procedure can be hooked.

minimal.cpp is the small case, a single inline hook on a plain function, and it is the example to copy when you want to see the API shape without the surrounding machinery.

dll.cpp hooks a procedure inside a loaded module, which is the common real case, since the interesting target is usually an export in some library rather than a function in your own binary.

midhook.cpp covers hooking in the middle of a function rather than at its entry, which is a harder problem. Redirecting at the entry means relocating the prologue, but redirecting mid-function means relocating an arbitrary instruction sequence from the middle of a function, and the instruction boundaries there are not always obvious from the source.

module.cpp, like dll.cpp, deals with hooks in loaded modules rather than functions in your own binary.

multiple.cpp covers several hooks at once, which is where the thread suspension machinery has to be right more than once and where interactions between trampolines can appear.

threadsafe.cpp covers hooking from several threads, and that is the case the whole design exists for. Two threads calling create_inline at the same time, or one creating a hook while another is inside a hooked function, is where the suspend-all-threads approach either works or does not.

vmthook.cpp covers hooking through the virtual method table, which is the interface-level case rather than the procedure-level one. It is the most fragile of the approaches, since it depends on the layout of the interface, and it is included because sometimes it is the only option that survives a hot patch.

That range, from the minimal inline hook to a virtual method table hook, is why the library exposes more than one API. The easy API, safetyhook::create_inline, covers the common case, and the rest are available for the cases where it does not fit.

Licence, versioning and what SafetyHook is not

The licence is BSL-1.0, the Boost Software Licence. It is a fully permissive licence: you can use, modify and redistribute the library, including in closed source products, as long as you keep the copyright notice.

The release history is active. v0.6.9 came in July 2025, v0.6.10 in June 2026 and v0.7.0 on 2026-06-15, with the last push on 2026-06-20. A jump from 0.6 to 0.7 in a project like this signals a compatibility break, so pinning matters if you have an existing integration.

It is Windows only, and the README says so without hedging. There is no Linux or macOS support, which distinguishes it from the cross-platform hooking libraries that exist. For a Linux developer, that means the alternatives are the Detours-style libraries, the kernel level approaches, or the platform's own dynamic linking facilities.

It operates on procedures, not on interfaces or API boundaries. That is a meaningful distinction. A procedure hook redirects a call to a specific function, which is the lowest level of the three. An interface hook redirects calls through a virtual function table, which survives some kinds of patching but requires knowing the interface layout. An API hook intercepts a boundary in a library, which is what tools that trace or sandbox at the API level do. The example directory has files for each of those approaches, so the library supports more than inline hooking, but the naming and the emphasis are on the procedure level.

The documentation lives at a Notion page, the SafetyHook Guide, rather than a documentation site in the repository, though there is a docs/ directory in the tree. The project also carries a githooks/ directory and a.pre-commit style configuration, so it expects contributors to run its own tooling.

Editorial conclusion

Adopt SafetyHook if you need runtime procedure hooking on Windows and want a library that handles thread suspension and instruction relocation for you rather than one you assemble from inline assembly patches. Skip it if your target is Linux or macOS, since it is Windows only, or if you need an interface-level interception layer, since this operates on procedures rather than on API boundaries. Verify first by building the minimal example against your own toolchain, then check the thread safety example, because suspending threads during hook creation is the behaviour most likely to surprise an application.

Frequently asked questions

What does SafetyHook do that other hooking libraries do not?

It stops all other threads while creating or deleting a hook, fixes the instruction pointer of threads affected by the change, relocates instructions with IP relative displacements and relative offsets correctly, widens short branches into near branches, and handles short branches landing inside the trampoline.

Which platforms does SafetyHook support?

Windows x86 and x86_64 only, and the project requires a C++23 compiler. The README states the Windows restriction without hedging, and there is no Linux or macOS support.

How do I install SafetyHook?

You can copy the amalgamated build from the releases page, with or without the bundled Zydis, use CMake FetchContent with a GIT_REPOSITORY entry pointing at the repository, or use git submodules. If you want it to fetch Zydis for you, enable the CMake option -DSAFETYHOOK_FETCH_ZYDIS=ON.

How do you remove a SafetyHook hook?

You assign an empty value to the hook object, for example g_add_hook = {}, and the hooked function returns to its original behaviour. Removal goes through the same thread suspension and instruction repair machinery as creation.

What licence is SafetyHook released under?

The Boost Software Licence, BSL-1.0. It is a fully permissive licence: use, modification and redistribution, including in closed source products, are allowed as long as the copyright notice is kept.

Official sources

  1. cursey/safetyhook on GitHub
  2. License: BSL-1.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cursey-safetyhook.svg)](https://hysenlabs.com/projects/cursey-safetyhook)

Was this useful?