⚠️ Malware / Trojaner / VirenMalicious npm package found in Antfarm Tech proof_of_dev coding assignment(17.09.2026 um 11:09 Uhr)
🪟 Windows TippsAxeos erhält ISO 9001:2015-Zertifizierung(17.09.2026 um 10:00 Uhr)
🤖 Android TippsAxeos erhält ISO 9001:2015-Zertifizierung(17.09.2026 um 10:00 Uhr)
🔧 ProgrammierungQ&D: Flutter App and Android-SDK(17.09.2026 um 10:00 Uhr)
🔧 ProgrammierungThe Code Worked. Then I Started Asking What Happens Next.(17.09.2026 um 10:00 Uhr)
🔧 ProgrammierungRegular Expressions Without the Fear(17.09.2026 um 10:00 Uhr)
⚠️ Malware / Trojaner / VirenMalicious npm package found in Antfarm Tech proof_of_dev coding assignment(17.09.2026 um 11:09 Uhr)
🪟 Windows TippsAxeos erhält ISO 9001:2015-Zertifizierung(17.09.2026 um 10:00 Uhr)
🤖 Android TippsAxeos erhält ISO 9001:2015-Zertifizierung(17.09.2026 um 10:00 Uhr)
🔧 ProgrammierungQ&D: Flutter App and Android-SDK(17.09.2026 um 10:00 Uhr)
🔧 ProgrammierungThe Code Worked. Then I Started Asking What Happens Next.(17.09.2026 um 10:00 Uhr)
🔧 ProgrammierungRegular Expressions Without the Fear(17.09.2026 um 10:00 Uhr)
🔧 Programmierung 🕛 vor 2 Monaten 12 Min Lesezeit
0

I replaced hooking libraries with one rust crate

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

TL;DR: I've shipped hooks with different libs before. I even

wrote own hooks in assembly, expected the usual C++ headache, and instead found : this is what that article looks like when you don't have to hand-write the whole unsafe engine room.






First contact: hooking in one line



The first thing I tried was the smallest possible thing redirect a plain Rust

function at runtime:




CODE
use neohook::detour_inline;

#[inline(never)]
fn target(x: i32) -> i32 { x * 2 }
fn detour(x: i32) -> i32 { x + 100 }

fn main() {
let _hook = detour_inline!(target, detour).expect("hook failed");
assert_eq!(target(5), 105); // intercepted!
}






target(5) stopped returning 10 and started returning 105. I didn't touch,

recompile, or relink anything the function was replaced in the running

process
. Okay, neat. But a real hook usually needs to wrap the original, not

throw it away: log it, check the arguments, tweak the result. For that you need a

trampoline a way to still reach the real original after you've patched over

it:




CODE
use std::sync::OnceLock;
use neohook::detour_helper;

type AddFn = fn(i32, i32) -> i32;
static ORG_ADD: OnceLock<AddFn> = OnceLock::new();

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

fn detour_add(a: i32, b: i32) -> i32 {
let original = ORG_ADD.get().unwrap(); // the real add, via the trampoline
original(a, b) * 10
}

fn main() {
let _hook = detour_helper!(ORG_ADD, add, detour_add, AddFn)
.expect("hook failed");
assert_eq!(add(2, 3), 50); // (2+3) * 10
}






detour_helper! installs the hook and stashes a typed pointer to the

trampoline in ORG_ADD. Keep the word trampoline in mind it shows up

literally once we look at the disassembly.





The real test: hooking a Win32 API



Toy functions are easy. I wanted to see it work on something real, so I went for

MessageBoxW from user32.dll. The goal: make every dialog in the process

run through my code first log the title and text, and rewrite the title.




CODE
use std::ffi::c_void;
use std::sync::OnceLock;
use neohook::{detour_helper, find_function};

// int MessageBoxW(HWND, LPCWSTR text, LPCWSTR caption, UINT type);
type MessageBoxWFn =
unsafe extern "system" fn(*mut c_void, *const u16, *const u16, u32) -> i32;

static ORIGINAL_MBW: OnceLock<MessageBoxWFn> = OnceLock::new();

unsafe extern "system" fn detour_mbw(
hwnd: *mut c_void,
text: *const u16,
caption: *const u16,
utype: u32,
) -> i32 {
println!("[hook] MessageBoxW intercepted");
// Rewrite the caption, forward the text unchanged ...
let new_caption: Vec<u16> = "[hooked]".encode_utf16().chain([0]).collect();
let original = ORIGINAL_MBW.get().unwrap();
original(hwnd, text, new_caption.as_ptr(), utype)
}

fn main() {
let target = find_function("user32.dll", "MessageBoxW").unwrap();
let _hook = detour_helper!(ORIGINAL_MBW, target, detour_mbw, MessageBoxWFn)
.expect("hook failed");
// every MessageBoxW call in the process now lands at me first
}






What I liked: find_function resolves the address from the loaded DLL at runtime

no hardcoded offset, no GetProcAddress dance. It just worked. The full,

