#[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;
pub struct HotPatchHook<A: Architecture> {
target: usize,
detour: usize,
_arch: PhantomData<A>,
}
impl<A: Architecture> HotPatchHook<A> {
pub fn new(target: usize, detour: usize) -> Self {
Self {
target,
detour,
_arch: PhantomData,
}
}
pub fn is_patchable(target: usize) -> bool {
let pre_bytes = unsafe {
core::slice::from_raw_parts((target - 5) as *const u8, 5)
};
let entry_bytes = unsafe {
core::slice::from_raw_parts(target as *const u8, 2)
};
let has_padding = pre_bytes.iter().all(|&b| b == 0xCC || b == 0x90);
let has_nop_entry = entry_bytes == [0x8B, 0xFF] || entry_bytes == [0x66, 0x90] || entry_bytes == [0x89, 0xFF];
has_padding && has_nop_entry
}
pub fn install(self) -> Result<HookGuard<A>> {
if !Self::is_patchable(self.target) {
return Err(WraithError::HookDetectionFailed {
function: format!("{:#x}", self.target),
reason: "function is not hot-patchable".into(),
});
}
let original_bytes = unsafe {
let ptr = (self.target - 5) as *const u8;
core::slice::from_raw_parts(ptr, 7).to_vec()
};
let mut trampoline = ExecutableMemory::allocate_near(self.target, 32)?;
let entry_bytes = &original_bytes[5..7];
let mut trampoline_code = Vec::with_capacity(16);
trampoline_code.extend_from_slice(entry_bytes);
let continuation = self.target + 2;
let trampoline_jmp_loc = trampoline.base() + trampoline_code.len();
if let Some(jmp_bytes) = A::encode_jmp_rel(trampoline_jmp_loc, continuation) {
trampoline_code.extend_from_slice(&jmp_bytes);
} else {
let jmp_bytes = A::encode_jmp_abs(continuation);
trampoline_code.extend_from_slice(&jmp_bytes);
}
trampoline.write(&trampoline_code)?;
trampoline.flush_icache()?;
let long_jmp_addr = self.target - 5;
{
let _guard = ProtectionGuard::new(
long_jmp_addr,
7,
PAGE_EXECUTE_READWRITE,
)?;
let long_jmp = A::encode_jmp_rel(long_jmp_addr, self.detour)
.or_else(|| Some(A::encode_jmp_abs(self.detour)))
.unwrap();
unsafe {
core::ptr::copy_nonoverlapping(
long_jmp.as_ptr(),
long_jmp_addr as *mut u8,
5.min(long_jmp.len()),
);
}
unsafe {
let short_jmp: u16 = 0xF9EB; core::ptr::write_volatile(self.target as *mut u16, short_jmp);
}
}
flush_icache(long_jmp_addr, 7)?;
Ok(HookGuard::new(
long_jmp_addr,
self.detour,
original_bytes,
Some(trampoline),
))
}
}
impl<A: Architecture> Hook for HotPatchHook<A> {
type Guard = HookGuard<A>;
fn install(self) -> Result<Self::Guard> {
HotPatchHook::install(self)
}
fn target(&self) -> usize {
self.target
}
fn detour(&self) -> usize {
self.detour
}
}
pub fn is_hot_patchable(target: usize) -> bool {
let pre_bytes = unsafe {
core::slice::from_raw_parts((target - 5) as *const u8, 5)
};
let entry_bytes = unsafe {
core::slice::from_raw_parts(target as *const u8, 2)
};
let has_padding = pre_bytes.iter().all(|&b| b == 0xCC || b == 0x90);
let has_nop_entry = entry_bytes == [0x8B, 0xFF]
|| entry_bytes == [0x66, 0x90]
|| entry_bytes == [0x89, 0xFF];
has_padding && has_nop_entry
}
pub fn hotpatch<A: Architecture>(target: usize, detour: usize) -> Result<HookGuard<A>> {
HotPatchHook::<A>::new(target, detour).install()
}
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;
}