![]()
When I introduced the cascading syscall pipeline in the initial release, the focus was strictly on resolution, finding the System Service Number (SSN) and bypassing user-mode hooks.
However, modern EDRs have evolved past hooking API calls. They now actively inspect the thread’s call stack when the kernel transition occurs. If a syscall instruction originates from outside of ntdll.dll’s memory space, the execution is flagged.
1.2.0 solves this by completely decoupling SSN resolution from syscall invocation, introducing indirect syscalls and dynamic gadget scanning without breaking the existing API contract.
Source, architecture docs, and PoCs: SindriKit on GitHub.
Direct syscalls (executing the syscall instruction directly from your payload’s memory) are becoming easy to detect. To bypass call-stack analysis, payloads must launch their execution flow through a legitimate syscall; ret instruction located inside NTDLL.
Most tools of course couple this logic together. They resolve the SSN and tightly couple it to a hardcoded offset or a static trampoline mechanic. When you need to pivot back to direct syscalls for a different environment, you’re rewriting core parts of the evasion logic.
SindriKit 1.2.0 formalizes a strict separation between finding the SSN (Resolvers) and executing it (Invokers).
You can now explicitly dictate how the invocation is handled independent of how the SSN was resolved:
// 1. How do we find the SSN?
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
// 2. How do we execute the syscall?
snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
If indirect syscalls start causing stability issues in a specific environment, you change exactly one pointer to drop back to direct execution:
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
The underlying memory allocators, remote process injectors, and reflective loaders don’t change a single line of code.
Indirect invocation requires a valid syscall; ret (x64) or sysenter (x86) gadget.
SindriKit’s snd_syscall_find_gadget_scan automates this safely. Rather than relying on static offsets or the user-supplied NTDLL (which might be loaded from disk and lack executable permissions), the gadget finder automatically walks the process PEB to locate the natively loaded ntdll.dll and dynamically scans for the transition stub.
This ensures that the payload always jumps into legitimate executable memory, regardless of how the SSN was originally discovered.
I could have easily hardcoded these new invokers as the default pointers inside the engine, but that creates a massive flaw.
If snd_syscall_indirect_invoke_asm and the gadget scanner were initialized by default, the C linker would be forced to bundle those entire ASM stubs and scanning algorithms into your final binary even if your implant never actually used them.
Instead, 1.2.0 introduces the SND_USE_DEFAULTS compile-time CMake macro:
set(SND_USE_DEFAULTS ON CACHE BOOL "")
When enabled, the preprocessor wires up the indirect syscall pipeline automatically. When disabled, the dependency graph is cut off at compile time, and the unused evasion logic is completely stripped from your payload, keeping the footprint lean and evasive.
Security tools shouldn’t trap you into a single evasion paradigm. By isolating the invocation mechanics from the resolution strategy, SindriKit 1.2.0 proves that advanced evasion techniques like indirect syscalls can be integrated cleanly, swapped dynamically, and stripped out entirely when not needed.