#[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, StatefulHookGuard};
use crate::manipulation::inline_hook::trampoline::TrampolineBuilder;
use super::Hook;
use core::marker::PhantomData;
const PAGE_EXECUTE_READWRITE: u32 = 0x40;
pub struct InlineHook<A: Architecture> {
target: usize,
detour: usize,
_arch: PhantomData<A>,
}
impl<A: Architecture> InlineHook<A> {
pub fn new(target: usize, detour: usize) -> Self {
Self {
target,
detour,
_arch: PhantomData,
}
}
fn hook_size(&self) -> usize {
A::preferred_hook_size(self.target, self.detour)
}
fn generate_hook_stub(&self) -> Vec<u8> {
if let Some(bytes) = A::encode_jmp_rel(self.target, self.detour) {
bytes
} else {
A::encode_jmp_abs(self.detour)
}
}
pub fn install(self) -> Result<HookGuard<A>> {
let hook_size = self.hook_size();
let mut builder = TrampolineBuilder::<A>::new(self.target);
builder.analyze(hook_size)?;
builder.allocate()?;
builder.build()?;
let prologue_bytes = builder.prologue_bytes().to_vec();
let prologue_size = builder.prologue_size();
let trampoline_memory = builder.take_memory();
let hook_stub = self.generate_hook_stub();
if prologue_size < hook_stub.len() {
return Err(WraithError::HookDetectionFailed {
function: format!("{:#x}", self.target),
reason: format!(
"insufficient space: need {} bytes, have {}",
hook_stub.len(),
prologue_size
),
});
}
let mut final_stub = hook_stub;
if final_stub.len() < prologue_size {
let padding = A::encode_nop_sled(prologue_size - final_stub.len());
final_stub.extend_from_slice(&padding);
}
{
let _guard = ProtectionGuard::new(
self.target,
prologue_size,
PAGE_EXECUTE_READWRITE,
)?;
unsafe {
core::ptr::copy_nonoverlapping(
final_stub.as_ptr(),
self.target as *mut u8,
prologue_size,
);
}
}
flush_icache(self.target, prologue_size)?;
Ok(HookGuard::new(
self.target,
self.detour,
prologue_bytes,
trampoline_memory,
))
}
pub fn install_stateful(self) -> Result<StatefulHookGuard<A>> {
let hook_size = self.hook_size();
let mut builder = TrampolineBuilder::<A>::new(self.target);
builder.analyze(hook_size)?;
builder.allocate()?;
builder.build()?;
let prologue_bytes = builder.prologue_bytes().to_vec();
let prologue_size = builder.prologue_size();
let trampoline_memory = builder.take_memory();
let hook_stub = self.generate_hook_stub();
if prologue_size < hook_stub.len() {
return Err(WraithError::HookDetectionFailed {
function: format!("{:#x}", self.target),
reason: format!(
"insufficient space: need {} bytes, have {}",
hook_stub.len(),
prologue_size
),
});
}
let mut final_stub = hook_stub;
if final_stub.len() < prologue_size {
let padding = A::encode_nop_sled(prologue_size - final_stub.len());
final_stub.extend_from_slice(&padding);
}
{
let _guard = ProtectionGuard::new(
self.target,
prologue_size,
PAGE_EXECUTE_READWRITE,
)?;
unsafe {
core::ptr::copy_nonoverlapping(
final_stub.as_ptr(),
self.target as *mut u8,
prologue_size,
);
}
}
flush_icache(self.target, prologue_size)?;
let guard = HookGuard::new(
self.target,
self.detour,
prologue_bytes,
trampoline_memory,
);
Ok(StatefulHookGuard::new(guard, final_stub))
}
}
impl<A: Architecture> Hook for InlineHook<A> {
type Guard = HookGuard<A>;
fn install(self) -> Result<Self::Guard> {
InlineHook::install(self)
}
fn target(&self) -> usize {
self.target
}
fn detour(&self) -> usize {
self.detour
}
}
pub fn hook<A: Architecture>(target: usize, detour: usize) -> Result<HookGuard<A>> {
InlineHook::<A>::new(target, detour).install()
}
#[cfg(target_arch = "x86_64")]
pub fn hook_native(target: usize, detour: usize) -> Result<HookGuard<crate::manipulation::inline_hook::arch::X64>> {
hook::<crate::manipulation::inline_hook::arch::X64>(target, detour)
}
#[cfg(target_arch = "x86")]
pub fn hook_native(target: usize, detour: usize) -> Result<HookGuard<crate::manipulation::inline_hook::arch::X86>> {
hook::<crate::manipulation::inline_hook::arch::X86>(target, detour)
}
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;
}