Technical Deep Dive: The Decoupled Execution Pipeline
The core architectural innovation of SindriKit lies in its complete separation of SSN resolution, gadget discovery, and execution mechanics. Most traditional offensive tools tightly couple their resolution logic with their assembly execution stubs, requiring significant manual rewrites if an operator needs to pivot from direct system calls to indirect system calls. SindriKit implements a dynamic pipeline where developers can dynamically swap components at runtime. This modular approach is handled through a sequence of configuration calls that set the resolver, invoker, and gadget finder components dynamically.
Process Environment Block Traversal and SSN Resolution
To establish an execution path without triggering static import table alerts, SindriKit begins by dynamically resolving necessary system APIs in memory. The framework utilizes a custom Process Environment Block (PEB) walker to locate the base memory address of ntdll.dll. This bypasses the need to call standard Windows API functions such as GetModuleHandle or LoadLibrary, which are heavily monitored by defensive tools. Once the base address is resolved, the framework parses the Export Address Table (EAT) of the module to locate system calls.
To determine the exact SSN required for a specific system call, such as NtAllocateVirtualMemory, the framework employs an API scanning utility. This process reads the function bytes directly from memory, mapping out system calls and extracting their associated numbers. By performing this resolution dynamically at runtime, the payload can seamlessly adapt to different Windows kernel builds without hardcoding static numbers, which frequently change between operating system updates.
Dynamic Gadget Hunting and Stack Allocation
To execute system calls indirectly, SindriKit must locate a legitimate syscall; ret instruction pair within the address space of ntdll.dll. The framework implements a custom scanning module that searches the memory of the natively loaded module for these execution gadgets. Once a valid gadget is located, its memory address is stored in the execution pipeline. Rather than invoking the system call directly from the payload's memory space, which would instantly trigger call-stack telemetry flags, the invoker jumps directly to the legitimate gadget inside ntdll.dll.
Before making this indirect transition, the framework must ensure that the CPU registers and execution stack conform to strict operating system requirements. On x64 Windows systems, the call stack must be 16-byte aligned before calling any function. Failing to maintain this alignment causes immediate stability issues or system crashes. SindriKit utilizes custom Microsoft Macro Assembler (MASM) stubs to handle this alignment, preparing the stack frame precisely before executing the jump to the indirect gadget.
Advanced Telemetry Evasion via Stack Spoofing
While locating indirect system call gadgets helps bypass basic hook detection, advanced EDR agents perform thread call-stack unwinding using the native RtlVirtualUnwind function. This check allows the security agent to trace the execution path back to its root. If the unwind process reveals that the call originated from unbacked, executable memory (such as a private heap or a shellcode allocation), the activity is classified as malicious. To counter this, advanced versions of the framework implement deep stack spoofing.
Exception Directory Parsing and Fat Frame Discovery
To trick the virtual unwinder, the execution stack must appear to contain a legitimate sequence of function calls originating from a trusted system library like kernel32.dll. The evasion framework achieves this by dynamically parsing the Exception Directory (the .pdata section) of natively loaded Windows DLLs. The .pdata directory contains an array of RUNTIME_FUNCTION structures used by the operating system for exception handling and stack unwinding. By reading these structures, the framework calculates the exact stack size allocated by legitimate system functions.
The evasion engine specifically hunts for what are known as "Fat Frames", which are legitimate, natively loaded functions that allocate a significant amount of stack space (typically 120 bytes or more). This massive stack space provides enough shadow space to host system call arguments and a trampoline pointer without corrupting adjacent data. To prevent defenders from establishing simple, deterministic signatures based on the chosen decoy function, the framework randomizes its selection of these Fat Frames using entropy derived from system call hashes and memory addresses.
The Spoofed Frame Execution Sequence
Once a suitable Fat Frame is identified, the framework constructs a synthetic stack layout. It places the address of a trampoline instruction (a simple RET inside the legitimate function body) as the primary return target. The actual return pointer of the malicious payload is hidden within the spoofed stack frame. When a kernel transition occurs, the EDR's virtual unwinder inspects the stack and reads a clean, structurally sound call chain back to kernel32.dll, failing to find any trace of the payload's memory space. Upon return, the CPU executes the trampoline, popping the hidden pointer and safely routing execution back to the payload's cleanup routine.
Enterprise Threat and Strategic Guidance
The availability of highly modular evasion frameworks like SindriKit lowers the barrier of entry for sophisticated adversaries looking to bypass enterprise defensive controls. These techniques allow off-the-shelf malware and custom implants to evade advanced detection mechanisms, rendering standard security monitoring ineffective against post-exploitation activities such as process injection and memory-only execution. For organizations aiming to test their defensive posture against these advanced techniques, implementing Red Team Operations is essential to simulate realistic adversary behaviors and identify telemetry gaps.