wraith/manipulation/inline_hook/
builder.rs1use 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, TrampolineBuilder};
10use crate::util::memory::ProtectionGuard;
11use core::marker::PhantomData;
12
13const PAGE_EXECUTE_READWRITE: u32 = 0x40;
14
15pub mod state {
17 pub struct Uninitialized;
19 pub struct TargetSet;
21 pub struct DetourSet;
23 pub struct TrampolineAllocated;
25 pub struct TrampolineBuilt;
27 pub struct Ready;
29}
30
31pub 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 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 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 pub fn get_target(&self) -> usize {
91 self.target.unwrap()
92 }
93
94 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 pub fn get_target(&self) -> usize {
118 self.target.unwrap()
119 }
120
121 pub fn get_detour(&self) -> usize {
123 self.detour.unwrap()
124 }
125
126 pub fn allocate_trampoline(self) -> Result<HookBuilder<A, state::TrampolineAllocated>> {
128 let target = self.target.unwrap();
129 let detour = self.detour.unwrap();
130
131 let hook_size = A::preferred_hook_size(target, detour);
133
134 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 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 pub fn get_target(&self) -> usize {
167 self.target.unwrap()
168 }
169
170 pub fn prologue_size(&self) -> usize {
172 self.prologue_size.unwrap()
173 }
174
175 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 let mut trampoline_code = Vec::with_capacity(prologue_size + A::JMP_ABS_SIZE);
186
187 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 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 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 pub fn get_target(&self) -> usize {
255 self.target.unwrap()
256 }
257
258 pub fn trampoline(&self) -> usize {
260 self.trampoline_memory.as_ref().unwrap().base()
261 }
262
263 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 let hook_stub = A::encode_jmp_rel(target, detour)
271 .unwrap_or_else(|| A::encode_jmp_abs(detour));
272
273 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 pub fn get_target(&self) -> usize {
296 self.target.unwrap()
297 }
298
299 pub fn trampoline(&self) -> usize {
301 self.trampoline_memory.as_ref().unwrap().base()
302 }
303
304 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 {
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_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
341pub 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}