Skip to main content

dyncvoke_core/sys/
asm.rs

1//! Hell's Hall variadic gateway.
2//!
3//! `(ssn, syscall_addr, n_args, ...)` then `jmp` to ntdll's `syscall`.
4//! Non-volatiles are saved in the x64 red zone. Args 5..N are slid from
5//! `[rsp+0x40]` to `[rsp+0x28]` with `rep movsq`. ntdll's trailing `ret`
6//! returns to the Rust caller.
7
8#[cfg(target_arch = "x86_64")]
9core::arch::global_asm!("
10.global do_syscall
11
12.section .text
13
14do_syscall:
15    mov [rsp - 0x8],  rsi
16    mov [rsp - 0x10], rdi
17    mov [rsp - 0x18], r12
18
19    mov eax, ecx              // eax = ssn (1st arg in rcx)
20    mov r12, rdx              // save syscall_addr (2nd arg in rdx)
21    mov rcx, r8               // rcx = n_args (3rd arg in r8)
22
23    mov r10, r9               // arg1 (4th arg in r9) -> r10 (syscall ABI)
24    mov rdx, [rsp + 0x28]     // arg2
25    mov r8,  [rsp + 0x30]     // arg3
26    mov r9,  [rsp + 0x38]     // arg4
27
28    sub rcx, 0x4              // remaining args destined for the stack
29    jle 2f                    // none -> skip the copy
30
31    lea rsi, [rsp + 0x40]     // src = caller's arg5 slot
32    lea rdi, [rsp + 0x28]     // dst = where the kernel reads arg5
33
34    rep movsq
352:
36
37    mov rcx, r12              // rcx = syscall_addr for jmp
38
39    mov rsi, [rsp - 0x8]
40    mov rdi, [rsp - 0x10]
41    mov r12, [rsp - 0x18]
42
43    jmp rcx
44");
45
46#[cfg(target_arch = "x86_64")]
47unsafe extern "C" {
48    /// Hell's Hall variadic syscall dispatcher.
49    ///
50    /// Returns the raw NTSTATUS that landed in `rax` after the kernel
51    /// transition, encoded as a pointer-width value so callers can route
52    /// it through the uniform `*mut c_void` flow that the `syscall!`,
53    /// `do_syscall!`, `spoof!`, and `spoof_syscall!` macros share.
54    /// `.unwrap() as i32` or `(... as usize) as i32` recovers the
55    /// 32-bit NTSTATUS.
56    ///
57    /// # Safety
58    ///
59    /// `syscall_addr` must point at a valid `syscall` instruction inside
60    /// ntdll (use [`crate::resolve_syscall`]). `n_args` must match the
61    /// number of variadic args that follow. The macros cast every arg
62    /// `as usize` and then transmute to `*mut c_void`, so each slot is a
63    /// uniform 64-bit pointer-width value at the call site. Calling this
64    /// function directly bypasses those casts; passing a 32-bit literal
65    /// without widening it first lands garbage in the upper bits.
66    pub fn do_syscall(
67        ssn: u16,
68        syscall_addr: usize,
69        n_args: u32,
70        ...
71    ) -> *mut core::ffi::c_void;
72}