use callghost::syscall;
const MEM_COMMIT: u32 = 0x1000;
const MEM_RESERVE: u32 = 0x2000;
const MEM_RELEASE: u32 = 0x8000;
const PAGE_READWRITE: u32 = 0x04;
const PAGE_EXECUTE_READWRITE: u32 = 0x40;
const HOOK_SENTINEL: u32 = 0xDEAD0001;
#[test]
fn hash_is_const_and_nonzero() {
const H: u32 = callghost::__private::fnv1a(b"NtAllocateVirtualMemory");
assert_ne!(H, 0);
assert_ne!(H, 0x811c9dc5, "hash should not be FNV offset basis (empty input)");
}
#[test]
fn hash_deterministic() {
assert_eq!(
callghost::__private::fnv1a(b"NtClose"),
callghost::__private::fnv1a(b"NtClose"),
);
}
#[test]
fn hash_differs_for_different_names() {
assert_ne!(
callghost::__private::fnv1a(b"NtClose"),
callghost::__private::fnv1a(b"NtOpenProcess"),
);
}
#[test]
fn stubs_are_clean_with_valid_ssns() {
let funcs = [
"NtAllocateVirtualMemory", "NtFreeVirtualMemory", "NtClose",
"NtProtectVirtualMemory", "NtQueryInformationProcess",
"NtWriteVirtualMemory", "NtCreateThreadEx",
];
for name in &funcs {
let hash = callghost::__private::fnv1a(name.as_bytes());
let addr = unsafe { callghost::__private::get_function_address(hash) };
let ssn = unsafe { callghost::__private::check_stub_clean(addr) };
assert!(ssn.is_some(), "{} stub at {:p} is NOT clean — bytes: {:02X} {:02X} {:02X} {:02X}",
name, addr,
unsafe { *addr }, unsafe { *addr.add(1) },
unsafe { *addr.add(2) }, unsafe { *addr.add(3) });
let ssn = ssn.unwrap();
assert!(ssn < 0x300, "{} SSN 0x{:04X} is unreasonably high", name, ssn);
}
}
#[test]
fn ssn_values_are_unique_across_functions() {
let funcs: &[&[u8]] = &[
b"NtAllocateVirtualMemory", b"NtFreeVirtualMemory", b"NtClose",
b"NtProtectVirtualMemory", b"NtQueryInformationProcess",
];
let ssns: Vec<u16> = funcs.iter()
.map(|n| unsafe { callghost::__private::get_ssn(callghost::__private::fnv1a(n)) })
.collect();
for i in 0..ssns.len() {
for j in (i + 1)..ssns.len() {
assert_ne!(ssns[i], ssns[j], "{} and {} share SSN 0x{:04X}",
core::str::from_utf8(funcs[i]).unwrap(),
core::str::from_utf8(funcs[j]).unwrap(), ssns[i]);
}
}
}
#[test]
fn gadget_is_inside_ntdll_text_section() {
let gadget = unsafe { callghost::__private::get_syscall_gadget() };
assert!(!gadget.is_null(), "gadget should not be null");
let b0 = unsafe { *gadget };
let b1 = unsafe { *gadget.add(1) };
let b2 = unsafe { *gadget.add(2) };
assert_eq!((b0, b1, b2), (0x0F, 0x05, 0xC3),
"gadget at {:p} should be 0F 05 C3 (syscall;ret), got {:02X} {:02X} {:02X}",
gadget, b0, b1, b2);
let ntdll_base = unsafe {
callghost::__private::get_function_address(callghost::__private::fnv1a(b"NtClose"))
};
let distance = (gadget as usize).abs_diff(ntdll_base as usize);
assert!(distance < 0x400000, "gadget is too far from ntdll ({} bytes)", distance);
}
unsafe fn install_inline_hook(stub: *mut u8) -> [u8; 6] {
let mut saved = [0u8; 6];
let hook: [u8; 6] = [0xB8, 0x01, 0x00, 0xAD, 0xDE, 0xC3];
unsafe {
core::ptr::copy_nonoverlapping(stub, saved.as_mut_ptr(), 6);
core::ptr::copy_nonoverlapping(hook.as_ptr(), stub, 6);
}
saved
}
unsafe fn make_writable(addr: *mut u8) -> u32 {
let mut old: u32 = 0;
let mut base = addr as *mut core::ffi::c_void;
let mut size: usize = 64;
let status = syscall!(NtProtectVirtualMemory, -1isize, &mut base, &mut size,
PAGE_EXECUTE_READWRITE, &mut old);
assert_eq!(status, 0, "NtProtectVirtualMemory failed: 0x{:08X}", status as u32);
old
}
unsafe fn restore_protection(addr: *mut u8, protect: u32) {
let mut dummy: u32 = 0;
let mut base = addr as *mut core::ffi::c_void;
let mut size: usize = 64;
syscall!(NtProtectVirtualMemory, -1isize, &mut base, &mut size, protect, &mut dummy);
}
struct HookGuard {
stub: *mut u8,
saved: [u8; 6],
old_protect: u32,
clean_ssn: u16,
}
impl HookGuard {
fn install(name: &[u8]) -> Self {
let hash = callghost::__private::fnv1a(name);
let stub = unsafe { callghost::__private::get_function_address(hash) };
let clean_ssn = unsafe { callghost::__private::check_stub_clean(stub) }
.expect("stub must be clean before hooking");
let old_protect = unsafe { make_writable(stub) };
let saved = unsafe { install_inline_hook(stub) };
assert!(
unsafe { callghost::__private::check_stub_clean(stub) }.is_none(),
"hook installation failed — stub still looks clean"
);
HookGuard { stub, saved, old_protect, clean_ssn }
}
fn verify_hooked(&self) {
assert!(
unsafe { callghost::__private::check_stub_clean(self.stub) }.is_none(),
"expected stub to be hooked, but it's clean"
);
}
fn verify_clean(&self) {
let ssn = unsafe { callghost::__private::check_stub_clean(self.stub) };
assert_eq!(ssn, Some(self.clean_ssn),
"expected clean stub with SSN 0x{:04X}, got {:?}", self.clean_ssn, ssn);
}
}
impl Drop for HookGuard {
fn drop(&mut self) {
unsafe {
let _ = make_writable(self.stub);
core::ptr::copy_nonoverlapping(self.saved.as_ptr(), self.stub, 6);
restore_protection(self.stub, self.old_protect);
}
}
}
#[test]
fn hook_itself_works() {
let guard = HookGuard::install(b"NtQueryInformationProcess");
let hooked_fn: unsafe extern "system" fn(isize, u32, *mut u8, u32, *mut u32) -> i32 =
unsafe { core::mem::transmute(guard.stub) };
let mut info = [0u8; 48];
let mut retlen: u32 = 0;
let result = unsafe { hooked_fn(-1, 0, info.as_mut_ptr(), 48, &mut retlen) };
assert_eq!(result as u32, HOOK_SENTINEL,
"calling through hooked stub should return 0x{:08X}, got 0x{:08X} — hook is broken!",
HOOK_SENTINEL, result as u32);
}
#[test]
fn direct_bypasses_hook() {
let guard = HookGuard::install(b"NtQueryInformationProcess");
let mut info = [0u8; 48];
let mut retlen: u32 = 0;
let status = syscall!(direct, NtQueryInformationProcess, -1isize, 0u32,
info.as_mut_ptr(), info.len() as u32, &mut retlen);
assert_ne!(status as u32, HOOK_SENTINEL, "direct went through hooked stub!");
assert_eq!(status, 0, "direct syscall failed: 0x{:08X}", status as u32);
assert!(retlen > 0, "direct returned no data");
let hash = callghost::__private::fnv1a(b"NtQueryInformationProcess");
let gate_ssn = unsafe { callghost::__private::get_ssn(hash) };
assert_eq!(gate_ssn, guard.clean_ssn,
"Halo's Gate SSN 0x{:04X} != clean SSN 0x{:04X}", gate_ssn, guard.clean_ssn);
}
#[test]
fn halos_gate_resolves_with_hooked_neighbor() {
let guard = HookGuard::install(b"NtClose");
let hash = callghost::__private::fnv1a(b"NtClose");
let gate_ssn = unsafe { callghost::__private::get_ssn(hash) };
assert_eq!(gate_ssn, guard.clean_ssn);
}
#[test]
fn indirect_works_with_stack_args() {
let mut base: *mut core::ffi::c_void = core::ptr::null_mut();
let mut size: usize = 4096;
let status = syscall!(indirect, NtAllocateVirtualMemory, -1isize,
&mut base, 0usize, &mut size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
assert_eq!(status, 0, "indirect alloc failed: 0x{:08X}", status as u32);
assert!(!base.is_null(), "indirect returned null base");
assert!(size >= 4096, "indirect returned undersized region: {}", size);
unsafe { *(base as *mut u64) = 0xBAADF00DCAFEBABE; }
assert_eq!(unsafe { *(base as *mut u64) }, 0xBAADF00DCAFEBABE);
let mut fs: usize = 0;
let status = syscall!(indirect, NtFreeVirtualMemory, -1isize, &mut base, &mut fs, MEM_RELEASE);
assert_eq!(status, 0, "indirect free failed: 0x{:08X}", status as u32);
}
#[test]
fn indirect_bypasses_hook() {
let guard = HookGuard::install(b"NtQueryInformationProcess");
let mut info = [0u8; 48];
let mut retlen: u32 = 0;
let status = syscall!(indirect, NtQueryInformationProcess, -1isize, 0u32,
info.as_mut_ptr(), info.len() as u32, &mut retlen);
assert_ne!(status as u32, HOOK_SENTINEL, "indirect went through hooked stub!");
assert_eq!(status, 0, "indirect failed: 0x{:08X}", status as u32);
assert!(retlen > 0, "indirect returned no data");
guard.verify_hooked();
}
#[test]
fn unhook_restores_stub_with_correct_ssn() {
let guard = HookGuard::install(b"NtQueryInformationProcess");
guard.verify_hooked();
let mut info = [0u8; 48];
let mut retlen: u32 = 0;
let status = syscall!(unhook, NtQueryInformationProcess, -1isize, 0u32,
info.as_mut_ptr(), info.len() as u32, &mut retlen);
assert_ne!(status as u32, HOOK_SENTINEL, "unhook went through hooked stub!");
assert_eq!(status, 0, "unhook call failed: 0x{:08X}", status as u32);
assert!(retlen > 0, "unhook returned no data");
guard.verify_clean();
}
#[test]
fn unhook_all_restores_multiple_stubs() {
let guards = [
HookGuard::install(b"NtClose"),
HookGuard::install(b"NtQueryInformationProcess"),
HookGuard::install(b"NtWriteVirtualMemory"),
];
for g in &guards { g.verify_hooked(); }
unsafe { callghost::__private::unhook_all() };
for g in &guards { g.verify_clean(); }
}
#[test]
fn perunsfart_bypasses_hook_and_rehooks() {
let guard = HookGuard::install(b"NtQueryInformationProcess");
guard.verify_hooked();
let mut info = [0u8; 48];
let mut retlen: u32 = 0;
let status = syscall!(perunsfart, NtQueryInformationProcess, -1isize, 0u32,
info.as_mut_ptr(), info.len() as u32, &mut retlen);
assert_ne!(status as u32, HOOK_SENTINEL, "perunsfart went through hooked stub!");
assert_eq!(status, 0, "perunsfart failed: 0x{:08X}", status as u32);
assert!(retlen > 0, "perunsfart returned no data");
guard.verify_hooked();
}
#[test]
fn perunsfart_works_with_stack_args() {
let mut base: *mut core::ffi::c_void = core::ptr::null_mut();
let mut size: usize = 4096;
let s = syscall!(perunsfart, NtAllocateVirtualMemory, -1isize,
&mut base, 0usize, &mut size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
assert_eq!(s, 0, "perunsfart alloc failed: 0x{:08X}", s as u32);
assert!(!base.is_null());
unsafe { *(base as *mut u32) = 0x41414141; }
assert_eq!(unsafe { *(base as *mut u32) }, 0x41414141);
let mut fs: usize = 0;
let s = syscall!(perunsfart, NtFreeVirtualMemory, -1isize, &mut base, &mut fs, MEM_RELEASE);
assert_eq!(s, 0, "perunsfart free failed: 0x{:08X}", s as u32);
}
#[test]
fn all_methods_produce_same_result_on_clean_stubs() {
for method in &["default", "direct", "indirect", "perunsfart"] {
let mut base: *mut core::ffi::c_void = core::ptr::null_mut();
let mut size: usize = 4096;
let s_alloc = match *method {
"default" => syscall!(NtAllocateVirtualMemory, -1isize, &mut base, 0usize, &mut size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE),
"direct" => syscall!(direct, NtAllocateVirtualMemory, -1isize, &mut base, 0usize, &mut size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE),
"indirect" => syscall!(indirect, NtAllocateVirtualMemory, -1isize, &mut base, 0usize, &mut size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE),
"perunsfart" => syscall!(perunsfart, NtAllocateVirtualMemory, -1isize, &mut base, 0usize, &mut size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE),
_ => unreachable!(),
};
assert_eq!(s_alloc, 0, "[{}] alloc failed: 0x{:08X}", method, s_alloc as u32);
assert!(!base.is_null(), "[{}] null base", method);
unsafe { *(base as *mut u32) = 0xDEADBEEF; }
assert_eq!(unsafe { *(base as *mut u32) }, 0xDEADBEEF, "[{}] write/read mismatch", method);
let mut fs: usize = 0;
let s_free = match *method {
"default" => syscall!(NtFreeVirtualMemory, -1isize, &mut base, &mut fs, MEM_RELEASE),
"direct" => syscall!(direct, NtFreeVirtualMemory, -1isize, &mut base, &mut fs, MEM_RELEASE),
"indirect" => syscall!(indirect, NtFreeVirtualMemory, -1isize, &mut base, &mut fs, MEM_RELEASE),
"perunsfart" => syscall!(perunsfart, NtFreeVirtualMemory, -1isize, &mut base, &mut fs, MEM_RELEASE),
_ => unreachable!(),
};
assert_eq!(s_free, 0, "[{}] free failed: 0x{:08X}", method, s_free as u32);
}
}
#[test]
fn all_methods_bypass_same_hook() {
let guard = HookGuard::install(b"NtQueryInformationProcess");
let hooked_fn: unsafe extern "system" fn(isize, u32, *mut u8, u32, *mut u32) -> i32 =
unsafe { core::mem::transmute(guard.stub) };
let mut info = [0u8; 48];
let mut retlen: u32 = 0;
let hooked_result = unsafe { hooked_fn(-1, 0, info.as_mut_ptr(), 48, &mut retlen) };
assert_eq!(hooked_result as u32, HOOK_SENTINEL,
"sanity: hook should return 0x{:08X}", HOOK_SENTINEL);
for method in &["direct", "indirect", "perunsfart"] {
info = [0u8; 48];
retlen = 0;
let status = match *method {
"direct" => syscall!(direct, NtQueryInformationProcess, -1isize, 0u32,
info.as_mut_ptr(), info.len() as u32, &mut retlen),
"indirect" => syscall!(indirect, NtQueryInformationProcess, -1isize, 0u32,
info.as_mut_ptr(), info.len() as u32, &mut retlen),
"perunsfart" => syscall!(perunsfart, NtQueryInformationProcess, -1isize, 0u32,
info.as_mut_ptr(), info.len() as u32, &mut retlen),
_ => unreachable!(),
};
assert_ne!(status as u32, HOOK_SENTINEL,
"[{}] returned HOOK_SENTINEL — call went through the hooked stub!", method);
assert_eq!(status, 0, "[{}] failed: 0x{:08X}", method, status as u32);
assert!(retlen > 0, "[{}] returned no data", method);
}
info = [0u8; 48];
retlen = 0;
let status = syscall!(unhook, NtQueryInformationProcess, -1isize, 0u32,
info.as_mut_ptr(), info.len() as u32, &mut retlen);
assert_ne!(status as u32, HOOK_SENTINEL, "[unhook] returned HOOK_SENTINEL!");
assert_eq!(status, 0, "[unhook] failed: 0x{:08X}", status as u32);
guard.verify_clean();
info = [0u8; 48];
retlen = 0;
let normal_result = unsafe { hooked_fn(-1, 0, info.as_mut_ptr(), 48, &mut retlen) };
assert_eq!(normal_result, 0,
"after unhook, calling through stub should succeed, got 0x{:08X}", normal_result as u32);
}
#[test]
fn basic_alloc_write_free() {
let mut base: *mut core::ffi::c_void = core::ptr::null_mut();
let mut size: usize = 4096;
let s = syscall!(NtAllocateVirtualMemory, -1isize, &mut base, 0usize, &mut size,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
assert_eq!(s, 0);
assert!(!base.is_null());
unsafe { *(base as *mut u64) = 0xCAFEBABE_DEADBEEF; }
assert_eq!(unsafe { *(base as *mut u64) }, 0xCAFEBABE_DEADBEEF);
let mut fs: usize = 0;
let s = syscall!(NtFreeVirtualMemory, -1isize, &mut base, &mut fs, MEM_RELEASE);
assert_eq!(s, 0);
}
#[test]
fn basic_query_process_info() {
let mut info = [0u8; 48];
let mut retlen: u32 = 0;
let s = syscall!(NtQueryInformationProcess, -1isize, 0u32,
info.as_mut_ptr(), info.len() as u32, &mut retlen);
assert_eq!(s, 0);
assert!(retlen > 0);
}