Security News

Cybersecurity news aggregator

🔓
MEDIUM Vulnerabilities Reddit r/netsec

Call Stack Spoofing via Runtime .pdata Parsing to Evade RtlVirtualUnwind

  • What: New technique to evade EDRs using call stack spoofing
  • Impact: Security tools may be bypassed by advanced attackers
Read Full Article →

In version 1.2.0, SindriKit decoupled syscall invocation to supportindirect syscalls. By jumping to a legitimatesyscall; retinstruction insidentdll.dll, payloads evaded user-mode API hooking. However, it’s far from enough. Modern EDRs leverage ETW telemetry and thread suspension to inspect thecall stackduring kernel transitions. Even if the instruction pointer (RIP) originates from a legitimate NTDLL address, the return address on the stack points into unverified payload memory. SindriKit 1.3.0closes this gap by introducing nativeCall Stack Spoofinginto the execution pipeline. Source, architecture docs, and PoCs:SindriKit on GitHub. Conceptually, spoofing a return address is simple: overwrite the top of the stack with the address of a legitimate function (e.g.,kernel32.dll!SleepEx) before invoking the syscall. But EDRs don’t just look at the raw bytes on the stack. They use Microsoft’sRtlVirtualUnwindAPI to reconstruct the call chain. When attempting to spoof a call stack, devs run into three issues: Fat Frames diagram for extra clarity SindriKit 1.3.0 introducessnd_syscall_find_spoof_scan. Instead of picking a random function to spoof, SindriKit manually parses the Exception Directory (.pdata) of the natively loadedkernel32.dll. It iterates through theRUNTIME_FUNCTIONarray and parses the underlyingUNWIND_INFOstructures to calculate the exact stack allocation of every function. The engine hunts for a“Fat Frame”, a legitimate function that allocates at least120 bytesof stack space. Why 120 bytes? Because we need enough shadow space to hide our syscall arguments and our trampoline gadget without overflowing into other frames. If the payload spoofs theexact same kernel32.dll functionfor every single syscall, the telemetry becomes highly deterministic and suspicious. To combat this,snd_syscall_find_spoof_scanincorporates a randomizer that calculates entropy using the SSN hash, a static counter, and the ASLR-dependent memory addresses of the module and the execution stack. It uses this entropy to randomly skip valid Fat Frames, ensuring the spoofed stack trace constantly shifts during execution. Once a randomized Fat Frame is chosen, the engine scans the function body for a 0xC3 (RET) instruction. This address becomes our Trampoline Gadget, and the calculated frame size is passed directly to our MASM stub. In this implementation, we must satisfy two entirely different execution models simultaneously: the physical CPU and the EDR’s virtual unwinder: And here is the exact assembly logic that builds it: When the kernel suspends the thread and the EDR inspects it: The unwinder parses a perfectly intact call chain. On x86 architectures, SindriKit falls back to resolvingBaseThreadInitThunkand scanning forward/backward for a simpleRETgadget. This is a necessary compromise due to architectural differences. Unlike x64, the x86 architecture does not use.pdataexception directories. Instead, it relies on Structured Exception Handling (SEH) chained via theFS:0register, and traditional frame pointer (EBP) chains.

Share this article