#![cfg_attr(not(feature = "std"), no_std)]
use core::{
cell::UnsafeCell,
marker::PhantomData,
mem::MaybeUninit,
ops::{Deref, DerefMut},
sync::atomic::AtomicBool,
};
use hooker::gen_hook_info;
use thiserror_no_std::Error;
use windows_sys::{
core::PCSTR,
Win32::{
Foundation::{FreeLibrary, GetLastError, HMODULE},
System::{
Diagnostics::Debug::WriteProcessMemory,
LibraryLoader::{GetProcAddress, LoadLibraryA},
Memory::{
VirtualAlloc, VirtualFree, VirtualProtect, MEM_COMMIT, MEM_RELEASE, PAGE_EXECUTE,
PAGE_PROTECTION_FLAGS, PAGE_READWRITE,
},
ProcessStatus::{GetModuleInformation, MODULEINFO},
Threading::GetCurrentProcess,
},
},
};
struct SingleLock<T> {
value: UnsafeCell<T>,
is_locked: AtomicBool,
}
impl<T> SingleLock<T> {
const fn new(value: T) -> Self {
Self {
value: UnsafeCell::new(value),
is_locked: AtomicBool::new(false),
}
}
fn lock(&self) -> Option<SingleLockGuard<T>> {
if self
.is_locked
.swap(true, core::sync::atomic::Ordering::AcqRel)
{
return None;
}
Some(SingleLockGuard { lock: self })
}
}
unsafe impl<T> Send for SingleLock<T> {}
unsafe impl<T> Sync for SingleLock<T> {}
struct SingleLockGuard<'a, T> {
lock: &'a SingleLock<T>,
}
impl<'a, T> Drop for SingleLockGuard<'a, T> {
fn drop(&mut self) {
self.lock
.is_locked
.store(false, core::sync::atomic::Ordering::Release)
}
}
impl<'a, T> Deref for SingleLockGuard<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.lock.value.get() }
}
}
impl<'a, T> DerefMut for SingleLockGuard<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.lock.value.get() }
}
}
pub struct StaticHook<F: Copy> {
hook: SingleLock<Option<Hook>>,
phantom: PhantomData<F>,
}
impl<F: Copy> StaticHook<F> {
const HOOK_USED_MULTIPLE_TIMES_ERR_MSG: &'static str = "static hook used multiple times";
pub const fn new() -> Self {
Self {
hook: SingleLock::new(None),
phantom: PhantomData,
}
}
fn lock_hook(&self) -> SingleLockGuard<Option<Hook>> {
self.hook
.lock()
.expect(Self::HOOK_USED_MULTIPLE_TIMES_ERR_MSG)
}
fn lock_hook_and_assert_empty(&self) -> SingleLockGuard<Option<Hook>> {
let hook = self.lock_hook();
if hook.is_some() {
panic!("{}", Self::HOOK_USED_MULTIPLE_TIMES_ERR_MSG)
}
hook
}
pub unsafe fn hook_function(
&self,
module: HMODULE,
fn_addr: usize,
hook_to_addr: usize,
) -> Result<()> {
let mut hook = self.lock_hook_and_assert_empty();
let created_hook = hook_function(module, fn_addr, hook_to_addr)?;
*hook = Some(created_hook);
Ok(())
}
pub fn hook_function_by_name(
&self,
library_name: PCSTR,
fn_name: PCSTR,
hook_to_addr: usize,
) -> Result<()> {
let mut hook = self.lock_hook_and_assert_empty();
let created_hook = hook_function_by_name(library_name, fn_name, hook_to_addr)?;
*hook = Some(created_hook);
Ok(())
}
pub fn get_hook(&self) -> &Hook {
let hook_guard = self.lock_hook();
let hook_opt = unsafe { &*(hook_guard.deref() as *const Option<Hook>) };
hook_opt
.as_ref()
.expect("static hook used before hooking any function")
}
pub fn original(&self) -> F {
let hook = self.get_hook();
unsafe { hook.original() }
}
}
struct ModuleHandleGuard(HMODULE);
impl Drop for ModuleHandleGuard {
fn drop(&mut self) {
let _ = unsafe { FreeLibrary(self.0) };
}
}
pub fn hook_function_by_name(
library_name: PCSTR,
fn_name: PCSTR,
hook_to_addr: usize,
) -> Result<Hook> {
let load_library_res = unsafe { LoadLibraryA(library_name) };
if load_library_res == 0 {
return Err(Error::FailedToLoadLibrary(WinapiError::last_error()));
}
let module_guard = ModuleHandleGuard(load_library_res);
let fn_addr =
unsafe { GetProcAddress(module_guard.0, fn_name).ok_or(Error::NoFunctionWithThatName)? };
hook_function(module_guard.0, fn_addr as usize, hook_to_addr)
}
pub fn hook_function(module: HMODULE, fn_addr: usize, hook_to_addr: usize) -> Result<Hook> {
let mut module_info_uninit: MaybeUninit<MODULEINFO> = MaybeUninit::uninit();
let res = unsafe {
GetModuleInformation(
GetCurrentProcess(),
module,
module_info_uninit.as_mut_ptr(),
core::mem::size_of::<MODULEINFO>() as u32,
)
};
if res == 0 {
return Err(Error::FailedToGetModuleInformation(
WinapiError::last_error(),
));
}
let module_info = unsafe { module_info_uninit.assume_init() };
let module_end_addr = module_info.lpBaseOfDll as usize + module_info.SizeOfImage as usize;
let fn_max_possible_size = module_end_addr - fn_addr;
let fn_possible_content =
unsafe { core::slice::from_raw_parts(fn_addr as *const u8, fn_max_possible_size) };
let hook_info = gen_hook_info(fn_possible_content, fn_addr as u64, hook_to_addr as u64)?;
let mut trampiline_alloc = Allocation::new(hook_info.trampoline_size());
let trampoline_code = hook_info.build_trampoline(trampiline_alloc.ptr as u64);
let trampoline_alloc_slice = unsafe { trampiline_alloc.as_mut_slice() };
trampoline_alloc_slice[..trampoline_code.len()].copy_from_slice(&trampoline_code);
trampiline_alloc.make_executable_and_read_only();
let jumper_code = hook_info.jumper();
let mut bytes_written = 0;
let res = unsafe {
WriteProcessMemory(
GetCurrentProcess(),
fn_addr as *const _,
jumper_code.as_ptr().cast(),
jumper_code.len(),
&mut bytes_written,
)
};
assert_ne!(res, 0, "failed to write jumper");
assert_eq!(
bytes_written,
jumper_code.len(),
"not all bytes of jumper were written to the start of the function"
);
Ok(Hook {
trampoline: trampiline_alloc,
fn_addr,
hook_to_addr,
})
}
pub struct Allocation {
ptr: *mut u8,
size: usize,
}
impl Allocation {
fn new(size: usize) -> Self {
let ptr = unsafe { VirtualAlloc(core::ptr::null(), size, MEM_COMMIT, PAGE_READWRITE) };
if ptr.is_null() {
panic!("failed to allocate read-write memory using VirtualAlloc");
}
Self {
ptr: ptr.cast(),
size,
}
}
fn make_executable_and_read_only(&mut self) {
let mut old_prot: MaybeUninit<PAGE_PROTECTION_FLAGS> = MaybeUninit::uninit();
let res = unsafe {
VirtualProtect(
self.ptr.cast(),
self.size,
PAGE_EXECUTE,
old_prot.as_mut_ptr(),
)
};
assert_ne!(res, 0, "failed to change memory protection to executable");
}
unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe { core::slice::from_raw_parts_mut(self.ptr, self.size) }
}
pub fn as_ptr(&self) -> *const u8 {
self.ptr
}
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.ptr
}
pub fn size(&self) -> usize {
self.size
}
}
impl Drop for Allocation {
fn drop(&mut self) {
unsafe {
let _ = VirtualFree(self.ptr.cast(), 0, MEM_RELEASE);
}
}
}
pub struct Hook {
trampoline: Allocation,
fn_addr: usize,
hook_to_addr: usize,
}
impl Hook {
pub fn original_addr(&self) -> usize {
self.trampoline.ptr as usize
}
pub unsafe fn original<F: Copy>(&self) -> F {
assert!(
core::mem::size_of::<F>() == core::mem::size_of::<usize>()
&& core::mem::align_of::<F>() == core::mem::align_of::<usize>(),
"provided function signature type {} is not a function pointer",
core::any::type_name::<F>()
);
let trampoline_ptr = self.trampoline.ptr;
core::mem::transmute_copy(&trampoline_ptr)
}
pub fn fn_addr(&self) -> usize {
self.fn_addr
}
pub fn hook_to_addr(&self) -> usize {
self.hook_to_addr
}
pub fn trampoline(&self) -> &Allocation {
&self.trampoline
}
pub fn into_trampoline(self) -> Allocation {
self.trampoline
}
}
#[derive(Debug, Error)]
#[error("winapi error code 0x{0:x}")]
pub struct WinapiError(pub u32);
impl WinapiError {
pub fn last_error() -> Self {
Self(unsafe { GetLastError() })
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("failed to get module information")]
FailedToGetModuleInformation(#[source] WinapiError),
#[error("failed to load library")]
FailedToLoadLibrary(#[source] WinapiError),
#[error("no function with the provided name exists in the specified library")]
NoFunctionWithThatName,
#[error("failed to generate hook info")]
FailedToGenHookInfo(
#[source]
#[from]
hooker::HookError,
),
}
pub type Result<T> = core::result::Result<T, Error>;