Module 8: Full Chain, Detection & Variants
Comparing sleep obfuscation approaches, detection engineering, and the evolution of the technique.
Module Objective
Compare Ekko against other sleep obfuscation implementations (Cronos, DeathSleep, FOLIAGE), understand the specific detection vectors for each approach, review defensive tools (BeaconEye, Hunt-Sleeping-Beacons, Patriot), and analyze how the technique has evolved since its initial disclosure.
1. The Sleep Obfuscation Family
Multiple open-source implementations of sleep obfuscation have been released, each taking a different approach to the core problem of encrypting an implant's memory while it sleeps. Understanding their differences illuminates the design tradeoffs in this technique.
| Tool | Author | Scheduling Mechanism | Execution Primitive | Encryption | Heap Encryption | Context Spoofing |
|---|---|---|---|---|---|---|
| Ekko | Cracked5pider | CreateTimerQueueTimer | NtContinue | RC4 (SystemFunction032) | No | No |
| Cronos | Idov31 | Waitable Timers (CreateWaitableTimer) | NtContinue | RC4 (SystemFunction032) | No | No |
| DeathSleep | janoglezcampos | Thread termination & recreation | Thread kill + recreate with saved context | RC4 (SystemFunction032) | No | Yes (no thread to inspect) |
| FOLIAGE | SecIdiot | NtQueueApcThread | APC dispatch | RC4 (SystemFunction032) | Yes | Yes (Patriot evasion) |
| KrakenMask | Cracked5pider | Various (evolved Ekko) | NtContinue | RC4 | Yes | Yes |
2. Ekko vs. Cronos
Cronos by Idov31 uses the same NtContinue-based execution model as Ekko but replaces CreateTimerQueueTimer with waitable timers (CreateWaitableTimer / SetWaitableTimer). Cronos creates waitable timer objects for scheduling the sleep obfuscation operations:
C// Cronos approach (simplified):
HANDLE hTimer = CreateWaitableTimer(
NULL, // Security attributes
TRUE, // Manual reset
NULL // Timer name
);
// Set the timer to fire after a delay
LARGE_INTEGER dueTime;
dueTime.QuadPart = -10000LL * delay_ms; // Negative = relative
SetWaitableTimer(hTimer, &dueTime, 0, NtContinue, &CtxProtRW, FALSE);
Key Differences from Ekko
- Waitable timers vs. timer queue — Cronos uses waitable timer objects (
CreateWaitableTimer) rather than Ekko's timer queue API. Waitable timers deliver their callbacks as APCs to the thread that set the timer, providing a different execution model. - Timer sequencing — Cronos uses waitable timers with APC completion routines, which requires the thread to be in an alertable wait state for the callbacks to fire.
- API surface —
CreateWaitableTimer/SetWaitableTimeris a different API entry point, which may have different monitoring coverage by EDR products compared toCreateTimerQueueTimer.
3. Ekko vs. DeathSleep
DeathSleep by janoglezcampos takes a fundamentally different approach. Instead of using callbacks and NtContinue, it terminates the main thread entirely and recreates it after the sleep period. The key insight is that a dead thread leaves no context to inspect — there is no sleeping thread for detection tools to analyze:
C// DeathSleep approach (conceptual):
// 1. Main thread saves its own context and state
// 2. Main thread creates a helper thread with the saved state
// 3. Main thread terminates itself (the thread is killed, not suspended)
// 4. Helper thread:
// a. VirtualProtect(main_image, RW)
// b. Encrypt(main_image)
// c. Sleeps for the requested duration
// d. Decrypt(main_image)
// e. VirtualProtect(main_image, RX)
// f. Creates a new thread with the saved context
// (effectively recreating the original thread)
// 5. New thread resumes execution where the old one left off
Advantages of DeathSleep
- No sleeping thread to inspect — Because the main thread is terminated (not suspended), tools like Patriot that inspect sleeping thread contexts find nothing — the thread simply does not exist during the sleep window
- Clean separation — The helper thread's code is distinct from the main thread's, avoiding the self-encryption paradox naturally
- No timer artifacts — No timer queue or waitable timer objects to detect
Disadvantages of DeathSleep
- Thread creation/destruction churn — Repeatedly terminating and recreating threads is a detectable artifact. Thread lifecycle monitoring can flag this unusual pattern.
- Helper thread's stack — The helper thread's call stack will show calls to VirtualProtect and thread creation APIs in sequence — a very unusual pattern.
- Complexity — Managing thread termination and recreation, including preserving the full execution state across the thread boundary, adds significant complexity and potential for subtle bugs.
4. Ekko vs. FOLIAGE
FOLIAGE by SecIdiot is the most comprehensive implementation, using a chain of Asynchronous Procedure Calls (APCs) queued to the current thread:
C// FOLIAGE approach (conceptual):
// 1. Generate fresh RC4 key
// 2. Encrypt the private heap (separately from the code)
// 3. Queue 10 APCs to the current thread:
// APC 0: Wait (setup timer)
// APC 1: NtGetContextThread (capture)
// APC 2: NtSetContextThread (spoof RIP to ntdll)
// APC 3: NtProtectVirtualMemory (RX -> RW)
// APC 4: SystemFunction032 (encrypt code)
// APC 5: NtWaitForSingleObject (actual sleep)
// APC 6: SystemFunction032 (decrypt code)
// APC 7: NtProtectVirtualMemory (RW -> RX)
// APC 8: NtSetContextThread (restore context)
// APC 9: NtSetEvent (signal done)
// 4. Enter alertable wait (NtWaitForSingleObject with alertable=TRUE)
// 5. APCs drain in FIFO order during the alertable wait
FOLIAGE Advantages Over Ekko
- Heap encryption — FOLIAGE encrypts the private heap separately, hiding strings, configuration data, and runtime state that live outside the code region
- Context spoofing — APCs 1-2 and 8 capture, spoof, and restore the thread context, defeating Patriot
- APC ordering guarantee — APCs drain in strict FIFO order, eliminating timer sequencing concerns
- Single thread — Everything happens on the current thread using APCs, no timer threads or helper threads
- Fresh key per cycle — Uses RtlGenRandom for each sleep, not a hardcoded key
5. Detection Vectors for Ekko
Understanding how defenders detect Ekko helps both offensive operators improve their implementations and defensive engineers build better detections:
| Detection Vector | Tool / Technique | What It Catches | Ekko PoC Vulnerable? |
|---|---|---|---|
| Timer Queue Callback = NtContinue | API monitoring, ETW | CreateTimerQueueTimer with NtContinue as callback is extremely unusual | Yes |
| Sleeping Thread Stack Walk | Hunt-Sleeping-Beacons | Return addresses in unbacked memory on sleeping threads | Yes |
| Thread Context Inspection | Patriot | RIP pointing to unbacked memory on sleeping threads | Yes (no context spoofing) |
| Memory Permission Transitions | ETW, VirtualProtect monitoring | RX/RWX -> RW -> RWX cycles on the same region | Yes |
| SystemFunction032 on Image Region | API monitoring | RC4 encrypting the process's own image is abnormal | Yes |
| Beacon Config Scanning | BeaconEye | CS config patterns visible before first sleep | Yes (pre-sleep) |
| Timer Queue Object Enumeration | Kernel analysis | Timer queues with multiple one-shot timers pointing to NtContinue | Yes |
6. Hunt-Sleeping-Beacons Deep Dive
Hunt-Sleeping-Beacons by thefLink is the primary open-source tool targeting sleep obfuscation techniques. It operates by:
Detection Algorithm
- Enumerate all threads in all processes
- Identify sleeping threads — threads in a wait state (WaitForSingleObject, NtWaitForSingleObject, NtDelayExecution, etc.)
- Walk the call stack using
StackWalk64for each sleeping thread - Check each return address against loaded modules — if a return address falls outside any known module's address range, it is in "unbacked" memory
- Flag suspicious threads — sleeping threads with unbacked return addresses are reported
Against Ekko's PoC, Hunt-Sleeping-Beacons will detect the sleeping thread because:
- The main thread is waiting on an event (detected as sleeping)
- The main thread's call stack contains return addresses from EkkoObf, which is in the implant's (unbacked) image region
- Even though the code is encrypted during sleep, the return addresses on the main thread's stack still point there
7. Evasion Improvements Over the PoC
A production-grade implementation based on Ekko's technique would need these enhancements:
Required Improvements for Production Use
| Improvement | Purpose | Difficulty |
|---|---|---|
| Random key per cycle | Prevent key reuse across sleep cycles | Easy |
| Context spoofing | Set RIP to ntdll during sleep to defeat Patriot | Medium |
| Heap encryption | Hide strings, config, and runtime data | Medium |
| Stack frame spoofing | Construct legitimate-looking call stack during sleep | Hard |
| Controlled return addresses | Place gadget addresses at [RSP] for clean returns | Medium |
| Restore to RX not RWX | Avoid suspicious RWX memory after wakeup | Easy |
| Syscall-based API calls | Bypass userland API hooks on VirtualProtect etc. | Medium |
| Timer queue cleanup | Ensure no timer artifacts remain after the cycle | Easy |
8. The Evolution: KrakenMask
KrakenMask, also by Cracked5pider, represents the evolution of Ekko into a more complete sleep masking solution. While the full implementation details differ, KrakenMask addresses many of the PoC's shortcomings:
KrakenMask Improvements
- Heap encryption — Encrypts the implant's private heap in addition to the code region
- Context spoofing — Manipulates the sleeping thread's context to appear legitimate
- Multiple scheduling backends — Supports timer queues, thread pool timers, and other mechanisms
- Improved stack handling — Better control over return addresses and stack frame construction
- Integration-ready — Designed as a library component for C2 frameworks rather than a standalone PoC
9. The Complete Ekko Chain — Final Review
Let us review the complete Ekko execution from start to finish with all the knowledge from this course:
Complete Ekko Execution Flow
CreateEvent, CreateTimerQueue
Resolve NtContinue, SysFunc032
Get ImageBase, ImageSize
Timer 0: RtlCaptureContext
on timer thread
Wait 50ms
Clone baseline x6
Set RIP, args, RSP-=8
for each operation
6 timers, 100ms apart
Callback=NtContinue
Param=CONTEXT*
Main thread blocks on
WaitForSingleObject
(hEvent, INFINITE)
RW → Encrypt → Sleep
Decrypt → RWX → Signal
SetEvent wakes main
thread, EkkoObf returns
Beacon resumes normally
DeleteTimerQueue
Free resources
During step 6, between the encrypt and decrypt operations, the implant's entire image is RC4-encrypted in non-executable memory. This is the stealth window. A memory scanner running during this period sees only encrypted data in RW pages — no code signatures, no configuration patterns, no executable memory.
10. Defensive Recommendations
For blue team engineers and detection developers, these are the highest-value detection opportunities:
Detection Engineering Priorities
- Monitor CreateTimerQueueTimer for callbacks pointing to NtContinue. This is the most distinctive indicator — no legitimate software uses NtContinue as a timer callback.
- ETW for VirtualProtect transitions — Track memory regions that cycle between RX/RWX and RW. Repeated permission toggling on the same region is abnormal.
- Sleeping thread stack walks — Periodically enumerate sleeping threads and check return addresses against loaded module ranges. Unbacked addresses are suspicious.
- Thread context auditing — For threads in wait states, check if RIP points into unbacked memory. This catches implementations without context spoofing.
- Timer queue object inspection — Enumerate timer queue objects in the kernel and check for queues containing multiple timers with NtContinue callbacks.
- Behavioral correlation — The pattern of CreateTimerQueue + multiple CreateTimerQueueTimer(NtContinue) + WaitForSingleObject(event) is distinctive even without individual API monitoring.
11. Summary: Sleep Obfuscation Landscape
Key Takeaways from This Course
- The problem: Implants spend 99%+ of their time sleeping with code exposed in memory
- The solution: Encrypt the image and change permissions before sleep, reverse after wakeup
- The paradox: Cannot encrypt yourself while executing from the encrypted region
- Ekko's approach: Timer queue callbacks with NtContinue to execute system DLL functions that operate on the implant's memory from outside
- The chain: VirtualProtect(RW) → RC4 Encrypt → Sleep → RC4 Decrypt → VirtualProtect(RWX) → Signal Event
- Known flaws: Uncontrolled return addresses, no stack spoofing, no context spoofing, no heap encryption, hardcoded key
- Detection: NtContinue as timer callback, unbacked stack addresses, VirtualProtect permission cycling
- Evolution: FOLIAGE (APC-based, heap + context spoofing), DeathSleep (thread termination + recreation), KrakenMask (production Ekko)
Knowledge Check
Q1: Which sleep obfuscation tool encrypts the private heap in addition to the code region?
Q2: What is the most distinctive detection indicator for Ekko?
Q3: How does DeathSleep differ fundamentally from Ekko and Cronos?