use crate::emulation::{
memory::MemoryProtection,
runtime::{Hook, HookManager, HookPriority, PreHookResult},
EmValue,
};
pub fn register(manager: &mut HookManager) {
register_virtual_protect(manager);
register_virtual_alloc(manager);
register_virtual_free(manager);
register_get_module_handle(manager);
register_get_proc_address(manager);
register_load_library(manager);
register_is_debugger_present(manager);
register_check_remote_debugger_present(manager);
register_get_current_process(manager);
register_get_current_thread(manager);
}
fn register_virtual_protect(manager: &mut HookManager) {
manager.register(
Hook::new("native-virtual-protect")
.with_priority(HookPriority::HIGH)
.match_native("kernel32", "VirtualProtect")
.pre(|ctx, thread| {
let args = ctx.args;
if args.len() < 4 {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let lp_address = match &args[0] {
EmValue::UnmanagedPtr(addr) if *addr != 0 => *addr,
EmValue::NativeInt(addr) if *addr > 0 => (*addr).cast_unsigned(),
EmValue::NativeUInt(addr) if *addr > 0 => *addr,
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
#[allow(clippy::cast_possible_truncation)]
let dw_size = match &args[1] {
EmValue::I32(size) if *size > 0 => (*size).cast_unsigned() as usize,
EmValue::NativeInt(size) if *size > 0 => (*size).cast_unsigned() as usize,
EmValue::NativeUInt(size) if *size > 0 => *size as usize,
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
#[allow(clippy::cast_possible_truncation)]
let fl_new_protect = match &args[2] {
EmValue::I32(p) => (*p).cast_unsigned(),
EmValue::NativeInt(p) => (*p).cast_unsigned() as u32,
EmValue::NativeUInt(p) => *p as u32,
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
let space = thread.address_space();
if !space.is_valid(lp_address) || !space.is_valid(lp_address + dw_size as u64 - 1) {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let new_protection = MemoryProtection::from_windows(fl_new_protect);
let old_protection = space
.set_protection(lp_address, dw_size, new_protection)
.unwrap_or(MemoryProtection::READ_EXECUTE);
let old_protect_windows = old_protection.to_windows();
let old_protect_value = EmValue::I32(old_protect_windows.cast_signed());
match &args[3] {
EmValue::ManagedPtr(ptr) => {
if thread
.store_through_pointer(ptr, old_protect_value)
.is_err()
{
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
}
EmValue::UnmanagedPtr(addr) if *addr != 0 => {
let old_protect_bytes = old_protect_windows.to_le_bytes();
if thread
.address_space()
.write(*addr, &old_protect_bytes)
.is_err()
{
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
}
EmValue::NativeInt(addr) if *addr > 0 => {
let old_protect_bytes = old_protect_windows.to_le_bytes();
if thread
.address_space()
.write((*addr).cast_unsigned(), &old_protect_bytes)
.is_err()
{
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
}
EmValue::NativeUInt(addr) if *addr > 0 => {
let old_protect_bytes = old_protect_windows.to_le_bytes();
if thread
.address_space()
.write(*addr, &old_protect_bytes)
.is_err()
{
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
}
_ => {
}
}
PreHookResult::Bypass(Some(EmValue::I32(1)))
}),
);
}
fn register_virtual_alloc(manager: &mut HookManager) {
manager.register(
Hook::new("native-virtual-alloc")
.with_priority(HookPriority::HIGH)
.match_native("kernel32", "VirtualAlloc")
.pre(|ctx, thread| {
let args = ctx.args;
#[allow(clippy::cast_possible_truncation)]
let size = match args.get(1) {
Some(EmValue::I32(size)) if *size > 0 => (*size).cast_unsigned() as usize,
Some(EmValue::NativeInt(size)) if *size > 0 => (*size).cast_unsigned() as usize,
Some(EmValue::NativeUInt(size)) if *size > 0 => *size as usize,
_ => return PreHookResult::Bypass(Some(EmValue::NativeInt(0))),
};
#[allow(clippy::cast_possible_truncation)]
let alloc_type = match args.get(2) {
Some(EmValue::I32(t)) if *t != 0 => (*t).cast_unsigned(),
Some(EmValue::NativeInt(t)) if *t != 0 => (*t).cast_unsigned() as u32,
_ => return PreHookResult::Bypass(Some(EmValue::NativeInt(0))),
};
if (alloc_type & 0x3000) == 0 {
return PreHookResult::Bypass(Some(EmValue::NativeInt(0)));
}
match thread.address_space().alloc_unmanaged(size) {
Ok(addr) => PreHookResult::Bypass(Some(EmValue::NativeInt(addr.cast_signed()))),
Err(_) => PreHookResult::Bypass(Some(EmValue::NativeInt(0))),
}
}),
);
}
fn register_virtual_free(manager: &mut HookManager) {
manager.register(
Hook::new("native-virtual-free")
.with_priority(HookPriority::HIGH)
.match_native("kernel32", "VirtualFree")
.pre(|ctx, thread| {
let args = ctx.args;
if args.is_empty() {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
let lp_address = match &args[0] {
EmValue::UnmanagedPtr(addr) if *addr != 0 => *addr,
EmValue::NativeInt(addr) if *addr > 0 => (*addr).cast_unsigned(),
EmValue::NativeUInt(addr) if *addr > 0 => *addr,
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
if let Some(free_type) = args.get(2) {
#[allow(clippy::cast_possible_truncation)]
let ft = match free_type {
EmValue::I32(t) => (*t).cast_unsigned(),
EmValue::NativeInt(t) => (*t).cast_unsigned() as u32,
_ => 0,
};
if (ft & 0x8000) != 0 {
#[allow(clippy::cast_possible_truncation)]
let dw_size = args
.get(1)
.and_then(|v| match v {
EmValue::I32(s) => Some((*s).cast_unsigned()),
EmValue::NativeInt(s) => Some((*s).cast_unsigned() as u32),
_ => None,
})
.unwrap_or(0);
if dw_size != 0 {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
}
}
if !thread.address_space().is_valid(lp_address) {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
PreHookResult::Bypass(Some(EmValue::I32(1)))
}),
);
}
fn register_get_module_handle(manager: &mut HookManager) {
manager.register(
Hook::new("native-get-module-handle-a")
.with_priority(HookPriority::HIGH)
.match_native("kernel32", "GetModuleHandleA")
.pre(|ctx, _thread| {
let is_null = ctx.args.first().is_none_or(|v| {
v.is_null()
|| matches!(v, EmValue::NativeInt(0))
|| matches!(v, EmValue::NativeUInt(0))
|| matches!(v, EmValue::UnmanagedPtr(0))
});
if is_null {
PreHookResult::Bypass(Some(EmValue::NativeInt(0x0040_0000)))
} else {
PreHookResult::Bypass(Some(EmValue::NativeInt(0x7FFE_0000)))
}
}),
);
manager.register(
Hook::new("native-get-module-handle-w")
.with_priority(HookPriority::HIGH)
.match_native("kernel32", "GetModuleHandleW")
.pre(|ctx, _thread| {
let is_null = ctx.args.first().is_none_or(|v| {
v.is_null()
|| matches!(v, EmValue::NativeInt(0))
|| matches!(v, EmValue::NativeUInt(0))
|| matches!(v, EmValue::UnmanagedPtr(0))
});
if is_null {
PreHookResult::Bypass(Some(EmValue::NativeInt(0x0040_0000)))
} else {
PreHookResult::Bypass(Some(EmValue::NativeInt(0x7FFE_0000)))
}
}),
);
}
fn register_get_proc_address(manager: &mut HookManager) {
manager.register(
Hook::new("native-get-proc-address")
.with_priority(HookPriority::HIGH)
.match_native("kernel32", "GetProcAddress")
.pre(|_ctx, _thread| {
PreHookResult::Bypass(Some(EmValue::NativeInt(0x7FFE_1000)))
}),
);
}
fn register_load_library(manager: &mut HookManager) {
manager.register(
Hook::new("native-load-library-a")
.with_priority(HookPriority::HIGH)
.match_native("kernel32", "LoadLibraryA")
.pre(|_ctx, _thread| {
PreHookResult::Bypass(Some(EmValue::NativeInt(0x7FFE_2000)))
}),
);
manager.register(
Hook::new("native-load-library-w")
.with_priority(HookPriority::HIGH)
.match_native("kernel32", "LoadLibraryW")
.pre(|_ctx, _thread| PreHookResult::Bypass(Some(EmValue::NativeInt(0x7FFE_2000)))),
);
}
fn register_is_debugger_present(manager: &mut HookManager) {
manager.register(
Hook::new("native-is-debugger-present")
.with_priority(HookPriority::HIGHEST) .match_native("kernel32", "IsDebuggerPresent")
.pre(|_ctx, _thread| {
PreHookResult::Bypass(Some(EmValue::I32(0)))
}),
);
}
fn register_check_remote_debugger_present(manager: &mut HookManager) {
manager.register(
Hook::new("native-check-remote-debugger-present")
.with_priority(HookPriority::HIGHEST)
.match_native("kernel32", "CheckRemoteDebuggerPresent")
.pre(|ctx, thread| {
let args = ctx.args;
if args.len() < 2 {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
match &args[0] {
EmValue::NativeInt(-1) => {} EmValue::NativeInt(h) if *h > 0 => {}
EmValue::NativeUInt(h) if *h > 0 => {}
EmValue::UnmanagedPtr(h) if *h > 0 => {}
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
}
let output_addr = match &args[1] {
EmValue::UnmanagedPtr(addr) if *addr != 0 => *addr,
EmValue::NativeInt(addr) if *addr > 0 => (*addr).cast_unsigned(),
EmValue::NativeUInt(addr) if *addr > 0 => *addr,
_ => return PreHookResult::Bypass(Some(EmValue::I32(0))),
};
let false_bytes = 0u32.to_le_bytes();
let space = thread.address_space();
if !space.is_valid(output_addr) {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
if space.write(output_addr, &false_bytes).is_err() {
return PreHookResult::Bypass(Some(EmValue::I32(0)));
}
PreHookResult::Bypass(Some(EmValue::I32(1)))
}),
);
}
fn register_get_current_process(manager: &mut HookManager) {
manager.register(
Hook::new("native-get-current-process")
.with_priority(HookPriority::HIGH)
.match_native("kernel32", "GetCurrentProcess")
.pre(|_ctx, _thread| {
PreHookResult::Bypass(Some(EmValue::NativeInt(-1)))
}),
);
}
fn register_get_current_thread(manager: &mut HookManager) {
manager.register(
Hook::new("native-get-current-thread")
.with_priority(HookPriority::HIGH)
.match_native("kernel32", "GetCurrentThread")
.pre(|_ctx, _thread| {
PreHookResult::Bypass(Some(EmValue::NativeInt(-2)))
}),
);
}
#[cfg(test)]
mod tests {
use crate::{
emulation::{
runtime::{HookContext, HookManager, PreHookResult},
EmValue,
},
metadata::{token::Token, typesystem::PointerSize},
test::emulation::create_test_thread,
};
use super::register;
fn create_native_context<'a>(dll: &'a str, function: &'a str) -> HookContext<'a> {
HookContext::native(Token::new(0x06000001), dll, function, PointerSize::Bit64)
}
#[test]
fn test_native_hooks_registered() {
let mut manager = HookManager::new();
register(&mut manager);
assert!(manager.len() >= 12);
}
#[test]
fn test_is_debugger_present_hook() {
let mut manager = HookManager::new();
register(&mut manager);
let mut thread = create_test_thread();
let context = create_native_context("kernel32", "IsDebuggerPresent").with_args(&[]);
let hook = manager.find_matching(&context, &thread);
assert!(hook.is_some(), "Should find IsDebuggerPresent hook");
if let Some(h) = hook {
let result = h.execute_pre(&context, &mut thread);
assert!(matches!(
result,
Some(PreHookResult::Bypass(Some(EmValue::I32(0))))
));
}
}
#[test]
fn test_get_current_process_hook() {
let mut manager = HookManager::new();
register(&mut manager);
let mut thread = create_test_thread();
let context = create_native_context("kernel32", "GetCurrentProcess").with_args(&[]);
let hook = manager.find_matching(&context, &thread);
assert!(hook.is_some(), "Should find GetCurrentProcess hook");
if let Some(h) = hook {
let result = h.execute_pre(&context, &mut thread);
assert!(matches!(
result,
Some(PreHookResult::Bypass(Some(EmValue::NativeInt(-1))))
));
}
}
#[test]
fn test_get_module_handle_null() {
let mut manager = HookManager::new();
register(&mut manager);
let mut thread = create_test_thread();
let args = [EmValue::NativeInt(0)];
let context = create_native_context("kernel32", "GetModuleHandleA").with_args(&args);
let hook = manager.find_matching(&context, &thread);
assert!(hook.is_some(), "Should find GetModuleHandleA hook");
if let Some(h) = hook {
let result = h.execute_pre(&context, &mut thread);
assert!(matches!(
result,
Some(PreHookResult::Bypass(Some(EmValue::NativeInt(0x0040_0000))))
));
}
}
}