mirror of
https://github.com/youssefnoob003/SindriKit
synced 2026-07-07 21:57:09 +00:00
Release v1.2.0: Indirect Syscalls & Pipeline Decoupling
- Implemented fully decoupled direct and indirect syscall invocation - Added dynamic PEB-based gadget scanning (syscall; ret / sysenter) - Added x86 & x64 inline MASM stubs with proper stack frame alignment - Introduced SND_USE_DEFAULTS compile-time OpSec macro for lean payload compilation - Extensive documentation across all primitives and examples
This commit is contained in:
@@ -76,7 +76,7 @@ There is **no** `snd_mod_sys`. Import resolution during reflective load uses PEB
|
||||
|---|---|---|
|
||||
| `_win` | Documented Win32 APIs | memory, modules, mapping, process |
|
||||
| `_nt` | NT function pointers resolved via PEB walk + EAT parse | memory, modules, mapping, process |
|
||||
| `_sys` | Direct syscalls (`snd_syscall_invoke_asm`) | memory, mapping, process — **not** modules |
|
||||
| `_sys` | Direct or indirect syscalls (configurable invoker) | memory, mapping, process — **not** modules |
|
||||
|
||||
### OpSec profile examples
|
||||
|
||||
@@ -99,8 +99,13 @@ PVOID ntdll = NULL;
|
||||
snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); // or disk / PEB
|
||||
|
||||
snd_syscall_set_ntdll(ntdll);
|
||||
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
|
||||
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
|
||||
// or for indirect syscalls:
|
||||
// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
```
|
||||
|
||||
This is **global execution state**, not part of any DI table. Implant init code runs it once; all `_sys` tables benefit.
|
||||
|
||||
@@ -47,8 +47,13 @@ static snd_status_t bootstrap_engine(void) {
|
||||
return status;
|
||||
|
||||
snd_syscall_set_ntdll(ntdll);
|
||||
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
|
||||
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
|
||||
// or for indirect syscalls:
|
||||
// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
|
||||
return SND_OK;
|
||||
}
|
||||
|
||||
@@ -10,19 +10,19 @@ The former monolithic `helpers.h` and `nt_defs.h` were split for clarity:
|
||||
|
||||
```
|
||||
include/sindri/common/
|
||||
├── macros.h ← compiler / linkage macros (was part of helpers)
|
||||
├── memory.h ← bounds + memzero/memcpy + SND_PTR_ADD
|
||||
├── string.h ← ASCII + wide string helpers (new wide APIs)
|
||||
├── buffer.h ← tracked buffers
|
||||
├── macros.h <- compiler / linkage macros (was part of helpers)
|
||||
├── memory.h <- bounds + memzero/memcpy + SND_PTR_ADD
|
||||
├── string.h <- ASCII + wide string helpers (new wide APIs)
|
||||
├── buffer.h <- tracked buffers
|
||||
├── hash.h
|
||||
├── status.h
|
||||
├── debug.h ← debug macros (was mixed into helpers)
|
||||
├── debug.h <- debug macros (was mixed into helpers)
|
||||
└── disk.h
|
||||
|
||||
include/sindri/internal/nt/ ← was sindri/internal/nt_defs.h
|
||||
├── types.h ← NT structs, SND_PAGE_SIZE, unicode init
|
||||
├── api.h ← Nt* typedefs
|
||||
└── peb.h ← PEB / LDR layouts
|
||||
include/sindri/internal/nt/ <- was sindri/internal/nt_defs.h
|
||||
├── types.h <- NT structs, SND_PAGE_SIZE, unicode init
|
||||
├── api.h <- Nt* typedefs
|
||||
└── peb.h <- PEB / LDR layouts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -10,10 +10,10 @@ Each technique adds engine functions and chains under a subdirectory but mutates
|
||||
|
||||
```
|
||||
include/sindri/injection/
|
||||
├── context.h ← shared across ALL techniques
|
||||
├── context.h <- shared across ALL techniques
|
||||
└── classic/
|
||||
├── engine.h ← per-stage classic engine
|
||||
└── chain.h ← snd_inj_classic_shell, snd_inj_classic_pe
|
||||
├── engine.h <- per-stage classic engine
|
||||
└── chain.h <- snd_inj_classic_shell, snd_inj_classic_pe
|
||||
```
|
||||
|
||||
Loader contexts are **per-technique** (`snd_ldr_pe_ctx_t` today).
|
||||
|
||||
@@ -100,7 +100,7 @@ The PE chain deliberately interleaves loader and injection stages so relocations
|
||||
| 1 | Loader | `snd_pe_parse(ldr_ctx->raw_source, FALSE, &ldr_ctx->pe)` → `SND_STAGE_PARSED` |
|
||||
| 2 | Loader | `snd_ldr_pe_compatibility_check` |
|
||||
| 3 | Loader | `snd_ldr_pe_allocate_and_copy_image` — local RW mapping |
|
||||
| 4 | Injection | `inj_ctx->payload` ← local mapped buffer (`local_base`, `allocated_size`) |
|
||||
| 4 | Injection | `inj_ctx->payload` <- local mapped buffer (`local_base`, `allocated_size`) |
|
||||
| 5 | Injection | `snd_inj_classic_open_target` |
|
||||
| 6 | Injection | `snd_inj_classic_alloc_remote` — remote RW region sized to `allocated_size` |
|
||||
| 7 | Loader | `ldr_ctx->target.execution_base = inj_ctx->remote_base` |
|
||||
|
||||
@@ -57,8 +57,8 @@ Loaders and injection PoCs use KnownDlls to bootstrap clean `ntdll` for syscall
|
||||
|
||||
```
|
||||
include/sindri/loaders/
|
||||
├── reflective/ ← implemented (snd_ldr_pe_ctx_t)
|
||||
└── <technique>/ ← future, technique-specific context + engine + chain
|
||||
├── reflective/ <- implemented (snd_ldr_pe_ctx_t)
|
||||
└── <technique>/ <- future, technique-specific context + engine + chain
|
||||
```
|
||||
|
||||
Each technique owns its context struct, stage enum, engine functions, and chain orchestrator.
|
||||
|
||||
@@ -169,8 +169,12 @@ Typical sequence before `snd_mem_sys` / `snd_proc_sys`:
|
||||
PVOID clean_ntdll = NULL;
|
||||
snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &clean_ntdll);
|
||||
snd_syscall_set_ntdll(clean_ntdll);
|
||||
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
|
||||
// or for indirect syscalls:
|
||||
// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -13,7 +13,7 @@ Foundation layer for SindriKit. Loaders, injection, and future domains rely on i
|
||||
| [modules/](modules/) | `snd_mod_win`, `snd_mod_nt` (no `_sys`) |
|
||||
| [mapping/](mapping/) | `snd_map_win`, `snd_map_nt`, `snd_map_sys`, KnownDlls |
|
||||
| [process/](process/) | `snd_proc_win`, `snd_proc_nt`, `snd_proc_sys` |
|
||||
| [syscalls/](syscalls/) | SSN resolution pipeline, `snd_syscall_invoke_asm` |
|
||||
| [syscalls/](syscalls/) | SSN resolution pipeline, configurable invoker (direct / indirect) |
|
||||
| [execution/](execution/) | FFI (`snd_ffi_execute`), Heaven's Gate |
|
||||
|
||||
Contract definitions: `include/sindri/primitives/os_api.h`
|
||||
|
||||
@@ -8,7 +8,7 @@ Low-level mechanisms for **calling unknown function signatures**, **crossing WoW
|
||||
|---|---|
|
||||
| `sindri/primitives/ffi.h` | `snd_ffi_execute` — dynamic call bridge for resolved exports |
|
||||
| `sindri/primitives/heavens_gate.h` | `snd_is_wow64`, `snd_hg_execute_64` — WoW64 → native x64 |
|
||||
| `sindri/primitives/syscalls.h` | SSN resolution pipeline + `snd_syscall_invoke_asm` (documented under [syscalls/](../syscalls/)) |
|
||||
| `sindri/primitives/syscalls.h` | SSN resolution pipeline + `snd_syscall_direct_invoke_asm` / `snd_syscall_indirect_invoke_asm` (documented under [syscalls/](../syscalls/)) |
|
||||
|
||||
All three are pulled in by `include/sindri/primitives.h` and `include/sindri.h`.
|
||||
|
||||
@@ -19,21 +19,24 @@ Implementation lives under `src/primitives/execution/`:
|
||||
```
|
||||
src/primitives/execution/
|
||||
├── ffi/
|
||||
│ ├── ffi_invoke.c ← snd_ffi_execute validation + dispatch
|
||||
│ ├── ffi_invoke.c <- snd_ffi_execute validation + dispatch
|
||||
│ └── asm/
|
||||
│ ├── ffi_x64.asm ← snd_ffi_bridge_x64
|
||||
│ └── ffi_x86.asm ← snd_ffi_bridge_x86
|
||||
│ ├── ffi_x64.asm <- snd_ffi_bridge_x64
|
||||
│ └── ffi_x86.asm <- snd_ffi_bridge_x86
|
||||
├── heavens_gate/
|
||||
│ ├── heavens_gate.c ← WoW64 detection + argument padding
|
||||
│ ├── heavens_gate.c <- WoW64 detection + argument padding
|
||||
│ └── asm/
|
||||
│ └── heavens_gate_x86.asm ← snd_hg_invoke_x86 (CS 0x33 transition)
|
||||
│ └── heavens_gate_x86.asm <- snd_hg_invoke_x86 (CS 0x33 transition)
|
||||
└── syscalls/
|
||||
├── syscalls.c ← strategy chain, snd_syscall_resolve
|
||||
├── syscalls_scan.c ← snd_syscall_resolve_ssn_scan
|
||||
├── syscalls_sort.c ← snd_syscall_resolve_ssn_sort
|
||||
└── asm/
|
||||
├── invoke_x64.asm ← snd_syscall_invoke_asm (x64)
|
||||
└── invoke_x86.asm ← snd_syscall_invoke_asm (x86)
|
||||
├── syscalls.c <- strategy chain, snd_syscall_resolve
|
||||
├── syscalls_scan.c <- snd_syscall_resolve_ssn_scan
|
||||
├── syscalls_sort.c <- snd_syscall_resolve_ssn_sort
|
||||
├── gadget_scan.c <- snd_syscall_find_gadget_scan
|
||||
└── asm/
|
||||
├── invoke_direct_x64.asm <- snd_syscall_direct_invoke_asm (x64)
|
||||
├── invoke_direct_x86.asm <- snd_syscall_direct_invoke_asm (x86)
|
||||
├── invoke_indirect_x64.asm <- snd_syscall_indirect_invoke_asm (x64)
|
||||
└── invoke_indirect_x86.asm <- snd_syscall_indirect_invoke_asm (x86)
|
||||
```
|
||||
|
||||
Syscall resolution and invocation are documented in the dedicated [syscalls](../syscalls/) subtree; this directory focuses on FFI and Heaven's Gate.
|
||||
@@ -56,6 +59,6 @@ DllMain, TLS, and EXE entry use **typed direct calls** inside `snd_ldr_pe_execut
|
||||
|
||||
## Related documentation
|
||||
|
||||
- [Syscalls domain](../syscalls/README.md) — SSN engines, bootstrap, `snd_syscall_invoke_asm`
|
||||
- [Syscalls domain](../syscalls/README.md) — SSN engines, bootstrap, `snd_syscall_direct_invoke_asm` / `snd_syscall_indirect_invoke_asm`
|
||||
- [Loaders: DLL export FFI](../../loaders/techniques.md)
|
||||
- [PoC: heavens_gate](../../../examples/heavens_gate.md)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Public headers: `include/sindri/primitives/ffi.h`, `include/sindri/primitives/heavens_gate.h`.
|
||||
|
||||
For the syscall surface (`snd_syscall_resolve`, `snd_syscall_invoke_asm`, strategy pipeline), see [syscalls/api_reference.md](../syscalls/api_reference.md).
|
||||
For the syscall surface (`snd_syscall_resolve`, `snd_syscall_direct_invoke_asm`, `snd_syscall_indirect_invoke_asm`, strategy pipeline), see [syscalls/api_reference.md](../syscalls/api_reference.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -93,6 +93,7 @@ snd_status_t snd_hg_execute_64(
|
||||
| `snd_ffi_bridge_x64` | `ffi/asm/ffi_x64.asm` | x64 fast-call + shadow space |
|
||||
| `snd_ffi_bridge_x86` | `ffi/asm/ffi_x86.asm` | Reverse-order stack push, ESP restore |
|
||||
| `snd_hg_invoke_x86` | `heavens_gate/asm/heavens_gate_x86.asm` | Far return to CS `0x33`, x64 call |
|
||||
| `snd_syscall_invoke_asm` | `syscalls/asm/invoke_x64.asm` or `invoke_x86.asm` (arch-selected) | Direct `syscall` instruction stub |
|
||||
| `snd_syscall_direct_invoke_asm` | `syscalls/asm/invoke_direct_x64.asm` or `invoke_direct_x86.asm` | Direct `syscall` / `sysenter` instruction stub |
|
||||
| `snd_syscall_indirect_invoke_asm` | `syscalls/asm/invoke_indirect_x64.asm` or `invoke_indirect_x86.asm` | Indirect syscall via NTDLL gadget |
|
||||
|
||||
These are linked into `sindri::engine`; callers use the C wrappers above.
|
||||
|
||||
@@ -41,7 +41,7 @@ The `snd_map_sys` implementation invokes `NtOpenSection`, `NtMapViewOfSection`,
|
||||
Direct syscalls bypass userland EDR hooks placed inside `ntdll.dll` stubs. This is the preferred backend for stealth KnownDlls mapping once the syscall pipeline is bootstrapped.
|
||||
|
||||
> [!WARNING]
|
||||
> `snd_map_sys` requires the operator to configure the syscall pipeline (`snd_syscall_set_ntdll`, `snd_syscall_strategy_set`, `snd_syscall_strategy_add`) before invocation. Unconfigured pipelines cause immediate resolution failures.
|
||||
> `snd_map_sys` requires the operator to configure the syscall pipeline (`snd_syscall_set_ntdll`, `snd_syscall_set_resolver`, `snd_syscall_set_invoker`) before invocation. Unconfigured pipelines cause immediate resolution failures.
|
||||
|
||||
## KnownDlls Bootstrapping
|
||||
|
||||
@@ -56,8 +56,12 @@ if (SND_FAILED(status)) {
|
||||
|
||||
// Feed the mapped image into the syscall pipeline
|
||||
snd_syscall_set_ntdll(clean_ntdll);
|
||||
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
|
||||
// or for indirect syscalls:
|
||||
// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
```
|
||||
|
||||
The helper builds the full Object Manager path (`SND_TARGET_KNOWNDLLS_DIR` + DLL name), calls `open` and `view`, then closes the section handle. The mapped view remains valid after the handle is closed.
|
||||
|
||||
@@ -45,12 +45,16 @@ Direct syscalls bypass userland EDR hooks inside `ntdll.dll` stubs. The kernel r
|
||||
> [!WARNING]
|
||||
> `snd_mem_sys` requires the operator to bootstrap the syscall pipeline before invocation:
|
||||
>
|
||||
|
||||
> ```c
|
||||
> snd_syscall_set_ntdll(clean_ntdll_base);
|
||||
> snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan);
|
||||
> snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort);
|
||||
> snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
> snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
> snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
|
||||
> // or for indirect syscalls:
|
||||
> // snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
> // snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
> ```
|
||||
>
|
||||
> If the pipeline is not configured, syscall resolution fails immediately and all `_sys` memory calls return an error.
|
||||
|
||||
## Mixing Backends
|
||||
|
||||
@@ -45,7 +45,7 @@ Remote thread creation uses `NtCreateThreadEx` with the same access mask (`0x1FF
|
||||
Direct syscalls bypass userland EDR hooks on `ntdll.dll` stubs. This is the preferred backend for stealth injection profiles.
|
||||
|
||||
> [!WARNING]
|
||||
> `snd_proc_sys` requires a bootstrapped syscall pipeline (`snd_syscall_set_ntdll`, `snd_syscall_strategy_set`, `snd_syscall_strategy_add`) before any `_sys` process call. See the [Syscall Primitives](../syscalls/README.md) documentation.
|
||||
> `snd_proc_sys` requires a bootstrapped syscall pipeline (`snd_syscall_set_ntdll`, `snd_syscall_set_resolver`, `snd_syscall_set_invoker`) before any `_sys` process call. See the [Syscall Primitives](../syscalls/README.md) documentation.
|
||||
|
||||
## Injection Integration
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Syscall Primitives
|
||||
|
||||
Direct syscall invocation bypasses hooked `ntdll.dll` stubs by resolving System Service Numbers (SSNs) and executing the `syscall` instruction from custom ASM stubs.
|
||||
Direct and indirect syscall invocation bypasses hooked `ntdll.dll` stubs by resolving System Service Numbers (SSNs) and executing the `syscall` instruction from custom ASM stubs — either inline (direct) or by jumping to a legitimate NTDLL gadget (indirect).
|
||||
|
||||
**Header:** `include/sindri/primitives/syscalls.h`
|
||||
**Implementation:** `src/primitives/execution/syscalls/`
|
||||
|
||||
> [!WARNING]
|
||||
> **Bootstrap required:** Call `snd_syscall_set_ntdll()` and configure at least one strategy via `snd_syscall_strategy_set()` before `snd_syscall_resolve()`. `_sys` memory/process/mapping backends depend on this pipeline.
|
||||
> **Bootstrap required:** Call `snd_syscall_set_ntdll()`, configure at least one strategy via `snd_syscall_set_resolver()`, and configure the invoker via `snd_syscall_set_invoker()` before `snd_syscall_resolve()`. For indirect invocation, also set a gadget finder via `snd_syscall_set_gadget_finder()`. With `SND_USE_DEFAULTS=ON`, only `snd_syscall_set_ntdll()` is required — the resolver, invoker, and gadget finder are pre-configured at compile time. `_sys` memory/process/mapping backends depend on this pipeline.
|
||||
|
||||
## How `_sys` primitives use syscalls
|
||||
|
||||
@@ -14,10 +14,18 @@ Direct syscall invocation bypasses hooked `ntdll.dll` stubs by resolving System
|
||||
snd_mem_sys / snd_proc_sys / snd_map_sys
|
||||
│
|
||||
▼
|
||||
snd_syscall_resolve(func_hash) ← cascading strategy chain
|
||||
snd_syscall_resolve(func_hash) <- cascading strategy chain
|
||||
│
|
||||
▼
|
||||
snd_syscall_invoke_asm(&args) ← x64/x86 ASM stub
|
||||
(gadget finder, if indirect) <- locates syscall/sysenter gadget in NTDLL
|
||||
│
|
||||
▼
|
||||
g_syscall_invoker(&args) <- dispatches to configured ASM stub
|
||||
│
|
||||
┌───┴───────────────────┐
|
||||
▼ ▼
|
||||
snd_syscall_direct_ snd_syscall_indirect_
|
||||
invoke_asm invoke_asm
|
||||
```
|
||||
|
||||
Function hashes come from `sindri_hashes.h` (build-generated, e.g. `SND_HASH_NTALLOCATEVIRTUALMEMORY`).
|
||||
@@ -29,15 +37,38 @@ Function hashes come from `sindri_hashes.h` (build-generated, e.g. `SND_HASH_NTA
|
||||
| `snd_syscall_resolve_ssn_scan` | `syscalls_scan.c` | Read SSN from stub bytes; neighbor scan if hooked |
|
||||
| `snd_syscall_resolve_ssn_sort` | `syscalls_sort.c` | Build sorted `Zw*` export table; index = SSN |
|
||||
|
||||
Typical registration — scan first, sort as fallback:
|
||||
Typical registration:
|
||||
|
||||
```c
|
||||
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
|
||||
// or for indirect:
|
||||
// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
```
|
||||
|
||||
Custom resolvers matching `snd_syscall_resolver_t` can be added to the chain (max 4 strategies).
|
||||
|
||||
## Invocation modes
|
||||
|
||||
| Function | File | Description |
|
||||
|---|---|---|
|
||||
| `snd_syscall_direct_invoke_asm` | `invoke_direct_x64.asm` / `invoke_direct_x86.asm` | Executes `syscall`/`sysenter` inline |
|
||||
| `snd_syscall_indirect_invoke_asm` | `invoke_indirect_x64.asm` / `invoke_indirect_x86.asm` | Jumps to a legitimate NTDLL gadget |
|
||||
|
||||
> [!NOTE]
|
||||
> Build with `SND_USE_DEFAULTS=ON` to pre-configure the invoker (`snd_syscall_direct_invoke_asm`), gadget finder (`snd_syscall_find_gadget_scan`), and primary resolver (`snd_syscall_resolve_ssn_scan`) at compile time. Only `snd_syscall_set_ntdll()` is then needed at runtime.
|
||||
|
||||
## Invocation modes
|
||||
|
||||
| Function | File | Description |
|
||||
|---|---|---|
|
||||
| `snd_syscall_direct_invoke_asm` | `invoke_direct_x64.asm` / `invoke_direct_x86.asm` | Executes `syscall`/`sysenter` inline |
|
||||
| `snd_syscall_indirect_invoke_asm` | `invoke_indirect_x64.asm` / `invoke_indirect_x86.asm` | Jumps to a legitimate NTDLL gadget |
|
||||
|
||||
> **Note on `SND_USE_DEFAULTS`**: When enabled, the framework pre-configures the invoker (indirect), gadget finder, and primary resolver automatically.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [pipeline.md](pipeline.md) — lifecycle from bootstrap to kernel transition
|
||||
|
||||
@@ -24,18 +24,23 @@ Internal pipeline depth: **4 strategies** (`SND_MAX_INTERNAL_STRATEGIES` in `sys
|
||||
| `pAddress` | `PVOID` | Stub address (populated by scan resolver) |
|
||||
| `dwHash` | `DWORD` | Target function hash |
|
||||
| `wSystemCall` | `WORD` | Resolved SSN |
|
||||
| `pSyscallAddr` | `PVOID` | Gadget address for indirect invocation (populated by gadget finder) |
|
||||
|
||||
> [!NOTE]
|
||||
> The sort resolver sets `wSystemCall` only. `_sys` backends use `entry.wSystemCall` for invocation.
|
||||
|
||||
### `snd_syscall_args_t`
|
||||
|
||||
Arguments for `snd_syscall_invoke_asm`. On x64, `arg1` is moved to `R10` per the syscall convention; `arg2`–`arg4` use `RDX`/`R8`/`R9`; `arg5`–`arg11` are stack arguments.
|
||||
Arguments for the syscall invoker (`snd_syscall_direct_invoke_asm` or `snd_syscall_indirect_invoke_asm`). On x64, `arg1` is moved to `R10` per the syscall convention; `arg2`–`arg4` use `RDX`/`R8`/`R9`; `arg5`–`arg11` are stack arguments.
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `ssn` | `WORD` | SSN placed in `EAX`/`RAX` |
|
||||
| `arg1`–`arg11` | `PVOID` | Syscall arguments |
|
||||
| `sys_addr` | `PVOID` | Gadget address for indirect invocation; ignored by direct stub |
|
||||
|
||||
> [!NOTE]
|
||||
> `sys_addr` is placed at the end of the struct to preserve memory offsets for args 1-11, ensuring backward compatibility with existing ASM stubs.
|
||||
|
||||
---
|
||||
|
||||
@@ -51,27 +56,27 @@ typedef snd_status_t (*snd_syscall_resolver_t)(
|
||||
);
|
||||
```
|
||||
|
||||
### `snd_syscall_strategy_set`
|
||||
### `snd_syscall_set_resolver`
|
||||
|
||||
Sets the **primary** strategy and **resets** the chain to a single entry.
|
||||
|
||||
```c
|
||||
void snd_syscall_strategy_set(snd_syscall_resolver_t resolver);
|
||||
void snd_syscall_set_resolver(snd_syscall_resolver_t resolver);
|
||||
```
|
||||
|
||||
**Returns:** `void`
|
||||
|
||||
---
|
||||
|
||||
### `snd_syscall_strategy_add`
|
||||
### `snd_syscall_add_resolver`
|
||||
|
||||
Appends a fallback strategy. Evaluated in registration order after the primary.
|
||||
|
||||
```c
|
||||
snd_status_t snd_syscall_strategy_add(snd_syscall_resolver_t resolver);
|
||||
snd_status_t snd_syscall_add_resolver(snd_syscall_resolver_t resolver);
|
||||
```
|
||||
|
||||
**Returns:** `SND_OK`, `SND_STATUS_INVALID_PARAMETER`, `SND_STATUS_BUFFER_TOO_SMALL` (chain full)
|
||||
**Returns:** `SND_OK`, `SND_STATUS_INVALID_PARAMETER`, `SND_STATUS_PIPELINE_EXHAUSTED` (chain full)
|
||||
|
||||
---
|
||||
|
||||
@@ -117,6 +122,34 @@ snd_status_t snd_syscall_resolve_ssn_sort(
|
||||
|
||||
---
|
||||
|
||||
### `snd_syscall_set_invoker`
|
||||
|
||||
Sets the global syscall invoker function pointer.
|
||||
|
||||
```c
|
||||
void snd_syscall_set_invoker(snd_syscall_invoker_t invoker);
|
||||
```
|
||||
|
||||
Built-in options: `snd_syscall_direct_invoke_asm`, `snd_syscall_indirect_invoke_asm`.
|
||||
|
||||
**Source:** `src/primitives/execution/syscalls/syscalls.c`
|
||||
|
||||
---
|
||||
|
||||
### `snd_syscall_set_gadget_finder`
|
||||
|
||||
Sets the global gadget finder function pointer. Only required when using indirect invocation.
|
||||
|
||||
```c
|
||||
void snd_syscall_set_gadget_finder(snd_syscall_gadget_finder_t finder);
|
||||
```
|
||||
|
||||
Built-in: `snd_syscall_find_gadget_scan`.
|
||||
|
||||
**Source:** `src/primitives/execution/syscalls/syscalls.c`
|
||||
|
||||
---
|
||||
|
||||
## Resolution & Execution
|
||||
|
||||
### `snd_syscall_resolve`
|
||||
@@ -131,26 +164,57 @@ snd_status_t snd_syscall_resolve(DWORD func_hash, snd_syscall_entry_t *entry_out
|
||||
|
||||
---
|
||||
|
||||
### `snd_syscall_invoke_asm`
|
||||
### `snd_syscall_direct_invoke_asm`
|
||||
|
||||
Architecture-specific ASM stub (`invoke_x64.asm` / `invoke_x86.asm`).
|
||||
Architecture-specific ASM stub (`invoke_direct_x64.asm` / `invoke_direct_x86.asm`). Executes the `syscall` / `sysenter` / `int 2Eh` instruction inline.
|
||||
|
||||
```c
|
||||
extern NTSTATUS snd_syscall_invoke_asm(snd_syscall_args_t *args);
|
||||
extern NTSTATUS snd_syscall_direct_invoke_asm(snd_syscall_args_t *args);
|
||||
```
|
||||
|
||||
**Returns:** raw `NTSTATUS` from the kernel
|
||||
|
||||
---
|
||||
|
||||
### `snd_syscall_indirect_invoke_asm`
|
||||
|
||||
Architecture-specific ASM stub (`invoke_indirect_x64.asm` / `invoke_indirect_x86.asm`). Jumps to a legitimate `syscall; ret` gadget inside NTDLL. Requires `sys_addr` to be populated (via `snd_syscall_find_gadget_scan` or a custom gadget finder).
|
||||
|
||||
```c
|
||||
extern NTSTATUS snd_syscall_indirect_invoke_asm(snd_syscall_args_t *args);
|
||||
```
|
||||
|
||||
**Returns:** raw `NTSTATUS`, or `STATUS_INVALID_PARAMETER` (`0xC000000D`) if `sys_addr` is NULL
|
||||
|
||||
---
|
||||
|
||||
## Compile-Time Defaults (`SND_USE_DEFAULTS`)
|
||||
|
||||
| Macro | Default value (when enabled) |
|
||||
|---|---|
|
||||
| `SND_SYSCALL_INVOKER_DEFAULT` | `snd_syscall_indirect_invoke_asm` |
|
||||
| `SND_SYSCALL_GADGET_FINDER_DEFAULT` | `snd_syscall_find_gadget_scan` |
|
||||
| `SND_SYSCALL_RESOLVER_DEFAULT` | `snd_syscall_resolve_ssn_scan` |
|
||||
|
||||
When `SND_USE_DEFAULTS` is disabled (default), all three macros expand to `NULL`.
|
||||
|
||||
> [!TIP]
|
||||
> **OpSec Rationale:** Why use a compile-time macro instead of just initializing the variables to default pointers in `syscalls.c`?
|
||||
> If the variables were unconditionally initialized with pointers to `snd_syscall_indirect_invoke_asm` and `snd_syscall_find_gadget_scan`, the C linker would be forced to pull those entire functions (including the scanner logic and ASM stubs) into the final compiled binary, even if the user explicitly chose to use direct syscalls or no syscalls at all.
|
||||
> By using `SND_USE_DEFAULTS`, we ensure the default dependency graph is completely severed when disabled, keeping the payload footprint as lean and evasive as possible.
|
||||
|
||||
---
|
||||
|
||||
## Typical bootstrap (PoC pattern)
|
||||
|
||||
```c
|
||||
PVOID ntdll = NULL;
|
||||
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);
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
|
||||
snd_syscall_entry_t entry = {0};
|
||||
snd_status_t status = snd_syscall_resolve(SND_HASH_NTALLOCATEVIRTUALMEMORY, &entry);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Syscall Resolution Engines
|
||||
|
||||
SindriKit ships two built-in SSN resolvers. Both implement `snd_syscall_resolver_t` and plug into the cascading pipeline via `snd_syscall_strategy_set` / `snd_syscall_strategy_add`.
|
||||
SindriKit ships two built-in SSN resolvers. Both implement `snd_syscall_resolver_t` and plug into the cascading pipeline via `snd_syscall_set_resolver` / `snd_syscall_add_resolver`.
|
||||
|
||||
| Resolver | File | Hook resistance |
|
||||
|---|---|---|
|
||||
@@ -57,7 +57,7 @@ The sorted table is built once per process (`g_table_initialized`). Subsequent r
|
||||
|
||||
### Output
|
||||
|
||||
Sets `entry_out->wSystemCall` to the table index. Does **not** populate `pAddress` or `dwHash` — sufficient for `snd_syscall_invoke_asm` which only needs the SSN.
|
||||
Sets `entry_out->wSystemCall` to the table index. Does **not** populate `pAddress` or `dwHash` — sufficient for the invoker which only needs the SSN.
|
||||
|
||||
### When to use
|
||||
|
||||
@@ -71,8 +71,10 @@ Sets `entry_out->wSystemCall` to the table index. Does **not** populate `pAddres
|
||||
PoCs and production profiles typically register both:
|
||||
|
||||
```c
|
||||
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
```
|
||||
|
||||
Evaluation order:
|
||||
@@ -93,13 +95,40 @@ snd_status_t my_resolver(PVOID ntdll, DWORD func_hash, snd_syscall_entry_t *entr
|
||||
return SND_OK;
|
||||
}
|
||||
|
||||
snd_syscall_strategy_add(my_resolver);
|
||||
snd_syscall_add_resolver(my_resolver);
|
||||
```
|
||||
|
||||
Maximum **4** strategies in the chain.
|
||||
|
||||
---
|
||||
|
||||
## Gadget finder (`snd_syscall_find_gadget_scan`)
|
||||
|
||||
**Source:** `gadget_scan.c`
|
||||
|
||||
Resolves the target function address from the natively loaded NTDLL (via PEB walk, not the user-supplied NTDLL image) and scans for a transition gadget.
|
||||
|
||||
### Algorithm
|
||||
|
||||
1. Get the natively loaded `ntdll.dll` base via `snd_peb_get_module_base_hash`.
|
||||
2. Resolve the export address of the target function using its hash.
|
||||
3. Scan forward up to 32 bytes for the transition signature:
|
||||
- **x64:** `syscall; ret` (`0F 05 C3`)
|
||||
- **x86:** Entry point + 5 bytes (skips `mov eax, SSN` and enters the OS-specific transition)
|
||||
|
||||
### Output
|
||||
|
||||
Populates `entry->pSyscallAddr` with the gadget address.
|
||||
|
||||
### When to use
|
||||
|
||||
Required when using `snd_syscall_indirect_invoke_asm`. The gadget finder runs automatically during `snd_syscall_resolve` if `g_syscall_gadget_finder` is set.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The gadget finder uses the **natively loaded** NTDLL from the process PEB, not the user-supplied NTDLL image. This ensures the gadget points to executable memory regardless of how the SSN source was obtained (disk load, KnownDlls map, etc.).
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
Both resolvers depend on:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Syscall Execution Pipeline
|
||||
|
||||
EDRs hook `ntdll.dll` syscall stubs in userland. Direct syscalls skip those stubs: the operator resolves the System Service Number (SSN) for a target `Nt*` function and invokes `syscall` with that number.
|
||||
EDRs hook `ntdll.dll` syscall stubs in userland. Direct syscalls skip those stubs: the operator resolves the System Service Number (SSN) for a target `Nt*` function and invokes `syscall` with that number. SindriKit supports both direct and indirect syscall invocation. Direct syscalls execute the `syscall` instruction inline, while indirect syscalls jump to a legitimate gadget within NTDLL to evade EDR call-stack analysis.
|
||||
|
||||
SSNs vary across Windows builds. SindriKit resolves them dynamically at runtime against a caller-supplied `ntdll` image.
|
||||
|
||||
@@ -10,9 +10,10 @@ SSNs vary across Windows builds. SindriKit resolves them dynamically at runtime
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["1. snd_syscall_set_ntdll"] --> B["2. snd_syscall_strategy_set / _add"]
|
||||
B --> C["3. snd_syscall_resolve"]
|
||||
C --> D["4. snd_syscall_invoke_asm"]
|
||||
A["1. snd_syscall_set_ntdll"] --> B["2. snd_syscall_set_resolver / _add"]
|
||||
B --> C["3. snd_syscall_set_invoker"]
|
||||
C --> D["4. snd_syscall_resolve"]
|
||||
D --> E["5. g_syscall_invoker"]
|
||||
```
|
||||
|
||||
### 1. Provide `ntdll` base
|
||||
@@ -34,13 +35,28 @@ See [mapping techniques](../mapping/techniques.md) for KnownDlls bootstrap.
|
||||
### 2. Configure strategy chain
|
||||
|
||||
```c
|
||||
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); // primary
|
||||
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); // fallback
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
|
||||
// or for indirect syscalls:
|
||||
// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
```
|
||||
|
||||
`snd_syscall_strategy_set` **replaces** the entire chain. Each `snd_syscall_strategy_add` appends up to 3 fallbacks (4 total).
|
||||
`snd_syscall_set_resolver` **replaces** the entire chain. Each `snd_syscall_add_resolver` appends up to 3 fallbacks (4 total).
|
||||
|
||||
### 3. Resolve SSN
|
||||
### 3. Configure invoker
|
||||
|
||||
```c
|
||||
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); // direct
|
||||
// or
|
||||
snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); // indirect
|
||||
snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); // required for indirect
|
||||
```
|
||||
|
||||
Indirect invocation requires a gadget finder. `snd_syscall_find_gadget_scan` resolves the target function in the natively loaded NTDLL via PEB and scans for a `syscall; ret` gadget (x64) or the transition stub entry (x86).
|
||||
|
||||
### 4. Resolve SSN
|
||||
|
||||
```c
|
||||
snd_syscall_entry_t entry = {0};
|
||||
@@ -49,20 +65,23 @@ snd_status_t status = snd_syscall_resolve(SND_HASH_NTOPENSECTION, &entry);
|
||||
|
||||
`snd_syscall_resolve` tries each registered strategy in order until one returns `SND_OK`.
|
||||
|
||||
### 4. Invoke
|
||||
### 5. Invoke
|
||||
|
||||
Populate `snd_syscall_args_t` and call the ASM stub:
|
||||
Populate `snd_syscall_args_t` and call via the global invoker:
|
||||
|
||||
```c
|
||||
snd_syscall_args_t args = {0};
|
||||
args.ssn = entry.wSystemCall;
|
||||
args.arg1 = ...;
|
||||
args.ssn = entry.wSystemCall;
|
||||
args.sys_addr = entry.pSyscallAddr;
|
||||
args.arg1 = ...;
|
||||
// arg2–arg11 as required by the target syscall
|
||||
|
||||
NTSTATUS nt_status = snd_syscall_invoke_asm(&args);
|
||||
NTSTATUS nt_status = g_syscall_invoker(&args);
|
||||
```
|
||||
|
||||
`_sys` primitive implementations (`snd_mem_sys`, `snd_proc_sys`, `snd_map_sys`) wrap steps 3–4 internally — operators only bootstrap once at startup.
|
||||
`sys_addr` is only used by the indirect invoker; the direct invoker ignores it.
|
||||
|
||||
`_sys` primitive implementations (`snd_mem_sys`, `snd_proc_sys`, `snd_map_sys`) wrap steps 4–5 internally — operators only bootstrap once at startup.
|
||||
|
||||
---
|
||||
|
||||
@@ -75,8 +94,12 @@ snd_status_t st = snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll);
|
||||
if (SND_FAILED(st)) return st;
|
||||
|
||||
snd_syscall_set_ntdll(ntdll);
|
||||
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
|
||||
// or for indirect syscalls:
|
||||
// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
|
||||
// Resolve + invoke NtClose
|
||||
snd_syscall_entry_t entry = {0};
|
||||
@@ -84,14 +107,26 @@ st = snd_syscall_resolve(SND_HASH_NTCLOSE, &entry);
|
||||
if (SND_FAILED(st)) return st;
|
||||
|
||||
snd_syscall_args_t args = {0};
|
||||
args.ssn = entry.wSystemCall;
|
||||
args.arg1 = handle;
|
||||
args.ssn = entry.wSystemCall;
|
||||
args.sys_addr = entry.pSyscallAddr;
|
||||
args.arg1 = handle;
|
||||
|
||||
NTSTATUS nt = snd_syscall_invoke_asm(&args);
|
||||
NTSTATUS nt = g_syscall_invoker(&args);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `SND_USE_DEFAULTS`
|
||||
|
||||
When enabled, all globals (invoker, gadget finder, primary resolver) are pre-configured. The only required bootstrap call is `snd_syscall_set_ntdll()`.
|
||||
|
||||
> [!TIP]
|
||||
> **OpSec Rationale:** Why use a compile-time macro instead of just initializing the variables to default pointers in `syscalls.c`?
|
||||
> If the variables were unconditionally initialized with pointers to `snd_syscall_indirect_invoke_asm` and `snd_syscall_find_gadget_scan`, the C linker would be forced to pull those entire functions (including the scanner logic and ASM stubs) into the final compiled binary, even if the user explicitly chose to use direct syscalls or no syscalls at all.
|
||||
> By using `SND_USE_DEFAULTS`, we ensure the default dependency graph is completely severed when disabled, keeping the payload footprint as lean and evasive as possible.
|
||||
|
||||
---
|
||||
|
||||
## Integration with `_sys` backends
|
||||
|
||||
After bootstrap, swapping to syscall-backed primitives requires no additional setup:
|
||||
@@ -101,7 +136,7 @@ ctx.mem_api = &snd_mem_sys;
|
||||
inj_ctx.proc_api = &snd_proc_sys;
|
||||
```
|
||||
|
||||
Each `_sys` API function calls `snd_syscall_resolve` with the appropriate hash, packs arguments into `snd_syscall_args_t`, and invokes `snd_syscall_invoke_asm`.
|
||||
Each `_sys` API function calls `snd_syscall_resolve` with the appropriate hash, packs arguments into `snd_syscall_args_t`, and invokes `g_syscall_invoker`.
|
||||
|
||||
---
|
||||
|
||||
@@ -109,10 +144,10 @@ Each `_sys` API function calls `snd_syscall_resolve` with the appropriate hash,
|
||||
|
||||
| Status | Cause |
|
||||
|---|---|
|
||||
| `SND_STATUS_NOT_INITIALIZED` | `snd_syscall_set_ntdll` not called, or no strategies registered |
|
||||
| `SND_STATUS_NOT_INITIALIZED` | `snd_syscall_set_ntdll` not called, invoker not set, or no strategies registered |
|
||||
| `SND_STATUS_SSN_NOT_FOUND` | All strategies failed for the hash |
|
||||
| `SND_STATUS_BUFFER_TOO_SMALL` | Strategy chain full (max 4) |
|
||||
| `SND_STATUS_INVALID_PARAMETER` | NULL resolver passed to `strategy_add` |
|
||||
| `SND_STATUS_PIPELINE_EXHAUSTED` | Strategy chain full (max 4) |
|
||||
| `SND_STATUS_NULL_POINTER` | NULL resolver passed to `strategy_add` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -47,8 +47,10 @@ ldr_ctx.raw_source = &file_buf;
|
||||
PVOID ntdll = NULL;
|
||||
status = 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);
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
```
|
||||
|
||||
### 3. Configure loader and injection contexts
|
||||
|
||||
@@ -44,9 +44,12 @@ status = snd_disk_buffer_load(file_path, &inject_buf);
|
||||
```c
|
||||
PVOID ntdll = NULL;
|
||||
status = 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);
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
```
|
||||
|
||||
This PoC bootstraps the pipeline but uses `snd_proc_win` for injection. Swap to `&snd_proc_sys` to use the prepared pipeline for stealth remote operations.
|
||||
|
||||
@@ -22,7 +22,7 @@ No CLI — edit `file_path` in `main.c` before building.
|
||||
PVOID ntdll = NULL;
|
||||
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_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
```
|
||||
|
||||
### 2. Configure loader context
|
||||
|
||||
@@ -31,8 +31,10 @@ status = snd_disk_buffer_load("C:\\Windows\\System32\\ntdll.dll", &ntdll_buf);
|
||||
PVOID ntdll = ntdll_buf.data;
|
||||
|
||||
snd_syscall_set_ntdll(ntdll);
|
||||
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
```
|
||||
|
||||
Commented alternatives in `pocs/loader_nowinapi/main.c` (swap in for different OpSec trade-offs):
|
||||
|
||||
@@ -40,7 +40,7 @@ See [Dependency injection](../architecture/dependency_injection.md) for the full
|
||||
## 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 clean `ntdll` base or SSN table.
|
||||
> Syscall-backed backends (`snd_mem_sys`, `snd_proc_sys`) and the syscall resolution pipeline require **explicit initialization**. The engine does not auto-discover a clean `ntdll` base or SSN table (unless `SND_USE_DEFAULTS=ON`, where only `snd_syscall_set_ntdll()` is strictly required).
|
||||
|
||||
### Minimum syscall bootstrap
|
||||
|
||||
@@ -50,24 +50,30 @@ 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)
|
||||
// Option B: PEB walk (no disk I/O)
|
||||
// status = snd_peb_get_module_base_hash(SND_HASH_NTDLL_DLL, &ntdll);
|
||||
|
||||
// Option C: disk load
|
||||
// ...
|
||||
|
||||
snd_syscall_set_ntdll(ntdll);
|
||||
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
|
||||
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
|
||||
// or for indirect syscalls:
|
||||
// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
```
|
||||
|
||||
| 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) |
|
||||
| `snd_syscall_set_resolver` | Primary resolver (scan) |
|
||||
| `snd_syscall_add_resolver` | Fallback resolver (sort) |
|
||||
| `snd_syscall_set_invoker` | Selects direct or indirect invocation |
|
||||
| `snd_syscall_set_gadget_finder` | Required for indirect invocation only |
|
||||
|
||||
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.
|
||||
|
||||
@@ -147,7 +153,7 @@ snd_inj_cleanup(&inj_ctx);
|
||||
|---|---|---|---|---|---|
|
||||
| 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) |
|
||||
| Direct/Indirect 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 |
|
||||
@@ -180,8 +186,10 @@ Avoids `VirtualAlloc` / `LoadLibrary` telemetry; calls still pass through in-pro
|
||||
```c
|
||||
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);
|
||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||
snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
||||
snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||
|
||||
ldr_ctx.mem_api = &snd_mem_sys;
|
||||
ldr_ctx.mod_api = &snd_mod_nt;
|
||||
|
||||
@@ -62,11 +62,13 @@ Include `sindri.h` or granular headers (`sindri/primitives.h`, etc.). Hash const
|
||||
| `SND_RANDOMIZE_SEED` | `OFF` | Random `SND_HASH_SEED` per configure | OFF keeps deterministic hashes for faster rebuilds |
|
||||
| `SND_BUILD_PAYLOADS` | `OFF` | Build `pocs/` executables | Full set when `SND_CRTLESS=OFF` |
|
||||
| `SND_BUILD_TESTS` | `OFF` | Build test payloads + integration harness inputs | **Requires CRT**; forces `SND_CRTLESS=OFF` |
|
||||
| `SND_USE_DEFAULTS` | `OFF` | Pre-configure syscall invoker, gadget finder, and resolver globals | Defaults to indirect invoke + scan resolver + gadget scan. **OpSec note**: Left OFF by default so unused ASM stubs and scanners aren't linked into the final binary. |
|
||||
|
||||
### Guards (CMake)
|
||||
|
||||
- `SND_BUILD_TESTS=ON` → `SND_CRTLESS` forced OFF
|
||||
- `SND_CRTLESS=ON` → `SND_ENABLE_DEBUG` and `SND_USE_PRINTF` forced OFF
|
||||
- `SND_USE_DEFAULTS=ON` → invoker = `snd_syscall_indirect_invoke_asm`, gadget finder = `snd_syscall_find_gadget_scan`, resolver = `snd_syscall_resolve_ssn_scan`
|
||||
- Non-Windows configure → fatal error
|
||||
|
||||
Use a **clean build directory** when switching between CRT-less and test builds.
|
||||
|
||||
Vendored
+3
-3
@@ -12,8 +12,8 @@ Keeping these separate reflects the code layout:
|
||||
|
||||
```
|
||||
include/sindri/parsers/
|
||||
├── pe/ ← buffer-backed PE format parsing
|
||||
└── env/ ← live process environment structures
|
||||
├── pe/ <- buffer-backed PE format parsing
|
||||
└── env/ <- live process environment structures
|
||||
```
|
||||
|
||||
NT layout definitions (`SND_PEB`, `SND_LDR_DATA_TABLE_ENTRY`, etc.) live in `include/sindri/internal/nt/peb.h`. Env parsers consume these layouts; they do not duplicate Windows SDK headers to avoid pulling in monitored imports.
|
||||
@@ -50,7 +50,7 @@ Loaded modules are tracked in three linked lists inside `SND_PEB_LDR_DATA`:
|
||||
|
||||
```
|
||||
InLoadOrderModuleList
|
||||
InMemoryOrderModuleList ← used by SindriKit
|
||||
InMemoryOrderModuleList <- used by SindriKit
|
||||
InInitializationOrderModuleList
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user