Files
youssefnoob003-SindriKit/docs/domains/primitives/syscalls/engines.md
T
sibouzitoun b7d3cf717c Release v1.2.0: Indirect Syscalls & Pipeline Decoupling
- 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
2026-06-29 19:31:15 +01:00

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

  1. Resolve export address via snd_pe_get_export_address_hash(ntdll, SND_SYS_DLL_SIZE_DEFAULT, func_hash, …).
  2. Read stub bytes at the export address.
  3. 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)
  4. If pattern mismatch (typical when inline-hooked), run 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 ntdll where stubs are pristine

Sort resolver (snd_syscall_resolve_ssn_sort)

Algorithm

  1. One-time table build (build_syscall_table):
    • Parse ntdll export directory
    • Collect exports prefixed with Zw
    • Normalize names to Nt* (ZwCreateFile → hash of NtCreateFile)
    • Store {hash, address} pairs
    • Sort by address ascending
  2. 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

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:

  1. Try scan (cheap, accurate on clean stubs)
  2. 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

  1. Get the natively loaded ntdll.dll base via snd_peb_get_module_base_hash.
  2. Resolve the export address of the target function using its hash.
  3. Scan forward up to 32 bytes for the transition signature:
    • x64: syscall; ret (0F 05 C3)
    • x86: Entry point + 5 bytes (skips mov eax, SSN and enters the OS-specific transition)

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_base passed to snd_syscall_resolve (from global snd_syscall_set_ntdll)
  • PE export parsing (snd_pe_get_export_address_hash for scan; manual EAT walk for sort)
  • Compile-time function hashes from sindri_hashes.h / config/hashes.ini

See also