imping_hook/hook.rs
1//! Typed function detours.
2
3use crate::error::{Error, Result};
4use crate::function::Function;
5use crate::registry;
6use crate::scan::{Pattern, Target};
7
8/// A typed function detour, and the RAII guard that owns it.
9///
10/// While enabled, calls to the target run a detour of the same signature `F`
11/// instead; the original stays callable through [`original`](Self::original).
12/// Build one with [`Hook::at`], or [`Hook::scan`] to find the target by a byte
13/// pattern.
14///
15/// # Threads
16///
17/// [`enable`](Self::enable), [`disable`](Self::disable) and drop suspend every
18/// other thread while patching and move any thread stopped inside the patched
19/// bytes, so hooks can be toggled in a running, multi-threaded process. Don't
20/// toggle or drop a hook from inside its own target on the same thread. The
21/// `unsafe` `*_unsuspended` methods skip the suspension.
22///
23/// # Batches
24///
25/// Inside [`batch`](crate::batch), `enable` and `disable` are queued and
26/// applied together when the batch ends. The `*_unsuspended` methods apply at
27/// once even there, and replace any change queued for the same hook.
28///
29/// # Dropping
30///
31/// Dropping a hook disables it and frees its generated code. If it can't be
32/// disabled, say because a third party rewrote the target, the code is leaked
33/// instead so the target never jumps into freed memory. [`leak`](Self::leak)
34/// keeps a hook installed on purpose.
35///
36/// # Examples
37///
38/// ```no_run
39/// use imping_hook::Hook;
40///
41/// #[inline(never)]
42/// extern "C" fn add(a: i32, b: i32) -> i32 { a + b }
43/// extern "C" fn sub(a: i32, b: i32) -> i32 { a - b }
44///
45/// # fn main() -> imping_hook::Result<()> {
46/// // SAFETY: `add` is a real function with the signature below, and `sub`
47/// // shares it.
48/// let hook = unsafe {
49/// Hook::at(add as *const ())
50/// .redirect(sub as extern "C" fn(i32, i32) -> i32)
51/// .install()?
52/// };
53///
54/// assert_eq!(add(3, 2), 5);
55/// hook.enable()?;
56/// assert_eq!(add(3, 2), 1);
57/// assert_eq!(hook.original().call(3, 2), 5);
58/// hook.disable()?;
59/// assert_eq!(add(3, 2), 5);
60/// # Ok(())
61/// # }
62/// ```
63pub struct Hook<F: Function = fn()> {
64 // Boxed so its address stays put while a `batch` has a change queued.
65 inner: Box<detour::TypedDetour<F>>,
66 target: usize,
67}
68
69// Non-generic so `Hook::at(..)` needs no turbofish; `redirect` fixes `F`.
70impl Hook {
71 /// Starts building a hook of the function at `target`.
72 ///
73 /// Nothing is patched until [`install`](HookWith::install).
74 pub fn at(target: *const ()) -> HookBuilder {
75 HookBuilder { target }
76 }
77
78 /// Starts building a hook of the function a byte pattern finds.
79 ///
80 /// The pattern is searched for in the main module's code when the hook is
81 /// installed, and must match exactly once; see [`scan`](crate::scan) for
82 /// the syntax.
83 ///
84 /// # Examples
85 ///
86 /// ```no_run
87 /// # use imping_hook::Hook;
88 /// # extern "C" fn my_detour(a: i32) -> i32 { a }
89 /// # fn main() -> imping_hook::Result<()> {
90 /// // SAFETY: the pattern finds a function with the detour's signature.
91 /// let hook = unsafe {
92 /// Hook::scan("48 89 5C 24 ?? 57 48 83 EC 20")
93 /// .in_module("engine.dll")
94 /// .redirect(my_detour as extern "C" fn(i32) -> i32)
95 /// .install()?
96 /// };
97 /// # Ok(())
98 /// # }
99 /// ```
100 pub fn scan(pattern: &str) -> HookScan {
101 HookScan {
102 pattern: Pattern::new(pattern),
103 }
104 }
105}
106
107// Send + Sync come for free: TypedDetour is both and the rest is an address.
108impl<F: Function> Hook<F> {
109 /// Enables the hook, redirecting the target to the detour.
110 ///
111 /// Does nothing if already enabled. Suspends other threads while patching;
112 /// see [`Hook`] for threads and batches.
113 ///
114 /// # Errors
115 ///
116 /// Returns an error naming the target if patching fails, such as
117 /// [`Error::TargetModified`] when a third party changed its code.
118 pub fn enable(&self) -> Result<()> {
119 self.toggle(true)
120 }
121
122 /// Disables the hook, restoring the original function.
123 ///
124 /// Does nothing if already disabled. Suspends other threads while patching;
125 /// see [`Hook`] for threads and batches.
126 ///
127 /// # Errors
128 ///
129 /// As for [`enable`](Self::enable).
130 pub fn disable(&self) -> Result<()> {
131 self.toggle(false)
132 }
133
134 /// Enables the hook without suspending other threads.
135 ///
136 /// Faster than [`enable`](Self::enable), and applied at once even inside a
137 /// batch; see [`Hook`].
138 ///
139 /// # Errors
140 ///
141 /// As for [`enable`](Self::enable).
142 ///
143 /// # Safety
144 ///
145 /// No other thread may run the target's first bytes while they are
146 /// written, or it may execute a half-written instruction.
147 pub unsafe fn enable_unsuspended(&self) -> Result<()> {
148 crate::batch::forget(self.target);
149 // SAFETY: forwarded from the caller.
150 unsafe { self.toggle_now(true, detour::Threads::None) }
151 }
152
153 /// Disables the hook without suspending other threads.
154 ///
155 /// Applied at once like [`enable_unsuspended`](Self::enable_unsuspended).
156 ///
157 /// # Errors
158 ///
159 /// As for [`enable`](Self::enable).
160 ///
161 /// # Safety
162 ///
163 /// No other thread may run the target's first bytes while the original
164 /// ones are written back.
165 pub unsafe fn disable_unsuspended(&self) -> Result<()> {
166 crate::batch::forget(self.target);
167 // SAFETY: forwarded from the caller.
168 unsafe { self.toggle_now(false, detour::Threads::None) }
169 }
170
171 /// Returns whether the hook is enabled.
172 pub fn is_enabled(&self) -> bool {
173 self.inner.is_enabled()
174 }
175
176 /// Returns the original, unhooked function, borrowed from the hook.
177 ///
178 /// Callable whether or not the hook is enabled; see [`Original`](crate::Original).
179 pub fn original(&self) -> F::Original<'_> {
180 self.inner.original()
181 }
182
183 /// Returns the original function as a pointer not tied to the hook's
184 /// lifetime.
185 ///
186 /// Prefer [`original`](Self::original) unless the pointer has to outlive a
187 /// borrow.
188 ///
189 /// # Safety
190 ///
191 /// Don't call the returned function after the hook is dropped. Use
192 /// [`leak`](Self::leak) if it must stay callable.
193 pub unsafe fn trampoline(&self) -> F {
194 // SAFETY: forwarded from the caller.
195 unsafe { self.inner.trampoline() }
196 }
197
198 /// Keeps the hook installed for the rest of the process and returns the
199 /// original function.
200 ///
201 /// The hook stays in its current state, so enable it first if it should
202 /// stay active. The target address stays reserved.
203 pub fn leak(self) -> F {
204 // SAFETY: `self` is forgotten right away, so the trampoline is never
205 // freed and stays valid for `'static`.
206 let original = unsafe { self.inner.trampoline() };
207 core::mem::forget(self);
208 original
209 }
210
211 /// Applies the change with every other thread suspended, or queues it
212 /// inside a batch.
213 fn toggle(&self, enable: bool) -> Result<()> {
214 if crate::batch::queue(&*self.inner, self.target, enable) {
215 return Ok(());
216 }
217 // SAFETY: `Threads::All` suspends every other thread and moves any that
218 // stopped inside the patch; the caller isn't running the target's prolog.
219 unsafe { self.toggle_now(enable, detour::Threads::All) }
220 }
221
222 /// Applies the change now, in its own transaction.
223 ///
224 /// # Safety
225 ///
226 /// Threads not suspended by `threads` must not run the patched bytes while
227 /// they are written.
228 unsafe fn toggle_now(&self, enable: bool, threads: detour::Threads<'_>) -> Result<()> {
229 let mut transaction = detour::Transaction::new();
230 if enable {
231 transaction.enable(&*self.inner);
232 } else {
233 transaction.disable(&*self.inner);
234 }
235 // SAFETY: forwarded from the caller.
236 unsafe { transaction.commit(threads) }
237 .map_err(|error| Error::from_detour(error, self.target))
238 }
239}
240
241impl<F: Function> Drop for Hook<F> {
242 fn drop(&mut self) {
243 // Release the address only once the target is restored. If it can't
244 // be, it's still patched (detour leaks its code) and stays reserved.
245 crate::batch::forget(self.target);
246 // SAFETY: as in `toggle`; the caller isn't running the target's prolog.
247 if unsafe { self.toggle_now(false, detour::Threads::All) }.is_ok() {
248 registry::release(self.target);
249 }
250 }
251}
252
253impl<F: Function> core::fmt::Debug for Hook<F> {
254 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
255 f.debug_struct("Hook")
256 .field("target", &(self.target as *const ()))
257 .field("enabled", &self.is_enabled())
258 .finish()
259 }
260}
261
262/// A hook builder holding the target address, returned by [`Hook::at`].
263#[must_use = "a hook builder does nothing until `redirect` and `install` are called"]
264pub struct HookBuilder {
265 target: *const (),
266}
267
268impl HookBuilder {
269 /// Sets the detour to redirect the target to, which fixes the hook's
270 /// signature `F`.
271 pub fn redirect<F: Function>(self, detour: F) -> HookWith<F> {
272 HookWith {
273 target: Target::Address(self.target),
274 detour,
275 }
276 }
277}
278
279/// A hook builder holding a byte pattern to find the target by, returned by
280/// [`Hook::scan`].
281#[must_use = "a hook builder does nothing until `redirect` and `install` are called"]
282pub struct HookScan {
283 pattern: Pattern,
284}
285
286impl HookScan {
287 /// Searches a loaded module such as `"engine.dll"` instead of the main
288 /// module.
289 pub fn in_module(self, module: &str) -> Self {
290 HookScan {
291 pattern: self.pattern.in_module(module),
292 }
293 }
294
295 /// Moves the target this many bytes on from the start of the match, or back
296 /// for a negative count.
297 pub fn offset(self, bytes: isize) -> Self {
298 HookScan {
299 pattern: self.pattern.offset(bytes),
300 }
301 }
302
303 /// Follows the `call` or `jmp` at the target to the function it goes to;
304 /// see [`Pattern::follow_call`].
305 pub fn follow_call(self) -> Self {
306 HookScan {
307 pattern: self.pattern.follow_call(),
308 }
309 }
310
311 /// Sets the detour to redirect the target to, which fixes the hook's
312 /// signature `F`.
313 pub fn redirect<F: Function>(self, detour: F) -> HookWith<F> {
314 HookWith {
315 target: Target::Pattern(self.pattern),
316 detour,
317 }
318 }
319}
320
321/// A hook builder holding the target and detour, returned by
322/// [`HookBuilder::redirect`] and [`HookScan::redirect`].
323#[must_use = "a hook builder does nothing until `install` is called"]
324pub struct HookWith<F: Function> {
325 target: Target,
326 detour: F,
327}
328
329impl<F: Function> HookWith<F> {
330 /// Installs the hook, disabled; enable it with [`Hook::enable`].
331 ///
332 /// Only calls that reach the target are redirected; copies inlined into
333 /// callers are not.
334 ///
335 /// # Errors
336 ///
337 /// - The [`scan`](crate::scan) errors, for a hook built with
338 /// [`Hook::scan`].
339 /// - [`Error::AlreadyHooked`] if a detour or injection already holds the
340 /// address.
341 /// - Otherwise an error naming the target if it can't be patched.
342 ///
343 /// # Safety
344 ///
345 /// - The target (the address given to [`Hook::at`], or what
346 /// [`Hook::scan`] finds) must be a function with signature `F`, not one
347 /// the compiler may have inlined into all its callers.
348 /// - The detour must match that signature and calling convention.
349 pub unsafe fn install(self) -> Result<Hook<F>> {
350 let target = self.target.resolve()?;
351 let address = target as usize;
352
353 // Claim the address up front so a second hook on it is refused.
354 if !registry::claim(address) {
355 return Err(Error::AlreadyHooked { address });
356 }
357
358 // SAFETY: the caller guarantees `target` is a function of signature `F`.
359 let target_fn: F = unsafe { F::from_ptr(target) };
360
361 // SAFETY: the target and detour share the signature `F`.
362 match unsafe { detour::TypedDetour::<F>::new(target_fn, self.detour) } {
363 Ok(inner) => Ok(Hook {
364 inner: Box::new(inner),
365 target: address,
366 }),
367 Err(error) => {
368 registry::release(address);
369 Err(Error::from_detour(error, address))
370 }
371 }
372 }
373}