dyncvoke 0.1.1

Dynamically invoke unmanaged Windows APIs via PEB walking, indirect syscalls, and call-stack spoofing
Documentation
//! 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
//!
//! ```toml
//! [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 |
//!
//! ```toml
//! 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.
//!
//! ```ignore
//! 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:
//!
//! ```ignore
//! 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`:
//!
//! ```ignore
//! 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`:
//!
//! ```ignore
//! 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:
//!
//! ```ignore
//! 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:
//!
//! ```ignore
//! 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
//!
//! ```ignore
//! 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();
//! ```
//!
//! ```ignore
//! use dyncvoke::overload;
//!
//! let payload = std::fs::read(r"c:\temp\payload.dll").unwrap();
//! let mapped = overload::overload_module(&payload, "").unwrap();
//! ```
//!
//! ```ignore
//! 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!` |

#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]

#[cfg(not(windows))]
compile_error!("dyncvoke is Windows-only");

pub use data;

#[cfg(feature = "syscall")]
pub use dyncvoke_core;

#[cfg(feature = "syscall")]
pub use dyncvoke_core::{do_syscall, dynamic_invoke, resolve_syscall, syscall};

#[cfg(feature = "manualmap")]
pub use manualmap;

#[cfg(feature = "overload")]
pub use overload;

#[cfg(feature = "dmanager")]
pub use dmanager;

#[cfg(feature = "spoof")]
pub use spoof;