#[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::trampoline::ExecutableMemory;
use core::marker::PhantomData;
const PAGE_EXECUTE_READWRITE: u32 = 0x40;
struct ChainEntry {
detour: usize,
trampoline: ExecutableMemory,
priority: i32,
}
pub struct HookChain<A: Architecture> {
target: usize,
entries: Vec<ChainEntry>,
original_bytes: Vec<u8>,
prologue_size: usize,
current_hook: Vec<u8>,
_arch: PhantomData<A>,
}
impl<A: Architecture> HookChain<A> {
pub fn new(target: usize) -> Result<Self> {
let min_size = A::MIN_HOOK_SIZE;
let target_bytes = unsafe {
core::slice::from_raw_parts(target as *const u8, 64)
};
let boundary = A::find_instruction_boundary(target_bytes, min_size)
.ok_or_else(|| WraithError::HookDetectionFailed {
function: format!("{:#x}", target),
reason: "failed to find instruction boundary".into(),
})?;
let original_bytes = target_bytes[..boundary].to_vec();
Ok(Self {
target,
entries: Vec::new(),
original_bytes,
prologue_size: boundary,
current_hook: Vec::new(),
_arch: PhantomData,
})
}
pub fn add(&mut self, detour: usize, priority: i32) -> Result<usize> {
let pos = self.entries
.iter()
.position(|e| e.priority > priority)
.unwrap_or(self.entries.len());
let next_target = if pos < self.entries.len() {
self.entries[pos].detour
} else {
self.target
};
let trampoline = self.build_trampoline_to(next_target)?;
let trampoline_addr = trampoline.base();
self.entries.insert(pos, ChainEntry {
detour,
trampoline,
priority,
});
self.rebuild_trampolines_before(pos)?;
self.update_target_hook()?;
Ok(trampoline_addr)
}
pub fn remove(&mut self, detour: usize) -> Result<bool> {
let pos = match self.entries.iter().position(|e| e.detour == detour) {
Some(p) => p,
None => return Ok(false),
};
self.entries.remove(pos);
if self.entries.is_empty() {
self.restore_original()?;
} else {
self.rebuild_all_trampolines()?;
self.update_target_hook()?;
}
Ok(true)
}
pub fn original(&self) -> Option<usize> {
self.entries.last().map(|e| e.trampoline.base())
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn target(&self) -> usize {
self.target
}
pub fn restore(mut self) -> Result<()> {
self.restore_original()?;
Ok(())
}
fn build_trampoline_to(&self, target: usize) -> Result<ExecutableMemory> {
let mut memory = ExecutableMemory::allocate_near(self.target, 64)?;
if target == self.target {
let mut code = Vec::with_capacity(self.prologue_size + A::JMP_ABS_SIZE);
let mut src_offset = 0;
while src_offset < self.prologue_size {
let remaining = &self.original_bytes[src_offset..];
let insn_len = A::find_instruction_boundary(remaining, 1).unwrap_or(1);
let instruction = &self.original_bytes[src_offset..src_offset + insn_len];
if A::needs_relocation(instruction) {
let old_addr = self.target + src_offset;
let new_addr = memory.base() + code.len();
if let Some(relocated) = A::relocate_instruction(instruction, old_addr, new_addr) {
code.extend_from_slice(&relocated);
} else {
code.extend_from_slice(instruction);
}
} else {
code.extend_from_slice(instruction);
}
src_offset += insn_len;
}
let continuation = self.target + self.prologue_size;
let jmp_location = memory.base() + code.len();
if let Some(jmp) = A::encode_jmp_rel(jmp_location, continuation) {
code.extend_from_slice(&jmp);
} else {
code.extend_from_slice(&A::encode_jmp_abs(continuation));
}
memory.write(&code)?;
} else {
let jmp = A::encode_jmp_rel(memory.base(), target)
.unwrap_or_else(|| A::encode_jmp_abs(target));
memory.write(&jmp)?;
}
memory.flush_icache()?;
Ok(memory)
}
fn rebuild_trampolines_before(&mut self, pos: usize) -> Result<()> {
if pos > 0 {
let new_target = self.entries[pos].detour;
let prev = &mut self.entries[pos - 1];
let mut new_tramp = ExecutableMemory::allocate_near(self.target, 64)?;
let jmp = A::encode_jmp_rel(new_tramp.base(), new_target)
.unwrap_or_else(|| A::encode_jmp_abs(new_target));
new_tramp.write(&jmp)?;
new_tramp.flush_icache()?;
prev.trampoline = new_tramp;
}
Ok(())
}
fn rebuild_all_trampolines(&mut self) -> Result<()> {
let len = self.entries.len();
for i in 0..len {
let next_target = if i + 1 < len {
self.entries[i + 1].detour
} else {
self.target };
let new_tramp = self.build_trampoline_to(next_target)?;
self.entries[i].trampoline = new_tramp;
}
Ok(())
}
fn update_target_hook(&mut self) -> Result<()> {
if self.entries.is_empty() {
return self.restore_original();
}
let first_detour = self.entries[0].detour;
let hook_stub = A::encode_jmp_rel(self.target, first_detour)
.unwrap_or_else(|| A::encode_jmp_abs(first_detour));
let mut padded = hook_stub.clone();
if padded.len() < self.prologue_size {
let padding = A::encode_nop_sled(self.prologue_size - padded.len());
padded.extend_from_slice(&padding);
}
{
let _guard = ProtectionGuard::new(
self.target,
self.prologue_size,
PAGE_EXECUTE_READWRITE,
)?;
unsafe {
core::ptr::copy_nonoverlapping(
padded.as_ptr(),
self.target as *mut u8,
self.prologue_size,
);
}
}
flush_icache(self.target, self.prologue_size)?;
self.current_hook = padded;
Ok(())
}
fn restore_original(&mut self) -> Result<()> {
let _guard = ProtectionGuard::new(
self.target,
self.prologue_size,
PAGE_EXECUTE_READWRITE,
)?;
unsafe {
core::ptr::copy_nonoverlapping(
self.original_bytes.as_ptr(),
self.target as *mut u8,
self.prologue_size,
);
}
flush_icache(self.target, self.prologue_size)?;
self.current_hook.clear();
Ok(())
}
}
impl<A: Architecture> Drop for HookChain<A> {
fn drop(&mut self) {
let _ = self.restore_original();
}
}
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;
}