#![allow(clippy::needless_doctest_main)]
mod bindings {
windows::include_bindings!();
}
use std::{
ffi::c_void,
ptr::{
write_bytes,
copy_nonoverlapping,
},
};
use bindings::Windows::Win32::System::Memory::{
MEM_COMMIT,
MEM_RELEASE,
VirtualFree,
MEM_RESERVE,
VirtualAlloc,
VirtualProtect,
PAGE_PROTECTION_FLAGS,
PAGE_EXECUTE_READWRITE,
};
const JUMP_CODES: [u8; 14] = [
0xFF, 0x25, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
];
#[derive(Debug)]
pub enum HookError {
Error(String),
}
trait Hook {
fn unhook(&mut self) -> Result<(), HookError>;
fn is_hooked(&self) -> bool;
}
#[derive(Debug)]
pub struct TrampolineHook32 {
gateway: *mut c_void,
hook: Hook32,
}
impl TrampolineHook32 {
pub fn hook(src: *mut c_void, dst: *mut c_void, len: usize) -> Result<Self, HookError> {
if len < 5 {
return Err(HookError::Error("Len to small".to_owned()));
}
let gateway = unsafe {
VirtualAlloc(
0 as *mut c_void,
len + 5,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
)
};
unsafe { copy_nonoverlapping(src, gateway, len); }
unsafe { *(((gateway as *mut usize) as usize + len) as *mut usize) = 0xE9; }
unsafe {
*(((gateway as *mut usize) as usize + len + 1) as *mut usize) =
(((src as *mut isize) as isize - (gateway as *mut isize) as isize) - 5) as usize;
}
let hook = Hook32::hook(src, dst, len)?;
Ok(Self { gateway, hook })
}
pub fn gateway(&self) -> *mut c_void {
self.gateway
}
}
impl Hook for TrampolineHook32 {
fn unhook(&mut self) -> Result<(), HookError> {
if !self.is_hooked() {
return Ok(());
}
let res = unsafe {
VirtualFree(
self.gateway,
0,
MEM_RELEASE
)
};
if res.as_bool() {
self.hook.unhook()?;
Ok(())
} else {
Err(HookError::Error("Region not freed".to_owned()))
}
}
fn is_hooked(&self) -> bool {
self.hook.is_hooked()
}
}
unsafe impl Sync for TrampolineHook32 { }
unsafe impl Send for TrampolineHook32 { }
impl Drop for TrampolineHook32 {
fn drop(&mut self) {
let _ = self.unhook();
}
}
#[derive(Debug)]
pub struct Hook32 {
src: *mut c_void,
len: usize,
orig_codes: Vec<u8>,
hooked: bool,
}
impl Hook32 {
pub fn hook(src: *mut c_void, dst: *mut c_void, len: usize) -> Result<Self, HookError> {
if len < 5 {
return Err(HookError::Error("Len to small".to_owned()));
}
let mut init_protection = PAGE_PROTECTION_FLAGS::default();
let res = unsafe {
VirtualProtect(
src,
len,
PAGE_EXECUTE_READWRITE,
&mut init_protection
)
};
if !res.as_bool() {
return Err(HookError::Error("Protection not changed".to_owned()));
}
let mut orig_codes: Vec<u8> = vec![0x90; len];
unsafe { copy_nonoverlapping(src, orig_codes.as_mut_ptr() as *mut c_void, len); }
unsafe { write_bytes(src, 0x90, len); }
unsafe { *(src as *mut usize) = 0xE9; }
unsafe {
*(((src as *mut usize) as usize + 1) as *mut usize) =
(((dst as *mut isize) as isize - (src as *mut isize) as isize) - 5) as usize;
}
let res = unsafe {
VirtualProtect(
src,
len,
init_protection,
&mut init_protection
)
};
if res.as_bool() {
Ok(Self { src, len, orig_codes, hooked: true })
} else {
Err(HookError::Error("Protection not changed".to_owned()))
}
}
}
impl Hook for Hook32 {
fn unhook(&mut self) -> Result<(), HookError> {
if !self.hooked {
return Ok(());
}
let mut init_protection = PAGE_PROTECTION_FLAGS::default();
let res = unsafe {
VirtualProtect(
self.src,
self.len,
PAGE_EXECUTE_READWRITE,
&mut init_protection
)
};
if !res.as_bool() {
return Err(HookError::Error("Protection not changed".to_owned()));
}
unsafe {
copy_nonoverlapping(
self.orig_codes.as_ptr() as *mut c_void,
self.src,
self.len
);
}
let res = unsafe {
VirtualProtect(
self.src,
self.len,
init_protection,
&mut init_protection
)
};
if res.as_bool() {
self.hooked = false;
Ok(())
} else {
Err(HookError::Error("Protection not changed".to_owned()))
}
}
fn is_hooked(&self) -> bool {
self.hooked
}
}
unsafe impl Sync for Hook32 { }
unsafe impl Send for Hook32 { }
impl Drop for Hook32 {
fn drop(&mut self) {
let _ = self.unhook();
}
}
#[derive(Debug)]
pub struct TrampolineHook64 {
gateway: *mut c_void,
hook: Hook64,
}
impl TrampolineHook64 {
pub fn hook(src: *mut c_void, dst: *mut c_void, len: usize) -> Result<Self, HookError> {
if len < 14 {
return Err(HookError::Error("Len to small".to_owned()));
}
let mut jump_codes = JUMP_CODES.clone();
let jump_codes_ptr = jump_codes.as_mut_ptr() as *mut c_void;
let gateway = unsafe {
VirtualAlloc(
0 as *mut c_void,
len + jump_codes.len(),
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
)
};
unsafe {
copy_nonoverlapping(
((&((src as usize) + len)) as *const usize) as *mut c_void,
jump_codes_ptr.offset(6),
8
);
}
unsafe { copy_nonoverlapping(src, gateway, len); }
unsafe {
copy_nonoverlapping(
jump_codes_ptr,
((gateway as usize) + len) as *mut c_void,
jump_codes.len()
);
}
let hook = Hook64::hook(src, dst, len)?;
Ok(Self { gateway, hook })
}
pub fn gateway(&self) -> *mut c_void {
self.gateway
}
}
impl Hook for TrampolineHook64 {
fn unhook(&mut self) -> Result<(), HookError> {
if !self.is_hooked() {
return Ok(());
}
let res = unsafe {
VirtualFree(
self.gateway,
0,
MEM_RELEASE
)
};
if res.as_bool() {
self.hook.unhook()?;
Ok(())
} else {
Err(HookError::Error("Region not freed".to_owned()))
}
}
fn is_hooked(&self) -> bool {
self.hook.is_hooked()
}
}
unsafe impl Sync for TrampolineHook64 { }
unsafe impl Send for TrampolineHook64 { }
impl Drop for TrampolineHook64 {
fn drop(&mut self) {
let _ = self.unhook();
}
}
#[derive(Debug)]
pub struct Hook64 {
src: *mut c_void,
len: usize,
orig_codes: Vec<u8>,
hooked: bool,
}
impl Hook64 {
pub fn hook(src: *mut c_void, dst: *mut c_void, len: usize) -> Result<Self, HookError> {
if len < 14 {
return Err(HookError::Error("Len to small".to_owned()));
}
let mut init_protection = PAGE_PROTECTION_FLAGS::default();
let res = unsafe {
VirtualProtect(
src,
len,
PAGE_EXECUTE_READWRITE,
&mut init_protection
)
};
if !res.as_bool() {
return Err(HookError::Error("Protection not changed".to_owned()));
}
let mut orig_codes: Vec<u8> = vec![0x90; len];
unsafe { copy_nonoverlapping(src, orig_codes.as_mut_ptr() as *mut c_void, len); }
unsafe { write_bytes(src, 0x90, len); }
let mut jump_codes = JUMP_CODES.clone();
let jump_codes_ptr = jump_codes.as_mut_ptr() as *mut c_void;
unsafe {
copy_nonoverlapping(
(&(dst as usize) as *const usize) as *mut c_void,
jump_codes_ptr.offset(6),
8
);
}
unsafe { copy_nonoverlapping(jump_codes_ptr, src, jump_codes.len()); }
let res = unsafe {
VirtualProtect(
src,
len,
init_protection,
&mut init_protection
)
};
if res.as_bool() {
Ok(Self { src, len, orig_codes, hooked: true })
} else {
Err(HookError::Error("Protection not changed".to_owned()))
}
}
}
impl Hook for Hook64 {
fn unhook(&mut self) -> Result<(), HookError> {
if !self.hooked {
return Ok(());
}
let mut init_protection = PAGE_PROTECTION_FLAGS::default();
let res = unsafe {
VirtualProtect(
self.src,
self.len,
PAGE_EXECUTE_READWRITE,
&mut init_protection
)
};
if !res.as_bool() {
return Err(HookError::Error("Protection not changed".to_owned()));
}
unsafe {
copy_nonoverlapping(
self.orig_codes.as_ptr() as *mut c_void,
self.src,
self.len
);
}
let res = unsafe {
VirtualProtect(
self.src,
self.len,
init_protection,
&mut init_protection
)
};
if res.as_bool() {
self.hooked = false;
Ok(())
} else {
Err(HookError::Error("Protection not changed".to_owned()))
}
}
fn is_hooked(&self) -> bool {
self.hooked
}
}
unsafe impl Sync for Hook64 { }
unsafe impl Send for Hook64 { }
impl Drop for Hook64 {
fn drop(&mut self) {
let _ = self.unhook();
}
}