The Evolution of Sindrikit: An Offensive Development Framework

I created SindriKit due to one problem: every time operational needs changed, the codebase changed. Obfuscation, abstractions, etc started becoming a requirement, and what began as clean code became unreadable.

The project’s argument was that this didn’t have to be the case. If the intent of an offensive technique could be separated from its execution mechanics then complexity and code quality wouldn’t have to be fighting.

After multiple releases, that argument was tested. SindriKit grew from a reflective loader into a toolkit spanning execution primitives, payload parsing, and remote injection. The most recent update (APC injection) is where the architecture proved itself: a new capability fit in without touching existing code.

Source, architecture docs, and PoCs: SindriKit on GitHub. Star for support :)

Primitives and Syscalls

Rather than hardcoding calls to Win32 or NT APIs, memory and cross-process operations are routed through injected structs like snd_process_api_t and snd_mem_api_t. This unlocked an amount of flexibility that remained highly useful down the line.

The execution layer being properly isolated, it became easier to build a configurable syscall pipeline on top of it. At the code level, operators can change between Win32 APIs, native NT APIs… even indirect syscalls via gadget scanning and spoofed call stacks (that dynamically discovered “fat frames”) without touching higher-level logic. For the rest of the framework it didn’t matter which execution profile was active. It just called through the interface and got its results.

Loaders

With a stable execution foundation, the next step was payload handling. SindriKit developed parsers for both PE images and COFF (Beacon Object Files), designing them to operate in local memory. Each context object tracks a payload through its lifecycle (parsing, allocation, relocation, and import resolution) as a series of states.

Resolving BOF external symbols and constructing trampolines dynamically is a complex matter, but because the parsing logic was already separated from the execution environment, the COFF engine could reuse the same injected primitives the PE loader depended on.

Modular Injection

Every injection technique in SindriKit shares a unified state machine, snd_inj_ctx_t, which drives the pipeline from target acquisition through to thread execution. The classic flow worked for both PE and COFF payloads across processes.

APC injection demands a different pipeline. It spawns a process in suspended state, write the payload, queue an APC, then resume. Integrating this with the existing loader infrastructure looked like it might require a rework.

But the PE and COFF loaders were already designed to parse images locally and target a remote execution base. They had no influence about how the payload would run. Two new orchestrators snd_inj_apc_pe and snd_inj_apc_coff integrated the local loader stages with the APC engine stages. The loaders handled relocations and import resolution against the remote base; the APC engine handled cross-process execution.

Static Signatures

The final problem was static analysis, which was quite an issue with any opensource redteaming tool. The traditional approach to signature evasion involves modifying source code like inserting junk, obfuscating strings… Problem was it permanently degrades the codebase and it wasn’t really practical.

SindriKit removed this issue by moving obfuscation to the pre-compilation phase. The Mutation Engine (SND_MORPH), called by CMake, runs a set of Python mutators against a temp copy of the source tree before the compiler does its job. These mutators inject generate dead functions to scramble the call graph, apply pragma-driven memory scrambling for structs… The codebase stays the same but the compiler receives a dynamic target on every build.

Code Examples

The following examples demonstrate Sindrikit in action.

APC + COFF

This snippet demonstrates how the framework handles a Beacon Object File execution via APC injection. The operator injects the desired OS primitive tables into the context, and the engine handles the rest.

snd_ldr_coff_ctx_t ldr_ctx = {0};
snd_inj_ctx_t      inj_ctx = {0};

// Inject local memory and module resolution primitives
ldr_ctx.mem_api    = &snd_mem_nt;
ldr_ctx.mod_api    = &snd_mod_nt;
ldr_ctx.raw_source = &bof_payload;

// Inject remote process and thread primitives
inj_ctx.target_image_path = L"C:\\Windows\\System32\\svchost.exe";
inj_ctx.proc_api   = &snd_proc_nt;
inj_ctx.thread_api = &snd_thread_nt;

// 3. Execute the COFF payload via APC
snd_status_t status = snd_inj_apc_coff(&ldr_ctx, &inj_ctx, "go", args, arg_len);
snd_inj_cleanup(&inj_ctx);

Syscall Decoupling

This example illustrates two evasive capabilities. First, the syscall pipeline is configured to use a spoofed indirect invoker. Second, internal structures are wrapped in macros that allow the pre-compilation mutation engine to randomize their memory layout.

// Pre-compilation struct scrambling
SND_SHUFFLE_START
typedef struct {
    snd_memory_alloc_cb   alloc;
    snd_memory_free_cb    free;
    snd_memory_protect_cb protect;
} snd_memory_api_t;
SND_SHUFFLE_END

// Dynamic Syscall Pipeline Configuration
void bootstrap_evasion() {
    PVOID ntdll = NULL;
    snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll);
    
    // Configure the pipeline
    snd_syscall_set_ntdll(ntdll);
    snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
    
    // Shift execution mechanic to Spoofed Indirect Syscalls
    snd_syscall_set_invoker(snd_syscall_spoofed_invoke_asm);
    snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
    snd_syscall_set_spoof_finder(snd_syscall_find_spoof_scan);
}

Conclusion

SindriKit’s evolution makes it clear that software engineering and offensive capability development aren’t opposed, and they can even be reinforcing. Separation between execution mechanics, payload handling, and injection orchestration means that adding new capabilities change from a difficult refactor to just assembling together existing blocs. The ease with which PE and COFF payloads can be routed through varying syscall profiles and injection is the argument for treating architecture as a real concern in offensive research.

*