Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 23 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Function hooking is deceptively difficult to get right. Writing a `JMP` patch is

- **Named Hook Registry** - Park hooks in a process-wide store and refer to them by name: `registry::register`, `enable` / `disable`, `unhook`, and `unhook_all` for a single teardown point (e.g. `DLL_PROCESS_DETACH`).

- **Mid-Function / Arbitrary-Address Detours** - Hook *any* instruction boundary, not just a function entry. NeoHook snapshots all general-purpose registers and flags into a `HookContext`, calls your handler with a pointer to it, restores the (possibly modified) registers, then resumes the original instructions. Rewrite arguments, results, or loop state in flight at a spot found by a signature scan - all on the thread-safe inline engine (thread suspension, IP/stack redirection, relocation, atomic rollback).
- **Mid-Function / Arbitrary-Address Detours** - Hook *any* instruction boundary, not just a function entry. NeoHook snapshots all general-purpose registers, flags, the XMM registers and `MXCSR` into a `HookContext`, calls your handler with a pointer to it, restores the (possibly modified) state, then resumes the original instructions. Rewrite integer or floating-point/SIMD arguments, results, or loop state in flight at a spot found by a signature scan - all on the thread-safe inline engine (thread suspension, IP/stack redirection, relocation, atomic rollback).

- **VTable Hooking** - Rewrites a selected VTable slot to detour virtual calls and restores the original slot on unhook.

Expand Down Expand Up @@ -124,8 +124,8 @@ Function hooking is deceptively difficult to get right. Writing a `JMP` patch is
| v0.8.0 | ✅ Done | Closure detours (`detour_closure!`) |
| v0.8.0 | ✅ Done | Delay / on-load hooks (`DelayHook`) |
| v0.8.0 | ✅ Done | Named hook registry (`registry`) |
| v0.9.0 | Planned | XMM / FPU context capture in `MidHook` |
| v0.9.0 | Planned | Control-flow redirect from a `MidHook` handler |
| v0.9.0 | ✅ Done | XMM / MXCSR context capture in `MidHook` |
| v0.9.0 | ✅ Done | Control-flow redirect from a `MidHook` handler |
| v0.9.0 | Planned | ARM64 inline hooking |

