Direct & Indirect Syscalls
Overview
If a hook lives in the ntdll stub, the obvious answer is not to call the stub. A direct syscall
loads the service number into EAX and executes syscall from the payload’s own code, skipping
userland entirely.
That works against userland hooks and fails against everything else. The kernel still sees the call,
ETW-TI still reports it, and the syscall instruction now sits in memory that no legitimate module
owns — which is a better signal than the hook it evaded. Indirect syscalls exist to close that gap
by jumping into a real syscall instruction inside ntdll, so a stack walk lands somewhere
plausible.
Service numbers change between Windows builds. Hardcoded SSN tables break on patch Tuesday; that fragility is why the “gate” family of techniques derives them at runtime instead.
The call chain
- 1resolve the syscall number (SSN)Read it from a clean ntdll, or derive it by sorting stub addresses (Hell's Gate / Halo's Gate).
- 2load arguments per the x64 ABIRCX moves to R10; the rest follow the standard register and stack layout.
- 3syscallDirect variant executes the instruction from the payload's own memory.
- 4jmp into ntdll's syscallIndirect variant jumps to a real syscall instruction inside ntdll so the return address looks legitimate.
Reference implementation
; direct: the syscall instruction lives in our own memory NtAllocateVirtualMemory PROC mov r10, rcx mov eax, 18h ; SSN, resolved at runtime in real tooling syscall ret NtAllocateVirtualMemory ENDP ; indirect: same setup, but the instruction executed belongs to ntdll NtAllocateVirtualMemoryIndirect PROC mov r10, rcx mov eax, 18h jmp qword ptr [ntdllSyscallGadget] NtAllocateVirtualMemoryIndirect ENDP
; direct: the syscall instruction lives in our own memory
NtAllocateVirtualMemory PROC
mov r10, rcx
mov eax, 18h ; SSN, resolved at runtime in real tooling
syscall
ret
NtAllocateVirtualMemory ENDP
; indirect: same setup, but the instruction executed belongs to ntdll
NtAllocateVirtualMemoryIndirect PROC
mov r10, rcx
mov eax, 18h
jmp qword ptr [ntdllSyscallGadget]
NtAllocateVirtualMemoryIndirect ENDPDetection
Do not build the detection story on userland hooks. ETW-TI plus a periodic scan for syscall
instructions outside mapped images covers both variants; the stack-walk check is what separates
indirect syscalls from ordinary API use.