https://github.com/dasaav-dsv/winhook
A next generation function hooking library for x86_64 Windows and Wine.
https://github.com/dasaav-dsv/winhook
function-hooking hooking threadsafe windows
Last synced: 14 days ago
JSON representation
A next generation function hooking library for x86_64 Windows and Wine.
- Host: GitHub
- URL: https://github.com/dasaav-dsv/winhook
- Owner: Dasaav-dsv
- License: apache-2.0
- Created: 2025-09-28T17:24:38.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2025-11-15T10:46:36.000Z (2 months ago)
- Last Synced: 2025-12-13T15:23:24.317Z (about 1 month ago)
- Topics: function-hooking, hooking, threadsafe, windows
- Language: Rust
- Homepage: https://crates.io/crates/winhook
- Size: 261 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE-APACHE
Awesome Lists containing this project
README
# 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](https://crates.io/crates/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](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention)
## 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.
```rust
#[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:: _>::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:
```rust
#[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::::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`](https://crates.io/crates/closure-ffi-iced-x86) due to an [upstream dependency on it](https://github.com/icedland/iced/issues/762) to prevent duplicating the `iced-x86` dependency in its own dependency tree.
## License
Licensed under either of
* Apache License, Version 2.0
([LICENSE-APACHE](LICENSE-APACHE) or )
* MIT license
([LICENSE-MIT](LICENSE-MIT) or )
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.