# CallGhost
Direct syscall framework for Rust on Windows x86_64. Bypasses usermode API hooks (EDR/AV) by avoiding ntdll stubs entirely or restoring them from clean copies.
## Features
- `syscall!` proc macro with per-call method selection, up to 12 args, compile-time function name hashing
- 4 syscall methods: `direct`, `indirect`, `unhook`, `perunsfart`
- Halo's Gate SSN resolution even when stubs are hooked
- `no_std` runtime, no CRT dependency, usable in shellcode loaders
- SSN cache (export table walked once per function, O(1) after)
- KnownDlls cache (`\KnownDlls\ntdll.dll` mapped once, reused for all unhook ops)
## Methods
| `direct` | `syscall!(NtFoo, ...)` | Inline `syscall` instruction in your code | Your module |
| `indirect` | `syscall!(indirect, NtFoo, ...)` | `call` to a `syscall;ret` gadget inside ntdll | ntdll |
| `unhook` | `syscall!(unhook, NtFoo, ...)` | Restore stub from KnownDlls, then direct syscall | Your module |
| `perunsfart` | `syscall!(perunsfart, NtFoo, ...)` | JIT unhook, call through clean stub, re-hook | ntdll |
### When to use what
**`direct`** is the default. Fast, no dependencies. Use when return-address checks aren't a concern.
**`indirect`** is for when EDR checks if the `syscall` instruction originates from ntdll. The gadget address is cached after first lookup.
**`unhook`** permanently restores a hooked stub so your code or other code in the process can call through ntdll normally again.
**`perunsfart`** is for when you need the call to look 100% legitimate (return address in ntdll) but don't want to leave the stub unhooked, since EDR periodic scans would notice.
## Usage
```toml
[dependencies]
callghost = { git = "https://github.com/PatchRequest/CallGhost", branch = "master" }
```
```rust
use callghost::syscall;
// Allocate memory via direct syscall, no ntdll involved
let mut base: *mut core::ffi::c_void = core::ptr::null_mut();
let mut size: usize = 4096;
let status = syscall!(NtAllocateVirtualMemory,
-1isize, // NtCurrentProcess
&mut base,
0usize,
&mut size,
0x3000u32, // MEM_COMMIT | MEM_RESERVE
0x04u32, // PAGE_READWRITE
);
// Same call via indirect syscall (return address points to ntdll)
let status = syscall!(indirect, NtAllocateVirtualMemory,
-1isize, &mut base, 0usize, &mut size, 0x3000u32, 0x04u32);
// Perun's Fart: JIT unhook, call through ntdll, re-hook
let status = syscall!(perunsfart, NtCreateThreadEx,
&mut thread, 0x1FFFFFu32, core::ptr::null_mut::<u8>(),
process, entry_point, core::ptr::null_mut::<u8>(),
0u32, 0usize, 0usize, 0usize, core::ptr::null_mut::<u8>());
```
### Standalone unhook
```rust
// Unhook a single function
unsafe { callghost::__private::unhook_stub(callghost::__private::fnv1a(b"NtWriteVirtualMemory")) };
// Unhook ALL of ntdll's .text section
unsafe { callghost::__private::unhook_all() };
// Release the cached KnownDlls mapping when done
unsafe { callghost::__private::release_clean_ntdll() };
```
## Architecture
```
callghost/ Facade crate, re-exports macro + runtime
callghost-macros/ Proc macro crate, generates inline asm per call site
callghost-runtime/ no_std runtime: PEB walking, SSN resolution, unhook infra
```
### How it works
1. **Compile time**: `syscall!` hashes the function name (FNV-1a) and generates method-specific inline assembly
2. **First call**: Walks PEB, finds ntdll, walks export table, resolves SSN, caches it
3. **Subsequent calls**: SSN from cache, direct/indirect asm or function pointer call
4. **Hooked stubs**: Halo's Gate sorts all Nt*/Zw* stubs by RVA, finds clean neighbors, calculates SSN by offset
5. **Unhook**: Maps `\KnownDlls\ntdll.dll` (cached), copies clean bytes over hooked stub
## Tests
```
cargo test -- --test-threads=1
```
19 tests covering hash correctness, stub cleanliness verification across 7 functions, SSN uniqueness, gadget location verification, hook proof (calling through a hooked stub returns the sentinel value), all 4 methods bypassing hooks, Halo's Gate with hooked neighbors, permanent unhook with SSN consistency, unhook_all restoring 3 hooked stubs, Perun's Fart succeeding while re-hooking, and full interchangeability (alloc+write+free with every method).
Single-threaded execution required because hook tests temporarily modify ntdll stubs.
## Requirements
Windows x86_64, Rust stable 1.85+ (edition 2024).