use windows::Win32::Foundation::HANDLE;
use crate::memory::{read_memory_bytes, write_memory_bytes, MemoryError};
use crate::memory_hook::shellcode::ShellcodeBuilder;
use crate::memory_hook::utils::{ProtectionGuard, SendableHandle};
pub struct InlineHook {
handle: SendableHandle,
target_address: usize,
detour_address: usize,
original_bytes: Vec<u8>,
is_installed: bool,
architecture: HookArchitecture,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HookArchitecture {
X86,
X64,
}
impl InlineHook {
pub fn builder() -> InlineHookBuilder {
InlineHookBuilder::new()
}
pub fn new_x86(handle: HANDLE, target_address: usize, detour_address: usize) -> Self {
Self {
handle: SendableHandle(handle),
target_address,
detour_address,
original_bytes: Vec::new(),
is_installed: false,
architecture: HookArchitecture::X86,
}
}
pub fn new_x64(handle: HANDLE, target_address: usize, detour_address: usize) -> Self {
Self {
handle: SendableHandle(handle),
target_address,
detour_address,
original_bytes: Vec::new(),
is_installed: false,
architecture: HookArchitecture::X64,
}
}
pub fn new(handle: HANDLE, target_address: usize, detour_address: usize) -> Self {
Self::new_x64(handle, target_address, detour_address)
}
pub fn set_architecture(&mut self, is_64bit: bool) {
self.architecture = if is_64bit {
HookArchitecture::X64
} else {
HookArchitecture::X86
};
}
pub fn reset(&mut self) {
if self.is_installed {
let _ = self.uninstall();
}
self.original_bytes.clear();
self.is_installed = false;
}
pub fn install(&mut self) -> Result<(), MemoryError> {
if self.is_installed {
return Err(MemoryError::WriteFailed("Hook already installed".to_string()));
}
let required_bytes = match self.architecture {
HookArchitecture::X86 => 5, HookArchitecture::X64 => 14, };
self.original_bytes = read_memory_bytes(self.handle.0, self.target_address, required_bytes)?;
let jump_code = self.generate_jump_code()?;
let _guard = ProtectionGuard::new(self.handle.0, self.target_address, required_bytes)?;
write_memory_bytes(self.handle.0, self.target_address, &jump_code)?;
self.is_installed = true;
Ok(())
}
pub fn uninstall(&mut self) -> Result<(), MemoryError> {
if !self.is_installed {
return Err(MemoryError::WriteFailed("Hook not installed".to_string()));
}
if self.original_bytes.is_empty() {
return Err(MemoryError::WriteFailed("No original bytes to restore".to_string()));
}
let _guard = ProtectionGuard::new(
self.handle.0,
self.target_address,
self.original_bytes.len()
)?;
write_memory_bytes(self.handle.0, self.target_address, &self.original_bytes)?;
self.is_installed = false;
Ok(())
}
pub fn is_installed(&self) -> bool {
self.is_installed
}
pub fn get_original_bytes(&self) -> &[u8] {
&self.original_bytes
}
fn generate_jump_code(&self) -> Result<Vec<u8>, MemoryError> {
let mut builder = match self.architecture {
HookArchitecture::X86 => ShellcodeBuilder::new_x86(),
HookArchitecture::X64 => ShellcodeBuilder::new_x64(),
};
match self.architecture {
HookArchitecture::X86 => {
let offset = (self.detour_address as i64) - (self.target_address as i64) - 5;
if offset >= i32::MIN as i64 && offset <= i32::MAX as i64 {
builder.jmp_relative(self.target_address, self.detour_address);
} else {
builder.jmp_absolute(self.detour_address);
}
}
HookArchitecture::X64 => {
builder.jmp_absolute(self.detour_address);
}
}
Ok(builder.build())
}
}
impl Drop for InlineHook {
fn drop(&mut self) {
if self.is_installed {
let _ = self.uninstall();
}
}
}
#[derive(Debug)]
pub struct InlineHookBuilder {
handle: Option<HANDLE>,
target_address: Option<usize>,
detour_address: Option<usize>,
architecture: Option<HookArchitecture>,
}
impl InlineHookBuilder {
fn new() -> Self {
Self {
handle: None,
target_address: None,
detour_address: None,
architecture: None,
}
}
pub fn handle(mut self, handle: HANDLE) -> Self {
self.handle = Some(handle);
self
}
pub fn target_address(mut self, addr: usize) -> Self {
self.target_address = Some(addr);
self
}
pub fn detour_address(mut self, addr: usize) -> Self {
self.detour_address = Some(addr);
self
}
pub fn architecture(mut self, arch: HookArchitecture) -> Self {
self.architecture = Some(arch);
self
}
pub fn x86(self) -> Self {
self.architecture(HookArchitecture::X86)
}
pub fn x64(self) -> Self {
self.architecture(HookArchitecture::X64)
}
pub fn build(self) -> Result<InlineHook, MemoryError> {
let handle = self.handle.ok_or_else(|| {
MemoryError::WriteFailed(
"handle must be set. Call .handle(handle) before build().".to_string()
)
})?;
let target_address = self.target_address.ok_or_else(|| {
MemoryError::WriteFailed(
"target_address must be set. Call .target_address(addr) before build().".to_string()
)
})?;
let detour_address = self.detour_address.ok_or_else(|| {
MemoryError::WriteFailed(
"detour_address must be set. Call .detour_address(addr) before build().".to_string()
)
})?;
Ok(InlineHook {
handle: SendableHandle(handle),
target_address,
detour_address,
original_bytes: Vec::new(),
is_installed: false,
architecture: self.architecture.unwrap_or(HookArchitecture::X64),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_inline_hook_creation() {
let hook = InlineHook::new_x64(HANDLE::default(), 0x1000, 0x2000);
assert!(!hook.is_installed());
assert_eq!(hook.get_original_bytes().len(), 0);
}
#[test]
fn test_builder_api() {
let result = InlineHook::builder()
.handle(HANDLE::default())
.target_address(0x1000)
.detour_address(0x2000)
.x86()
.build();
assert!(result.is_ok());
let hook = result.unwrap();
assert_eq!(hook.architecture, HookArchitecture::X86);
}
}