- 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
4.9 KiB
Syscall Resolution Engines
SindriKit ships two built-in SSN resolvers. Both implement snd_syscall_resolver_t and plug into the cascading pipeline via snd_syscall_set_resolver / snd_syscall_add_resolver.
| Resolver | File | Hook resistance |
|---|---|---|
snd_syscall_resolve_ssn_scan |
syscalls_scan.c |
Neighbor scan fallback when stub bytes are patched |
snd_syscall_resolve_ssn_sort |
syscalls_sort.c |
Does not read stub bytes; derives SSN from export order |
Scan resolver (snd_syscall_resolve_ssn_scan)
Algorithm
- Resolve export address via
snd_pe_get_export_address_hash(ntdll, SND_SYS_DLL_SIZE_DEFAULT, func_hash, …). - Read stub bytes at the export address.
- Extract SSN from the canonical pattern:
- x64:
4C 8B D1 B8 xx xx(mov r10, rcx; mov eax, SSN) - x86:
B8 xx xx(mov eax, SSN)
- x64:
- If pattern mismatch (typical when inline-hooked), run neighbor search.
Neighbor search
When the target stub is hooked, scans adjacent syscall stubs above and below in memory (step 32 bytes on x64, 16 on x86). For each neighbor with a valid pattern, derives the target SSN by offset index — the same technique family as Halo's Gate.
Caps search at 500 neighbors per direction. Returns SND_STATUS_SSN_NOT_FOUND if no valid neighbor is found.
Output
Populates all snd_syscall_entry_t fields: pAddress, dwHash, wSystemCall.
When to use
- Primary strategy — fast path when stubs are unhooked or only partially hooked
- Works well with clean KnownDlls
ntdllwhere stubs are pristine
Sort resolver (snd_syscall_resolve_ssn_sort)
Algorithm
- One-time table build (
build_syscall_table):- Parse
ntdllexport directory - Collect exports prefixed with
Zw - Normalize names to
Nt*(ZwCreateFile→ hash ofNtCreateFile) - Store
{hash, address}pairs - Sort by address ascending
- Parse
- Lookup: find matching hash; array index = SSN
Windows maps Zw* stubs sequentially in memory; sorted export order correlates with SSN assignment.
Caching
The sorted table is built once per process (g_table_initialized). Subsequent resolves are O(n) linear scan over cached entries (n ≤ SND_MAX_SYSCALLS).
Output
Sets entry_out->wSystemCall to the table index. Does not populate pAddress or dwHash — sufficient for the invoker which only needs the SSN.
When to use
- Fallback strategy when scan fails (heavy hooking, patched stub bytes)
- Immune to inline hooks on individual stubs because SSN is derived from export layout, not stub disassembly
Recommended pipeline
PoCs and production profiles typically register both:
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
Evaluation order:
- Try scan (cheap, accurate on clean stubs)
- On failure, try sort (hook-resistant)
Custom resolvers
Any function matching snd_syscall_resolver_t can be registered:
snd_status_t my_resolver(PVOID ntdll, DWORD func_hash, snd_syscall_entry_t *entry_out) {
// populate entry_out->wSystemCall (required)
// optionally pAddress, dwHash
return SND_OK;
}
snd_syscall_add_resolver(my_resolver);
Maximum 4 strategies in the chain.
Gadget finder (snd_syscall_find_gadget_scan)
Source: gadget_scan.c
Resolves the target function address from the natively loaded NTDLL (via PEB walk, not the user-supplied NTDLL image) and scans for a transition gadget.
Algorithm
- Get the natively loaded
ntdll.dllbase viasnd_peb_get_module_base_hash. - Resolve the export address of the target function using its hash.
- Scan forward up to 32 bytes for the transition signature:
- x64:
syscall; ret(0F 05 C3) - x86: Entry point + 5 bytes (skips
mov eax, SSNand enters the OS-specific transition)
- x64:
Output
Populates entry->pSyscallAddr with the gadget address.
When to use
Required when using snd_syscall_indirect_invoke_asm. The gadget finder runs automatically during snd_syscall_resolve if g_syscall_gadget_finder is set.
Important
The gadget finder uses the natively loaded NTDLL from the process PEB, not the user-supplied NTDLL image. This ensures the gadget points to executable memory regardless of how the SSN source was obtained (disk load, KnownDlls map, etc.).
Dependencies
Both resolvers depend on:
- Valid
ntdll_basepassed tosnd_syscall_resolve(from globalsnd_syscall_set_ntdll) - PE export parsing (
snd_pe_get_export_address_hashfor scan; manual EAT walk for sort) - Compile-time function hashes from
sindri_hashes.h/config/hashes.ini
See also
- Pipeline — bootstrap and invocation
- API reference