runnable version is in the repo at

examples/win32_messagebox.rs.



At this point I got suspicious. "Hook any function in one line" is the kind of

claim that's usually hiding something. So I opened a disassembler.





What it actually does, in assembly



Say MessageBoxW starts like this (x64, representative the exact bytes depend

on the Windows version):




CODE
; --- BEFORE: original prologue at address 0x7FFA1234ABC0 ---
MessageBoxW:
48 89 5C 24 08 mov [rsp+8], rbx ; 5 bytes
57 push rdi ; 1 byte
48 83 EC 20 sub rsp, 20h ; 4 bytes
...






A relative jump E9 rel32 needs 5 bytes, so neohook overwrites the start

with a jump to my detour:




CODE
; --- AFTER: patched ---
MessageBoxW:
E9 ?? ?? ?? ?? jmp detour_mbw ; 5-byte rel32 jump
90 (remainder of the displaced instruction, now dead)
48 83 EC 20 sub rsp, 20h ; never runs directly anymore
...






So MessageBoxW is redirected. But this is exactly the part that worried me: the

first instructions are now destroyed. How does my detour still call the

original?





The trampoline



It turns out neohook copies the displaced instructions ("stolen bytes") into a

freshly allocated, executable block and appends a jump back to just past the

patch. That block is the trampoline:




CODE
; --- TRAMPOLINE (allocated memory) ---
trampoline:
48 89 5C 24 08 mov [rsp+8], rbx ; relocated stolen bytes
57 push rdi
48 83 EC 20 sub rsp, 20h
E9 ?? ?? ?? ?? jmp MessageBoxW+10 ; back, right past the patch






ORIGINAL_MBW points at trampoline. When my detour calls original(...), the

CPU runs the real first instructions and then jumps into the rest of the

untouched function. The original never knows it was hooked.




CODE
  caller ──> MessageBoxW ──jmp──> detour_mbw ──> (my code)

└─ original() ──> trampoline ──jmp──> MessageBoxW+10 ──> rest






That's the clean version of the story. The reason I'd been burned by this before

is that the clean version is a lie there are at least four ways it goes wrong.

So I went looking for whether neohook actually handles them.





The footguns I expected and what neohook does about them





1. Instruction relocation (the one that gets everybody)



You can't just memcpy machine code to a new address. Lots of x64 instructions

are RIP-relative they address relative to the instruction's own location:




CODE
48 8B 0D 39 2C 04 00   mov rcx, [rip + 0x42C39]   ; loads from "here + 0x42C39"






Move that into the trampoline and rip is different now, so 0x42C39 points at

garbage. Same story for relative call/jmp. This is the classic homemade-hook

crash, and it's miserable to debug.



neohook disassembles the stolen bytes and re-encodes them for

their new address displacements and relative targets get recomputed. I checked

the trampoline's bytes against the original in a disassembler and the relative

operands had indeed been fixed up. This was the moment I started trusting it.





2. A thread running into the patch mid-write



While you're overwriting bytes, another thread could be executing inside those

very bytes, or have a return address pointing into them. You end up with half-old,

half-new instructions and a random, unreproducible crash.



neohook suspends the other threads during the patch, checks each one's

instruction pointer, nudges it somewhere safe if needed, and even rewrites return

addresses on the stack before resuming them. This is the kind of thing you almost

never get right by hand.





3. Atomicity all or nothing



A hook is several steps: flip memory protection, back up bytes, build the

trampoline, write the patch, restore protection. If step 4 fails, you're left

with a half-patched, corrupt function.



neohook is transactional: multiple hooks commit together, and if anything

fails it does an atomic rollback to the original state. No half-applied mess. One can see it was influenced by other great libraries like Microsoft Detours which also rely on a Transaction API.





4. Cleanup RAII instead of a leak



Forget to remove a hook and the patch (plus its trampoline) lingers after your

tool is done. In neohook the hook is an RAII value: drop it and the original

bytes are restored, the stubs freed. That's why every example binds it to

let _hook = ... the hook lives exactly as long as that value does.




The pattern I noticed: every place I'd previously written pages of unsafe and

edge-case handling in C++, neohook had already absorbed into the library.






The feature that sold me: mid-function hooks



An inline hook swaps out a whole function. But sometimes you want to land in

the middle of one at the exact spot where a value is computed but not yet

used and just nudge a register. neohook captures the full register state at the

target into a HookContext you can read and write:




CODE
use neohook::{HookContext, MidHook};

// rcx = 1st argument (Win64 ABI) = damage value
extern "system" fn apply_damage(amount: u64) { /* ... HP -= amount ... */ }

unsafe extern "system" fn gmode(ctx: *mut HookContext) {
let ctx = &mut *ctx;
ctx.rcx = 0; // zero the damage before the math runs
// ctx.redirect_rip stays 0 -> the function continues with the modified rcx
}