--
Expand Down Expand Up @@ -370,11 +370,12 @@ typically located with a [signature scan](#pattern--signature-scanning).

Because such a site is reached with arbitrary registers live, a normal detour
would clobber them. Instead NeoHook installs a context bridge: it snapshots all
general-purpose registers and flags into a [`HookContext`], calls your handler
with a pointer to it, restores the (possibly modified) registers, then runs the
original instructions and resumes the function. The patch runs on the full
inline engine - threads suspended, instruction pointers/return addresses
redirected, stolen bytes relocated, atomic rollback on failure.
general-purpose registers, flags, every XMM register and `MXCSR` into a
[`HookContext`], calls your handler with a pointer to it, restores the
(possibly modified) state, then runs the original instructions and resumes the
function. The patch runs on the full inline engine - threads suspended,
instruction pointers/return addresses redirected, stolen bytes relocated,
atomic rollback on failure.

```rust
use neohook::{HookContext, MidHook};
Expand Down Expand Up @@ -402,10 +403,20 @@ fn main() {
```

A handler may read any field of `HookContext` to observe a live register, or
write one to change it before execution continues. The detour always *continues*
the original function - it cannot skip it or redirect control flow, and only
general-purpose registers and flags are captured (not XMM/FPU state). `target`
must sit on a real instruction boundary.
write one to change it before execution continues - including the floating-point
/ SIMD argument registers via `ctx.xmm[..]` (e.g.
`f64::from_bits(ctx.xmm[0].low)` for a scalar `double`). General-purpose
registers, flags, all XMM registers and `MXCSR` are captured; the legacy x87
stack registers are not. `target` must sit on a real instruction boundary.

By default the detour *continues* the original function. A handler can instead
**redirect control flow** by setting `ctx.redirect_rip` (`redirect_eip` on x86)
to a code address: the stub restores the (possibly modified) state and jumps
there, skipping the stolen instructions. Use it to replace a routine wholesale
(redirect a hooked entry to a same-ABI drop-in that returns to the caller) or to
skip the patched region via `hook.resume_address()` (`= target + stolen_len`).
The redirect is an indirect `jmp`, not a `ret`, so it leaves the CET shadow
stack intact.

The C ABI exposes `detours_midhook_install(target, handler)` and
`detours_midhook_unhook(hook)`; the C++ wrapper provides an RAII `neohook::MidHook`.
Expand Down
19 changes: 12 additions & 7 deletions examples/attach_export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ unsafe extern "system" fn hooked_get_tick_count() -> u32 {
}

fn main() -> Result<(), Box<dyn Error>> {
println!("before hook: GetTickCount() = {}", unsafe { GetTickCount() });
println!("before hook: GetTickCount() = {}", unsafe {
GetTickCount()
});

let mut tx = DetourTransaction::begin();
tx.update_all_threads();
Expand All @@ -39,15 +41,18 @@ fn main() -> Result<(), Box<dyn Error>> {
let _hooks = tx.commit()?;
let _ = ORIG.set(unsafe { std::mem::transmute::<*mut u8, GetTickCountFn>(tramp) });

println!("after hook: GetTickCount() = {}", unsafe { GetTickCount() });
println!(
"original via trampoline: = {}",
unsafe { (ORIG.get().unwrap())() }
);
println!("after hook: GetTickCount() = {}", unsafe {
GetTickCount()
});
println!("original via trampoline: = {}", unsafe {
(ORIG.get().unwrap())()
});

// _hooks drops here -> the original bytes are restored automatically.
drop(_hooks);
println!("after unhook:GetTickCount() = {}", unsafe { GetTickCount() });
println!("after unhook:GetTickCount() = {}", unsafe {
GetTickCount()
});

Ok(())
}
17 changes: 13 additions & 4 deletions examples/closure_detour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
*/
use neohook::detour_closure;
use std::error::Error;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};

#[inline(never)]
extern "system" fn add(a: i32, b: i32) -> i32 {
Expand Down Expand Up @@ -40,9 +40,18 @@ fn main() -> Result<(), Box<dyn Error>> {
},
)?;

println!("after hook: add(2, 3) = {} (intercepted, call #1)", call(2, 3));
println!("after hook: add(4, 5) = {} (intercepted, call #2)", call(4, 5));
println!("total intercepted calls: {}", counter.load(Ordering::Relaxed));
println!(
"after hook: add(2, 3) = {} (intercepted, call #1)",
call(2, 3)
);
println!(
"after hook: add(4, 5) = {} (intercepted, call #2)",
call(4, 5)
);
println!(
"total intercepted calls: {}",
counter.load(Ordering::Relaxed)
);

drop(hooks); // RAII restores the original bytes.
println!("after unhook: add(2, 3) = {}", call(2, 3));
Expand Down
32 changes: 18 additions & 14 deletions examples/d3d11_present.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ use std::sync::atomic::{AtomicPtr, Ordering};
use windows_sys::Win32::Foundation::HWND;
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress, LoadLibraryW};
use windows_sys::Win32::UI::WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, MSG, PM_REMOVE,
PeekMessageW, PostQuitMessage, RegisterClassW, SW_SHOW, ShowWindow, TranslateMessage,
WM_DESTROY, WM_QUIT, WNDCLASSW, WS_OVERLAPPEDWINDOW,
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, MSG, PM_REMOVE, PeekMessageW,
PostQuitMessage, RegisterClassW, SW_SHOW, ShowWindow, TranslateMessage, WM_DESTROY, WM_QUIT,
WNDCLASSW, WS_OVERLAPPEDWINDOW,
};
use windows_sys::core::GUID;

