Indirect
Indirect Method
Principle
The indirect method does not contain a syscall instruction in your binary. Instead, it resolves the address of the original ntdll stub for the requested function and jumps directly to it. The syscall instruction that eventually executes resides inside ntdll's memory range.
The motivation is that EDR products watching the kernel's syscall handler can compare the return address of the syscall with the expected ntdll address. With indirect, the return address points inside ntdll — because that is where the syscall instruction is.
Generated stub (x64, from source)
SPT_Syscall:
pop rax ; discard return address
pop r15 ; load function hash
mov [rsp+ 8], rcx ; save arg1
mov [rsp+16], rdx ; save arg2
mov [rsp+24], r8 ; save arg3
mov [rsp+32], r9 ; save arg4
sub rsp, 0x28 ; shadow space
mov rcx, r15 ; function hash → argument for SPT_GetSyscallAddress
call SPT_GetSyscallAddress ; returns Entries[i].Address (ntdll function start)
mov r15, rax ; store ntdll address
add rsp, 0x28 ; restore stack
mov rcx, [rsp+ 8] ; restore arg1
mov rdx, [rsp+16] ; restore arg2
mov r8, [rsp+24] ; restore arg3
mov r9, [rsp+32] ; restore arg4
jmp r15 ; jump to ntdll function start (NO inline syscall)
What SPT_GetSyscallAddress returns
SPT_GetSyscallAddress (from resolvers/basic.c) returns Entries[i].Address, which is the start address of the ntdll function identified by the given hash.
This is not the syscall opcode's address — it is the beginning of the ntdll stub, which typically starts with MOV R10, RCX / MOV EAX, <SSN>. The kernel sets EAX from the ntdll stub's MOV EAX, <SSN> instruction.
When the jump lands there, the standard ntdll stub executes: it moves the SSN into EAX and then executes syscall. The syscall return address is inside ntdll.
Important: hook behaviour
If the ntdll function is hooked (patched with a JMP at its entry), the jump in the indirect stub lands on the hook, not on the real syscall stub. The iterator is responsible for having verified the address at list-population time, but the .Address field always holds the raw export address — which could be the hooked location.
This means indirect trades return-address detection resistance for potential vulnerability to in-process hooks landing at the entry point. Pair it with iterators that do not depend on the unhooked stub content (FreshyCalls, SysWhispers) rather than opcode-scanning iterators (Hell, Halo, Tartarus).
Characteristics
| Property | Value |
|---|---|
Inline syscall in binary | No |
| SSN resolved explicitly in stub | No — ntdll's own MOV EAX, <SSN> handles it |
| Return address during syscall | Inside ntdll |
| Call-stack origin | Your module |
| Sensitive to in-process entry-point hooks | Yes |
Default iterator
indirect is the default method for the syswhispers iterator.