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