- What: Research on callstack spoofing techniques
- Impact: Relevant to security researchers and EDR developers
by Tiziano Marra Disclaimer.This research is published foreducational and defensive purposes only. I do not endorse the use of this technique for unauthorized access to any computer system. Always obtain explicit written authorization before testing. If you use this on systems you don’t own, that’s on you, and it’s illegal. GitHub Repository:MrTiz/CET-Enum-CallStack-Spoofer Modern Endpoint Detection and Response (EDR) solutions increasingly rely on call stack inspection to catch suspicious syscalls. When something likeNtProtectVirtualMemoryfires, the EDR walks the thread’s call stack. If any return address points to unbacked memory, the operation gets flagged. This article presents a callstack spoofing technique that combines three primitives: The result: a call stack where every frame is backed by a signed Windows module at the moment thesyscallexecutes. No synthetic frames, no fabricated unwind data, no smoke and mirrors. The enum function is genuinely on the stack because we genuinely called it. The actual contribution here is not in the individual components. All of them have been published before by other researchers (credited inSection 13). What I put together is their composition and, most importantly, theCET compliance mechanism: ajmp-based context switch combined with direct shadow stack pointer reconciliation usingRDSSPQ/INCSSPQ. This makes the technique work on systems with Intel CET hardware enforcement without touching unwind metadata. The PoC is written in Rust with inline assembly and compiles with full CET support (/CETCOMPAT,/guard:ehcont,control-flow-guard). Before diving in, let me give some context on how we got here. The cat-and-mouse game between attackers and defenders around system calls has been going on for years, and understanding the history helps appreciate why CET compliance matters now. For years, EDRs relied on user-mode API hooking. The EDR injects a DLL into every process, places trampolines at the beginning of sensitiventdll.dllfunctions (NtAllocateVirtualMemory,NtProtectVirtualMemory,NtWriteVirtualMemory, etc.), and intercepts every call to inspect arguments before letting it through. Image credit:RedOps The offensive response wasdirect syscalls: skipntdll.dllentirely. Load the System Service Number (SSN) intoRAX, executesyscallfrom your own code. No API call, no hook, no interception. SysWhispersby @jthuraisamy made this accessible by generating header/ASM stubs.SysWhispers2improvedSSNresolution. Around the same time,Hell’s Gateby am0nsec and smelly__vx introduced dynamicSSNresolution by parsingntdll.dllin memory.Halo’s GateandTartarus’ Gateby trickster0 handled cases where some stubs were hooked. Image credit:RedOps EDR vendors realized that hookingntdll.dllwas a losing battle. If the attacker runs code in your process, they can just unhook everything. So the focus shifted to kernel-level telemetry: theMicrosoft-Windows-Threat-IntelligenceETW provider, kernel callbacks, and most importantly, call stack walking from the kernel side. With direct syscalls, the call stack at the moment of thesyscalllooks like this: That first frame is a dead giveaway. Game over. The answer wasindirect syscalls: instead of executingsyscallfrom your code, jump to an existingsyscall; retsequence insidentdll.dll. This was popularized by @modexpblog (MDSec) in their blog post “Bypassing User-Mode Hooks and Direct Invocation of System Calls for Red Teams” and later formalized inSysWhispers3by klezVirus. The immediate return address now points intontdll.dll. Clean. But the rest of the stack still reveals the real caller. EDRs started walking deeper. Image credit:RedOps