use super::thunk;
use crate::error::{Error, Result};
use crate::{pic, util};
use std::{mem, slice};
pub struct Patcher {
patch_area: &'static mut [u8],
original_prolog: Vec<u8>,
detour_prolog: Vec<u8>,
}
impl Patcher {
pub unsafe fn new(target: *const (), detour: *const (), prolog_size: usize) -> Result<Patcher> {
let patch_area = Self::patch_area(target, prolog_size)?;
let emitter = Self::hook_template(detour, patch_area);
let patch_address = patch_area.as_ptr() as *const ();
let original_prolog = patch_area.to_vec();
Ok(Patcher {
detour_prolog: emitter.emit(patch_address),
original_prolog,
patch_area,
})
}
pub fn area(&self) -> &[u8] {
self.patch_area
}
pub unsafe fn toggle(&mut self, enable: bool) {
self.patch_area.copy_from_slice(if enable {
&self.detour_prolog
} else {
&self.original_prolog
});
}
unsafe fn patch_area(target: *const (), prolog_size: usize) -> Result<&'static mut [u8]> {
let jump_rel08_size = mem::size_of::<thunk::x86::JumpShort>();
let jump_rel32_size = mem::size_of::<thunk::x86::JumpRel>();
if !Self::is_patchable(target, prolog_size, jump_rel32_size) {
if Self::is_patchable(target, prolog_size, jump_rel08_size) {
let hot_patch = target as usize - jump_rel32_size;
let hot_patch_area = slice::from_raw_parts(hot_patch as *const u8, jump_rel32_size);
if !Self::is_code_padding(hot_patch_area)
|| !util::is_executable_address(hot_patch_area.as_ptr() as *const _)?
{
Err(Error::NoPatchArea)?;
}
let patch_size = jump_rel32_size + jump_rel08_size;
Ok(slice::from_raw_parts_mut(hot_patch as *mut u8, patch_size))
} else {
Err(Error::NoPatchArea)
}
} else {
Ok(slice::from_raw_parts_mut(
target as *mut u8,
jump_rel32_size,
))
}
}
fn hook_template(detour: *const (), patch_area: &[u8]) -> pic::CodeEmitter {
let mut emitter = pic::CodeEmitter::new();
emitter.add_thunk(thunk::x86::jmp_rel32(detour as usize));
let jump_rel32_size = mem::size_of::<thunk::x86::JumpRel>();
let uses_hot_patch = patch_area.len() > jump_rel32_size;
if uses_hot_patch {
let displacement = -(jump_rel32_size as i8);
emitter.add_thunk(thunk::x86::jmp_rel8(displacement));
}
while emitter.len() < patch_area.len() {
emitter.add_thunk(thunk::x86::nop());
}
emitter
}
unsafe fn is_patchable(target: *const (), prolog_size: usize, patch_size: usize) -> bool {
if prolog_size >= patch_size {
return true;
}
let slice = slice::from_raw_parts(
(target as usize + prolog_size) as *const u8,
patch_size - prolog_size,
);
Self::is_code_padding(slice)
}
fn is_code_padding(buffer: &[u8]) -> bool {
const PADDING: [u8; 3] = [0x00, 0x90, 0xCC];
buffer.iter().all(|code| PADDING.contains(code))
}
}