wraith/manipulation/inline_hook/hook/
inline.rs

1//! Standard inline hook implementation
2//!
3//! Overwrites the function prologue with a jump to the detour function.
4//! A trampoline is created to call the original function.
5
6use crate::error::{Result, WraithError};
7use crate::util::memory::ProtectionGuard;
8use crate::manipulation::inline_hook::arch::Architecture;
9use crate::manipulation::inline_hook::guard::{HookGuard, StatefulHookGuard};
10use crate::manipulation::inline_hook::trampoline::TrampolineBuilder;
11use super::Hook;
12use core::marker::PhantomData;
13
14const PAGE_EXECUTE_READWRITE: u32 = 0x40;
15
16/// standard inline hook
17///
18/// overwrites the function prologue with a jump to the detour.
19/// creates a trampoline for calling the original function.
20pub struct InlineHook<A: Architecture> {
21    target: usize,
22    detour: usize,
23    _arch: PhantomData<A>,
24}
25
26impl<A: Architecture> InlineHook<A> {
27    /// create a new inline hook
28    ///
29    /// # Arguments
30    /// * `target` - address of the function to hook
31    /// * `detour` - address of the detour function
32    pub fn new(target: usize, detour: usize) -> Self {
33        Self {
34            target,
35            detour,
36            _arch: PhantomData,
37        }
38    }
39
40    /// calculate the required hook size based on distance
41    fn hook_size(&self) -> usize {
42        A::preferred_hook_size(self.target, self.detour)
43    }
44
45    /// generate the hook stub bytes
46    fn generate_hook_stub(&self) -> Vec<u8> {
47        // try relative jump first
48        if let Some(bytes) = A::encode_jmp_rel(self.target, self.detour) {
49            bytes
50        } else {
51            // fall back to absolute jump
52            A::encode_jmp_abs(self.detour)
53        }
54    }
55
56    /// install the hook and return a guard
57    pub fn install(self) -> Result<HookGuard<A>> {
58        let hook_size = self.hook_size();
59
60        // build trampoline
61        let mut builder = TrampolineBuilder::<A>::new(self.target);
62        builder.analyze(hook_size)?;
63        builder.allocate()?;
64        builder.build()?;
65
66        let prologue_bytes = builder.prologue_bytes().to_vec();
67        let prologue_size = builder.prologue_size();
68        let trampoline_memory = builder.take_memory();
69
70        // generate hook stub
71        let hook_stub = self.generate_hook_stub();
72
73        // verify we have enough space
74        if prologue_size < hook_stub.len() {
75            return Err(WraithError::HookDetectionFailed {
76                function: format!("{:#x}", self.target),
77                reason: format!(
78                    "insufficient space: need {} bytes, have {}",
79                    hook_stub.len(),
80                    prologue_size
81                ),
82            });
83        }
84
85        // pad hook stub with NOPs if needed
86        let mut final_stub = hook_stub;
87        if final_stub.len() < prologue_size {
88            let padding = A::encode_nop_sled(prologue_size - final_stub.len());
89            final_stub.extend_from_slice(&padding);
90        }
91
92        // write hook stub to target
93        {
94            let _guard = ProtectionGuard::new(
95                self.target,
96                prologue_size,
97                PAGE_EXECUTE_READWRITE,
98            )?;
99
100            // SAFETY: protection changed to RWX, size matches prologue
101            unsafe {
102                core::ptr::copy_nonoverlapping(
103                    final_stub.as_ptr(),
104                    self.target as *mut u8,
105                    prologue_size,
106                );
107            }
108        }
109
110        // flush instruction cache
111        flush_icache(self.target, prologue_size)?;
112
113        Ok(HookGuard::new(
114            self.target,
115            self.detour,
116            prologue_bytes,
117            trampoline_memory,
118        ))
119    }
120
121    /// install and return a stateful guard with enable/disable support
122    pub fn install_stateful(self) -> Result<StatefulHookGuard<A>> {
123        let hook_size = self.hook_size();
124
125        // build trampoline
126        let mut builder = TrampolineBuilder::<A>::new(self.target);
127        builder.analyze(hook_size)?;
128        builder.allocate()?;
129        builder.build()?;
130
131        let prologue_bytes = builder.prologue_bytes().to_vec();
132        let prologue_size = builder.prologue_size();
133        let trampoline_memory = builder.take_memory();
134
135        // generate hook stub
136        let hook_stub = self.generate_hook_stub();
137
138        if prologue_size < hook_stub.len() {
139            return Err(WraithError::HookDetectionFailed {
140                function: format!("{:#x}", self.target),
141                reason: format!(
142                    "insufficient space: need {} bytes, have {}",
143                    hook_stub.len(),
144                    prologue_size
145                ),
146            });
147        }
148
149        let mut final_stub = hook_stub;
150        if final_stub.len() < prologue_size {
151            let padding = A::encode_nop_sled(prologue_size - final_stub.len());
152            final_stub.extend_from_slice(&padding);
153        }
154
155        // write hook stub
156        {
157            let _guard = ProtectionGuard::new(
158                self.target,
159                prologue_size,
160                PAGE_EXECUTE_READWRITE,
161            )?;
162
163            unsafe {
164                core::ptr::copy_nonoverlapping(
165                    final_stub.as_ptr(),
166                    self.target as *mut u8,
167                    prologue_size,
168                );
169            }
170        }
171
172        flush_icache(self.target, prologue_size)?;
173
174        let guard = HookGuard::new(
175            self.target,
176            self.detour,
177            prologue_bytes,
178            trampoline_memory,
179        );
180
181        Ok(StatefulHookGuard::new(guard, final_stub))
182    }
183}
184
185impl<A: Architecture> Hook for InlineHook<A> {
186    type Guard = HookGuard<A>;
187
188    fn install(self) -> Result<Self::Guard> {
189        InlineHook::install(self)
190    }
191
192    fn target(&self) -> usize {
193        self.target
194    }
195
196    fn detour(&self) -> usize {
197        self.detour
198    }
199}
200
201/// convenience function to create and install an inline hook
202pub fn hook<A: Architecture>(target: usize, detour: usize) -> Result<HookGuard<A>> {
203    InlineHook::<A>::new(target, detour).install()
204}
205
206/// convenience function using native architecture
207#[cfg(target_arch = "x86_64")]
208pub fn hook_native(target: usize, detour: usize) -> Result<HookGuard<crate::manipulation::inline_hook::arch::X64>> {
209    hook::<crate::manipulation::inline_hook::arch::X64>(target, detour)
210}
211
212#[cfg(target_arch = "x86")]
213pub fn hook_native(target: usize, detour: usize) -> Result<HookGuard<crate::manipulation::inline_hook::arch::X86>> {
214    hook::<crate::manipulation::inline_hook::arch::X86>(target, detour)
215}
216
217/// flush instruction cache
218fn flush_icache(address: usize, size: usize) -> Result<()> {
219    let result = unsafe {
220        FlushInstructionCache(
221            GetCurrentProcess(),
222            address as *const _,
223            size,
224        )
225    };
226
227    if result == 0 {
228        Err(WraithError::from_last_error("FlushInstructionCache"))
229    } else {
230        Ok(())
231    }
232}
233
234#[link(name = "kernel32")]
235extern "system" {
236    fn FlushInstructionCache(
237        hProcess: *mut core::ffi::c_void,
238        lpBaseAddress: *const core::ffi::c_void,
239        dwSize: usize,
240    ) -> i32;
241
242    fn GetCurrentProcess() -> *mut core::ffi::c_void;
243}