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}