Thanks to visit codestin.com
Credit goes to lib.rs

#x86-64 #hook #wine #windows #thread-safe

winhook

x86_64 function hooking library for Windows and Wine

3 releases

Uses new Rust 2024

0.1.2 Dec 30, 2025
0.1.1 Nov 15, 2025
0.1.0 Nov 15, 2025

#248 in Concurrency

MIT/Apache

130KB
2.5K SLoC

WinHook

A next generation function hooking library for x86_64 Windows and Wine.

Pros

  • First of its kind, fully atomic and threadsafe hook installation without IP relocation
  • Constant time hooking without ever "stopping the world" (suspending all threads in a process)
  • Support for stateful closures as hooks via closure-ffi
  • No instruction boundary splitting (which can otherwise cause crashes when a branch target is clobbered with a jump instruction)

Cons

  • Windows and Wine on x86_64 only (for now)
  • Function addresses not aligned to 16 bytes are more likely to fail to be hooked, see InstallerResultExt::retry_unchecked
  • Hooked functions must be a part of a module like an executable or a DLL
  • Limited to the Microsoft x64 calling convention ABI

Examples

WinHook can be used with "C", "system" and "win64" ABI functions when targeting x86_64-pc-windows. The "Rust" calling convention is inherently unstable and unsupported.

Aside from Fn closures with internal state, WinHook provides a method for installing FnMut hooks, i.e. using closures with mutable internal state. Under the hood, they are wrapped in a Mutex (note that recursive calls are not allowed, as they would mutably borrow the state a second time). FnOnce hooks are likewise supported.

Let's take a simple "add two numbers" function and instrument it to notify a callback whenever a new highest sum is returned.

#[inline(never)]
extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}

let new_highest = |num: i32| println!("new highest sum: {}", num);

use winhook::HookInstaller;

// Note how we added "unsafe" to the function signature.
// Intercepting a function call must preserve all invariants of the original
// and it is not possible to broadly claim for it to be memory safe.
let hook_handle = HookInstaller::<unsafe extern "C" fn(_, _) -> _>::for_function(add)
    .install_mut({
        let mut max = i32::MIN;
        move |original| move |a, b| {
            // SAFETY: it's definitely safe to call the original function once
            // with the original arguments.
            let sum = unsafe { original(a, b) };
            
            if sum > max {
                // Notify our callback and update the maximum value.
                new_highest(sum);
                max = sum;
            }

            // Return the original result.
            sum
        }
    })
    .unwrap();

// NOTE: always assign the returned `HookHandle` to a variable.
// The hook will be uninstalled when the handle goes out of scope.

// Installing a hook *is* safe, enabling it *is NOT*.
// You guarantee all invariants of the original function are preserved.
unsafe {
    hook_handle.enable(true);
}

// Should print 4, 8, 15, 16, 23, 42.
let sequence = [
    add(3, 1),
    add(1, 2),
    add(6, 2),
    add(6, 9),
    add(10, 2),
    add(14, 2),
    add(3, 20),
    add(34, 8),
];

// The original output should not be affected.
assert_eq!(sequence, [4, 3, 8, 15, 12, 16, 23, 42]);

Broadly speaking, the return value of the outer closure passed to HookInstaller::install_* methods is the function to be invoked in place of the hooked one. If you plan to completely detour the original function, it does not have to be a closure:

#[inline(never)]
#[allow(improper_ctypes_definitions)]
extern "system" fn hello(name: String) {
    println!("Hello, {name}");
}

// The hook is using the "Rust" calling convention as it must coerce to a `Fn`.
fn goodbye(name: String) {
    println!("Goodbye, {name}");
}

use winhook::HookInstaller;

// `enable` can be used when installing the hook directly,
// which is more efficient (and still *unsafe*, see above example).
let _hook_handle = unsafe {
    HookInstaller::<unsafe extern "system" fn(String)>::for_function(hello)
        .enable(true)
        .install(|_original| goodbye)
        .unwrap()
};

// Should print "Goodbye, Mario".
hello("Mario".to_owned());

In cases where you do not wish to store a hook handle directly, you can leak it with std::mem::forget or use the associated HookHandle::into_raw method. Do not store a handle past its module's lifetime (after the module is unloaded).

Note about iced-x86

This crate currently depends on closure-ffi-iced-x86 due to an upstream dependency on it to prevent duplicating the iced-x86 dependency in its own dependency tree.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~28MB
~562K SLoC