1use crate::error::{Result, WraithError};
7use crate::util::memory::ProtectionGuard;
8use crate::manipulation::inline_hook::arch::Architecture;
9use crate::manipulation::inline_hook::guard::HookGuard;
10use crate::manipulation::inline_hook::trampoline::ExecutableMemory;
11use crate::manipulation::inline_hook::asm::Encoder;
12use super::Hook;
13use core::marker::PhantomData;
14
15const PAGE_EXECUTE_READWRITE: u32 = 0x40;
16
17#[cfg(target_arch = "x86_64")]
19#[repr(C)]
20#[derive(Debug, Clone, Copy)]
21pub struct HookContext {
22 pub rflags: u64,
23 pub r15: u64,
24 pub r14: u64,
25 pub r13: u64,
26 pub r12: u64,
27 pub r11: u64,
28 pub r10: u64,
29 pub r9: u64,
30 pub r8: u64,
31 pub rdi: u64,
32 pub rsi: u64,
33 pub rbp: u64,
34 pub rsp: u64,
35 pub rbx: u64,
36 pub rdx: u64,
37 pub rcx: u64,
38 pub rax: u64,
39}
40
41#[cfg(target_arch = "x86")]
43#[repr(C)]
44#[derive(Debug, Clone, Copy)]
45pub struct HookContext {
46 pub eflags: u32,
47 pub edi: u32,
48 pub esi: u32,
49 pub ebp: u32,
50 pub esp: u32,
51 pub ebx: u32,
52 pub edx: u32,
53 pub ecx: u32,
54 pub eax: u32,
55}
56
57#[cfg(target_arch = "x86_64")]
59pub type MidHookFn = extern "C" fn(ctx: *mut HookContext);
60
61#[cfg(target_arch = "x86")]
62pub type MidHookFn = extern "cdecl" fn(ctx: *mut HookContext);
63
64pub struct MidFunctionHook<A: Architecture> {
70 address: usize,
72 detour: usize,
74 _arch: PhantomData<A>,
75}
76
77impl<A: Architecture> MidFunctionHook<A> {
78 pub fn new(address: usize, detour: MidHookFn) -> Self {
84 Self {
85 address,
86 detour: detour as usize,
87 _arch: PhantomData,
88 }
89 }
90
91 pub fn from_raw(address: usize, detour: usize) -> Self {
93 Self {
94 address,
95 detour,
96 _arch: PhantomData,
97 }
98 }
99
100 #[cfg(target_arch = "x86_64")]
102 pub fn install(self) -> Result<HookGuard<A>> {
103 let min_hook_size = A::MIN_HOOK_SIZE;
104
105 let original_bytes = unsafe {
107 let target_bytes = core::slice::from_raw_parts(self.address as *const u8, 64);
108 let boundary = A::find_instruction_boundary(target_bytes, min_hook_size)
109 .ok_or_else(|| WraithError::HookDetectionFailed {
110 function: format!("{:#x}", self.address),
111 reason: "failed to find instruction boundary".into(),
112 })?;
113 core::slice::from_raw_parts(self.address as *const u8, boundary).to_vec()
114 };
115
116 let prologue_size = original_bytes.len();
117
118 let mut stub_memory = ExecutableMemory::allocate_near(self.address, 256)?;
120 let stub_base = stub_memory.base();
121
122 let wrapper_code = build_x64_wrapper(
124 stub_base,
125 self.detour,
126 self.address,
127 &original_bytes,
128 )?;
129
130 stub_memory.write(&wrapper_code)?;
131 stub_memory.flush_icache()?;
132
133 let hook_stub = A::encode_jmp_rel(self.address, stub_base)
135 .or_else(|| Some(A::encode_jmp_abs(stub_base)))
136 .unwrap();
137
138 let mut padded_stub = hook_stub;
139 if padded_stub.len() < prologue_size {
140 let padding = A::encode_nop_sled(prologue_size - padded_stub.len());
141 padded_stub.extend_from_slice(&padding);
142 }
143
144 {
146 let _guard = ProtectionGuard::new(
147 self.address,
148 prologue_size,
149 PAGE_EXECUTE_READWRITE,
150 )?;
151
152 unsafe {
153 core::ptr::copy_nonoverlapping(
154 padded_stub.as_ptr(),
155 self.address as *mut u8,
156 prologue_size,
157 );
158 }
159 }
160
161 flush_icache(self.address, prologue_size)?;
162
163 Ok(HookGuard::new(
164 self.address,
165 self.detour,
166 original_bytes,
167 Some(stub_memory),
168 ))
169 }
170
171 #[cfg(target_arch = "x86")]
172 pub fn install(self) -> Result<HookGuard<A>> {
173 let min_hook_size = A::MIN_HOOK_SIZE;
174
175 let original_bytes = unsafe {
176 let target_bytes = core::slice::from_raw_parts(self.address as *const u8, 32);
177 let boundary = A::find_instruction_boundary(target_bytes, min_hook_size)
178 .ok_or_else(|| WraithError::HookDetectionFailed {
179 function: format!("{:#x}", self.address),
180 reason: "failed to find instruction boundary".into(),
181 })?;
182 core::slice::from_raw_parts(self.address as *const u8, boundary).to_vec()
183 };
184
185 let prologue_size = original_bytes.len();
186
187 let mut stub_memory = ExecutableMemory::allocate_near(self.address, 128)?;
188 let stub_base = stub_memory.base();
189
190 let wrapper_code = build_x86_wrapper(
191 stub_base,
192 self.detour,
193 self.address,
194 &original_bytes,
195 )?;
196
197 stub_memory.write(&wrapper_code)?;
198 stub_memory.flush_icache()?;
199
200 let hook_stub = A::encode_jmp_rel(self.address, stub_base)
201 .or_else(|| Some(A::encode_jmp_abs(stub_base)))
202 .unwrap();
203
204 let mut padded_stub = hook_stub;
205 if padded_stub.len() < prologue_size {
206 let padding = A::encode_nop_sled(prologue_size - padded_stub.len());
207 padded_stub.extend_from_slice(&padding);
208 }
209
210 {
211 let _guard = ProtectionGuard::new(
212 self.address,
213 prologue_size,
214 PAGE_EXECUTE_READWRITE,
215 )?;
216
217 unsafe {
218 core::ptr::copy_nonoverlapping(
219 padded_stub.as_ptr(),
220 self.address as *mut u8,
221 prologue_size,
222 );
223 }
224 }
225
226 flush_icache(self.address, prologue_size)?;
227
228 Ok(HookGuard::new(
229 self.address,
230 self.detour,
231 original_bytes,
232 Some(stub_memory),
233 ))
234 }
235}
236
237impl<A: Architecture> Hook for MidFunctionHook<A> {
238 type Guard = HookGuard<A>;
239
240 fn install(self) -> Result<Self::Guard> {
241 MidFunctionHook::install(self)
242 }
243
244 fn target(&self) -> usize {
245 self.address
246 }
247
248 fn detour(&self) -> usize {
249 self.detour
250 }
251}
252
253#[cfg(target_arch = "x86_64")]
255fn build_x64_wrapper(
256 stub_base: usize,
257 detour: usize,
258 original_addr: usize,
259 original_bytes: &[u8],
260) -> Result<Vec<u8>> {
261 use crate::manipulation::inline_hook::arch::X64;
262
263 let mut code = Vec::with_capacity(256);
264
265 code.push(0x9C);
268
269 code.extend_from_slice(&[0x41, 0x57]); code.extend_from_slice(&[0x41, 0x56]); code.extend_from_slice(&[0x41, 0x55]); code.extend_from_slice(&[0x41, 0x54]); code.extend_from_slice(&[0x41, 0x53]); code.extend_from_slice(&[0x41, 0x52]); code.extend_from_slice(&[0x41, 0x51]); code.extend_from_slice(&[0x41, 0x50]); code.push(0x57); code.push(0x56); code.push(0x55); code.push(0x54); code.push(0x53); code.push(0x52); code.push(0x51); code.push(0x50); code.extend_from_slice(&[0x48, 0x89, 0x64, 0x24, 0x38]); code.extend_from_slice(&[0x48, 0x83, 0x44, 0x24, 0x38, 0x90]); code.extend_from_slice(&[0x48, 0x89, 0xE1]);
294
295 code.extend_from_slice(&[0x48, 0x83, 0xEC, 0x28]);
297
298 code.extend_from_slice(&[0x48, 0xB8]); code.extend_from_slice(&detour.to_le_bytes());
302 code.extend_from_slice(&[0xFF, 0xD0]); code.extend_from_slice(&[0x48, 0x83, 0xC4, 0x28]);
306
307 code.push(0x58); code.push(0x59); code.push(0x5A); code.push(0x5B); code.extend_from_slice(&[0x48, 0x83, 0xC4, 0x08]); code.push(0x5D); code.push(0x5E); code.push(0x5F); code.extend_from_slice(&[0x41, 0x58]); code.extend_from_slice(&[0x41, 0x59]); code.extend_from_slice(&[0x41, 0x5A]); code.extend_from_slice(&[0x41, 0x5B]); code.extend_from_slice(&[0x41, 0x5C]); code.extend_from_slice(&[0x41, 0x5D]); code.extend_from_slice(&[0x41, 0x5E]); code.extend_from_slice(&[0x41, 0x5F]); code.push(0x9D); let mut src_offset = 0;
329
330 while src_offset < original_bytes.len() {
331 let remaining = &original_bytes[src_offset..];
332
333 let insn_len = X64::find_instruction_boundary(remaining, 1)
335 .ok_or_else(|| WraithError::HookDetectionFailed {
336 function: format!("{:#x}", original_addr + src_offset),
337 reason: "failed to decode instruction during relocation".into(),
338 })?;
339
340 let instruction = &original_bytes[src_offset..src_offset + insn_len];
341 let old_addr = original_addr + src_offset;
342 let new_addr = stub_base + code.len();
343
344 if X64::needs_relocation(instruction) {
346 match X64::relocate_instruction(instruction, old_addr, new_addr) {
347 Some(relocated) => {
348 code.extend_from_slice(&relocated);
349 }
350 None => {
351 return Err(WraithError::RelocationFailed {
354 rva: src_offset as u32,
355 reason: format!(
356 "instruction at {:#x} cannot be relocated to {:#x}",
357 old_addr, new_addr
358 ),
359 });
360 }
361 }
362 } else {
363 code.extend_from_slice(instruction);
365 }
366
367 src_offset += insn_len;
368 }
369
370 let continuation = original_addr + original_bytes.len();
372 let jmp_location = stub_base + code.len();
373
374 if let Some(jmp) = X64::encode_jmp_rel(jmp_location, continuation) {
375 code.extend_from_slice(&jmp);
376 } else {
377 code.extend_from_slice(&X64::encode_jmp_abs(continuation));
378 }
379
380 Ok(code)
381}
382
383#[cfg(target_arch = "x86")]
385fn build_x86_wrapper(
386 stub_base: usize,
387 detour: usize,
388 original_addr: usize,
389 original_bytes: &[u8],
390) -> Result<Vec<u8>> {
391 use crate::manipulation::inline_hook::arch::X86;
392
393 let mut code = Vec::with_capacity(128);
394
395 code.push(0x9C);
397 code.push(0x60);
399
400 code.push(0x54);
402
403 code.push(0xB8); code.extend_from_slice(&(detour as u32).to_le_bytes());
406 code.extend_from_slice(&[0xFF, 0xD0]); code.extend_from_slice(&[0x83, 0xC4, 0x04]);
410
411 code.push(0x61);
413 code.push(0x9D);
415
416 let mut src_offset = 0;
418
419 while src_offset < original_bytes.len() {
420 let remaining = &original_bytes[src_offset..];
421
422 let insn_len = X86::find_instruction_boundary(remaining, 1)
424 .ok_or_else(|| WraithError::HookDetectionFailed {
425 function: format!("{:#x}", original_addr + src_offset),
426 reason: "failed to decode instruction during relocation".into(),
427 })?;
428
429 let instruction = &original_bytes[src_offset..src_offset + insn_len];
430 let old_addr = original_addr + src_offset;
431 let new_addr = stub_base + code.len();
432
433 if X86::needs_relocation(instruction) {
435 match X86::relocate_instruction(instruction, old_addr, new_addr) {
436 Some(relocated) => {
437 code.extend_from_slice(&relocated);
438 }
439 None => {
440 return Err(WraithError::RelocationFailed {
441 rva: src_offset as u32,
442 reason: format!(
443 "instruction at {:#x} cannot be relocated to {:#x}",
444 old_addr, new_addr
445 ),
446 });
447 }
448 }
449 } else {
450 code.extend_from_slice(instruction);
452 }
453
454 src_offset += insn_len;
455 }
456
457 let continuation = original_addr + original_bytes.len();
459 let jmp_location = stub_base + code.len();
460
461 if let Some(jmp) = X86::encode_jmp_rel(jmp_location, continuation) {
462 code.extend_from_slice(&jmp);
463 } else {
464 code.extend_from_slice(&X86::encode_jmp_abs(continuation as usize));
465 }
466
467 Ok(code)
468}
469
470fn flush_icache(address: usize, size: usize) -> Result<()> {
471 let result = unsafe {
472 FlushInstructionCache(
473 GetCurrentProcess(),
474 address as *const _,
475 size,
476 )
477 };
478
479 if result == 0 {
480 Err(WraithError::from_last_error("FlushInstructionCache"))
481 } else {
482 Ok(())
483 }
484}
485
486#[link(name = "kernel32")]
487extern "system" {
488 fn FlushInstructionCache(
489 hProcess: *mut core::ffi::c_void,
490 lpBaseAddress: *const core::ffi::c_void,
491 dwSize: usize,
492 ) -> i32;
493
494 fn GetCurrentProcess() -> *mut core::ffi::c_void;
495}