mirror of
https://github.com/ApfelTeeSaft/Tetanus.git
synced 2026-08-26 19:33:41 +00:00
Initial Release
This commit is contained in:
+22
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "tetanus"
|
||||
version = "1.0.0"
|
||||
edition = "2026"
|
||||
|
||||
[lib]
|
||||
name = "tetanus"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
iced-x86 = { version = "1.21.0", default-features = false, features = ["decoder", "encoder"] }
|
||||
windows-sys = { version = "0.52.0", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_Diagnostics_Debug",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_Threading"
|
||||
] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
@@ -0,0 +1,55 @@
|
||||
# Tetanus
|
||||
|
||||
## Windows Detouring Library in Rust
|
||||
|
||||
### x86 · x64 · ARM · ARM64
|
||||
|
||||
**Tetanus** is a lightweight **Windows user-mode function hooking library written in Rust**, providing **inline detours and related hooking techniques** across all major Windows architectures.
|
||||
|
||||
Inspired by **MinHook**, Tetanus reimplements its core behavior in **idiomatic Rust**, with added **ARM support** and a modular, safety-focused design.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
* Inline detours with trampolines (MinHook-like)
|
||||
* Enable / disable hooks at runtime
|
||||
* Original function access via trampoline
|
||||
* Architecture-aware instruction patching
|
||||
* Safe for use in injected or external DLLs
|
||||
|
||||
### Supported Architectures
|
||||
|
||||
* x86 (32-bit)
|
||||
* x64 (64-bit)
|
||||
* ARM32 (Windows on ARM)
|
||||
* ARM64 (Windows on ARM64)
|
||||
|
||||
### Hooking Techniques
|
||||
|
||||
* Inline detours
|
||||
* IAT (Import Address Table) hooking
|
||||
* EAT (Export Address Table) hooking
|
||||
* VEH (Vectored Exception Handler) hooks
|
||||
|
||||
---
|
||||
|
||||
## Design Goals
|
||||
|
||||
* Correctness over cleverness
|
||||
* Explicit and well-documented `unsafe` code
|
||||
* Clear hook lifecycle management
|
||||
* No MinHook source reuse
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
**BSD 2-Clause License**
|
||||
|
||||
---
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This is a low-level library. Incorrect usage can crash the target process.
|
||||
Use responsibly.
|
||||
@@ -0,0 +1,65 @@
|
||||
use std::ffi::c_void;
|
||||
use std::slice;
|
||||
|
||||
use crate::arch::{ArchHook, DetourPlan, TrampolineLayout};
|
||||
use crate::status::TetanusStatus;
|
||||
|
||||
pub struct Arm32;
|
||||
|
||||
impl Arm32 {
|
||||
fn make_abs_jump(detour: *const c_void) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(8);
|
||||
bytes.extend_from_slice(&0xE51FF004u32.to_le_bytes());
|
||||
bytes.extend_from_slice(&(detour as u32).to_le_bytes());
|
||||
bytes
|
||||
}
|
||||
|
||||
fn make_trampoline_jump(back: *const c_void) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(8);
|
||||
bytes.extend_from_slice(&0xE51FF004u32.to_le_bytes());
|
||||
bytes.extend_from_slice(&(back as u32).to_le_bytes());
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchHook for Arm32 {
|
||||
fn plan_detour(_target: *const c_void, detour: *const c_void) -> Result<DetourPlan, TetanusStatus> {
|
||||
let detour_bytes = Self::make_abs_jump(detour);
|
||||
Ok(DetourPlan {
|
||||
patch_size: detour_bytes.len(),
|
||||
detour_bytes,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_trampoline(
|
||||
target: *const c_void,
|
||||
trampoline: *mut c_void,
|
||||
original: &[u8],
|
||||
) -> Result<TrampolineLayout, TetanusStatus> {
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(original.as_ptr(), trampoline as *mut u8, original.len());
|
||||
}
|
||||
let back = unsafe { (target as *const u8).add(original.len()) };
|
||||
let offset = original.len();
|
||||
let exit_size = Self::write_trampoline_exit(
|
||||
unsafe { (trampoline as *mut u8).add(offset) },
|
||||
back as *const c_void,
|
||||
)?;
|
||||
Ok(TrampolineLayout {
|
||||
size: offset + exit_size,
|
||||
exit_offset: offset,
|
||||
exit_size,
|
||||
})
|
||||
}
|
||||
|
||||
fn write_trampoline_exit(
|
||||
trampoline: *mut u8,
|
||||
target: *const c_void,
|
||||
) -> Result<usize, TetanusStatus> {
|
||||
let jump = Self::make_trampoline_jump(target);
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(trampoline, jump.len()).copy_from_slice(&jump);
|
||||
}
|
||||
Ok(jump.len())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use std::ffi::c_void;
|
||||
use std::slice;
|
||||
|
||||
use crate::arch::{ArchHook, DetourPlan, TrampolineLayout};
|
||||
use crate::status::TetanusStatus;
|
||||
|
||||
pub struct Arm64;
|
||||
|
||||
impl Arm64 {
|
||||
fn make_abs_jump(detour: *const c_void) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(16);
|
||||
bytes.extend_from_slice(&0x58000071u32.to_le_bytes());
|
||||
bytes.extend_from_slice(&0xD61F0220u32.to_le_bytes());
|
||||
bytes.extend_from_slice(&(detour as u64).to_le_bytes());
|
||||
bytes
|
||||
}
|
||||
|
||||
fn make_trampoline_jump(back: *const c_void) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(16);
|
||||
bytes.extend_from_slice(&0x58000071u32.to_le_bytes());
|
||||
bytes.extend_from_slice(&0xD61F0220u32.to_le_bytes());
|
||||
bytes.extend_from_slice(&(back as u64).to_le_bytes());
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchHook for Arm64 {
|
||||
fn plan_detour(_target: *const c_void, detour: *const c_void) -> Result<DetourPlan, TetanusStatus> {
|
||||
let detour_bytes = Self::make_abs_jump(detour);
|
||||
Ok(DetourPlan {
|
||||
patch_size: detour_bytes.len(),
|
||||
detour_bytes,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_trampoline(
|
||||
target: *const c_void,
|
||||
trampoline: *mut c_void,
|
||||
original: &[u8],
|
||||
) -> Result<TrampolineLayout, TetanusStatus> {
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(original.as_ptr(), trampoline as *mut u8, original.len());
|
||||
}
|
||||
let back = unsafe { (target as *const u8).add(original.len()) };
|
||||
let offset = original.len();
|
||||
let exit_size = Self::write_trampoline_exit(
|
||||
unsafe { (trampoline as *mut u8).add(offset) },
|
||||
back as *const c_void,
|
||||
)?;
|
||||
Ok(TrampolineLayout {
|
||||
size: offset + exit_size,
|
||||
exit_offset: offset,
|
||||
exit_size,
|
||||
})
|
||||
}
|
||||
|
||||
fn write_trampoline_exit(
|
||||
trampoline: *mut u8,
|
||||
target: *const c_void,
|
||||
) -> Result<usize, TetanusStatus> {
|
||||
let jump = Self::make_trampoline_jump(target);
|
||||
unsafe {
|
||||
slice::from_raw_parts_mut(trampoline, jump.len()).copy_from_slice(&jump);
|
||||
}
|
||||
Ok(jump.len())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
use std::ffi::c_void;
|
||||
|
||||
use crate::status::TetanusStatus;
|
||||
|
||||
pub struct DetourPlan {
|
||||
pub patch_size: usize,
|
||||
pub detour_bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct TrampolineLayout {
|
||||
pub size: usize,
|
||||
pub exit_offset: usize,
|
||||
pub exit_size: usize,
|
||||
}
|
||||
|
||||
pub trait ArchHook {
|
||||
fn plan_detour(target: *const c_void, detour: *const c_void) -> Result<DetourPlan, TetanusStatus>;
|
||||
fn build_trampoline(
|
||||
target: *const c_void,
|
||||
trampoline: *mut c_void,
|
||||
original: &[u8],
|
||||
) -> Result<TrampolineLayout, TetanusStatus>;
|
||||
fn write_trampoline_exit(
|
||||
trampoline: *mut u8,
|
||||
target: *const c_void,
|
||||
) -> Result<usize, TetanusStatus>;
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
pub mod x86;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
pub mod x64;
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub mod arm32;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod arm64;
|
||||
|
||||
pub fn arch_plan(target: *const c_void, detour: *const c_void) -> Result<DetourPlan, TetanusStatus> {
|
||||
#[cfg(target_arch = "x86")]
|
||||
return x86::X86::plan_detour(target, detour);
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
return x64::X64::plan_detour(target, detour);
|
||||
#[cfg(target_arch = "arm")]
|
||||
return arm32::Arm32::plan_detour(target, detour);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
return arm64::Arm64::plan_detour(target, detour);
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm", target_arch = "aarch64")))]
|
||||
{
|
||||
let _ = (target, detour);
|
||||
Err(TetanusStatus::UnsupportedArchitecture)
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
use std::ffi::c_void;
|
||||
use std::slice;
|
||||
|
||||
use iced_x86::{Decoder, DecoderOptions, Encoder, Instruction, OpKind};
|
||||
|
||||
use crate::arch::{ArchHook, DetourPlan, TrampolineLayout};
|
||||
use crate::status::TetanusStatus;
|
||||
|
||||
pub struct X64;
|
||||
|
||||
impl X64 {
|
||||
fn required_patch_size(target: *const c_void, min_len: usize) -> Result<usize, TetanusStatus> {
|
||||
let mut decoder = unsafe {
|
||||
Decoder::new(64, slice::from_raw_parts(target as *const u8, 64), DecoderOptions::NONE)
|
||||
};
|
||||
let mut total = 0usize;
|
||||
while total < min_len {
|
||||
let instr = decoder.decode();
|
||||
if instr.is_invalid() {
|
||||
return Err(TetanusStatus::InstructionDecodeFailed);
|
||||
}
|
||||
total += instr.len();
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
fn make_abs_jmp(detour: *const c_void, patch_size: usize) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(patch_size);
|
||||
bytes.extend_from_slice(&[0x48, 0xB8]);
|
||||
bytes.extend_from_slice(&(detour as u64).to_le_bytes());
|
||||
bytes.extend_from_slice(&[0xFF, 0xE0]);
|
||||
while bytes.len() < patch_size {
|
||||
bytes.push(0x90);
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
fn relocate_instruction(
|
||||
encoder: &mut Encoder,
|
||||
instr: &mut Instruction,
|
||||
ip: u64,
|
||||
) -> Result<(), TetanusStatus> {
|
||||
if instr.is_ip_rel_memory_operand() {
|
||||
let target = instr.ip_rel_memory_address();
|
||||
instr.set_ip_rel_memory_address(target);
|
||||
}
|
||||
if instr.is_near_branch() {
|
||||
let target = instr.near_branch_target();
|
||||
instr.set_near_branch64(target);
|
||||
}
|
||||
if instr.op0_kind() == OpKind::NearBranch64 || instr.op0_kind() == OpKind::NearBranch32 {
|
||||
let target = instr.near_branch_target();
|
||||
instr.set_near_branch64(target);
|
||||
}
|
||||
encoder
|
||||
.encode(instr, ip)
|
||||
.map_err(|_| TetanusStatus::InstructionDecodeFailed)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchHook for X64 {
|
||||
fn plan_detour(target: *const c_void, detour: *const c_void) -> Result<DetourPlan, TetanusStatus> {
|
||||
let patch_size = Self::required_patch_size(target, 12)?;
|
||||
let detour_bytes = Self::make_abs_jmp(detour, patch_size);
|
||||
Ok(DetourPlan {
|
||||
patch_size,
|
||||
detour_bytes,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_trampoline(
|
||||
target: *const c_void,
|
||||
trampoline: *mut c_void,
|
||||
original: &[u8],
|
||||
) -> Result<TrampolineLayout, TetanusStatus> {
|
||||
let mut decoder = Decoder::new(64, original, DecoderOptions::NONE);
|
||||
decoder.set_ip(target as u64);
|
||||
let mut encoder = Encoder::new(64);
|
||||
encoder.set_buffer(unsafe { slice::from_raw_parts_mut(trampoline as *mut u8, 256) });
|
||||
let mut written = 0usize;
|
||||
let mut ip = trampoline as u64;
|
||||
while decoder.can_decode() && written < original.len() {
|
||||
let mut instr = decoder.decode();
|
||||
if instr.is_invalid() {
|
||||
return Err(TetanusStatus::InstructionDecodeFailed);
|
||||
}
|
||||
Self::relocate_instruction(&mut encoder, &mut instr, ip)?;
|
||||
let len = encoder.len();
|
||||
ip += len as u64;
|
||||
written = decoder.position();
|
||||
}
|
||||
let exit_offset = encoder.len();
|
||||
let exit_size = Self::write_trampoline_exit(
|
||||
unsafe { (trampoline as *mut u8).add(exit_offset) },
|
||||
unsafe { (target as *const u8).add(original.len()) as *const c_void },
|
||||
)?;
|
||||
Ok(TrampolineLayout {
|
||||
size: exit_offset + exit_size,
|
||||
exit_offset,
|
||||
exit_size,
|
||||
})
|
||||
}
|
||||
|
||||
fn write_trampoline_exit(
|
||||
trampoline: *mut u8,
|
||||
target: *const c_void,
|
||||
) -> Result<usize, TetanusStatus> {
|
||||
unsafe {
|
||||
*trampoline = 0x48;
|
||||
*trampoline.add(1) = 0xB8;
|
||||
std::ptr::copy_nonoverlapping((target as u64).to_le_bytes().as_ptr(), trampoline.add(2), 8);
|
||||
*trampoline.add(10) = 0xFF;
|
||||
*trampoline.add(11) = 0xE0;
|
||||
}
|
||||
Ok(12)
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
use std::ffi::c_void;
|
||||
use std::slice;
|
||||
|
||||
use iced_x86::{Decoder, DecoderOptions, Encoder, Instruction, OpKind};
|
||||
|
||||
use crate::arch::{ArchHook, DetourPlan, TrampolineLayout};
|
||||
use crate::status::TetanusStatus;
|
||||
|
||||
pub struct X86;
|
||||
|
||||
impl X86 {
|
||||
fn required_patch_size(target: *const c_void, min_len: usize) -> Result<usize, TetanusStatus> {
|
||||
let mut decoder = unsafe {
|
||||
Decoder::new(32, slice::from_raw_parts(target as *const u8, 64), DecoderOptions::NONE)
|
||||
};
|
||||
let mut total = 0usize;
|
||||
while total < min_len {
|
||||
let instr = decoder.decode();
|
||||
if instr.is_invalid() {
|
||||
return Err(TetanusStatus::InstructionDecodeFailed);
|
||||
}
|
||||
total += instr.len();
|
||||
}
|
||||
Ok(total)
|
||||
}
|
||||
|
||||
fn make_jmp(detour: *const c_void, origin: *const c_void, patch_size: usize) -> Vec<u8> {
|
||||
let mut bytes = Vec::with_capacity(patch_size);
|
||||
let origin = origin as usize;
|
||||
let detour = detour as usize;
|
||||
let rel = detour.wrapping_sub(origin + 5) as i32;
|
||||
bytes.push(0xE9);
|
||||
bytes.extend_from_slice(&rel.to_le_bytes());
|
||||
while bytes.len() < patch_size {
|
||||
bytes.push(0x90);
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
fn relocate_instruction(
|
||||
encoder: &mut Encoder,
|
||||
instr: &mut Instruction,
|
||||
ip: u64,
|
||||
) -> Result<(), TetanusStatus> {
|
||||
if instr.is_ip_rel_memory_operand() {
|
||||
let target = instr.ip_rel_memory_address();
|
||||
instr.set_ip_rel_memory_address(target);
|
||||
}
|
||||
if instr.is_near_branch() {
|
||||
let target = instr.near_branch_target();
|
||||
instr.set_near_branch64(target);
|
||||
}
|
||||
if instr.op0_kind() == OpKind::NearBranch64 || instr.op0_kind() == OpKind::NearBranch32 {
|
||||
let target = instr.near_branch_target();
|
||||
instr.set_near_branch64(target);
|
||||
}
|
||||
encoder
|
||||
.encode(instr, ip)
|
||||
.map_err(|_| TetanusStatus::InstructionDecodeFailed)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ArchHook for X86 {
|
||||
fn plan_detour(target: *const c_void, detour: *const c_void) -> Result<DetourPlan, TetanusStatus> {
|
||||
let patch_size = Self::required_patch_size(target, 5)?;
|
||||
let detour_bytes = Self::make_jmp(detour, target, patch_size);
|
||||
Ok(DetourPlan {
|
||||
patch_size,
|
||||
detour_bytes,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_trampoline(
|
||||
target: *const c_void,
|
||||
trampoline: *mut c_void,
|
||||
original: &[u8],
|
||||
) -> Result<TrampolineLayout, TetanusStatus> {
|
||||
let mut decoder = Decoder::new(32, original, DecoderOptions::NONE);
|
||||
decoder.set_ip(target as u64);
|
||||
let mut encoder = Encoder::new(32);
|
||||
encoder.set_buffer(unsafe { slice::from_raw_parts_mut(trampoline as *mut u8, 128) });
|
||||
let mut written = 0usize;
|
||||
let mut ip = trampoline as u64;
|
||||
while decoder.can_decode() && written < original.len() {
|
||||
let mut instr = decoder.decode();
|
||||
if instr.is_invalid() {
|
||||
return Err(TetanusStatus::InstructionDecodeFailed);
|
||||
}
|
||||
Self::relocate_instruction(&mut encoder, &mut instr, ip)?;
|
||||
let len = encoder.len();
|
||||
ip += len as u64;
|
||||
written = decoder.position();
|
||||
}
|
||||
let exit_offset = encoder.len();
|
||||
let exit_size = Self::write_trampoline_exit(
|
||||
unsafe { (trampoline as *mut u8).add(exit_offset) },
|
||||
unsafe { (target as *const u8).add(original.len()) as *const c_void },
|
||||
)?;
|
||||
Ok(TrampolineLayout {
|
||||
size: exit_offset + exit_size,
|
||||
exit_offset,
|
||||
exit_size,
|
||||
})
|
||||
}
|
||||
|
||||
fn write_trampoline_exit(
|
||||
trampoline: *mut u8,
|
||||
target: *const c_void,
|
||||
) -> Result<usize, TetanusStatus> {
|
||||
let jump_origin = trampoline as usize;
|
||||
let jump_target = target as usize;
|
||||
let rel = jump_target.wrapping_sub(jump_origin + 5) as i32;
|
||||
unsafe {
|
||||
*trampoline = 0xE9;
|
||||
std::ptr::copy_nonoverlapping(rel.to_le_bytes().as_ptr(), trampoline.add(1), 4);
|
||||
}
|
||||
Ok(5)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use std::ffi::c_void;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::hook::HookState;
|
||||
|
||||
static LOG_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HookInfo {
|
||||
pub target: *const c_void,
|
||||
pub detour: *const c_void,
|
||||
pub state: HookState,
|
||||
pub priority: i32,
|
||||
}
|
||||
|
||||
pub fn set_logging(enabled: bool) {
|
||||
LOG_ENABLED.store(enabled, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn log(message: &str) {
|
||||
if LOG_ENABLED.load(Ordering::Relaxed) {
|
||||
eprintln!("[tetanus] {message}");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn architecture() -> &'static str {
|
||||
#[cfg(target_arch = "x86")]
|
||||
return "x86";
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
return "x64";
|
||||
#[cfg(target_arch = "arm")]
|
||||
return "arm32";
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
return "arm64";
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm", target_arch = "aarch64")))]
|
||||
"unknown"
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
use windows_sys::Win32::System::Memory::VirtualProtect;
|
||||
use windows_sys::Win32::System::SystemServices::{
|
||||
IMAGE_DIRECTORY_ENTRY_EXPORT, IMAGE_DOS_HEADER, IMAGE_EXPORT_DIRECTORY, IMAGE_NT_HEADERS32,
|
||||
IMAGE_NT_HEADERS64, PAGE_READWRITE,
|
||||
};
|
||||
|
||||
use crate::status::TetanusStatus;
|
||||
|
||||
struct EatEntry {
|
||||
address: *mut u32,
|
||||
original_rva: u32,
|
||||
}
|
||||
|
||||
static EAT_HOOKS: OnceLock<Mutex<HashMap<(usize, String), EatEntry>>> = OnceLock::new();
|
||||
|
||||
fn eat_hooks() -> &'static Mutex<HashMap<(usize, String), EatEntry>> {
|
||||
EAT_HOOKS.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
pub unsafe fn hook_eat(
|
||||
module: *const c_void,
|
||||
export_name: &str,
|
||||
detour: *const c_void,
|
||||
original: *mut *const c_void,
|
||||
) -> TetanusStatus {
|
||||
if module.is_null() || detour.is_null() || original.is_null() {
|
||||
return TetanusStatus::InvalidParameter;
|
||||
}
|
||||
let (entry_ptr, entry_rva, export_base, export_dir_size) = match find_eat_entry(module, export_name) {
|
||||
Ok(result) => result,
|
||||
Err(status) => return status,
|
||||
};
|
||||
let key = (module as usize, export_name.to_string());
|
||||
let mut guard = eat_hooks().lock().unwrap();
|
||||
if guard.contains_key(&key) {
|
||||
return TetanusStatus::AlreadyCreated;
|
||||
}
|
||||
let original_addr = (module as usize + entry_rva as usize) as *const c_void;
|
||||
*original = original_addr;
|
||||
let detour_rva = (detour as usize).wrapping_sub(module as usize) as u32;
|
||||
let mut old = 0u32;
|
||||
let ok = VirtualProtect(
|
||||
entry_ptr as *mut c_void,
|
||||
std::mem::size_of::<u32>(),
|
||||
PAGE_READWRITE,
|
||||
&mut old,
|
||||
);
|
||||
if ok == 0 {
|
||||
return TetanusStatus::MemoryProtectFailed;
|
||||
}
|
||||
*entry_ptr = detour_rva;
|
||||
let _ = VirtualProtect(
|
||||
entry_ptr as *mut c_void,
|
||||
std::mem::size_of::<u32>(),
|
||||
old,
|
||||
&mut old,
|
||||
);
|
||||
guard.insert(
|
||||
key,
|
||||
EatEntry {
|
||||
address: entry_ptr,
|
||||
original_rva: entry_rva,
|
||||
},
|
||||
);
|
||||
if is_forwarder(entry_rva, export_base, export_dir_size) {
|
||||
return TetanusStatus::InvalidParameter;
|
||||
}
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
|
||||
pub unsafe fn unhook_eat(module: *const c_void, export_name: &str) -> TetanusStatus {
|
||||
if module.is_null() {
|
||||
return TetanusStatus::InvalidParameter;
|
||||
}
|
||||
let key = (module as usize, export_name.to_string());
|
||||
let mut guard = eat_hooks().lock().unwrap();
|
||||
let entry = match guard.remove(&key) {
|
||||
Some(entry) => entry,
|
||||
None => return TetanusStatus::NotCreated,
|
||||
};
|
||||
let mut old = 0u32;
|
||||
let ok = VirtualProtect(
|
||||
entry.address as *mut c_void,
|
||||
std::mem::size_of::<u32>(),
|
||||
PAGE_READWRITE,
|
||||
&mut old,
|
||||
);
|
||||
if ok == 0 {
|
||||
return TetanusStatus::MemoryProtectFailed;
|
||||
}
|
||||
*entry.address = entry.original_rva;
|
||||
let _ = VirtualProtect(
|
||||
entry.address as *mut c_void,
|
||||
std::mem::size_of::<u32>(),
|
||||
old,
|
||||
&mut old,
|
||||
);
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
|
||||
unsafe fn find_eat_entry(
|
||||
module: *const c_void,
|
||||
export_name: &str,
|
||||
) -> Result<(*mut u32, u32, usize, u32), TetanusStatus> {
|
||||
let base = module as *const u8;
|
||||
let dos = &*(base as *const IMAGE_DOS_HEADER);
|
||||
if dos.e_magic != 0x5A4D {
|
||||
return Err(TetanusStatus::InvalidParameter);
|
||||
}
|
||||
let nt_headers = base.add(dos.e_lfanew as usize);
|
||||
let signature = *(nt_headers as *const u32);
|
||||
if signature != 0x00004550 {
|
||||
return Err(TetanusStatus::InvalidParameter);
|
||||
}
|
||||
let magic = *(nt_headers.add(24) as *const u16);
|
||||
let (export_rva, export_size) = if magic == 0x20B {
|
||||
let nt = &*(nt_headers as *const IMAGE_NT_HEADERS64);
|
||||
let dir = nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize];
|
||||
(dir.VirtualAddress, dir.Size)
|
||||
} else {
|
||||
let nt = &*(nt_headers as *const IMAGE_NT_HEADERS32);
|
||||
let dir = nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT as usize];
|
||||
(dir.VirtualAddress, dir.Size)
|
||||
};
|
||||
if export_rva == 0 || export_size == 0 {
|
||||
return Err(TetanusStatus::NotCreated);
|
||||
}
|
||||
let export_dir = &*(base.add(export_rva as usize) as *const IMAGE_EXPORT_DIRECTORY);
|
||||
let names = base.add(export_dir.AddressOfNames as usize) as *const u32;
|
||||
let ordinals = base.add(export_dir.AddressOfNameOrdinals as usize) as *const u16;
|
||||
let functions = base.add(export_dir.AddressOfFunctions as usize) as *mut u32;
|
||||
for i in 0..export_dir.NumberOfNames {
|
||||
let name_rva = *names.add(i as usize);
|
||||
let name_ptr = base.add(name_rva as usize) as *const i8;
|
||||
let name = CStr::from_ptr(name_ptr);
|
||||
if name.to_string_lossy() == export_name {
|
||||
let ordinal = *ordinals.add(i as usize) as usize;
|
||||
let func_rva_ptr = functions.add(ordinal);
|
||||
let func_rva = *func_rva_ptr;
|
||||
return Ok((func_rva_ptr, func_rva, export_rva as usize, export_size));
|
||||
}
|
||||
}
|
||||
Err(TetanusStatus::NotCreated)
|
||||
}
|
||||
|
||||
fn is_forwarder(rva: u32, export_base: usize, export_size: u32) -> bool {
|
||||
let rva = rva as usize;
|
||||
let export_end = export_base + export_size as usize;
|
||||
rva >= export_base && rva < export_end
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::slice;
|
||||
|
||||
use crate::status::TetanusStatus;
|
||||
use crate::trampoline::{plan_detour, Trampoline};
|
||||
use crate::windows::{flush_icache, protect_rw, restore_protect};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum HookState {
|
||||
Created,
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub struct Hook {
|
||||
target: *const c_void,
|
||||
detour: *const c_void,
|
||||
trampoline: Trampoline,
|
||||
trampoline_ptr: *const c_void,
|
||||
original_return: *const c_void,
|
||||
original: Vec<u8>,
|
||||
detour_patch: Vec<u8>,
|
||||
state: HookState,
|
||||
}
|
||||
|
||||
impl Hook {
|
||||
pub unsafe fn create(
|
||||
target: *const c_void,
|
||||
detour: *const c_void,
|
||||
original_out: *mut *const c_void,
|
||||
) -> Result<Self, TetanusStatus> {
|
||||
if target.is_null() || detour.is_null() || original_out.is_null() {
|
||||
return Err(TetanusStatus::InvalidParameter);
|
||||
}
|
||||
let (patch_size, detour_patch) = plan_detour(target, detour)?;
|
||||
let original = slice::from_raw_parts(target as *const u8, patch_size).to_vec();
|
||||
let (trampoline, trampoline_ptr) = Trampoline::create(target, &original)?;
|
||||
let original_return = (target as *const u8).add(original.len()) as *const c_void;
|
||||
*original_out = trampoline_ptr;
|
||||
Ok(Self {
|
||||
target,
|
||||
detour,
|
||||
trampoline,
|
||||
trampoline_ptr,
|
||||
original_return,
|
||||
original,
|
||||
detour_patch,
|
||||
state: HookState::Created,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn target(&self) -> *const c_void {
|
||||
self.target
|
||||
}
|
||||
|
||||
pub fn detour(&self) -> *const c_void {
|
||||
self.detour
|
||||
}
|
||||
|
||||
pub fn original_return(&self) -> *const c_void {
|
||||
self.original_return
|
||||
}
|
||||
|
||||
pub fn state(&self) -> HookState {
|
||||
self.state
|
||||
}
|
||||
|
||||
pub fn enable(&mut self) -> Result<(), TetanusStatus> {
|
||||
if self.state == HookState::Enabled {
|
||||
return Err(TetanusStatus::Enabled);
|
||||
}
|
||||
unsafe {
|
||||
let ptr = self.target as *mut c_void;
|
||||
let old = protect_rw(ptr, self.detour_patch.len())?;
|
||||
ptr::copy_nonoverlapping(self.detour_patch.as_ptr(), ptr as *mut u8, self.detour_patch.len());
|
||||
restore_protect(ptr, self.detour_patch.len(), old)?;
|
||||
flush_icache(self.target, self.detour_patch.len())?;
|
||||
}
|
||||
self.state = HookState::Enabled;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disable(&mut self) -> Result<(), TetanusStatus> {
|
||||
if self.state == HookState::Disabled || self.state == HookState::Created {
|
||||
return Err(TetanusStatus::Disabled);
|
||||
}
|
||||
unsafe {
|
||||
let ptr = self.target as *mut c_void;
|
||||
let old = protect_rw(ptr, self.original.len())?;
|
||||
ptr::copy_nonoverlapping(self.original.as_ptr(), ptr as *mut u8, self.original.len());
|
||||
restore_protect(ptr, self.original.len(), old)?;
|
||||
flush_icache(self.target, self.original.len())?;
|
||||
}
|
||||
self.state = HookState::Disabled;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_exit_target(&self, target: *const c_void) -> Result<(), TetanusStatus> {
|
||||
self.trampoline.patch_exit(target)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_state(&mut self, state: HookState) {
|
||||
self.state = state;
|
||||
}
|
||||
|
||||
pub fn destroy(self) -> Result<(), TetanusStatus> {
|
||||
if self.state == HookState::Enabled {
|
||||
return Err(TetanusStatus::Enabled);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{c_void, CStr};
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
use windows_sys::Win32::System::Memory::VirtualProtect;
|
||||
use windows_sys::Win32::System::SystemServices::{
|
||||
IMAGE_DIRECTORY_ENTRY_IMPORT, IMAGE_DOS_HEADER, IMAGE_IMPORT_BY_NAME, IMAGE_IMPORT_DESCRIPTOR,
|
||||
IMAGE_NT_HEADERS32, IMAGE_NT_HEADERS64, IMAGE_ORDINAL_FLAG32, IMAGE_ORDINAL_FLAG64,
|
||||
PAGE_READWRITE,
|
||||
};
|
||||
|
||||
use crate::status::TetanusStatus;
|
||||
|
||||
struct IatEntry {
|
||||
address: *mut usize,
|
||||
original: usize,
|
||||
}
|
||||
|
||||
static IAT_HOOKS: OnceLock<Mutex<HashMap<(usize, String), IatEntry>>> = OnceLock::new();
|
||||
|
||||
fn iat_hooks() -> &'static Mutex<HashMap<(usize, String), IatEntry>> {
|
||||
IAT_HOOKS.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
pub unsafe fn hook_iat(
|
||||
module: *const c_void,
|
||||
import_name: &str,
|
||||
detour: *const c_void,
|
||||
original: *mut *const c_void,
|
||||
) -> TetanusStatus {
|
||||
if module.is_null() || detour.is_null() || original.is_null() {
|
||||
return TetanusStatus::InvalidParameter;
|
||||
}
|
||||
let (entry_ptr, entry_value) = match find_iat_entry(module, import_name) {
|
||||
Ok(result) => result,
|
||||
Err(status) => return status,
|
||||
};
|
||||
let key = (module as usize, import_name.to_string());
|
||||
let mut guard = iat_hooks().lock().unwrap();
|
||||
if guard.contains_key(&key) {
|
||||
return TetanusStatus::AlreadyCreated;
|
||||
}
|
||||
let mut old = 0u32;
|
||||
let ok = VirtualProtect(
|
||||
entry_ptr as *mut c_void,
|
||||
std::mem::size_of::<usize>(),
|
||||
PAGE_READWRITE,
|
||||
&mut old,
|
||||
);
|
||||
if ok == 0 {
|
||||
return TetanusStatus::MemoryProtectFailed;
|
||||
}
|
||||
*entry_ptr = detour as usize;
|
||||
let _ = VirtualProtect(
|
||||
entry_ptr as *mut c_void,
|
||||
std::mem::size_of::<usize>(),
|
||||
old,
|
||||
&mut old,
|
||||
);
|
||||
*original = entry_value as *const c_void;
|
||||
guard.insert(
|
||||
key,
|
||||
IatEntry {
|
||||
address: entry_ptr,
|
||||
original: entry_value,
|
||||
},
|
||||
);
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
|
||||
pub unsafe fn unhook_iat(module: *const c_void, import_name: &str) -> TetanusStatus {
|
||||
if module.is_null() {
|
||||
return TetanusStatus::InvalidParameter;
|
||||
}
|
||||
let key = (module as usize, import_name.to_string());
|
||||
let mut guard = iat_hooks().lock().unwrap();
|
||||
let entry = match guard.remove(&key) {
|
||||
Some(entry) => entry,
|
||||
None => return TetanusStatus::NotCreated,
|
||||
};
|
||||
let mut old = 0u32;
|
||||
let ok = VirtualProtect(
|
||||
entry.address as *mut c_void,
|
||||
std::mem::size_of::<usize>(),
|
||||
PAGE_READWRITE,
|
||||
&mut old,
|
||||
);
|
||||
if ok == 0 {
|
||||
return TetanusStatus::MemoryProtectFailed;
|
||||
}
|
||||
*entry.address = entry.original;
|
||||
let _ = VirtualProtect(
|
||||
entry.address as *mut c_void,
|
||||
std::mem::size_of::<usize>(),
|
||||
old,
|
||||
&mut old,
|
||||
);
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
|
||||
unsafe fn find_iat_entry(
|
||||
module: *const c_void,
|
||||
import_name: &str,
|
||||
) -> Result<(*mut usize, usize), TetanusStatus> {
|
||||
let base = module as *const u8;
|
||||
let dos = &*(base as *const IMAGE_DOS_HEADER);
|
||||
if dos.e_magic != 0x5A4D {
|
||||
return Err(TetanusStatus::InvalidParameter);
|
||||
}
|
||||
let nt_headers = base.add(dos.e_lfanew as usize);
|
||||
let signature = *(nt_headers as *const u32);
|
||||
if signature != 0x00004550 {
|
||||
return Err(TetanusStatus::InvalidParameter);
|
||||
}
|
||||
let magic = *(nt_headers.add(24) as *const u16);
|
||||
let (import_rva, import_size, is_64) = if magic == 0x20B {
|
||||
let nt = &*(nt_headers as *const IMAGE_NT_HEADERS64);
|
||||
let dir = nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT as usize];
|
||||
(dir.VirtualAddress, dir.Size, true)
|
||||
} else {
|
||||
let nt = &*(nt_headers as *const IMAGE_NT_HEADERS32);
|
||||
let dir = nt.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT as usize];
|
||||
(dir.VirtualAddress, dir.Size, false)
|
||||
};
|
||||
if import_rva == 0 || import_size == 0 {
|
||||
return Err(TetanusStatus::NotCreated);
|
||||
}
|
||||
let mut descriptor = base.add(import_rva as usize) as *const IMAGE_IMPORT_DESCRIPTOR;
|
||||
while (*descriptor).Name != 0 {
|
||||
let name_ptr = base.add((*descriptor).Name as usize) as *const i8;
|
||||
let _dll_name = CStr::from_ptr(name_ptr);
|
||||
let mut thunk = if (*descriptor).OriginalFirstThunk != 0 {
|
||||
base.add((*descriptor).OriginalFirstThunk as usize)
|
||||
} else {
|
||||
base.add((*descriptor).FirstThunk as usize)
|
||||
};
|
||||
let mut iat = base.add((*descriptor).FirstThunk as usize);
|
||||
loop {
|
||||
if is_64 {
|
||||
let thunk_data = thunk as *const u64;
|
||||
let val = *thunk_data;
|
||||
if val == 0 {
|
||||
break;
|
||||
}
|
||||
let is_ordinal = (val & IMAGE_ORDINAL_FLAG64 as u64) != 0;
|
||||
if !is_ordinal {
|
||||
let name_ptr = base.add(val as usize) as *const IMAGE_IMPORT_BY_NAME;
|
||||
let name = CStr::from_ptr((*name_ptr).Name.as_ptr() as *const i8);
|
||||
if name.to_string_lossy() == import_name {
|
||||
let iat_entry = iat as *mut usize;
|
||||
return Ok((iat_entry, *iat_entry));
|
||||
}
|
||||
}
|
||||
thunk = thunk.add(std::mem::size_of::<u64>());
|
||||
iat = iat.add(std::mem::size_of::<u64>());
|
||||
} else {
|
||||
let thunk_data = thunk as *const u32;
|
||||
let val = *thunk_data;
|
||||
if val == 0 {
|
||||
break;
|
||||
}
|
||||
let is_ordinal = (val & IMAGE_ORDINAL_FLAG32) != 0;
|
||||
if !is_ordinal {
|
||||
let name_ptr = base.add(val as usize) as *const IMAGE_IMPORT_BY_NAME;
|
||||
let name = CStr::from_ptr((*name_ptr).Name.as_ptr() as *const i8);
|
||||
if name.to_string_lossy() == import_name {
|
||||
let iat_entry = iat as *mut usize;
|
||||
return Ok((iat_entry, *iat_entry));
|
||||
}
|
||||
}
|
||||
thunk = thunk.add(std::mem::size_of::<u32>());
|
||||
iat = iat.add(std::mem::size_of::<u32>());
|
||||
}
|
||||
}
|
||||
descriptor = descriptor.add(1);
|
||||
}
|
||||
Err(TetanusStatus::NotCreated)
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
//! Tetanus: a Windows user-mode inline hooking framework inspired by MinHook.
|
||||
//!
|
||||
//! # Safety
|
||||
//!
|
||||
//! Inline hooks overwrite instructions in the target function. All unsafe blocks
|
||||
//! in this crate are narrowly scoped to Windows API calls or raw pointer writes
|
||||
//! needed to patch code safely.
|
||||
|
||||
mod arch;
|
||||
mod diagnostics;
|
||||
mod hook;
|
||||
mod manager;
|
||||
mod safety;
|
||||
mod status;
|
||||
mod trampoline;
|
||||
mod windows;
|
||||
mod iat;
|
||||
mod eat;
|
||||
mod veh;
|
||||
|
||||
pub use status::TetanusStatus;
|
||||
pub use diagnostics::{HookInfo, set_logging, architecture, log};
|
||||
|
||||
use std::ffi::c_void;
|
||||
|
||||
/// Initialize the global hook manager.
|
||||
pub fn initialize() -> TetanusStatus {
|
||||
manager::initialize()
|
||||
}
|
||||
|
||||
/// Uninitialize the global hook manager.
|
||||
pub fn uninitialize() -> TetanusStatus {
|
||||
manager::uninitialize()
|
||||
}
|
||||
|
||||
/// Create a hook and return a trampoline pointer for the original function.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `target` and `detour` must be valid function pointers.
|
||||
/// - `original` must be a valid pointer to receive the trampoline.
|
||||
/// - The caller must ensure the target code is safe to patch.
|
||||
pub unsafe fn create_hook(
|
||||
target: *const c_void,
|
||||
detour: *const c_void,
|
||||
original: *mut *const c_void,
|
||||
) -> TetanusStatus {
|
||||
manager::create_hook(target, detour, original)
|
||||
}
|
||||
|
||||
/// Create a hook with an explicit priority for chaining.
|
||||
///
|
||||
/// Higher priority hooks run first. Hooks with equal priority preserve creation order.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - `target` and `detour` must be valid function pointers.
|
||||
/// - `original` must be a valid pointer to receive the trampoline.
|
||||
/// - The caller must ensure the target code is safe to patch.
|
||||
pub unsafe fn create_hook_with_priority(
|
||||
target: *const c_void,
|
||||
detour: *const c_void,
|
||||
original: *mut *const c_void,
|
||||
priority: i32,
|
||||
) -> TetanusStatus {
|
||||
manager::create_hook_with_priority(target, detour, original, priority)
|
||||
}
|
||||
|
||||
/// Enable the hook for a specific target.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The target must be a valid hook created by `create_hook`.
|
||||
pub unsafe fn enable_hook(target: *const c_void) -> TetanusStatus {
|
||||
manager::enable_hook(target)
|
||||
}
|
||||
|
||||
/// Disable the hook for a specific target.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The target must be a valid hook created by `create_hook`.
|
||||
pub unsafe fn disable_hook(target: *const c_void) -> TetanusStatus {
|
||||
manager::disable_hook(target)
|
||||
}
|
||||
|
||||
/// Enable all created hooks.
|
||||
pub fn enable_all_hooks() -> TetanusStatus {
|
||||
manager::enable_all_hooks()
|
||||
}
|
||||
|
||||
/// Disable all created hooks.
|
||||
pub fn disable_all_hooks() -> TetanusStatus {
|
||||
manager::disable_all_hooks()
|
||||
}
|
||||
|
||||
/// Return a snapshot of registered hooks for diagnostics.
|
||||
pub fn hook_snapshot() -> Vec<HookInfo> {
|
||||
manager::hook_snapshot()
|
||||
}
|
||||
|
||||
/// Validate internal hook state for diagnostics.
|
||||
pub fn validate_hooks() -> TetanusStatus {
|
||||
manager::validate_hooks()
|
||||
}
|
||||
|
||||
/// Hook a function through a module's Import Address Table.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure the module and target import are valid.
|
||||
pub unsafe fn hook_iat(
|
||||
module: *const c_void,
|
||||
import_name: &str,
|
||||
detour: *const c_void,
|
||||
original: *mut *const c_void,
|
||||
) -> TetanusStatus {
|
||||
iat::hook_iat(module, import_name, detour, original)
|
||||
}
|
||||
|
||||
/// Restore a previously hooked IAT entry.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure the module and import name match a previous hook.
|
||||
pub unsafe fn unhook_iat(module: *const c_void, import_name: &str) -> TetanusStatus {
|
||||
iat::unhook_iat(module, import_name)
|
||||
}
|
||||
|
||||
/// Hook a function through a module's Export Address Table.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure the module and export name are valid.
|
||||
pub unsafe fn hook_eat(
|
||||
module: *const c_void,
|
||||
export_name: &str,
|
||||
detour: *const c_void,
|
||||
original: *mut *const c_void,
|
||||
) -> TetanusStatus {
|
||||
eat::hook_eat(module, export_name, detour, original)
|
||||
}
|
||||
|
||||
/// Restore a previously hooked EAT entry.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure the module and export name match a previous hook.
|
||||
pub unsafe fn unhook_eat(module: *const c_void, export_name: &str) -> TetanusStatus {
|
||||
eat::unhook_eat(module, export_name)
|
||||
}
|
||||
|
||||
/// Enable a VEH-based hook for a target address.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The target must be a valid executable address.
|
||||
pub unsafe fn enable_veh_hook(
|
||||
target: *const c_void,
|
||||
detour: *const c_void,
|
||||
) -> TetanusStatus {
|
||||
veh::enable_veh_hook(target, detour)
|
||||
}
|
||||
|
||||
/// Disable a VEH-based hook for a target address.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The target must be a valid executable address.
|
||||
pub unsafe fn disable_veh_hook(target: *const c_void) -> TetanusStatus {
|
||||
veh::disable_veh_hook(target)
|
||||
}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::c_void;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
use crate::diagnostics::HookInfo;
|
||||
use crate::hook::{Hook, HookState};
|
||||
use crate::status::TetanusStatus;
|
||||
use crate::safety;
|
||||
|
||||
struct HookEntry {
|
||||
hook: Hook,
|
||||
priority: i32,
|
||||
}
|
||||
|
||||
struct HookChain {
|
||||
entries: Vec<HookEntry>,
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
struct HookManager {
|
||||
initialized: bool,
|
||||
hooks: HashMap<usize, HookChain>,
|
||||
}
|
||||
|
||||
impl HookManager {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
initialized: false,
|
||||
hooks: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static MANAGER: OnceLock<Mutex<HookManager>> = OnceLock::new();
|
||||
|
||||
fn manager() -> &'static Mutex<HookManager> {
|
||||
MANAGER.get_or_init(|| Mutex::new(HookManager::new()))
|
||||
}
|
||||
|
||||
pub fn initialize() -> TetanusStatus {
|
||||
let mut guard = manager().lock().unwrap();
|
||||
if guard.initialized {
|
||||
return TetanusStatus::AlreadyInitialized;
|
||||
}
|
||||
safety::install_panic_hook(disable_all_hooks);
|
||||
guard.initialized = true;
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
|
||||
pub fn uninitialize() -> TetanusStatus {
|
||||
let mut guard = manager().lock().unwrap();
|
||||
if !guard.initialized {
|
||||
return TetanusStatus::NotInitialized;
|
||||
}
|
||||
for (_, chain) in guard.hooks.drain() {
|
||||
if chain.enabled {
|
||||
return TetanusStatus::Enabled;
|
||||
}
|
||||
}
|
||||
guard.initialized = false;
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
|
||||
pub unsafe fn create_hook(
|
||||
target: *const c_void,
|
||||
detour: *const c_void,
|
||||
original: *mut *const c_void,
|
||||
) -> TetanusStatus {
|
||||
create_hook_with_priority(target, detour, original, 0)
|
||||
}
|
||||
|
||||
pub unsafe fn create_hook_with_priority(
|
||||
target: *const c_void,
|
||||
detour: *const c_void,
|
||||
original: *mut *const c_void,
|
||||
priority: i32,
|
||||
) -> TetanusStatus {
|
||||
let mut guard = manager().lock().unwrap();
|
||||
if !guard.initialized {
|
||||
return TetanusStatus::NotInitialized;
|
||||
}
|
||||
let key = target as usize;
|
||||
match Hook::create(target, detour, original) {
|
||||
Ok(hook) => {
|
||||
let chain = guard.hooks.entry(key).or_insert_with(|| HookChain {
|
||||
entries: Vec::new(),
|
||||
enabled: false,
|
||||
});
|
||||
if chain.entries.iter().any(|entry| entry.hook.detour() == detour) {
|
||||
return TetanusStatus::AlreadyCreated;
|
||||
}
|
||||
chain.entries.push(HookEntry { hook, priority });
|
||||
if let Err(status) = rebuild_chain(chain) {
|
||||
return status;
|
||||
}
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
Err(status) => status,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn enable_hook(target: *const c_void) -> TetanusStatus {
|
||||
let mut guard = manager().lock().unwrap();
|
||||
if !guard.initialized {
|
||||
return TetanusStatus::NotInitialized;
|
||||
}
|
||||
let key = target as usize;
|
||||
let chain = match guard.hooks.get_mut(&key) {
|
||||
Some(chain) => chain,
|
||||
None => return TetanusStatus::NotCreated,
|
||||
};
|
||||
if chain.enabled {
|
||||
return TetanusStatus::Enabled;
|
||||
}
|
||||
if let Some(first) = chain.entries.first_mut() {
|
||||
match first.hook.enable() {
|
||||
Ok(()) => {
|
||||
chain.enabled = true;
|
||||
for entry in chain.entries.iter_mut() {
|
||||
entry.hook.set_state(HookState::Enabled);
|
||||
}
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
Err(status) => status,
|
||||
}
|
||||
} else {
|
||||
TetanusStatus::NotCreated
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn disable_hook(target: *const c_void) -> TetanusStatus {
|
||||
let mut guard = manager().lock().unwrap();
|
||||
if !guard.initialized {
|
||||
return TetanusStatus::NotInitialized;
|
||||
}
|
||||
let key = target as usize;
|
||||
let chain = match guard.hooks.get_mut(&key) {
|
||||
Some(chain) => chain,
|
||||
None => return TetanusStatus::NotCreated,
|
||||
};
|
||||
if !chain.enabled {
|
||||
return TetanusStatus::Disabled;
|
||||
}
|
||||
if let Some(first) = chain.entries.first_mut() {
|
||||
match first.hook.disable() {
|
||||
Ok(()) => {
|
||||
chain.enabled = false;
|
||||
for entry in chain.entries.iter_mut() {
|
||||
entry.hook.set_state(HookState::Disabled);
|
||||
}
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
Err(status) => status,
|
||||
}
|
||||
} else {
|
||||
TetanusStatus::NotCreated
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_all_hooks() -> TetanusStatus {
|
||||
let mut guard = manager().lock().unwrap();
|
||||
if !guard.initialized {
|
||||
return TetanusStatus::NotInitialized;
|
||||
}
|
||||
for chain in guard.hooks.values_mut() {
|
||||
if chain.enabled {
|
||||
continue;
|
||||
}
|
||||
if let Some(first) = chain.entries.first_mut() {
|
||||
if let Err(status) = first.hook.enable() {
|
||||
return status;
|
||||
}
|
||||
chain.enabled = true;
|
||||
for entry in chain.entries.iter_mut() {
|
||||
entry.hook.set_state(HookState::Enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
|
||||
pub fn disable_all_hooks() -> TetanusStatus {
|
||||
let mut guard = manager().lock().unwrap();
|
||||
if !guard.initialized {
|
||||
return TetanusStatus::NotInitialized;
|
||||
}
|
||||
for chain in guard.hooks.values_mut() {
|
||||
if !chain.enabled {
|
||||
continue;
|
||||
}
|
||||
if let Some(first) = chain.entries.first_mut() {
|
||||
if let Err(status) = first.hook.disable() {
|
||||
return status;
|
||||
}
|
||||
chain.enabled = false;
|
||||
for entry in chain.entries.iter_mut() {
|
||||
entry.hook.set_state(HookState::Disabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
|
||||
pub fn hook_snapshot() -> Vec<HookInfo> {
|
||||
let guard = manager().lock().unwrap();
|
||||
let mut info = Vec::new();
|
||||
for chain in guard.hooks.values() {
|
||||
for entry in chain.entries.iter() {
|
||||
info.push(HookInfo {
|
||||
target: entry.hook.target(),
|
||||
detour: entry.hook.detour(),
|
||||
state: entry.hook.state(),
|
||||
priority: entry.priority,
|
||||
});
|
||||
}
|
||||
}
|
||||
info
|
||||
}
|
||||
|
||||
pub fn validate_hooks() -> TetanusStatus {
|
||||
let guard = manager().lock().unwrap();
|
||||
if !guard.initialized {
|
||||
return TetanusStatus::NotInitialized;
|
||||
}
|
||||
for chain in guard.hooks.values() {
|
||||
if chain.entries.is_empty() {
|
||||
return TetanusStatus::InvalidParameter;
|
||||
}
|
||||
}
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
|
||||
fn rebuild_chain(chain: &mut HookChain) -> Result<(), TetanusStatus> {
|
||||
chain.entries.sort_by(|a, b| b.priority.cmp(&a.priority));
|
||||
for idx in 0..chain.entries.len() {
|
||||
let exit_target = if idx + 1 < chain.entries.len() {
|
||||
chain.entries[idx + 1].hook.detour()
|
||||
} else {
|
||||
chain.entries[idx].hook.original_return()
|
||||
};
|
||||
chain.entries[idx].hook.set_exit_target(exit_target)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static PANIC_HOOK_INSTALLED: OnceLock<()> = OnceLock::new();
|
||||
|
||||
pub fn install_panic_hook(unhook: fn()) {
|
||||
PANIC_HOOK_INSTALLED.get_or_init(|| {
|
||||
let previous = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
unhook();
|
||||
previous(info);
|
||||
}));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#[repr(i32)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum TetanusStatus {
|
||||
Ok = 0,
|
||||
AlreadyInitialized = 1,
|
||||
NotInitialized = 2,
|
||||
AlreadyCreated = 3,
|
||||
NotCreated = 4,
|
||||
Enabled = 5,
|
||||
Disabled = 6,
|
||||
UnsupportedArchitecture = 7,
|
||||
MemoryAllocFailed = 8,
|
||||
MemoryProtectFailed = 9,
|
||||
InstructionDecodeFailed = 10,
|
||||
InvalidParameter = 11,
|
||||
UnknownError = 12,
|
||||
}
|
||||
|
||||
impl TetanusStatus {
|
||||
pub fn is_ok(self) -> bool {
|
||||
matches!(self, TetanusStatus::Ok)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use std::ffi::c_void;
|
||||
|
||||
use crate::arch::arch_plan;
|
||||
use crate::status::TetanusStatus;
|
||||
use crate::windows::ExecutableMemory;
|
||||
|
||||
pub struct Trampoline {
|
||||
memory: ExecutableMemory,
|
||||
size: usize,
|
||||
exit_offset: usize,
|
||||
exit_size: usize,
|
||||
}
|
||||
|
||||
impl Trampoline {
|
||||
pub fn create(
|
||||
target: *const c_void,
|
||||
original: &[u8],
|
||||
) -> Result<(Self, *const c_void), TetanusStatus> {
|
||||
let memory = ExecutableMemory::allocate_guarded(256)?;
|
||||
let tramp_ptr = memory.as_ptr();
|
||||
let layout = build_arch_trampoline(target, tramp_ptr, original)?;
|
||||
Ok((
|
||||
Self {
|
||||
memory,
|
||||
size: layout.size,
|
||||
exit_offset: layout.exit_offset,
|
||||
exit_size: layout.exit_size,
|
||||
},
|
||||
tramp_ptr as *const c_void,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn patch_exit(&self, target: *const c_void) -> Result<(), TetanusStatus> {
|
||||
let exit_ptr = unsafe { (self.memory.as_ptr() as *mut u8).add(self.exit_offset) };
|
||||
let written = write_arch_exit(exit_ptr, target)?;
|
||||
if written != self.exit_size {
|
||||
return Err(TetanusStatus::InstructionDecodeFailed);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn build_arch_trampoline(
|
||||
target: *const c_void,
|
||||
trampoline: *mut c_void,
|
||||
original: &[u8],
|
||||
) -> Result<crate::arch::TrampolineLayout, TetanusStatus> {
|
||||
#[cfg(target_arch = "x86")]
|
||||
return crate::arch::x86::X86::build_trampoline(target, trampoline, original);
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
return crate::arch::x64::X64::build_trampoline(target, trampoline, original);
|
||||
#[cfg(target_arch = "arm")]
|
||||
return crate::arch::arm32::Arm32::build_trampoline(target, trampoline, original);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
return crate::arch::arm64::Arm64::build_trampoline(target, trampoline, original);
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm", target_arch = "aarch64")))]
|
||||
{
|
||||
let _ = (target, trampoline, original);
|
||||
Err(TetanusStatus::UnsupportedArchitecture)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_arch_exit(trampoline: *mut u8, target: *const c_void) -> Result<usize, TetanusStatus> {
|
||||
#[cfg(target_arch = "x86")]
|
||||
return crate::arch::x86::X86::write_trampoline_exit(trampoline, target);
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
return crate::arch::x64::X64::write_trampoline_exit(trampoline, target);
|
||||
#[cfg(target_arch = "arm")]
|
||||
return crate::arch::arm32::Arm32::write_trampoline_exit(trampoline, target);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
return crate::arch::arm64::Arm64::write_trampoline_exit(trampoline, target);
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm", target_arch = "aarch64")))]
|
||||
{
|
||||
let _ = (trampoline, target);
|
||||
Err(TetanusStatus::UnsupportedArchitecture)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn plan_detour(target: *const c_void, detour: *const c_void) -> Result<(usize, Vec<u8>), TetanusStatus> {
|
||||
let plan = arch_plan(target, detour)?;
|
||||
Ok((plan.patch_size, plan.detour_bytes))
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
use windows_sys::Win32::Foundation::EXCEPTION_CONTINUE_EXECUTION;
|
||||
use windows_sys::Win32::Foundation::EXCEPTION_CONTINUE_SEARCH;
|
||||
use windows_sys::Win32::System::Diagnostics::Debug::{
|
||||
AddVectoredExceptionHandler, RemoveVectoredExceptionHandler, EXCEPTION_POINTERS,
|
||||
};
|
||||
use windows_sys::Win32::System::Memory::VirtualProtect;
|
||||
use windows_sys::Win32::System::SystemServices::EXCEPTION_BREAKPOINT;
|
||||
use windows_sys::Win32::System::SystemServices::PAGE_EXECUTE_READWRITE;
|
||||
|
||||
use crate::status::TetanusStatus;
|
||||
|
||||
struct VehEntry {
|
||||
detour: *const c_void,
|
||||
original: u8,
|
||||
}
|
||||
|
||||
static VEH_STATE: OnceLock<Mutex<HashMap<usize, VehEntry>>> = OnceLock::new();
|
||||
static VEH_HANDLE: OnceLock<Mutex<*mut c_void>> = OnceLock::new();
|
||||
|
||||
fn veh_state() -> &'static Mutex<HashMap<usize, VehEntry>> {
|
||||
VEH_STATE.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
fn veh_handle() -> &'static Mutex<*mut c_void> {
|
||||
VEH_HANDLE.get_or_init(|| Mutex::new(ptr::null_mut()))
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
unsafe extern "system" fn veh_handler(info: *mut EXCEPTION_POINTERS) -> i32 {
|
||||
if info.is_null() {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
let record = (*info).ExceptionRecord;
|
||||
if record.is_null() {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
if (*record).ExceptionCode != EXCEPTION_BREAKPOINT {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
let addr = (*record).ExceptionAddress as usize;
|
||||
let state = veh_state().lock().unwrap();
|
||||
let entry = match state.get(&addr) {
|
||||
Some(entry) => entry,
|
||||
None => return EXCEPTION_CONTINUE_SEARCH,
|
||||
};
|
||||
let context = (*info).ContextRecord;
|
||||
if context.is_null() {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
#[cfg(target_arch = "x86")]
|
||||
{
|
||||
(*context).Eip = entry.detour as u32;
|
||||
}
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
{
|
||||
(*context).Rip = entry.detour as u64;
|
||||
}
|
||||
EXCEPTION_CONTINUE_EXECUTION
|
||||
}
|
||||
|
||||
pub unsafe fn enable_veh_hook(target: *const c_void, detour: *const c_void) -> TetanusStatus {
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
{
|
||||
let _ = (target, detour);
|
||||
return TetanusStatus::UnsupportedArchitecture;
|
||||
}
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
{
|
||||
if target.is_null() || detour.is_null() {
|
||||
return TetanusStatus::InvalidParameter;
|
||||
}
|
||||
let mut state = veh_state().lock().unwrap();
|
||||
let key = target as usize;
|
||||
if state.contains_key(&key) {
|
||||
return TetanusStatus::AlreadyCreated;
|
||||
}
|
||||
install_handler();
|
||||
let byte_ptr = target as *mut u8;
|
||||
let original = *byte_ptr;
|
||||
let mut old = 0u32;
|
||||
let ok = VirtualProtect(
|
||||
target as *mut c_void,
|
||||
1,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
&mut old,
|
||||
);
|
||||
if ok == 0 {
|
||||
return TetanusStatus::MemoryProtectFailed;
|
||||
}
|
||||
*byte_ptr = 0xCC;
|
||||
let _ = VirtualProtect(target as *mut c_void, 1, old, &mut old);
|
||||
state.insert(key, VehEntry { detour, original });
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn disable_veh_hook(target: *const c_void) -> TetanusStatus {
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
{
|
||||
let _ = target;
|
||||
return TetanusStatus::UnsupportedArchitecture;
|
||||
}
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
{
|
||||
if target.is_null() {
|
||||
return TetanusStatus::InvalidParameter;
|
||||
}
|
||||
let mut state = veh_state().lock().unwrap();
|
||||
let key = target as usize;
|
||||
let entry = match state.remove(&key) {
|
||||
Some(entry) => entry,
|
||||
None => return TetanusStatus::NotCreated,
|
||||
};
|
||||
let mut old = 0u32;
|
||||
let ok = VirtualProtect(
|
||||
target as *mut c_void,
|
||||
1,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
&mut old,
|
||||
);
|
||||
if ok == 0 {
|
||||
return TetanusStatus::MemoryProtectFailed;
|
||||
}
|
||||
*(target as *mut u8) = entry.original;
|
||||
let _ = VirtualProtect(target as *mut c_void, 1, old, &mut old);
|
||||
if state.is_empty() {
|
||||
uninstall_handler();
|
||||
}
|
||||
TetanusStatus::Ok
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn install_handler() {
|
||||
let mut handle = veh_handle().lock().unwrap();
|
||||
if handle.is_null() {
|
||||
unsafe {
|
||||
*handle = AddVectoredExceptionHandler(1, Some(veh_handler)) as *mut c_void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn uninstall_handler() {
|
||||
let mut handle = veh_handle().lock().unwrap();
|
||||
if !handle.is_null() {
|
||||
unsafe {
|
||||
RemoveVectoredExceptionHandler(*handle as *const c_void);
|
||||
}
|
||||
*handle = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
|
||||
use windows_sys::Win32::Foundation::GetLastError;
|
||||
use windows_sys::Win32::System::Memory::{
|
||||
VirtualAlloc, VirtualFree, VirtualProtect, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE,
|
||||
PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_NOACCESS, PAGE_READWRITE,
|
||||
};
|
||||
use windows_sys::Win32::System::SystemServices::SYSTEM_INFO;
|
||||
use windows_sys::Win32::System::SystemServices::GetSystemInfo;
|
||||
use windows_sys::Win32::System::Threading::FlushInstructionCache;
|
||||
|
||||
use crate::status::TetanusStatus;
|
||||
|
||||
pub struct ExecutableMemory {
|
||||
ptr: *mut c_void,
|
||||
size: usize,
|
||||
}
|
||||
|
||||
impl ExecutableMemory {
|
||||
pub fn allocate(size: usize) -> Result<Self, TetanusStatus> {
|
||||
unsafe {
|
||||
let ptr = VirtualAlloc(
|
||||
ptr::null_mut(),
|
||||
size,
|
||||
MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
);
|
||||
if ptr.is_null() {
|
||||
return Err(TetanusStatus::MemoryAllocFailed);
|
||||
}
|
||||
Ok(Self { ptr, size })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocate_guarded(size: usize) -> Result<Self, TetanusStatus> {
|
||||
unsafe {
|
||||
let page_size = system_page_size();
|
||||
let rounded = ((size + page_size - 1) / page_size) * page_size;
|
||||
let alloc_size = rounded + page_size;
|
||||
let ptr = VirtualAlloc(
|
||||
ptr::null_mut(),
|
||||
alloc_size,
|
||||
MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
);
|
||||
if ptr.is_null() {
|
||||
return Err(TetanusStatus::MemoryAllocFailed);
|
||||
}
|
||||
let guard_ptr = (ptr as *mut u8).add(rounded) as *mut c_void;
|
||||
let mut old = 0u32;
|
||||
let ok = VirtualProtect(guard_ptr, page_size, PAGE_NOACCESS, &mut old);
|
||||
if ok == 0 {
|
||||
VirtualFree(ptr, 0, MEM_RELEASE);
|
||||
return Err(TetanusStatus::MemoryProtectFailed);
|
||||
}
|
||||
Ok(Self { ptr, size: rounded })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *mut c_void {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ExecutableMemory {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.ptr.is_null() {
|
||||
VirtualFree(self.ptr, 0, MEM_RELEASE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn protect_rw(ptr: *mut c_void, size: usize) -> Result<u32, TetanusStatus> {
|
||||
let mut old = 0u32;
|
||||
let ok = VirtualProtect(ptr, size, PAGE_READWRITE, &mut old);
|
||||
if ok == 0 {
|
||||
return Err(TetanusStatus::MemoryProtectFailed);
|
||||
}
|
||||
Ok(old)
|
||||
}
|
||||
|
||||
pub unsafe fn protect_rx(ptr: *mut c_void, size: usize) -> Result<u32, TetanusStatus> {
|
||||
let mut old = 0u32;
|
||||
let ok = VirtualProtect(ptr, size, PAGE_EXECUTE_READ, &mut old);
|
||||
if ok == 0 {
|
||||
return Err(TetanusStatus::MemoryProtectFailed);
|
||||
}
|
||||
Ok(old)
|
||||
}
|
||||
|
||||
pub unsafe fn restore_protect(ptr: *mut c_void, size: usize, old: u32) -> Result<(), TetanusStatus> {
|
||||
let mut _discard = 0u32;
|
||||
let ok = VirtualProtect(ptr, size, old, &mut _discard);
|
||||
if ok == 0 {
|
||||
return Err(TetanusStatus::MemoryProtectFailed);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe fn flush_icache(ptr: *const c_void, size: usize) -> Result<(), TetanusStatus> {
|
||||
let ok = FlushInstructionCache(0, ptr, size);
|
||||
if ok == 0 {
|
||||
let _ = GetLastError();
|
||||
return Err(TetanusStatus::UnknownError);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn system_page_size() -> usize {
|
||||
unsafe {
|
||||
let mut info = SYSTEM_INFO::default();
|
||||
GetSystemInfo(&mut info);
|
||||
info.dwPageSize as usize
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user