Decouples native_load_library from the global NTDLL state. Calling LdrLoadDll on a raw disk image obviously segfaults because the loader locks aren't initialized. Always walking the PEB for this fixes it.
SindriKit
The infrastructure offensive development never had.
A foundational C library for building operationally credible offensive capabilities.
What SindriKit Actually Is
SindriKit is not an implementation. It is the architecture underneath implementations.
Most offensive libraries give you parts. A syscall wrapper. A reflective loader. A process injector. Useful individually, incompatible by default, and you inherit the task of rewriting them from scratch every time an EDR changes its mind.
The real problem is that offensive tools couple two things that should never be coupled: the logic of a technique and the mechanics of its execution. Your loader doesn't just load a PE image. It loads a PE image using VirtualAlloc, LoadLibrary... or whatever Win32 calls you hardcoded months ago. When that gets flagged, you rewrite. The target changes. You rewrite again. You are not fighting the problem. You are fighting your own architecture.
SindriKit applies a single fix to that problem at the design level: any offensive technique is expressed as a context structure that consumes injected API tables. The execution mechanics (e.g. Win32, direct syscall, driver-backed, anything an operator defines) are resolved at runtime through those tables, completely invisible to the technique's logic.
The consequence is immediate: swapping your entire execution strategy from Win32-visible to raw direct-syscall becomes one line of code without having to rewrite your entire toolchain. The technique never knew the difference.
What You Get
| What it means | |
|---|---|
| One mental model | Every domain ships with the same context + API table contract, standardized across each and every technique. |
| Zero rewrite cycle | Swap functions pointers to shift your entire execution profile. The technique never changes. |
| Zero integration friction | One add_subdirectory and target_link_libraries to inherit the full engine. |
| Zero production overhead | Silent tier compiles away every diagnostic string, file reference, and line number. |
60-Second Drop-In
cmake_minimum_required(VERSION 3.16)
project(MyTool C ASM_MASM)
set(SND_BUILD_PAYLOADS OFF CACHE BOOL "")
set(SND_ENABLE_DEBUG OFF CACHE BOOL "")
set(SND_HASH_ALGO "DJB2" CACHE STRING "")
set(SND_RANDOMIZE_SEED ON CACHE BOOL "")
add_subdirectory(libs/SindriKit)
add_executable(my_tool src/main.c)
target_link_libraries(my_tool PRIVATE sindri::engine)
cmake -B build && cmake --build build --config Release
Just two lines for your tool to inherit all of SindriKit's capabilities: PE parsing, syscall resolution, reflective loading...
The Engine
API Abstraction Layer
┌────────────────────────────────────────────────────────────────────────────┐
│ ANY OFFENSIVE INTENT │
│ Loader · Injector · Spoofer · Patcher · Bypasser · Harvester · ... │
├────────────────────────────────────────────────────────────────────────────┤
│ SINDRIKIT API ABSTRACTION LAYER │
│ snd_memory_api_t -> alloc · free · protect │
│ snd_module_api_t -> load_library · get_proc_address · ... │
│ [ future tables ] -> thread · process · object · ... │
├──────────────────┬──────────────────────┬──────────────────────────────────┤
│ Win32 Profile │ Native Profile │ Bring Your Own Mechanic │
│ VirtualAlloc │ NtAllocateVirtual │ Driver · ROP · Exotic │
│ LoadLibraryA │ PEB Walk + EAT │ Operator-defined functions │
└──────────────────┴──────────────────────┴──────────────────────────────────┘
In practice, this means every domain follows the same contract:
// Reflective Loader
snd_loader_ctx_t loader = {0};
loader.raw_source = &payload;
loader.mem_api = &snd_mem_native;
loader.mod_api = &snd_mod_native;
snd_prepare_reflective_image(&loader);
snd_execute_reflective_image(&loader);
// Remote Injector (planned)
snd_injector_ctx_t injector = {0};
injector.mem_api = &snd_mem_native;
injector.target_pid = 1337;
snd_execute_injection(&injector);
// ETW Patcher, Stack Spoofer, Credential Harvester... Same contract, always.
Cascading Syscall Pipeline
No single extraction technique works everywhere. The hook architecture inside an EDR is not what you'll find in another. Hell's Gate for example works until it doesn't.
SindriKit treats syscall resolution as an injectable mechanic. Stack strategies in priority order. The engine falls through until one succeeds:
snd_set_syscall_strategy(snd_hell_extract_syscall);
snd_add_syscall_strategy(snd_halo_extract_syscall);
snd_add_syscall_strategy(snd_veles_extract_syscall);
snd_add_syscall_strategy(any_other_technique_you_want);
Compile-Time Algorithm Agility
Every API name and module string is stripped from the final binary at compile time. Swap hashing algorithms across the entire codebase with a single CMake variable:
set(SND_HASH_ALGO "FNV1A") # or DJB2 recomputes everything automatically
set(SND_RANDOMIZE_SEED ON) # generates a fresh 32-bit seed on next configure
Each hash is embedded alongside a randomly generated seed (if SND_RANDOMIZE_SEED=ON). Static footprint shifts completely between compilations without touching a line of C.
Architecture-Aware Dynamic FFI
A custom MASM assembly bridge for arbitrary runtime function invocation. x64 builds follow the Microsoft x64 calling convention precisely (shadow space, register argument placement, stack alignment). x86 builds push arguments in reverse order with support for both cdecl and stdcall targets.
Bounds-Checked PE Parser
A unified PE32/PE32+ parser with a critical is_mapped flag that correctly handles both raw on-disk images and memory-mapped views. Every data directory access is validated against tracked buffer bounds before dereferencing. Export resolution supports forwarder chains up to depth 4 with hash-based lookup.
Tested against inputs that actually break naive parsers:
- 40+ core test combinations targeting edge-case EXEs, DLLs, bad arguments, missing exports, and TLS callbacks across x86 and x64.
- 100+ dynamic PE mutations generated by the
pe_mutatormodule: zeroed section names, integer overflows, invalide_lfanewbounds, mangled imports. - Full Corkami corpus: cleanly loads valid samples, cleanly rejects malformed ones With no crashes across 99% of the samples.
State-Tracked Domain Contexts
Every offensive operation is managed through a discrete context structure with an explicit stage enumeration. Operations can be paused between stages for sleep obfuscation or staged deployment, resumed cleanly, and inspected for the exact failure point down to the subsystem and reason.
The API Design Philosophy
A context structure owns the operation's state. Injected API tables own the execution mechanics. The two are completely separate.
Bootstrap the execution subsystem once:
PVOID clean_ntdll = NULL;
snd_map_knowndll(&snd_knowndlls_win, L"ntdll.dll", &clean_ntdll);
snd_set_ntdll(clean_ntdll);
snd_set_syscall_strategy(snd_hell_extract_syscall);
snd_add_syscall_strategy(snd_halo_extract_syscall);
One line to change your entire execution strategy:
// Win32 profile
const snd_memory_api_t *mem_api = &snd_mem_win;
// Native profile. Everything above this line stays identical
const snd_memory_api_t *mem_api = &snd_mem_native;
snd_module_api_t follows the same contract. mod_api->get_proc_address resolves to GetProcAddress under snd_mod_win and to manual EAT parsing over PEB-walked module bases under snd_mod_native. The operator's algorithm never changes regardless of which profile is active.
Build Tiers
Debug Tier — SND_ENABLE_DEBUG=ON
For local development. snd_status_t expands to include file, line, and a 128-byte context string buffer. SND_ERR_CTX and SND_DEBUG_PRINT emit state machine transitions, parsed PE field values, and syscall resolution outcomes. Use SND_USE_PRINTF=ON to route output to stdout instead of the debug console.
Silent Tier — SND_ENABLE_DEBUG=OFF
The only acceptable configuration for any binary destined for a target environment. Every diagnostic string, file reference, and line number compiles away completely. snd_status_t collapses to two integers. Nothing else.
set(SND_ENABLE_DEBUG OFF CACHE BOOL "")
set(SND_BUILD_PAYLOADS OFF CACHE BOOL "")
set(SND_RANDOMIZE_SEED ON CACHE BOOL "")
set(SND_HASH_ALGO "DJB2" CACHE STRING "")
add_subdirectory(vendor/SindriKit)
target_link_libraries(my_tool PRIVATE sindri::engine)
Documentation
- Getting Started — Installation, CMake integration, and bootstrapping the engine.
- Core Architecture — The design philosophy, state machines, dependency injection.
- Domain: Primitives — Direct syscall pipelines (Hell's Gate / Halo's Gate etc).
- Domain: Loaders — The staged reflective PE loading pipeline and KnownDlls bootstrapping.
- Parsers — Bounds-checked PE32/PE32+ parsing: exports, imports, relocations.
- Infrastructure — Common API definitions, configuration, and hash manifests.
- Tooling & Tests — Test runner, PE mutator, and Python scripts.
- Examples & PoCs — Step-by-step walkthroughs of
loader_winapiandloader_nowinapi.
Planned: Injection and Evasion domains.
Disclaimer
SindriKit is built for educational, research, and authorized Red Teaming purposes only. For the full legal disclaimer and information regarding OpSec considerations, see the Security Policy.

