wraith/manipulation/inline_hook/
builder.rs

1//! Type-state hook builder
2//!
3//! Provides a compile-time safe builder pattern for creating hooks,
4//! similar to ManualMapper in the manual_map module.
5
6use crate::error::{Result, WraithError};
7use crate::manipulation::inline_hook::arch::Architecture;
8use crate::manipulation::inline_hook::guard::HookGuard;
9use crate::manipulation::inline_hook::trampoline::ExecutableMemory;
10use crate::util::memory::ProtectionGuard;
11use core::marker::PhantomData;
12
13const PAGE_EXECUTE_READWRITE: u32 = 0x40;
14
15/// type-state markers for hook building stages
16pub mod state {
17    /// initial state - no target set
18    pub struct Uninitialized;
19    /// target function has been set
20    pub struct TargetSet;
21    /// detour function has been set
22    pub struct DetourSet;
23    /// trampoline has been allocated
24    pub struct TrampolineAllocated;
25    /// trampoline has been built
26    pub struct TrampolineBuilt;
27    /// ready to install
28    pub struct Ready;
29}
30
31/// type-state hook builder
32///
33/// ensures hook creation follows the correct sequence:
34/// `Uninitialized -> TargetSet -> DetourSet -> TrampolineAllocated -> TrampolineBuilt -> Ready`
35pub struct HookBuilder<A: Architecture, S> {
36    target: Option<usize>,
37    detour: Option<usize>,
38    prologue_bytes: Option<Vec<u8>>,
39    prologue_size: Option<usize>,
40    trampoline_memory: Option<ExecutableMemory>,
41    hook_stub: Option<Vec<u8>>,
42    _arch: PhantomData<A>,
43    _state: PhantomData<S>,
44}
45
46impl<A: Architecture> HookBuilder<A, state::Uninitialized> {
47    /// create a new hook builder
48    pub fn new() -> Self {
49        Self {
50            target: None,
51            detour: None,
52            prologue_bytes: None,
53            prologue_size: None,
54            trampoline_memory: None,
55            hook_stub: None,
56            _arch: PhantomData,
57            _state: PhantomData,
58        }
59    }
60
61    /// set the target function address
62    pub fn target(self, addr: usize) -> Result<HookBuilder<A, state::TargetSet>> {
63        if addr == 0 {
64            return Err(WraithError::NullPointer {
65                context: "hook target",
66            });
67        }
68
69        Ok(HookBuilder {
70            target: Some(addr),
71            detour: None,
72            prologue_bytes: None,
73            prologue_size: None,
74            trampoline_memory: None,
75            hook_stub: None,
76            _arch: PhantomData,
77            _state: PhantomData,
78        })
79    }
80}
81
82impl<A: Architecture> Default for HookBuilder<A, state::Uninitialized> {
83    fn default() -> Self {
84        Self::new()
85    }
86}
87
88impl<A: Architecture> HookBuilder<A, state::TargetSet> {
89    /// get the target address
90    pub fn get_target(&self) -> usize {
91        self.target.unwrap()
92    }
93
94    /// set the detour function address
95    pub fn detour(self, addr: usize) -> Result<HookBuilder<A, state::DetourSet>> {
96        if addr == 0 {
97            return Err(WraithError::NullPointer {
98                context: "hook detour",
99            });
100        }
101
102        Ok(HookBuilder {
103            target: self.target,
104            detour: Some(addr),
105            prologue_bytes: None,
106            prologue_size: None,
107            trampoline_memory: None,
108            hook_stub: None,
109            _arch: PhantomData,
110            _state: PhantomData,
111        })
112    }
113}
114
115impl<A: Architecture> HookBuilder<A, state::DetourSet> {
116    /// get the target address
117    pub fn get_target(&self) -> usize {
118        self.target.unwrap()
119    }
120
121    /// get the detour address
122    pub fn get_detour(&self) -> usize {
123        self.detour.unwrap()
124    }
125
126    /// analyze target and allocate trampoline
127    pub fn allocate_trampoline(self) -> Result<HookBuilder<A, state::TrampolineAllocated>> {
128        let target = self.target.unwrap();
129        let detour = self.detour.unwrap();
130
131        // calculate required hook size
132        let hook_size = A::preferred_hook_size(target, detour);
133
134        // analyze target function
135        let target_bytes = unsafe {
136            core::slice::from_raw_parts(target as *const u8, 64)
137        };
138
139        let boundary = A::find_instruction_boundary(target_bytes, hook_size)
140            .ok_or_else(|| WraithError::HookDetectionFailed {
141                function: format!("{:#x}", target),
142                reason: "failed to find instruction boundary".into(),
143            })?;
144
145        let prologue_bytes = target_bytes[..boundary].to_vec();
146
147        // allocate trampoline memory
148        let trampoline_size = boundary + A::JMP_ABS_SIZE + 16;
149        let trampoline_memory = ExecutableMemory::allocate_near(target, trampoline_size)?;
150
151        Ok(HookBuilder {
152            target: self.target,
153            detour: self.detour,
154            prologue_bytes: Some(prologue_bytes),
155            prologue_size: Some(boundary),
156            trampoline_memory: Some(trampoline_memory),
157            hook_stub: None,
158            _arch: PhantomData,
159            _state: PhantomData,
160        })
161    }
162}
163
164impl<A: Architecture> HookBuilder<A, state::TrampolineAllocated> {
165    /// get the target address
166    pub fn get_target(&self) -> usize {
167        self.target.unwrap()
168    }
169
170    /// get prologue size
171    pub fn prologue_size(&self) -> usize {
172        self.prologue_size.unwrap()
173    }
174
175    /// build the trampoline
176    pub fn build_trampoline(mut self) -> Result<HookBuilder<A, state::TrampolineBuilt>> {
177        let target = self.target.unwrap();
178        let prologue_bytes = self.prologue_bytes.as_ref().unwrap();
179        let prologue_size = self.prologue_size.unwrap();
180        let trampoline = self.trampoline_memory.as_mut().unwrap();
181
182        let trampoline_base = trampoline.base();
183
184        // build trampoline code
185        let mut trampoline_code = Vec::with_capacity(prologue_size + A::JMP_ABS_SIZE);
186
187        // relocate prologue bytes
188        let mut src_offset = 0;
189        let mut dst_offset = 0;
190
191        while src_offset < prologue_size {
192            let remaining = &prologue_bytes[src_offset..];
193            if remaining.is_empty() {
194                break;
195            }
196
197            let insn_len = A::find_instruction_boundary(remaining, 1)
198                .ok_or_else(|| WraithError::HookDetectionFailed {
199                    function: format!("{:#x}", target + src_offset),
200                    reason: "failed to decode instruction".into(),
201                })?;
202
203            let instruction = &prologue_bytes[src_offset..src_offset + insn_len];
204
205            if A::needs_relocation(instruction) {
206                let old_addr = target + src_offset;
207                let new_addr = trampoline_base + dst_offset;
208
209                let relocated = A::relocate_instruction(instruction, old_addr, new_addr)
210                    .ok_or_else(|| WraithError::RelocationFailed {
211                        rva: src_offset as u32,
212                        reason: "instruction cannot be relocated".into(),
213                    })?;
214
215                trampoline_code.extend_from_slice(&relocated);
216                dst_offset += relocated.len();
217            } else {
218                trampoline_code.extend_from_slice(instruction);
219                dst_offset += insn_len;
220            }
221
222            src_offset += insn_len;
223        }
224
225        // append jump back
226        let continuation = target + prologue_size;
227        let jmp_location = trampoline_base + dst_offset;
228
229        if let Some(jmp_bytes) = A::encode_jmp_rel(jmp_location, continuation) {
230            trampoline_code.extend_from_slice(&jmp_bytes);
231        } else {
232            trampoline_code.extend_from_slice(&A::encode_jmp_abs(continuation));
233        }
234
235        // write to memory
236        trampoline.write(&trampoline_code)?;
237        trampoline.flush_icache()?;
238
239        Ok(HookBuilder {
240            target: self.target,
241            detour: self.detour,
242            prologue_bytes: self.prologue_bytes,
243            prologue_size: self.prologue_size,
244            trampoline_memory: self.trampoline_memory,
245            hook_stub: None,
246            _arch: PhantomData,
247            _state: PhantomData,
248        })
249    }
250}
251
252impl<A: Architecture> HookBuilder<A, state::TrampolineBuilt> {
253    /// get the target address
254    pub fn get_target(&self) -> usize {
255        self.target.unwrap()
256    }
257
258    /// get the trampoline address
259    pub fn trampoline(&self) -> usize {
260        self.trampoline_memory.as_ref().unwrap().base()
261    }
262
263    /// generate hook stub and prepare for installation
264    pub fn prepare(mut self) -> Result<HookBuilder<A, state::Ready>> {
265        let target = self.target.unwrap();
266        let detour = self.detour.unwrap();
267        let prologue_size = self.prologue_size.unwrap();
268
269        // generate hook stub
270        let hook_stub = A::encode_jmp_rel(target, detour)
271            .unwrap_or_else(|| A::encode_jmp_abs(detour));
272
273        // pad with NOPs if needed
274        let mut padded_stub = hook_stub;
275        if padded_stub.len() < prologue_size {
276            let padding = A::encode_nop_sled(prologue_size - padded_stub.len());
277            padded_stub.extend_from_slice(&padding);
278        }
279
280        Ok(HookBuilder {
281            target: self.target,
282            detour: self.detour,
283            prologue_bytes: self.prologue_bytes,
284            prologue_size: self.prologue_size,
285            trampoline_memory: self.trampoline_memory,
286            hook_stub: Some(padded_stub),
287            _arch: PhantomData,
288            _state: PhantomData,
289        })
290    }
291}
292
293impl<A: Architecture> HookBuilder<A, state::Ready> {
294    /// get the target address
295    pub fn get_target(&self) -> usize {
296        self.target.unwrap()
297    }
298
299    /// get the trampoline address
300    pub fn trampoline(&self) -> usize {
301        self.trampoline_memory.as_ref().unwrap().base()
302    }
303
304    /// install the hook
305    pub fn install(mut self) -> Result<HookGuard<A>> {
306        let target = self.target.unwrap();
307        let detour = self.detour.unwrap();
308        let prologue_bytes = self.prologue_bytes.take().unwrap();
309        let prologue_size = self.prologue_size.unwrap();
310        let hook_stub = self.hook_stub.as_ref().unwrap();
311
312        // write hook stub to target
313        {
314            let _guard = ProtectionGuard::new(
315                target,
316                prologue_size,
317                PAGE_EXECUTE_READWRITE,
318            )?;
319
320            unsafe {
321                core::ptr::copy_nonoverlapping(
322                    hook_stub.as_ptr(),
323                    target as *mut u8,
324                    prologue_size,
325                );
326            }
327        }
328
329        // flush instruction cache
330        flush_icache(target, prologue_size)?;
331
332        Ok(HookGuard::new(
333            target,
334            detour,
335            prologue_bytes,
336            self.trampoline_memory.take(),
337        ))
338    }
339}
340
341/// convenience function to build a hook step-by-step
342///
343/// # Example
344/// ```ignore
345/// let guard = HookBuilder::<NativeArch, _>::new()
346///     .target(target_addr)?
347///     .detour(detour_addr)?
348///     .allocate_trampoline()?
349///     .build_trampoline()?
350///     .prepare()?
351///     .install()?;
352/// ```
353pub fn build<A: Architecture>() -> HookBuilder<A, state::Uninitialized> {
354    HookBuilder::new()
355}
356
357fn flush_icache(address: usize, size: usize) -> Result<()> {
358    let result = unsafe {
359        FlushInstructionCache(
360            GetCurrentProcess(),
361            address as *const _,
362            size,
363        )
364    };
365
366    if result == 0 {
367        Err(WraithError::from_last_error("FlushInstructionCache"))
368    } else {
369        Ok(())
370    }
371}
372
373#[link(name = "kernel32")]
374extern "system" {
375    fn FlushInstructionCache(
376        hProcess: *mut core::ffi::c_void,
377        lpBaseAddress: *const core::ffi::c_void,
378        dwSize: usize,
379    ) -> i32;
380
381    fn GetCurrentProcess() -> *mut core::ffi::c_void;
382}