Expand description
Dynamically invoke unmanaged Windows APIs without putting their names in your import table.
Dyncvoke walks the PEB for modules, parses export tables for functions, and can dispatch NT calls as indirect syscalls (Tartarus Gate) optionally under a spoofed call stack. Higher-level crates cover manual PE mapping, section overloading, and module fluctuation.
§Platform
Windows x86_64 only (x86_64-pc-windows-msvc or x86_64-pc-windows-gnu).
GNU spoof builds need NASM on PATH. The crate is no_std plus alloc.
§Install
[dependencies]
dyncvoke = "0.1"Feature flags:
| Feature | What it turns on |
|---|---|
syscall (default) | PEB walk, EAT parse, Tartarus Gate, syscall! / do_syscall! |
spoof | Call-stack spoofing, synthetic mode, spoof! / spoof_syscall! |
spoof-desync | Same as spoof, but desync mode instead of synthetic |
manualmap | Map a PE from disk or a buffer |
overload | Section overload / module stomp. Implies manualmap |
dmanager | Fluctuate an overloaded module. Implies overload |
full | All of the above, including desync |
dyncvoke = { version = "0.1", features = ["spoof"] }
dyncvoke = { version = "0.1", features = ["full"] }§How the call macros work
syscall!, do_syscall!, spoof!, and spoof_syscall! all take the
same argument shape. Each argument is widened as usize and passed as a
pointer-width slot. You do not pad with dummy nulls. Trailing commas are
fine. Zero-argument syscalls work (NtYieldExecution).
syscall! and spoof_syscall! return Result<*mut c_void, _>.
Ok(ptr) is the raw NTSTATUS in pointer-width form. Recover it with
as i32. Err means name or SSN resolution failed and the kernel was
never entered. do_syscall! skips resolution and returns *mut c_void
directly.
NtCurrentProcess is -1isize.
§Indirect syscall
Resolve the SSN from ntdll (Hell’s / Halo’s / Tartarus Gate), then jump
to a real syscall; ret gadget inside ntdll.
use dyncvoke::syscall;
use core::ffi::c_void;
use core::ptr::null_mut;
let mut addr: *mut c_void = null_mut();
let mut size: usize = 0x1000;
let mut old: u32 = 0;
let status = syscall!(
"NtProtectVirtualMemory",
-1isize,
&mut addr,
&mut size,
0x20u32,
&mut old,
).unwrap() as i32;Cache the SSN if you call the same function in a loop:
use dyncvoke::{do_syscall, resolve_syscall};
let (ssn, addr) = resolve_syscall("NtClose").unwrap();
let status = do_syscall!(ssn, addr, handle) as i32;Module and export lookup without going through GetModuleHandle /
GetProcAddress:
use dyncvoke::dyncvoke_core::{get_module_base_address, get_function_address};
let ntdll = get_module_base_address("ntdll.dll");
let nt_close = get_function_address(ntdll, "NtClose");Hash form so the plaintext name never lands in .rdata:
use dyncvoke::dyncvoke_core::{get_module_base_address_h, peb};
const NTDLL: u32 = peb::hash_name(b"ntdll.dll");
let base = get_module_base_address_h(NTDLL);§Call-stack spoofing
Two modes, selected at compile time:
- Synthetic (
spooffeature, default). Builds a fake stackRtlUserThreadStart -> BaseThreadInitThunk -> gadget frames -> target. Works from any thread, including pool threads. - Desync (
spoof-desync). Finds a liveBaseThreadInitThunkreturn address on the current thread and splices spoofed frames on top. Looks more like a normal user thread. Does not work on pool threads.
You cannot enable both at once. spoof-desync replaces synthetic.
Spoofed indirect syscall:
use dyncvoke::spoof::{spoof_syscall, AsPointer};
use core::ffi::c_void;
use core::ptr::null_mut;
let mut addr: *mut c_void = null_mut();
let mut size: usize = 0x1000;
let status = spoof_syscall!(
"NtAllocateVirtualMemory",
-1isize,
addr.as_ptr_mut(),
0usize,
size.as_ptr_mut(),
0x3000u32,
0x04u32,
).unwrap() as i32;Spoofed kernel32 call:
use dyncvoke::dyncvoke_core::{get_module_base_address, get_function_address};
use dyncvoke::spoof::spoof;
let k32 = get_module_base_address("kernel32.dll");
let virtual_alloc = get_function_address(k32, "VirtualAlloc");
let addr = unsafe {
spoof!(
virtual_alloc,
core::ptr::null_mut::<core::ffi::c_void>(),
0x1000usize,
0x3000u32,
0x04u32,
)
}.unwrap();AsPointer lets you write addr.as_ptr_mut() instead of
&mut addr as *mut _ as *mut c_void.
§Manual map, overload, fluctuation
use dyncvoke::manualmap;
let (_pe, base) = manualmap::read_and_map_module(
r"C:\Windows\System32\ntdll.dll",
true, // wipe DOS stub
false, // skip TLS callbacks
).unwrap();use dyncvoke::overload;
let payload = std::fs::read(r"c:\temp\payload.dll").unwrap();
let mapped = overload::overload_module(&payload, "").unwrap();use dyncvoke::{overload, dmanager::Manager};
let mut manager = Manager::new();
let m = overload::managed_read_and_overload(
r"c:\windows\system32\payload.dll",
r"c:\windows\system32\cdp.dll",
).unwrap();
manager.new_module(m.1, m.0.0, m.0.1).unwrap();
manager.map_module(m.1).unwrap();
// call into the payload
manager.hide_module(m.1).unwrap();§String obfuscation
lc!("ntdll.dll") encrypts the literal at compile time via obfstr and
returns an alloc::string::String.
§Safety
Almost every public function talks to NT or walks process memory. Wrong
argument types, a bad module base, or a hooked stub that Tartarus Gate
cannot recover will crash the process. Treat the macros as unsafe even
where the wrapper is not marked unsafe.
§Crate map
| Module | Role |
|---|---|
dyncvoke_core | PEB walker, EAT, Tartarus Gate, nt_* wrappers |
dyncvoke_core::sys | SSN extraction and the variadic syscall gateway |
spoof | Synthetic / desync call-stack spoofing |
manualmap | Relocations, IAT rewrite, section permissions |
overload | File-backed section overload and stomping |
dmanager | XOR fluctuation of an overloaded module |
data | FFI types, constants, lc! |
Re-exports§
pub use data;pub use dyncvoke_core;syscallpub use manualmap;manualmappub use overload;overloadpub use dmanager;dmanagerpub use spoof;spoof
Macros§
- do_
syscall syscall - Low-level escape hatch when you already have a resolved
(ssn, addr). - dynamic_
invoke syscall - Dynamically calls an exported function from the specified module.
- syscall
syscall - Resolve a Zw/Nt syscall by name (Tartarus Gate) and dispatch via the variadic Hell’s Hall gateway.
Functions§
- do_
syscall ⚠syscall - Hell’s Hall variadic syscall dispatcher.
- resolve_
syscall syscall - Resolve
(SSN, syscall_instr_addr)for a Zw/Nt export of ntdll.