#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::{format, string::String, vec::Vec};
#[cfg(feature = "std")]
use std::{format, string::String, vec::Vec};
use crate::error::{Result, WraithError};
use crate::util::memory::ProtectionGuard;
use crate::manipulation::inline_hook::arch::Architecture;
use crate::manipulation::inline_hook::guard::HookGuard;
use crate::manipulation::inline_hook::trampoline::ExecutableMemory;
use super::Hook;
use core::marker::PhantomData;
const PAGE_EXECUTE_READWRITE: u32 = 0x40;
#[cfg(target_arch = "x86_64")]
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct HookContext {
pub rflags: u64,
pub r15: u64,
pub r14: u64,
pub r13: u64,
pub r12: u64,
pub r11: u64,
pub r10: u64,
pub r9: u64,
pub r8: u64,
pub rdi: u64,
pub rsi: u64,
pub rbp: u64,
pub rsp: u64,
pub rbx: u64,
pub rdx: u64,
pub rcx: u64,
pub rax: u64,
}
#[cfg(target_arch = "x86")]
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct HookContext {
pub eflags: u32,
pub edi: u32,
pub esi: u32,
pub ebp: u32,
pub esp: u32,
pub ebx: u32,
pub edx: u32,
pub ecx: u32,
pub eax: u32,
}
#[cfg(target_arch = "x86_64")]
pub type MidHookFn = extern "C" fn(ctx: *mut HookContext);
#[cfg(target_arch = "x86")]
pub type MidHookFn = extern "cdecl" fn(ctx: *mut HookContext);
pub struct MidFunctionHook<A: Architecture> {
address: usize,
detour: usize,
_arch: PhantomData<A>,
}
impl<A: Architecture> MidFunctionHook<A> {
pub fn new(address: usize, detour: MidHookFn) -> Self {
Self {
address,
detour: detour as usize,
_arch: PhantomData,
}
}
pub fn from_raw(address: usize, detour: usize) -> Self {
Self {
address,
detour,
_arch: PhantomData,
}
}
#[cfg(target_arch = "x86_64")]
pub fn install(self) -> Result<HookGuard<A>> {
let min_hook_size = A::MIN_HOOK_SIZE;
let original_bytes = unsafe {
let target_bytes = core::slice::from_raw_parts(self.address as *const u8, 64);
let boundary = A::find_instruction_boundary(target_bytes, min_hook_size)
.ok_or_else(|| WraithError::HookDetectionFailed {
function: format!("{:#x}", self.address),
reason: "failed to find instruction boundary".into(),
})?;
core::slice::from_raw_parts(self.address as *const u8, boundary).to_vec()
};
let prologue_size = original_bytes.len();
let mut stub_memory = ExecutableMemory::allocate_near(self.address, 256)?;
let stub_base = stub_memory.base();
let wrapper_code = build_x64_wrapper(
stub_base,
self.detour,
self.address,
&original_bytes,
)?;
stub_memory.write(&wrapper_code)?;
stub_memory.flush_icache()?;
let hook_stub = A::encode_jmp_rel(self.address, stub_base)
.or_else(|| Some(A::encode_jmp_abs(stub_base)))
.unwrap();
let mut padded_stub = hook_stub;
if padded_stub.len() < prologue_size {
let padding = A::encode_nop_sled(prologue_size - padded_stub.len());
padded_stub.extend_from_slice(&padding);
}
{
let _guard = ProtectionGuard::new(
self.address,
prologue_size,
PAGE_EXECUTE_READWRITE,
)?;
unsafe {
core::ptr::copy_nonoverlapping(
padded_stub.as_ptr(),
self.address as *mut u8,
prologue_size,
);
}
}
flush_icache(self.address, prologue_size)?;
Ok(HookGuard::new(
self.address,
self.detour,
original_bytes,
Some(stub_memory),
))
}
#[cfg(target_arch = "x86")]
pub fn install(self) -> Result<HookGuard<A>> {
let min_hook_size = A::MIN_HOOK_SIZE;
let original_bytes = unsafe {
let target_bytes = core::slice::from_raw_parts(self.address as *const u8, 32);
let boundary = A::find_instruction_boundary(target_bytes, min_hook_size)
.ok_or_else(|| WraithError::HookDetectionFailed {
function: format!("{:#x}", self.address),
reason: "failed to find instruction boundary".into(),
})?;
core::slice::from_raw_parts(self.address as *const u8, boundary).to_vec()
};
let prologue_size = original_bytes.len();
let mut stub_memory = ExecutableMemory::allocate_near(self.address, 128)?;
let stub_base = stub_memory.base();
let wrapper_code = build_x86_wrapper(
stub_base,
self.detour,
self.address,
&original_bytes,
)?;
stub_memory.write(&wrapper_code)?;
stub_memory.flush_icache()?;
let hook_stub = A::encode_jmp_rel(self.address, stub_base)
.or_else(|| Some(A::encode_jmp_abs(stub_base)))
.unwrap();
let mut padded_stub = hook_stub;
if padded_stub.len() < prologue_size {
let padding = A::encode_nop_sled(prologue_size - padded_stub.len());
padded_stub.extend_from_slice(&padding);
}
{
let _guard = ProtectionGuard::new(
self.address,
prologue_size,
PAGE_EXECUTE_READWRITE,
)?;
unsafe {
core::ptr::copy_nonoverlapping(
padded_stub.as_ptr(),
self.address as *mut u8,
prologue_size,
);
}
}
flush_icache(self.address, prologue_size)?;
Ok(HookGuard::new(
self.address,
self.detour,
original_bytes,
Some(stub_memory),
))
}
}
impl<A: Architecture> Hook for MidFunctionHook<A> {
type Guard = HookGuard<A>;
fn install(self) -> Result<Self::Guard> {
MidFunctionHook::install(self)
}
fn target(&self) -> usize {
self.address
}
fn detour(&self) -> usize {
self.detour
}
}
#[cfg(target_arch = "x86_64")]
fn build_x64_wrapper(
stub_base: usize,
detour: usize,
original_addr: usize,
original_bytes: &[u8],
) -> Result<Vec<u8>> {
use crate::manipulation::inline_hook::arch::X64;
let mut code = Vec::with_capacity(256);
code.push(0x9C);
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]);
code.extend_from_slice(&[0x48, 0x83, 0xEC, 0x28]);
code.extend_from_slice(&[0x48, 0xB8]); code.extend_from_slice(&detour.to_le_bytes());
code.extend_from_slice(&[0xFF, 0xD0]);
code.extend_from_slice(&[0x48, 0x83, 0xC4, 0x28]);
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;
while src_offset < original_bytes.len() {
let remaining = &original_bytes[src_offset..];
let insn_len = X64::find_instruction_boundary(remaining, 1)
.ok_or_else(|| WraithError::HookDetectionFailed {
function: format!("{:#x}", original_addr + src_offset),
reason: "failed to decode instruction during relocation".into(),
})?;
let instruction = &original_bytes[src_offset..src_offset + insn_len];
let old_addr = original_addr + src_offset;
let new_addr = stub_base + code.len();
if X64::needs_relocation(instruction) {
match X64::relocate_instruction(instruction, old_addr, new_addr) {
Some(relocated) => {
code.extend_from_slice(&relocated);
}
None => {
return Err(WraithError::RelocationFailed {
rva: src_offset as u32,
reason: format!(
"instruction at {:#x} cannot be relocated to {:#x}",
old_addr, new_addr
),
});
}
}
} else {
code.extend_from_slice(instruction);
}
src_offset += insn_len;
}
let continuation = original_addr + original_bytes.len();
let jmp_location = stub_base + code.len();
if let Some(jmp) = X64::encode_jmp_rel(jmp_location, continuation) {
code.extend_from_slice(&jmp);
} else {
code.extend_from_slice(&X64::encode_jmp_abs(continuation));
}
Ok(code)
}
#[cfg(target_arch = "x86")]
fn build_x86_wrapper(
stub_base: usize,
detour: usize,
original_addr: usize,
original_bytes: &[u8],
) -> Result<Vec<u8>> {
use crate::manipulation::inline_hook::arch::X86;
let mut code = Vec::with_capacity(128);
code.push(0x9C);
code.push(0x60);
code.push(0x54);
code.push(0xB8); code.extend_from_slice(&(detour as u32).to_le_bytes());
code.extend_from_slice(&[0xFF, 0xD0]);
code.extend_from_slice(&[0x83, 0xC4, 0x04]);
code.push(0x61);
code.push(0x9D);
let mut src_offset = 0;
while src_offset < original_bytes.len() {
let remaining = &original_bytes[src_offset..];
let insn_len = X86::find_instruction_boundary(remaining, 1)
.ok_or_else(|| WraithError::HookDetectionFailed {
function: format!("{:#x}", original_addr + src_offset),
reason: "failed to decode instruction during relocation".into(),
})?;
let instruction = &original_bytes[src_offset..src_offset + insn_len];
let old_addr = original_addr + src_offset;
let new_addr = stub_base + code.len();
if X86::needs_relocation(instruction) {
match X86::relocate_instruction(instruction, old_addr, new_addr) {
Some(relocated) => {
code.extend_from_slice(&relocated);
}
None => {
return Err(WraithError::RelocationFailed {
rva: src_offset as u32,
reason: format!(
"instruction at {:#x} cannot be relocated to {:#x}",
old_addr, new_addr
),
});
}
}
} else {
code.extend_from_slice(instruction);
}
src_offset += insn_len;
}
let continuation = original_addr + original_bytes.len();
let jmp_location = stub_base + code.len();
if let Some(jmp) = X86::encode_jmp_rel(jmp_location, continuation) {
code.extend_from_slice(&jmp);
} else {
code.extend_from_slice(&X86::encode_jmp_abs(continuation as usize));
}
Ok(code)
}
fn flush_icache(address: usize, size: usize) -> Result<()> {
let result = unsafe {
FlushInstructionCache(
GetCurrentProcess(),
address as *const _,
size,
)
};
if result == 0 {
Err(WraithError::from_last_error("FlushInstructionCache"))
} else {
Ok(())
}
}
#[link(name = "kernel32")]
extern "system" {
fn FlushInstructionCache(
hProcess: *mut core::ffi::c_void,
lpBaseAddress: *const core::ffi::c_void,
dwSize: usize,
) -> i32;
fn GetCurrentProcess() -> *mut core::ffi::c_void;
}