- Implemented fully decoupled direct and indirect syscall invocation - Added dynamic PEB-based gadget scanning (syscall; ret / sysenter) - Added x86 & x64 inline MASM stubs with proper stack frame alignment - Introduced SND_USE_DEFAULTS compile-time OpSec macro for lean payload compilation - Extensive documentation across all primitives and examples
3.8 KiB
Syscall Primitives
Direct and indirect syscall invocation bypasses hooked ntdll.dll stubs by resolving System Service Numbers (SSNs) and executing the syscall instruction from custom ASM stubs — either inline (direct) or by jumping to a legitimate NTDLL gadget (indirect).
Header: include/sindri/primitives/syscalls.h
Implementation: src/primitives/execution/syscalls/
Warning
Bootstrap required: Call
snd_syscall_set_ntdll(), configure at least one strategy viasnd_syscall_set_resolver(), and configure the invoker viasnd_syscall_set_invoker()beforesnd_syscall_resolve(). For indirect invocation, also set a gadget finder viasnd_syscall_set_gadget_finder(). WithSND_USE_DEFAULTS=ON, onlysnd_syscall_set_ntdll()is required — the resolver, invoker, and gadget finder are pre-configured at compile time._sysmemory/process/mapping backends depend on this pipeline.
How _sys primitives use syscalls
snd_mem_sys / snd_proc_sys / snd_map_sys
│
▼
snd_syscall_resolve(func_hash) <- cascading strategy chain
│
▼
(gadget finder, if indirect) <- locates syscall/sysenter gadget in NTDLL
│
▼
g_syscall_invoker(&args) <- dispatches to configured ASM stub
│
┌───┴───────────────────┐
▼ ▼
snd_syscall_direct_ snd_syscall_indirect_
invoke_asm invoke_asm
Function hashes come from sindri_hashes.h (build-generated, e.g. SND_HASH_NTALLOCATEVIRTUALMEMORY).
Resolution strategies
| Function | Source | Approach |
|---|---|---|
snd_syscall_resolve_ssn_scan |
syscalls_scan.c |
Read SSN from stub bytes; neighbor scan if hooked |
snd_syscall_resolve_ssn_sort |
syscalls_sort.c |
Build sorted Zw* export table; index = SSN |
Typical registration:
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
// or for indirect:
// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
Custom resolvers matching snd_syscall_resolver_t can be added to the chain (max 4 strategies).
Invocation modes
| Function | File | Description |
|---|---|---|
snd_syscall_direct_invoke_asm |
invoke_direct_x64.asm / invoke_direct_x86.asm |
Executes syscall/sysenter inline |
snd_syscall_indirect_invoke_asm |
invoke_indirect_x64.asm / invoke_indirect_x86.asm |
Jumps to a legitimate NTDLL gadget |
Note
Build with
SND_USE_DEFAULTS=ONto pre-configure the invoker (snd_syscall_direct_invoke_asm), gadget finder (snd_syscall_find_gadget_scan), and primary resolver (snd_syscall_resolve_ssn_scan) at compile time. Onlysnd_syscall_set_ntdll()is then needed at runtime.
Invocation modes
| Function | File | Description |
|---|---|---|
snd_syscall_direct_invoke_asm |
invoke_direct_x64.asm / invoke_direct_x86.asm |
Executes syscall/sysenter inline |
snd_syscall_indirect_invoke_asm |
invoke_indirect_x64.asm / invoke_indirect_x86.asm |
Jumps to a legitimate NTDLL gadget |
Note on
SND_USE_DEFAULTS: When enabled, the framework pre-configures the invoker (indirect), gadget finder, and primary resolver automatically.
Table of Contents
- pipeline.md — lifecycle from bootstrap to kernel transition
- engines.md — scan vs sort resolver internals
- api_reference.md — full public API
Related documentation
- Execution domain — shared
src/primitives/execution/layout - Mapping — KnownDlls bootstrap for clean
ntdll - Primitives domain