Expand Down Expand Up @@ -109,9 +109,11 @@ type D3d11CreateFn = unsafe extern "system" fn(
/// `HRESULT IDXGISwapChain::Present(this, SyncInterval, Flags)`.
type PresentFn = unsafe extern "system" fn(*mut c_void, u32, u32) -> i32;
/// `HRESULT GetBuffer(this, Buffer, riid, ppSurface)`.
type GetBufferFn = unsafe extern "system" fn(*mut c_void, u32, *const GUID, *mut *mut c_void) -> i32;
type GetBufferFn =
unsafe extern "system" fn(*mut c_void, u32, *const GUID, *mut *mut c_void) -> i32;
/// `HRESULT QueryInterface(this, riid, ppvObject)`.
type QueryInterfaceFn = unsafe extern "system" fn(*mut c_void, *const GUID, *mut *mut c_void) -> i32;
type QueryInterfaceFn =
unsafe extern "system" fn(*mut c_void, *const GUID, *mut *mut c_void) -> i32;
/// `HRESULT CreateRenderTargetView(this, pResource, pDesc, ppRTV)`.
type CreateRtvFn =
unsafe extern "system" fn(*mut c_void, *mut c_void, *const c_void, *mut *mut c_void) -> i32;
Expand Down Expand Up @@ -184,14 +186,16 @@ fn main() -> Result<(), Box<dyn Error>> {
println!("skipped: d3d11.dll not available");
return Ok(());
}
let create_ptr =
match GetProcAddress(d3d11, c"D3D11CreateDeviceAndSwapChain".as_ptr() as *const u8) {
Some(p) => p,
None => {
println!("skipped: D3D11CreateDeviceAndSwapChain not found");
return Ok(());
}
};
let create_ptr = match GetProcAddress(
d3d11,
c"D3D11CreateDeviceAndSwapChain".as_ptr() as *const u8,
) {
Some(p) => p,
None => {
println!("skipped: D3D11CreateDeviceAndSwapChain not found");
return Ok(());
}
};
let create: D3d11CreateFn = std::mem::transmute(create_ptr);

//Create the window
Expand Down Expand Up @@ -308,7 +312,7 @@ fn main() -> Result<(), Box<dyn Error>> {
RTV.store(rtv, Ordering::Relaxed);
CTX1.store(context1, Ordering::Relaxed);

// Hook Present by cloning the swapchain's vtable, replacing the Present slot, and writing a
// Hook Present by cloning the swapchain's vtable, replacing the Present slot, and writing a
// pointer to the clone back into the swapchain.
let mut tx = DetourTransaction::begin();
let original = tx.attach_vtable_instance(
Expand Down
25 changes: 19 additions & 6 deletions examples/d3d9_endscene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ use std::sync::OnceLock;
use windows_sys::Win32::Foundation::HWND;
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress, LoadLibraryW};
use windows_sys::Win32::UI::WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, MSG, PM_REMOVE,
PeekMessageW, PostQuitMessage, RegisterClassW, SW_SHOW, ShowWindow, TranslateMessage,
WM_DESTROY, WM_QUIT, WNDCLASSW, WS_OVERLAPPEDWINDOW,
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, MSG, PM_REMOVE, PeekMessageW,
PostQuitMessage, RegisterClassW, SW_SHOW, ShowWindow, TranslateMessage, WM_DESTROY, WM_QUIT,
WNDCLASSW, WS_OVERLAPPEDWINDOW,
};

// --- Minimal Direct3D 9 definitions (not provided by windows-sys) ---
Expand Down Expand Up @@ -90,8 +90,13 @@ type SceneFn = unsafe extern "system" fn(*mut c_void) -> i32;
type ClearFn =
unsafe extern "system" fn(*mut c_void, u32, *const D3dRect, u32, u32, f32, u32) -> i32;
/// `HRESULT Present(this, pSrcRect, pDstRect, hDestWindow, pDirtyRegion)`.
type PresentFn =
unsafe extern "system" fn(*mut c_void, *const c_void, *const c_void, HWND, *const c_void) -> i32;
type PresentFn = unsafe extern "system" fn(
*mut c_void,
*const c_void,
*const c_void,
HWND,
*const c_void,
) -> i32;
type ReleaseFn = unsafe extern "system" fn(*mut c_void) -> u32;

/// The original `EndScene` returned by `attach_vtable`, used to finalize frames.
Expand Down Expand Up @@ -265,7 +270,15 @@ fn main() -> Result<(), Box<dyn Error>> {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
clear(device, 0, std::ptr::null(), D3DCLEAR_TARGET, 0xFF00_0000, 1.0, 0);
clear(
device,
0,
std::ptr::null(),
D3DCLEAR_TARGET,
0xFF00_0000,
1.0,
0,
);
begin_scene(device);
// EndScene dispatches through the hooked vtable slot.
let end_scene: SceneFn = std::mem::transmute(*vtable.add(END_SCENE_SLOT));
Expand Down
11 changes: 6 additions & 5 deletions examples/delay_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,14 @@ fn main() -> Result<(), Box<dyn Error>> {
let proc = unsafe { GetProcAddress(module, c"timeGetTime".as_ptr() as *const u8) }
.ok_or("timeGetTime not found")?;
let time_get_time: unsafe extern "system" fn() -> u32 = unsafe { std::mem::transmute(proc) };
println!(
"timeGetTime() = {} (0xDEADBEE1 - intercepted)",
unsafe { time_get_time() }
);
println!("timeGetTime() = {} (0xDEADBEE1 - intercepted)", unsafe {
time_get_time()
});

hook.unhook()?;
println!("after unhook: timeGetTime() = {}", unsafe { time_get_time() });
println!("after unhook: timeGetTime() = {}", unsafe {
time_get_time()
});

Ok(())
}
3 changes: 2 additions & 1 deletion examples/eat_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ fn main() -> Result<(), Box<dyn Error>> {

// The hook records the original resolved address; calling it bypasses the
// detour and reaches the real function body.
let original = unsafe { std::mem::transmute::<*const u8, GetTickCountFn>(hooks[0].original_ptr()) };
let original =
unsafe { std::mem::transmute::<*const u8, GetTickCountFn>(hooks[0].original_ptr()) };
println!("original still returns ~{}", unsafe { original() });

for hook in hooks {
Expand Down
6 changes: 3 additions & 3 deletions examples/opengl_swapbuffers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ use windows_sys::Win32::Graphics::OpenGL::{
};
use windows_sys::Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress};
use windows_sys::Win32::UI::WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, MSG, PM_REMOVE,
PeekMessageW, PostQuitMessage, RegisterClassW, SW_SHOW, ShowWindow, TranslateMessage,
WM_DESTROY, WM_QUIT, WNDCLASSW, WS_OVERLAPPEDWINDOW,
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, MSG, PM_REMOVE, PeekMessageW,
PostQuitMessage, RegisterClassW, SW_SHOW, ShowWindow, TranslateMessage, WM_DESTROY, WM_QUIT,
WNDCLASSW, WS_OVERLAPPEDWINDOW,
};

const GL_COLOR_BUFFER_BIT: u32 = 0x0000_4000;
Expand Down
4 changes: 2 additions & 2 deletions examples/pattern_scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ fn main() {
}

// You can also build a signature from a byte array + mask string:
let _code_style = Pattern::from_code_style(b"\x48\x8B\x00\x00", "xx??")
.expect("valid code-style signature");
let _code_style =
Pattern::from_code_style(b"\x48\x8B\x00\x00", "xx??").expect("valid code-style signature");

// --- 2. Hook the function purely by signature ---------------------------
println!("\ncompute(10) before hook = {}", compute(10)); // 31
Expand Down
Loading
Loading