Module 8: Full Chain, Detection & Ecosystem
The complete execution pipeline, what defeats it, and the broader landscape of call stack spoofing tools.
Final Module
This module ties together everything from the previous seven modules into a single end-to-end view. We trace a DRAUGR_SYSCALL call from the macro expansion to the kernel and back. Then we examine the detection surfaces that make this technique imperfect, the hardware features that fundamentally break it, and the ecosystem of related tools that preceded and followed Draugr.
End-to-End Execution
Here is the complete numbered sequence when a Beacon operator calls a Draugr-wrapped syscall. Every step maps to a concept from a previous module.
Complete Execution Pipeline
- DRAUGR_SYSCALL macro is called with the function name and arguments (e.g.,
DRAUGR_SYSCALL(NtAllocateVirtualMemory, ...)) - DraugrInit resolves three addresses:
BaseThreadInitThunk+0x14,RtlUserThreadStart+0x21, andkernelbase.dllbase (Module 5, Phase 1) - InitVxTable resolves the SSN via pattern matching on the ntdll stub. If the stub is hooked, the neighbor-based fallback finds the SSN from adjacent syscalls (Module 4)
- DraugrCalculateStackSize parses the UNWIND_CODE arrays for both target functions and computes exact frame sizes in bytes (Module 5, Phase 2)
- DraugrFindGadget scans kernelbase.dll's
.textsection for the0xFF 0x23byte sequence (JMP [RBX]) (Module 7) - DraugrCall packages everything into the PRM structure: Fixup address, frame sizes, target addresses, SSN, syscall address, and all function arguments (Module 5)
- Spoof() assembly routine builds the three-layer synthetic stack: NULL terminator, 0x200 buffer, RtlUserThreadStart frame, BaseThreadInitThunk frame, gadget (Module 6)
- Register setup: RAX = SSN, R10 = arg1, RDX = arg2, R8 = arg3, R9 = arg4, RBX = &PRM. JMP to the syscall instruction in ntdll (Module 6)
- Kernel executes the syscall. The synthetic stack is visible to any kernel-mode stack walker during this time. Return value placed in RAX.
- Return path: RET pops gadget address →
JMP [RBX]→ Fixup deallocates synthetic stack → restores registers → JMP to original return address (Module 7)
Full Chain Flow Diagram
macro entry
resolve addresses
parse UNWIND_CODEs
scan kernelbase
build stack
kernel transition
pop gadget
to Fixup
regs + stack
to caller
What the EDR Sees
The entire point of Draugr is to change what the EDR observes when it walks the call stack during a syscall. Here is the before-and-after comparison.
Without Draugr (Detected)
Call Stack - SuspiciousFrame 0: ntdll!NtAllocateVirtualMemory+0x14
Frame 1: 0x00007FFE12340000 <-- UNBACKED MEMORY
[No module found for this address]
[Return address is in private RWX region]
EDR Verdict: BLOCKED
- Return address in unbacked memory
- No valid RUNTIME_FUNCTION for caller
- Shellcode injection detected
With Draugr (Legitimate)
Call Stack - CleanFrame 0: ntdll!NtAllocateVirtualMemory+0x14
Frame 1: kernel32!BaseThreadInitThunk+0x14
Frame 2: ntdll!RtlUserThreadStart+0x21
Frame 3: 0x0000000000000000 (end of stack)
EDR Verdict: ALLOWED
- All return addresses in signed modules
- Standard thread initialization chain
- Matches expected Windows thread pattern
Why This Fools the EDR
The synthetic stack replicates the exact pattern that every legitimate Windows thread produces. The EDR's call stack analysis checks three things: (1) all return addresses resolve to known modules, (2) each frame has valid UNWIND_INFO, and (3) the chain terminates properly. Draugr satisfies all three checks. The return addresses are in kernel32.dll and ntdll.dll (signed Microsoft DLLs), the frame sizes match the UNWIND_CODEs exactly, and the NULL terminator provides a clean end-of-stack signal.
Intel CET & Shadow Stacks
CRITICAL LIMITATION: CET Breaks Draugr
Intel Control-flow Enforcement Technology (CET) introduces a hardware shadow stack that fundamentally defeats all stack spoofing techniques, including Draugr. This is the single most important limitation of the entire approach.
How Shadow Stacks Work
| Operation | Regular Stack | Shadow Stack |
|---|---|---|
CALL | Pushes return address to [RSP] | Also pushes return address to shadow stack |
RET | Pops return address from [RSP] | Pops from shadow stack and compares |
| Mismatch | — | Generates #CP (Control Protection) exception |
MOV [rsp], value | Modifies the regular stack | Does NOT modify the shadow stack |
Why Draugr Cannot Bypass CET
Draugr writes synthetic return addresses using MOV instructions (e.g., mov [rsp], r15). These writes only affect the regular stack. The shadow stack is a separate hardware-protected memory region that can only be modified by CALL and RET instructions (or supervisor-mode instructions like WRSSD/WRSSQ). When the syscall returns and RET executes, the CPU compares the popped return address (the gadget) against the shadow stack entry. They will not match, because no CALL ever pushed the gadget address to the shadow stack. The CPU raises a #CP exception and the process crashes.
CET Availability
| Platform | CET Support |
|---|---|
| Intel 11th Gen (Tiger Lake) and newer | Hardware support present |
| AMD Zen 3 and newer | Hardware support present |
| Windows 11 | OS support for user-mode shadow stacks |
| Windows 10 | No user-mode shadow stack enforcement |
| Older CPUs | No hardware support — Draugr works normally |
As of 2025, CET enforcement is still being gradually rolled out. Many enterprise endpoints run Windows 10 or have CET disabled. However, the long-term trend is clear: hardware-based call stack integrity will eventually make all user-mode stack spoofing obsolete.
Detection Surface
Even without CET, Draugr is not invisible. Multiple detection opportunities exist for defenders who know what to look for.
Detection Methods
| Method | How It Works | Effectiveness |
|---|---|---|
| Semantic call chain analysis | Verify that the function call sequence is logically possible. For example, BaseThreadInitThunk should only call thread entry points, not arbitrary syscalls. The call chain is syntactically valid but semantically wrong. | High — requires domain knowledge but catches all synthetic stacks |
| Intel CET / Shadow Stacks | Hardware-enforced return address integrity. Synthetic addresses on the regular stack do not match shadow stack entries. | Absolute — architectural incompatibility, no user-mode bypass |
| Kernel ETW-TI | EtwTi (Threat Intelligence) providers log thread context modifications and syscall metadata. Unusual patterns in syscall origin can be flagged. | Medium — requires analysis of telemetry streams |
| Gadget scanning | Detect threads that have return addresses pointing to JMP [RBX] instructions. Legitimate code rarely returns through such gadgets. | Medium — generates false positives in ROP-heavy code |
| BOF artifact detection | Detect COFF loading patterns in memory: RWX allocations, COFF headers, relocations applied to private memory. | Medium — detects the BOF loader, not Draugr specifically |
Defense in Depth
No single detection method is foolproof (except CET). Effective defense combines multiple layers: stack semantics checking + ETW monitoring + memory region analysis + hardware enforcement. Draugr can evade any one of these in isolation, but the combination narrows the window significantly.
Prior Art Evolution
Draugr did not emerge in a vacuum. It is the culmination of years of research into call stack manipulation for evasion. Each prior tool contributed a key insight.
Timeline of Stack Spoofing Techniques
| Author / Tool | Year | Key Contribution |
|---|---|---|
| namazso | 2021 | Original return address spoofing concept — first public demonstration of replacing return addresses to evade stack inspection |
| ThreadStackSpoofer (mgeeky) | 2021 | First public PoC. Overwrites the call stack during Beacon sleep to hide shellcode return addresses. Simple but effective for sleep-time evasion. |
| SilentMoonWalk (klezVirus) | 2022 | Fully dynamic approach with two modes: synthetic frame construction and thread desynchronization. First tool to parse UNWIND_CODEs for exact frame sizes. |
| LoudSunRun (susMdT) | 2023 | Direct predecessor to Draugr. Refined the synthetic frame approach and introduced the gadget-based return mechanism. Draugr extends this with BOF integration and production hardening. |
| WithSecure CallStackSpoofer | 2023 | Thread context manipulation via GetThreadContext/SetThreadContext + VEH-based recovery after crashing at the fake return address. |
| Draugr (NtDallas) | 2024 | Production-quality BOF with gadget-based return, exact frame sizes from UNWIND_CODE parsing, indirect syscalls, and Cobalt Strike integration. |
| Cobalt Strike BeaconGate + Eden | 2024 | Official Cobalt Strike integration. BeaconGate hooks all Beacon syscalls; Eden UDRL integrates Draugr-style spoofing into the loader itself. |
The NtDallas Ecosystem
Draugr is part of a larger collection of offensive tools by NtDallas. Together, they cover the full lifecycle of post-exploitation: loading, execution, evasion, and persistence.
Related Tools
| Tool | Purpose | Relationship to Draugr |
|---|---|---|
| OdinLdr | Reflective DLL loader | Integrates Draugr for stack-spoofed syscalls during reflective loading. Also includes EAF (Export Address Filtering) evasion. |
| BOF_Spawn | Fork-and-run execution | Uses Draugr to spoof the call stack when spawning sacrificial processes for BOF execution. |
| Huginn | Standalone COFF loader | Loads and executes COFF (BOF) files outside of Cobalt Strike, with integrated stack spoofing via Draugr. |
| KrakenMask | Sleep obfuscation | Encrypts Beacon memory and spoofs the stack during sleep periods. Complementary to Draugr (sleep-time vs. execution-time evasion). |
| MemLoader | In-memory PE/.NET execution | Loads and runs PE files and .NET assemblies entirely in memory, avoiding disk writes. |
| draugrgen (ziggoon) | BeaconGate hook generator | Automatically generates the BeaconGate hook code needed to integrate Draugr with Cobalt Strike's syscall interception. |
Build Requirements
Draugr is built with MinGW and requires specific compiler versions due to assembly compatibility.
GCC Version Constraint
Draugr requires GCC 13 (or earlier). GCC 14 introduced changes to inline assembly handling and register allocation that break the Spoof routine's assumptions about register preservation across inline asm blocks. If you build with GCC 14, the non-volatile register saves may be optimized away or reordered, causing crashes at Fixup time.
Shell - Building Draugr# Native build (requires MinGW with GCC 13)
$ x86_64-w64-mingw32-gcc --version
x86_64-w64-mingw32-gcc (GCC) 13.2.0
$ make
# Docker build (recommended for reproducibility)
$ docker build -t draugr-build .
$ docker run --rm -v $(pwd)/output:/output draugr-build
Build Output
The build produces a .o file (COFF object) that can be loaded directly by Cobalt Strike as a BOF. The object contains all Draugr code: the C orchestration functions, the Spoof/Fixup assembly routines, and the syscall stub. No additional dependencies are required at runtime — everything is resolved dynamically from loaded system DLLs.
Course Complete
Congratulations
You have completed the Draugr course. Over eight modules, you learned:
- Module 1: How EDRs use kernel callbacks and stack walking to detect shellcode
- Module 2: Beacon Object Files — what they are, how they load, and why Draugr is a BOF
- Module 3: x64 stack unwinding internals — RUNTIME_FUNCTION, UNWIND_INFO, and UNWIND_CODEs
- Module 4: System Service Numbers, pattern-based SSN resolution, and indirect syscalls
- Module 5: The five-phase execution model and the PRM data structure
- Module 6: Three-layer synthetic stack construction with exact frame sizes
- Module 7: The JMP [RBX] gadget, Fixup routine, and clean return mechanism
- Module 8: End-to-end chain, CET limitations, detection surfaces, and the broader ecosystem
Next steps: Read the Draugr source code (github.com/NtDallas/Draugr), study Stub.s line by line, experiment with the Docker build, and explore the related tools (OdinLdr, KrakenMask). For defense, investigate CET enforcement on your endpoints and implement semantic call chain validation in your detection stack.
Module 8 Quiz: Full Chain, Detection & Ecosystem
Q1: In the complete Draugr execution chain, what happens immediately after the syscall returns from the kernel?
Q2: Why does Intel CET (Control-flow Enforcement Technology) fundamentally break Draugr?
Q3: What distinguishes ThreadStackSpoofer from Draugr in terms of when the stack is spoofed?
Q4: Which tool from the NtDallas ecosystem handles sleep-time memory encryption, complementing Draugr's execution-time stack spoofing?