| Crates.io | winhook |
| lib.rs | winhook |
| version | 0.1.2 |
| created_at | 2025-11-15 10:40:11.77152+00 |
| updated_at | 2025-12-30 11:37:32.266956+00 |
| description | x86_64 function hooking library for Windows and Wine |
| homepage | |
| repository | https://github.com/Dasaav-dsv/WinHook |
| max_upload_size | |
| id | 1934218 |
| size | 153,552 |
A next generation function hooking library for x86_64 Windows and Wine.
InstallerResultExt::retry_uncheckedWinHook 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).
iced-x86This 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.
Licensed under either of
at your option.
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.