dyncvoke-core 0.1.0

PEB walking, dynamic invoke, and Tartarus Gate indirect syscalls for Dyncvoke
//! Hell's Hall variadic gateway.
//!
//! `(ssn, syscall_addr, n_args, ...)` then `jmp` to ntdll's `syscall`.
//! Non-volatiles are saved in the x64 red zone. Args 5..N are slid from
//! `[rsp+0x40]` to `[rsp+0x28]` with `rep movsq`. ntdll's trailing `ret`
//! returns to the Rust caller.

#[cfg(target_arch = "x86_64")]
core::arch::global_asm!("
.global do_syscall

.section .text

do_syscall:
    mov [rsp - 0x8],  rsi
    mov [rsp - 0x10], rdi
    mov [rsp - 0x18], r12

    mov eax, ecx              // eax = ssn (1st arg in rcx)
    mov r12, rdx              // save syscall_addr (2nd arg in rdx)
    mov rcx, r8               // rcx = n_args (3rd arg in r8)

    mov r10, r9               // arg1 (4th arg in r9) -> r10 (syscall ABI)
    mov rdx, [rsp + 0x28]     // arg2
    mov r8,  [rsp + 0x30]     // arg3
    mov r9,  [rsp + 0x38]     // arg4

    sub rcx, 0x4              // remaining args destined for the stack
    jle 2f                    // none -> skip the copy

    lea rsi, [rsp + 0x40]     // src = caller's arg5 slot
    lea rdi, [rsp + 0x28]     // dst = where the kernel reads arg5

    rep movsq
2:

    mov rcx, r12              // rcx = syscall_addr for jmp

    mov rsi, [rsp - 0x8]
    mov rdi, [rsp - 0x10]
    mov r12, [rsp - 0x18]

    jmp rcx
");

#[cfg(target_arch = "x86_64")]
unsafe extern "C" {
    /// Hell's Hall variadic syscall dispatcher.
    ///
    /// Returns the raw NTSTATUS that landed in `rax` after the kernel
    /// transition, encoded as a pointer-width value so callers can route
    /// it through the uniform `*mut c_void` flow that the `syscall!`,
    /// `do_syscall!`, `spoof!`, and `spoof_syscall!` macros share.
    /// `.unwrap() as i32` or `(... as usize) as i32` recovers the
    /// 32-bit NTSTATUS.
    ///
    /// # Safety
    ///
    /// `syscall_addr` must point at a valid `syscall` instruction inside
    /// ntdll (use [`crate::resolve_syscall`]). `n_args` must match the
    /// number of variadic args that follow. The macros cast every arg
    /// `as usize` and then transmute to `*mut c_void`, so each slot is a
    /// uniform 64-bit pointer-width value at the call site. Calling this
    /// function directly bypasses those casts; passing a 32-bit literal
    /// without widening it first lands garbage in the upper bits.
    pub fn do_syscall(
        ssn: u16,
        syscall_addr: usize,
        n_args: u32,
        ...
    ) -> *mut core::ffi::c_void;
}