Skip to main content

asdf_overlay_hook/
lib.rs

1//! Function hooking for Windows using Frida Gum.
2//!
3//! This crate is intended to be used only as `asdf-overlay`'s internal dependency.
4//! Hook installation is unsafe: callers must uphold function-pointer and code-lifetime
5//! requirements. Dropping a hook does not undo the replacement.
6
7#[allow(
8    non_camel_case_types,
9    non_upper_case_globals,
10    non_snake_case,
11    unused,
12    clippy::all
13)]
14mod bindings {
15    // Generated using `bindgen gum_wrapper.h --allowlist-function gum_bindings_.* --use-core -o src/bindings.rs`
16    include!("./bindings.rs");
17}
18
19use fn_ptr::{FnPtr, UntypedFnPtr};
20use scopeguard::defer;
21use tracing::{Level, debug};
22
23use core::{fmt::Debug, ptr};
24use std::sync::LazyLock;
25
26/// A function replacement with a trampoline for calling the original.
27///
28/// Dropping the hook does not detach it.
29#[derive(Debug)]
30pub struct DetourHook<F> {
31    trampoline: F,
32}
33
34impl<F: FnPtr> DetourHook<F> {
35    /// Replace calls to the target with the detour.
36    ///
37    /// Returns an error if the target cannot be intercepted.
38    ///
39    /// # Safety
40    /// Both pointers must have the same signature and calling convention. Their code
41    /// must remain loaded while the hook is installed, and the detour must uphold
42    /// the target's contract.
43    #[tracing::instrument(level = Level::TRACE)]
44    pub unsafe fn attach(func: F, detour: F) -> DetourResult<Self> {
45        let mut trampoline: UntypedFnPtr = ptr::null_mut();
46        let code = unsafe {
47            bindings::gum_bindings_interceptor_replace_fast(
48                INTERCEPTER.0,
49                func.as_ptr() as _,
50                detour.as_ptr() as _,
51                (&raw mut trampoline).cast(),
52            )
53        };
54        match code {
55            bindings::GumReplaceReturn_GUM_REPLACE_WRONG_SIGNATURE => {
56                return Err(HookError::BadSignature);
57            }
58            bindings::GumReplaceReturn_GUM_REPLACE_ALREADY_REPLACED => {
59                return Err(HookError::AlreadyReplaced);
60            }
61            bindings::GumReplaceReturn_GUM_REPLACE_POLICY_VIOLATION => {
62                return Err(HookError::PolicyViolation);
63            }
64            bindings::GumReplaceReturn_GUM_REPLACE_WRONG_TYPE => {
65                return Err(HookError::WrongType);
66            }
67
68            _ => {}
69        }
70
71        debug!("hook attached");
72        Ok(DetourHook {
73            trampoline: unsafe { F::from_ptr(trampoline as _) },
74        })
75    }
76
77    /// Get the original function pointer.
78    ///
79    /// # Safety
80    /// The returned function pointer is valid only if the attach transaction is finished and the hook is still attached.
81    #[inline(always)]
82    pub unsafe fn original_fn(&self) -> F {
83        self.trampoline
84    }
85}
86
87/// Batch hook changes in a transaction and return the closure's result.
88///
89/// Errors do not roll back changes. Do not call newly installed trampolines until
90/// the transaction finishes.
91pub fn with_transaction<R>(f: impl FnOnce() -> R) -> R {
92    unsafe {
93        bindings::gum_bindings_interceptor_begin_transaction(INTERCEPTER.0);
94    }
95    defer!(unsafe { bindings::gum_bindings_interceptor_end_transaction(INTERCEPTER.0) });
96
97    f()
98}
99
100type DetourResult<T> = Result<T, HookError>;
101
102/// Detour error code.
103#[derive(Debug, Clone, Copy, thiserror::Error)]
104pub enum HookError {
105    #[error("Bad interceptor signature")]
106    BadSignature,
107
108    #[error("Function already replaced")]
109    AlreadyReplaced,
110
111    #[error("Policy violation")]
112    PolicyViolation,
113
114    #[error("Wrong type")]
115    WrongType,
116}
117
118static INTERCEPTER: LazyLock<Intercepter> = LazyLock::new(|| {
119    Intercepter(unsafe {
120        bindings::gum_bindings_init();
121        bindings::gum_bindings_interceptor_obtain()
122    })
123});
124
125#[derive(Debug, Clone, Copy)]
126#[repr(transparent)]
127struct Intercepter(*mut bindings::GumInterceptor);
128
129unsafe impl Send for Intercepter {}
130unsafe impl Sync for Intercepter {}