wraith/manipulation/inline_hook/
builder.rs1#[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::manipulation::inline_hook::arch::Architecture;
14use crate::manipulation::inline_hook::guard::HookGuard;
15use crate::manipulation::inline_hook::trampoline::ExecutableMemory;
16use crate::util::memory::ProtectionGuard;
17use core::marker::PhantomData;
18
19const PAGE_EXECUTE_READWRITE: u32 = 0x40;
20
21pub mod state {
23 pub struct Uninitialized;
25 pub struct TargetSet;
27 pub struct DetourSet;
29 pub struct TrampolineAllocated;
31 pub struct TrampolineBuilt;
33 pub struct Ready;
35}
36
37pub struct HookBuilder<A: Architecture, S> {
42 target: Option<usize>,
43 detour: Option<usize>,
44 prologue_bytes: Option<Vec<u8>>,
45 prologue_size: Option<usize>,
46 trampoline_memory: Option<ExecutableMemory>,
47 hook_stub: Option<Vec<u8>>,
48 _arch: PhantomData<A>,
49 _state: PhantomData<S>,
50}
51
52impl<A: Architecture> HookBuilder<A, state::Uninitialized> {
53 pub fn new() -> Self {
55 Self {
56 target: None,
57 detour: None,
58 prologue_bytes: None,
59 prologue_size: None,
60 trampoline_memory: None,
61 hook_stub: None,
62 _arch: PhantomData,
63 _state: PhantomData,
64 }
65 }
66
67 pub fn target(self, addr: usize) -> Result<HookBuilder<A, state::TargetSet>> {
69 if addr == 0 {
70 return Err(WraithError::NullPointer {
71 context: "hook target",
72 });
73 }
74
75 Ok(HookBuilder {
76 target: Some(addr),
77 detour: None,
78 prologue_bytes: None,
79 prologue_size: None,
80 trampoline_memory: None,
81 hook_stub: None,
82 _arch: PhantomData,
83 _state: PhantomData,
84 })
85 }
86}
87
88impl<A: Architecture> Default for HookBuilder<A, state::Uninitialized> {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94impl<A: Architecture> HookBuilder<A, state::TargetSet> {
95 pub fn get_target(&self) -> usize {
97 self.target.unwrap()
98 }
99
100 pub fn detour(self, addr: usize) -> Result<HookBuilder<A, state::DetourSet>> {
102 if addr == 0 {
103 return Err(WraithError::NullPointer {
104 context: "hook detour",
105 });
106 }
107
108 Ok(HookBuilder {
109 target: self.target,
110 detour: Some(addr),
111 prologue_bytes: None,
112 prologue_size: None,
113 trampoline_memory: None,
114 hook_stub: None,
115 _arch: PhantomData,
116 _state: PhantomData,
117 })
118 }
119}
120
121impl<A: Architecture> HookBuilder<A, state::DetourSet> {
122 pub fn get_target(&self) -> usize {
124 self.target.unwrap()
125 }
126
127 pub fn get_detour(&self) -> usize {
129 self.detour.unwrap()
130 }
131
132 pub fn allocate_trampoline(self) -> Result<HookBuilder<A, state::TrampolineAllocated>> {
134 let target = self.target.unwrap();
135 let detour = self.detour.unwrap();
136
137 let hook_size = A::preferred_hook_size(target, detour);
139
140 let target_bytes = unsafe {
142 core::slice::from_raw_parts(target as *const u8, 64)
143 };
144
145 let boundary = A::find_instruction_boundary(target_bytes, hook_size)
146 .ok_or_else(|| WraithError::HookDetectionFailed {
147 function: format!("{:#x}", target),
148 reason: "failed to find instruction boundary".into(),
149 })?;
150
151 let prologue_bytes = target_bytes[..boundary].to_vec();
152
153 let trampoline_size = boundary + A::JMP_ABS_SIZE + 16;
155 let trampoline_memory = ExecutableMemory::allocate_near(target, trampoline_size)?;
156
157 Ok(HookBuilder {
158 target: self.target,
159 detour: self.detour,
160 prologue_bytes: Some(prologue_bytes),
161 prologue_size: Some(boundary),
162 trampoline_memory: Some(trampoline_memory),
163 hook_stub: None,
164 _arch: PhantomData,
165 _state: PhantomData,
166 })
167 }
168}
169
170impl<A: Architecture> HookBuilder<A, state::TrampolineAllocated> {
171 pub fn get_target(&self) -> usize {
173 self.target.unwrap()
174 }
175
176 pub fn prologue_size(&self) -> usize {
178 self.prologue_size.unwrap()
179 }
180
181 pub fn build_trampoline(mut self) -> Result<HookBuilder<A, state::TrampolineBuilt>> {
183 let target = self.target.unwrap();
184 let prologue_bytes = self.prologue_bytes.as_ref().unwrap();
185 let prologue_size = self.prologue_size.unwrap();
186 let trampoline = self.trampoline_memory.as_mut().unwrap();
187
188 let trampoline_base = trampoline.base();
189
190 let mut trampoline_code = Vec::with_capacity(prologue_size + A::JMP_ABS_SIZE);
192
193 let mut src_offset = 0;
195 let mut dst_offset = 0;
196
197 while src_offset < prologue_size {
198 let remaining = &prologue_bytes[src_offset..];
199 if remaining.is_empty() {
200 break;
201 }
202
203 let insn_len = A::find_instruction_boundary(remaining, 1)
204 .ok_or_else(|| WraithError::HookDetectionFailed {
205 function: format!("{:#x}", target + src_offset),
206 reason: "failed to decode instruction".into(),
207 })?;
208
209 let instruction = &prologue_bytes[src_offset..src_offset + insn_len];
210
211 if A::needs_relocation(instruction) {
212 let old_addr = target + src_offset;
213 let new_addr = trampoline_base + dst_offset;
214
215 let relocated = A::relocate_instruction(instruction, old_addr, new_addr)
216 .ok_or_else(|| WraithError::RelocationFailed {
217 rva: src_offset as u32,
218 reason: "instruction cannot be relocated".into(),
219 })?;
220
221 trampoline_code.extend_from_slice(&relocated);
222 dst_offset += relocated.len();
223 } else {
224 trampoline_code.extend_from_slice(instruction);
225 dst_offset += insn_len;
226 }
227
228 src_offset += insn_len;
229 }
230
231 let continuation = target + prologue_size;
233 let jmp_location = trampoline_base + dst_offset;
234
235 if let Some(jmp_bytes) = A::encode_jmp_rel(jmp_location, continuation) {
236 trampoline_code.extend_from_slice(&jmp_bytes);
237 } else {
238 trampoline_code.extend_from_slice(&A::encode_jmp_abs(continuation));
239 }
240
241 trampoline.write(&trampoline_code)?;
243 trampoline.flush_icache()?;
244
245 Ok(HookBuilder {
246 target: self.target,
247 detour: self.detour,
248 prologue_bytes: self.prologue_bytes,
249 prologue_size: self.prologue_size,
250 trampoline_memory: self.trampoline_memory,
251 hook_stub: None,
252 _arch: PhantomData,
253 _state: PhantomData,
254 })
255 }
256}
257
258impl<A: Architecture> HookBuilder<A, state::TrampolineBuilt> {
259 pub fn get_target(&self) -> usize {
261 self.target.unwrap()
262 }
263
264 pub fn trampoline(&self) -> usize {
266 self.trampoline_memory.as_ref().unwrap().base()
267 }
268
269 pub fn prepare(mut self) -> Result<HookBuilder<A, state::Ready>> {
271 let target = self.target.unwrap();
272 let detour = self.detour.unwrap();
273 let prologue_size = self.prologue_size.unwrap();
274
275 let hook_stub = A::encode_jmp_rel(target, detour)
277 .unwrap_or_else(|| A::encode_jmp_abs(detour));
278
279 let mut padded_stub = hook_stub;
281 if padded_stub.len() < prologue_size {
282 let padding = A::encode_nop_sled(prologue_size - padded_stub.len());
283 padded_stub.extend_from_slice(&padding);
284 }
285
286 Ok(HookBuilder {
287 target: self.target,
288 detour: self.detour,
289 prologue_bytes: self.prologue_bytes,
290 prologue_size: self.prologue_size,
291 trampoline_memory: self.trampoline_memory,
292 hook_stub: Some(padded_stub),
293 _arch: PhantomData,
294 _state: PhantomData,
295 })
296 }
297}
298
299impl<A: Architecture> HookBuilder<A, state::Ready> {
300 pub fn get_target(&self) -> usize {
302 self.target.unwrap()
303 }
304
305 pub fn trampoline(&self) -> usize {
307 self.trampoline_memory.as_ref().unwrap().base()
308 }
309
310 pub fn install(mut self) -> Result<HookGuard<A>> {
312 let target = self.target.unwrap();
313 let detour = self.detour.unwrap();
314 let prologue_bytes = self.prologue_bytes.take().unwrap();
315 let prologue_size = self.prologue_size.unwrap();
316 let hook_stub = self.hook_stub.as_ref().unwrap();
317
318 {
320 let _guard = ProtectionGuard::new(
321 target,
322 prologue_size,
323 PAGE_EXECUTE_READWRITE,
324 )?;
325
326 unsafe {
327 core::ptr::copy_nonoverlapping(
328 hook_stub.as_ptr(),
329 target as *mut u8,
330 prologue_size,
331 );
332 }
333 }
334
335 flush_icache(target, prologue_size)?;
337
338 Ok(HookGuard::new(
339 target,
340 detour,
341 prologue_bytes,
342 self.trampoline_memory.take(),
343 ))
344 }
345}
346
347pub fn build<A: Architecture>() -> HookBuilder<A, state::Uninitialized> {
360 HookBuilder::new()
361}
362
363fn flush_icache(address: usize, size: usize) -> Result<()> {
364 let result = unsafe {
365 FlushInstructionCache(
366 GetCurrentProcess(),
367 address as *const _,
368 size,
369 )
370 };
371
372 if result == 0 {
373 Err(WraithError::from_last_error("FlushInstructionCache"))
374 } else {
375 Ok(())
376 }
377}
378
379#[link(name = "kernel32")]
380extern "system" {
381 fn FlushInstructionCache(
382 hProcess: *mut core::ffi::c_void,
383 lpBaseAddress: *const core::ffi::c_void,
384 dwSize: usize,
385 ) -> i32;
386
387 fn GetCurrentProcess() -> *mut core::ffi::c_void;
388}