fn main() {
let target = apply_damage as *const u8;
let _hook = unsafe { MidHook::install(target, gmode) }.unwrap();
apply_damage(9999); // the damage arrives as 0
}






HookContext exposes rax, rcx, rdx, r8r15, rflags, mxcsr, and even

xmm[0..16]. Set redirect_rip and you can divert control flow and skip whole

regions of code.



Under the hood it builds a context-bridge stub that saves the registers,

calls your handler, and restores them 1:1 afterward so the function only sees

the values you deliberately changed. I'd written something like this by hand once

and it took an afternoon and three crashes. Here it was a single install call.





The part that surprised me: it isn't Rust-only



Here's the thing that actually made me retire my old tools. My world isn't pure

Rust. I wasn't even a fan of Rust since I always did it with C++, and a bunch of tooling glued together in Python. With

MinHook I was already in C/C++; switching to a Rust crate sounded like it would

lose me those languages. It does the opposite.



neohook ships a C ABI generated with cbindgen. You build the header once:




CODE
cargo build --features generate-headers   # writes the header into include/






…and now the whole engine inline hooks, mid-hooks, VEH/INT3 hooks, pattern

scanning is callable from C, C++, or anything that speaks the C

ABI.





From C / C++



The same mid-hook from earlier, but in C:




CODE
#include "neohook.h"   // generated by `cargo build --features generate-headers`

// Handler receives a HookContext* same layout as the Rust struct.
void gmode(HookContext* ctx) {
ctx->rcx = 0; // zero the damage argument
}

int main(void) {
void* target = /* address of the function to hook */;
MidHook* h = detours_midhook_install(target, gmode);
// ... run the program ...
detours_midhook_unhook(h);
return 0;
}






No Rust in sight at the call site. You link against the built library, include

the generated header, and you get the exact same transactional, thread-safe

engine I disassembled above.





From Python (ctypes)



This is the one that sold me. My instrumentation harness is Python, and I could

drive the hooks straight from it no extension module, no maturin, just

ctypes against the built DLL:




CODE
import ctypes

neo = ctypes.CDLL("./neohook.dll")

# Mirror the leading fields of HookContext (full layout is in the generated
# header); the first three u64s are rflags, rax, rcx enough to reach rcx.
class HookContext(ctypes.Structure):
_fields_ = [
("rflags", ctypes.c_uint64),
("rax", ctypes.c_uint64),
("rcx", ctypes.c_uint64),
# ... rax/rdx/rbx/.../r15, mxcsr, xmm[16], redirect_rip ...
]

HANDLER = ctypes.CFUNCTYPE(None, ctypes.POINTER(HookContext))

@HANDLER
def gmode(ctx):
ctx.contents.rcx = 0 # same trick, in Python

neo.detours_midhook_install.restype = ctypes.c_void_p
hook = neo.detours_midhook_install(ctypes.c_void_p(target), gmode)
# ... run ...
neo.detours_midhook_unhook(ctypes.c_void_p(hook))






One hooking engine, three languages, identical guarantees. That's the moment I

stopped maintaining a separate C++ hooking layer and a separate "something for

Python" and just standardized on this. (Keep the HANDLER object alive for as

long as the hook is installed ctypes will otherwise garbage-collect your

callback out from under the CPU.)





How it stacks up against the C++ classics



To be fair, Detours, MinHook and PolyHook2 are battle-tested and great. The

difference is that they're C/C++ with manual memory management, and the feature

coverage varies by which one you pick. What made neohook stick for me is that

it folds all of this into one Rust crate



Two more things I bumped into while exploring and thought were genuinely nice a

capturing closure as a detour …




CODE
let calls = AtomicU32::new(0);
let _h = detour_closure!(add, "system" fn(a: i32, b: i32) -> i32,
move |orig, a, b| { calls.fetch_add(1, Ordering::Relaxed); orig(a, b) * 10 })?;






… and an automatic tracing hook that logs the arguments and return value with

no detour body at all:




CODE
let _h = detour_trace!(add, "system" fn(a: i32, b: i32) -> i32)?;
// add(2, 3) -> 5 logged automatically, the real result stays 5









Am I going back to the old tools? No.



I came in skeptical of the "one line" marketing and left having verified, in a

disassembler, that the hard parts relocation, thread safety, atomic patching,

cleanup are actually handled. Then I found I could call the same engine from

C, C++ and Python, which collapsed three separate hooking setups in my stack

into one. neohook is where I've standardized.

Vollständiger Original-Artikel
Den kompletten Beitrag mit allen Details direkt auf dev.to lesen.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
2 Quellen
Axeos erhält ISO 9001:2015-Zertifizierung
1 Quelle
Amazon bietet Soundcore-In-Ears zum ersten Mal günstiger an: Mit ANC, Dolby Atmos & mehr Highlights
1 Quelle
Höllenmaschine HMX 6 im Halo-Design – passend zum Spiele-Release!
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten I replaced hooking libraries with one rust crate

Thematisch verwandte Begriffe: replaced, hooking, libraries, with · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...