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