Difficulty: Advanced

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

  1. DRAUGR_SYSCALL macro is called with the function name and arguments (e.g., DRAUGR_SYSCALL(NtAllocateVirtualMemory, ...))
  2. DraugrInit resolves three addresses: BaseThreadInitThunk+0x14, RtlUserThreadStart+0x21, and kernelbase.dll base (Module 5, Phase 1)
  3. 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)
  4. DraugrCalculateStackSize parses the UNWIND_CODE arrays for both target functions and computes exact frame sizes in bytes (Module 5, Phase 2)
  5. DraugrFindGadget scans kernelbase.dll's .text section for the 0xFF 0x23 byte sequence (JMP [RBX]) (Module 7)
  6. DraugrCall packages everything into the PRM structure: Fixup address, frame sizes, target addresses, SSN, syscall address, and all function arguments (Module 5)
  7. Spoof() assembly routine builds the three-layer synthetic stack: NULL terminator, 0x200 buffer, RtlUserThreadStart frame, BaseThreadInitThunk frame, gadget (Module 6)
  8. Register setup: RAX = SSN, R10 = arg1, RDX = arg2, R8 = arg3, R9 = arg4, RBX = &PRM. JMP to the syscall instruction in ntdll (Module 6)
  9. Kernel executes the syscall. The synthetic stack is visible to any kernel-mode stack walker during this time. Return value placed in RAX.
  10. 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

DRAUGR_SYSCALL
macro entry
Init + SSN
resolve addresses
CalcSize
parse UNWIND_CODEs
FindGadget
scan kernelbase
Spoof()
build stack
syscall
kernel transition
RET
pop gadget
JMP [RBX]
to Fixup
Restore
regs + stack
Return
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

OperationRegular StackShadow Stack
CALLPushes return address to [RSP]Also pushes return address to shadow stack
RETPops return address from [RSP]Pops from shadow stack and compares
MismatchGenerates #CP (Control Protection) exception
MOV [rsp], valueModifies the regular stackDoes 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

PlatformCET Support
Intel 11th Gen (Tiger Lake) and newerHardware support present
AMD Zen 3 and newerHardware support present
Windows 11OS support for user-mode shadow stacks
Windows 10No user-mode shadow stack enforcement
Older CPUsNo 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

MethodHow It WorksEffectiveness
Semantic call chain analysisVerify 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 StacksHardware-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-TIEtwTi (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 scanningDetect 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 detectionDetect 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 / ToolYearKey Contribution
namazso2021Original return address spoofing concept — first public demonstration of replacing return addresses to evade stack inspection
ThreadStackSpoofer (mgeeky)2021First public PoC. Overwrites the call stack during Beacon sleep to hide shellcode return addresses. Simple but effective for sleep-time evasion.
SilentMoonWalk (klezVirus)2022Fully dynamic approach with two modes: synthetic frame construction and thread desynchronization. First tool to parse UNWIND_CODEs for exact frame sizes.
LoudSunRun (susMdT)2023Direct 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 CallStackSpoofer2023Thread context manipulation via GetThreadContext/SetThreadContext + VEH-based recovery after crashing at the fake return address.
Draugr (NtDallas)2024Production-quality BOF with gadget-based return, exact frame sizes from UNWIND_CODE parsing, indirect syscalls, and Cobalt Strike integration.
Cobalt Strike BeaconGate + Eden2024Official 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

ToolPurposeRelationship to Draugr
OdinLdrReflective DLL loaderIntegrates Draugr for stack-spoofed syscalls during reflective loading. Also includes EAF (Export Address Filtering) evasion.
BOF_SpawnFork-and-run executionUses Draugr to spoof the call stack when spawning sacrificial processes for BOF execution.
HuginnStandalone COFF loaderLoads and executes COFF (BOF) files outside of Cobalt Strike, with integrated stack spoofing via Draugr.
KrakenMaskSleep obfuscationEncrypts Beacon memory and spoofs the stack during sleep periods. Complementary to Draugr (sleep-time vs. execution-time evasion).
MemLoaderIn-memory PE/.NET executionLoads and runs PE files and .NET assemblies entirely in memory, avoiding disk writes.
draugrgen (ziggoon)BeaconGate hook generatorAutomatically 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:

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?

After the syscall returns, the ntdll stub's RET instruction pops the top of the synthetic stack (the gadget address) into RIP. The CPU executes JMP [RBX], which reads PRM.Fixup from offset 0x00, and jumps to the Fixup routine. No exceptions, no timers, no VEH involvement.

Q2: Why does Intel CET (Control-flow Enforcement Technology) fundamentally break Draugr?

The hardware shadow stack records return addresses pushed by CALL. Draugr constructs its synthetic stack using MOV instructions, which only modify the regular stack. When RET executes, the CPU compares the regular stack value (gadget address) against the shadow stack value (no matching entry). The mismatch triggers a Control Protection exception (#CP), crashing the process.

Q3: What distinguishes ThreadStackSpoofer from Draugr in terms of when the stack is spoofed?

ThreadStackSpoofer modifies return addresses on the existing stack while Beacon is sleeping, hiding the shellcode origin during sleep-time stack scans. Draugr constructs an entirely synthetic stack that is active during the actual syscall execution. This means Draugr protects against stack inspection that happens during API calls (e.g., kernel callbacks), while ThreadStackSpoofer only protects during sleep.

Q4: Which tool from the NtDallas ecosystem handles sleep-time memory encryption, complementing Draugr's execution-time stack spoofing?

KrakenMask handles sleep obfuscation: it encrypts Beacon memory and spoofs the call stack during sleep periods. This complements Draugr, which handles execution-time evasion. Together, they cover both phases: the Beacon is protected while sleeping (KrakenMask) and while executing syscalls (Draugr).