Syscall Trampoline
; syscall_trampoline_x64.asm
; v0.13.0 (c) Alexander 'xaitax' Hagenah
; Licensed under the MIT License. See LICENSE file in the project root for full license information.
;
; The definitive, ABI-compliant, and argument-aware x64 trampoline.
; This version combines a stable stack frame with meticulous preservation of
; non-volatile registers, guaranteeing no corruption of the C++ caller's state.
.code
ALIGN 16
PUBLIC SyscallTrampoline
SyscallTrampoline PROC FRAME
; — Prologue: Establish a stable, ABI-compliant stack frame —
; We push RBP to create the frame, then immediately push every non-volatile
; register that this function will modify. This is the most critical step
; for preventing caller-state corruption.
push rbp
mov rbp, rsp
push r12
push r13
push rdi
push rsi
sub rsp, 128 ; Allocate stack space for shadow space and locals.
; Mark the end of the prologue for the ml64 assembler.
.ENDPROLOG
; — Preserve SYSCALL_ENTRY* pointer —
; We save it in a preserved register (R12) for use throughout the function.
mov r12, rcx
; — Marshal C arguments to the x64 Syscall Convention —
; Kernel requires arguments in: R10, RDX, R8, R9, and then the stack.
mov rcx, rdx ; C-Arg2 (e.g., ProcessHandle) -> goes into RCX temporarily.
mov rdx, r8 ; C-Arg3 -> Syscall-Arg2 (RDX)
mov r8, r9 ; C-Arg4 -> Syscall-Arg3 (R8)
mov r9, [rbp+30h] ; C-Arg5 (from original caller's stack) -> Syscall-Arg4 (R9)
; — Dynamically marshal stack arguments using an argument-aware loop —
; This prevents reading garbage from the caller's stack, which would cause
; STATUS_INVALID_PARAMETER_MIX errors.
mov r13d, [r12+8] ; Load nArgs from SYSCALL_ENTRY into our preserved R13 register.
cmp r13d, 4
jle _DispatchSetup ; If 4 or fewer args, no stack marshalling is needed.
sub r13d, 4 ; R13d now holds the exact count of stack args to move.
; Prepare pointers for the block move using our preserved registers.
lea rdi, [rsp+20h] ; RDI = Destination (Syscall-Arg 5's slot on our local stack).
lea rsi, [rbp+38h] ; RSI = Source (C-Arg 6's slot on the caller's stack).
push rcx ; Temporarily save Syscall-Arg1, as REP MOVSQ uses RCX.
mov ecx, r13d ; Load the argument count into the loop counter.
rep movsq ; Execute the block move of QWORDs.
pop rcx ; Restore Syscall-Arg1.
_DispatchSetup:
; — Final preparation for kernel transition —
; Emulate the mandatory ntdll stub behavior to prevent STATUS_INVALID_HANDLE.
mov r10, rcx ; Copy Syscall-Arg1 from RCX to R10.
; Load the SSN and gadget address.
movzx eax, word ptr [r12+12] ; Load ssn from SYSCALL_ENTRY (offset 12).
mov r11, [r12] ; Load pSyscallGadget from SYSCALL_ENTRY (offset 0).
; — Dispatch the syscall —
call r11
; — Epilogue: Cleanly unwind and return to C++ —
; The NTSTATUS result is already in RAX, the correct return register.
add rsp, 128 ; Deallocate local stack space.
pop rsi ; Restore all preserved registers in reverse order.
pop rdi
pop r13
pop r12
pop rbp ; Restore the caller's frame pointer.
ret
SyscallTrampoline ENDP
END
Last updated