mirror of
https://github.com/youssefnoob003/SindriKit
synced 2026-07-07 21:57:09 +00:00
Release v1.3.1: OpSec Patch (Dynamic Fat Frame Entropy)
- Implemented entropy-based pseudo-randomizer in snd_syscall_find_spoof_scan to prevent deterministic telemetry. - Randomized Fat Frame selection using the SSN hash, a static counter, and ASLR-dependent module/stack addresses. - Hardened spoof scanner to reject frames using Frame Registers (UWOP_SET_FPREG) to prevent RtlVirtualUnwind crashes. - Added strict bounds checking to cap Fat Frame sizes at 240 bytes, preventing MASM local stack corruption.Release v1.3.0: Stack Spoofing & Dynamic Fat Frame.
This commit is contained in:
@@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [1.3.1] - 2026-07-07
|
||||||
|
|
||||||
|
OpSec Patch release addressing deterministic telemetry in stack spoofing.
|
||||||
|
|
||||||
|
### Security & OpSec
|
||||||
|
- **Fat Frame Entropy:** Implemented a pseudo-randomizer in `snd_syscall_find_spoof_scan` to prevent deterministic stack spoofing telemetry. The engine now uses the target function's SSN hash, a static counter, and the ASLR-dependent memory addresses of the `ntdll` base and the entry struct to randomize the selection of Fat Frames. This ensures payloads do not spoof the exact same `kernel32.dll` function on every execution, shifting the stack trace dynamically.
|
||||||
|
- **Fat Frame Hardening:** Added strict bounds checking to the spoof scanner. It now rejects functions that use a Frame Register (which could break `RtlVirtualUnwind` offset calculations) and caps the selected `frame_size` to a maximum of 240 bytes to ensure it cannot overflow the pre-allocated local stack space inside the MASM stub.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [1.3.0] - 2026-07-06
|
## [1.3.0] - 2026-07-06
|
||||||
|
|
||||||
Fourth major release. The framework introduces Call Stack Spoofing to defeat EDR virtual unwinding telemetry.
|
Fourth major release. The framework introduces Call Stack Spoofing to defeat EDR virtual unwinding telemetry.
|
||||||
|
|||||||
@@ -142,9 +142,10 @@ To prevent frame desynchronization during stack unwinding (where the EDR's `RtlV
|
|||||||
1. Resolve the natively loaded `kernel32.dll` via PEB.
|
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).
|
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.
|
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.
|
4. Find a function that allocates at least **120 bytes** and at most **240 bytes** (a capped "Fat Frame"). This ensures the legitimate function's shadow space is large enough to encapsulate our spoofed JMP-trampoline and its arguments without risking stack overflow.
|
||||||
5. Scan the body of the Fat Frame function for a `0xC3` (`RET`) instruction.
|
5. **Entropy Randomization:** To avoid creating a deterministic telemetry pattern (where every syscall spoofs the exact same function), the scanner uses an internal pseudo-randomizer seeded by the target function's hash, a static counter, and the memory addresses of the `ntdll` base and the entry struct. This guarantees it will skip a randomized number of valid Fat Frames before settling on one.
|
||||||
6. Populate `entry->pSpoofAddr` with the gadget address and `entry->dwSpoofFrameSize` with the required stack offset.
|
6. Scan the body of the chosen Fat Frame function for a `0xC3` (`RET`) instruction.
|
||||||
|
7. Populate `entry->pSpoofAddr` with the gadget address and `entry->dwSpoofFrameSize` with the required stack offset.
|
||||||
|
|
||||||
### Algorithm (x86)
|
### Algorithm (x86)
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,15 @@ snd_status_t snd_syscall_find_spoof_scan(snd_syscall_entry_t *entry) {
|
|||||||
PRUNTIME_FUNCTION pdata = (PRUNTIME_FUNCTION)((ULONG_PTR)kernel32 + pdata_rva);
|
PRUNTIME_FUNCTION pdata = (PRUNTIME_FUNCTION)((ULONG_PTR)kernel32 + pdata_rva);
|
||||||
DWORD pdata_count = pdata_size / sizeof(RUNTIME_FUNCTION);
|
DWORD pdata_count = pdata_size / sizeof(RUNTIME_FUNCTION);
|
||||||
|
|
||||||
|
// Simple randomizer using a static counter + hash + memory addresses for entropy
|
||||||
|
static ULONG call_count = 0;
|
||||||
|
ULONG entropy = (ULONG)((ULONG_PTR)kernel32 & 0xFFFFFFFF) ^ (ULONG)((ULONG_PTR)entry & 0xFFFFFFFF);
|
||||||
|
ULONG skip_count = (entry->dwHash + ++call_count + entropy) % 15; // Skip up to 14 fat frames
|
||||||
|
ULONG current_fat_frames = 0;
|
||||||
|
|
||||||
|
PVOID fallback_gadget = NULL;
|
||||||
|
DWORD fallback_frame_size = 0;
|
||||||
|
|
||||||
for (DWORD i = 0; i < pdata_count; i++) {
|
for (DWORD i = 0; i < pdata_count; i++) {
|
||||||
PRUNTIME_FUNCTION func = &pdata[i];
|
PRUNTIME_FUNCTION func = &pdata[i];
|
||||||
|
|
||||||
@@ -48,6 +57,11 @@ snd_status_t snd_syscall_find_spoof_scan(snd_syscall_entry_t *entry) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BYTE frameReg = unwind_info[3] & 0x0F;
|
||||||
|
if (frameReg != 0) {
|
||||||
|
continue; // Reject functions that use a frame pointer to prevent RtlVirtualUnwind desync
|
||||||
|
}
|
||||||
|
|
||||||
BYTE countOfCodes = unwind_info[2];
|
BYTE countOfCodes = unwind_info[2];
|
||||||
PBYTE codes = unwind_info + 4;
|
PBYTE codes = unwind_info + 4;
|
||||||
|
|
||||||
@@ -99,7 +113,7 @@ snd_status_t snd_syscall_find_spoof_scan(snd_syscall_entry_t *entry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frame_size >= 120) {
|
if (frame_size >= 120 && frame_size <= 240) {
|
||||||
PBYTE func_body = (PBYTE)((ULONG_PTR)kernel32 + func->BeginAddress);
|
PBYTE func_body = (PBYTE)((ULONG_PTR)kernel32 + func->BeginAddress);
|
||||||
DWORD func_size = func->EndAddress - func->BeginAddress;
|
DWORD func_size = func->EndAddress - func->BeginAddress;
|
||||||
|
|
||||||
@@ -111,10 +125,26 @@ snd_status_t snd_syscall_find_spoof_scan(snd_syscall_entry_t *entry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gadget)
|
if (gadget) {
|
||||||
|
if (!fallback_gadget) {
|
||||||
|
fallback_gadget = gadget;
|
||||||
|
fallback_frame_size = frame_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_fat_frames < skip_count) {
|
||||||
|
current_fat_frames++;
|
||||||
|
gadget = NULL; // Keep searching for the next one
|
||||||
|
continue;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!gadget && fallback_gadget) {
|
||||||
|
gadget = fallback_gadget;
|
||||||
|
entry->dwSpoofFrameSize = fallback_frame_size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
FARPROC exec_addr = NULL;
|
FARPROC exec_addr = NULL;
|
||||||
|
|||||||
Reference in New Issue
Block a user