Skip to main content

Crate dyncvoke

Crate dyncvoke 

Source
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:

FeatureWhat it turns on
syscall (default)PEB walk, EAT parse, Tartarus Gate, syscall! / do_syscall!
spoofCall-stack spoofing, synthetic mode, spoof! / spoof_syscall!
spoof-desyncSame as spoof, but desync mode instead of synthetic
manualmapMap a PE from disk or a buffer
overloadSection overload / module stomp. Implies manualmap
dmanagerFluctuate an overloaded module. Implies overload
fullAll 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 (spoof feature, default). Builds a fake stack RtlUserThreadStart -> BaseThreadInitThunk -> gadget frames -> target. Works from any thread, including pool threads.
  • Desync (spoof-desync). Finds a live BaseThreadInitThunk return 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

ModuleRole
dyncvoke_corePEB walker, EAT, Tartarus Gate, nt_* wrappers
dyncvoke_core::sysSSN extraction and the variadic syscall gateway
spoofSynthetic / desync call-stack spoofing
manualmapRelocations, IAT rewrite, section permissions
overloadFile-backed section overload and stomping
dmanagerXOR fluctuation of an overloaded module
dataFFI types, constants, lc!

Re-exports§

pub use data;
pub use dyncvoke_core;syscall
pub use manualmap;manualmap
pub use overload;overload
pub use dmanager;dmanager
pub use spoof;spoof

Macros§

do_syscallsyscall
Low-level escape hatch when you already have a resolved (ssn, addr).
dynamic_invokesyscall
Dynamically calls an exported function from the specified module.
syscallsyscall
Resolve a Zw/Nt syscall by name (Tartarus Gate) and dispatch via the variadic Hell’s Hall gateway.

Functions§

do_syscallsyscall
Hell’s Hall variadic syscall dispatcher.
resolve_syscallsyscall
Resolve (SSN, syscall_instr_addr) for a Zw/Nt export of ntdll.