Module 1: Why Sleep Obfuscation Matters
Your implant spends 99% of its life sleeping — and that is exactly when it gets caught.
Module Objective
Understand why Command & Control (C2) implants are vulnerable during their sleep cycle, how memory scanners exploit this window, and why encrypting the implant in memory during sleep is a critical evasion requirement. This module sets the stage for everything Ekko solves.
1. The Beacon Sleep Cycle
Modern C2 implants — whether Cobalt Strike Beacons, Sliver implants, or custom agents — operate on a check-in cycle. The implant wakes up, contacts the C2 server, executes any tasked commands, sends results back, and then goes to sleep for a configured interval (typically 30 to 300 seconds). This cycle repeats indefinitely until the operator kills the session or the implant is discovered.
The critical insight is the time distribution. If a beacon sleeps for 60 seconds between check-ins and the active communication phase takes roughly 200 milliseconds, the implant spends approximately 99.7% of its lifetime sleeping. During that sleep window, the implant's code and data sit in memory completely idle, waiting for the next cycle.
Beacon Lifecycle Timeline
~50ms
~100ms
~50ms
~60,000ms
∞
This asymmetry creates a fundamental problem: the implant is most vulnerable during the phase where it spends most of its time.
2. Memory Scanners & the Detection Window
Endpoint Detection and Response (EDR) products and memory forensics tools periodically scan process memory for indicators of compromise. These memory scanners look for several telltale signs of an implant:
| Detection Signal | What the Scanner Checks | Why It Works |
|---|---|---|
| RWX Memory | Pages with PAGE_EXECUTE_READWRITE | Legitimate code rarely needs all three permissions simultaneously |
| Unbacked Executable Memory | Executable pages not backed by a file on disk | Normal code comes from loaded DLLs/EXEs with corresponding files |
| Signature Matches | Known byte patterns (e.g., Cobalt Strike config) | Beacon configurations have recognizable structures |
| Beacon Config Extraction | Parsing known offsets for C2 URLs, sleep times | Tools like BeaconEye know the exact layout of CS config blocks |
| String Patterns | Named pipes, URLs, user-agent strings in memory | Implant data contains operational strings that normal programs lack |
The key point is that these scanners can run at any time, and since the beacon is sleeping 99%+ of the time, a scan will almost always catch the beacon in its idle state — with all of its code and configuration data sitting in memory in plaintext, in executable memory pages.
The Detection Window Problem
Without sleep obfuscation, the detection window is essentially 100% of the implant's lifetime. The beacon's code is readable, its configuration is parseable, and its memory pages have suspicious permissions. A single memory scan at any point during the sleep cycle will detect the implant.
3. What Sleep Obfuscation Solves
Sleep obfuscation addresses this problem by transforming the implant's memory before entering the sleep state and reversing the transformation upon wakeup. The goal is to make the memory region indistinguishable from random data while the implant sleeps.
An effective sleep obfuscation technique must accomplish three things during the sleep window:
The Three Pillars of Sleep Masking
- Encrypt the code region — The implant's executable code must be encrypted so byte-pattern scanners and signature matchers find nothing recognizable
- Change memory permissions — Executable pages must be changed to non-executable (RW) so scanners looking for unbacked executable memory find nothing suspicious
- Restore cleanly on wakeup — The entire process must be perfectly reversible so the implant can resume execution as if nothing happened
The challenge is that you cannot simply call VirtualProtect and an encryption function from your own code — because your code is in the region being encrypted. You would encrypt yourself mid-execution and crash. This is the self-encryption paradox, and it is the central problem that Ekko, Foliage, Cronos, and DeathSleep each solve in different ways.
4. The Self-Encryption Paradox
Consider what happens if a beacon naively tries to encrypt itself:
C// BROKEN: This will crash
void sleep_and_encrypt(DWORD sleep_ms) {
DWORD old;
// Step 1: Change permissions to RW (removes execute)
VirtualProtect(image_base, image_size, PAGE_READWRITE, &old);
// Step 2: Encrypt our own code region
// CRASH: This instruction is INSIDE the region we just
// made non-executable. We can't even reach the encrypt call.
rc4_encrypt(image_base, image_size, key);
// Steps 3-5 never execute...
Sleep(sleep_ms);
rc4_decrypt(image_base, image_size, key);
VirtualProtect(image_base, image_size, PAGE_EXECUTE_READ, &old);
}
The code above fails at Step 1 — the moment VirtualProtect removes the execute permission from the page containing the currently executing code, the CPU faults on the next instruction fetch because the instruction pointer is in a non-executable page.
Even if we reversed steps 1 and 2 (encrypt first, then change permissions), the encryption function itself is in the region being encrypted. Halfway through the encryption operation, the bytes of the encrypt function get encrypted, and the CPU starts executing encrypted garbage.
Ekko's Solution: External Execution
Ekko solves the self-encryption paradox by arranging for all the masking operations to be performed by code that lives outside the implant's memory region. Specifically, it uses timer queue callbacks with NtContinue to execute Windows API functions (VirtualProtect, SystemFunction032) that reside in system DLLs (kernel32.dll, advapi32.dll). Since these DLLs are not part of the implant's image, they remain functional even after the implant's code is encrypted and marked non-executable.
5. The Ekko Approach at a Glance
Ekko, created by Cracked5pider (C5pider), uses a technique originally discovered by Peter Winter-Smith and implemented in MDSec's Nighthawk. The approach chains together CreateTimerQueueTimer callbacks, each of which uses NtContinue to restore a pre-configured CONTEXT structure. Each CONTEXT has its RIP set to a different Windows API function, and its registers set to the arguments that function expects.
The result is a sequence of API calls executed by the timer thread, not by the beacon itself:
Ekko's 6-Step Timer Chain
RX → RW
RC4 Encrypt
Sleep Duration
RC4 Decrypt
RW → RWX
Signal Done
During step 3, the implant's code is encrypted with RC4, its memory permissions are RW (non-executable), and the main thread is waiting on an event. A memory scanner running during this window sees nothing but encrypted data in a non-executable memory region.
6. Detection Without Sleep Obfuscation vs. With
| Aspect | Without Sleep Obfuscation | With Ekko |
|---|---|---|
| Code Region Permissions | RWX or RX (executable) at all times | RW (non-executable) during sleep |
| Code Content | Plaintext shellcode/beacon code | RC4-encrypted random-looking bytes |
| Signature Detection | Beacon patterns and configs visible | No recognizable patterns during sleep |
| Detection Window | ~100% of implant lifetime | ~0.3% (only during active phase) |
| Memory Scanner Result | Immediate detection | Likely miss during sleep cycle |
Reducing the Window
Sleep obfuscation does not make an implant undetectable — it reduces the detection window from nearly 100% of the implant's lifetime to only the brief moments when the implant is actively executing. A scanner that happens to run during the 200ms active window can still catch the beacon. But the probability of that happening is dramatically lower than catching a beacon that is exposed for 60 seconds of every 60.2-second cycle.
7. Historical Context
Sleep obfuscation evolved through several milestones in the red team community:
| Technique / Tool | Author | Mechanism | Year |
|---|---|---|---|
| Cobalt Strike Sleep Mask | Raphael Mudge | XOR-based BOF that encrypts beacon in-place using a stub outside the encrypted region | 2021 |
| Nighthawk (MDSec) | Peter Winter-Smith | Timer queue callbacks with NtContinue for context-driven sleep masking | 2022 |
| Ekko | Cracked5pider | Open-source PoC of the Nighthawk technique using CreateTimerQueueTimer + NtContinue | 2022 |
| Cronos | Idov31 | Waitable timer-based sleep obfuscation using NtContinue for context-driven execution | 2022 |
| DeathSleep | janoglezcampos | Thread termination and recreation approach, killing the main thread during sleep and recreating it after | 2022 |
| FOLIAGE | SecIdiot | APC chain with 10 queued operations for full code + heap encryption | 2022 |
Ekko occupies an important position in this lineage as the first public, clean implementation of the timer queue approach. Its simplicity makes it an ideal learning tool for understanding the underlying concepts that all sleep obfuscation techniques share.
8. What You Will Learn in This Course
Course Roadmap
This course progresses from foundational concepts to the complete Ekko implementation and beyond:
- Modules 1-3 (Beginner) — Sleep obfuscation motivation, Windows timer queues, and RC4 encryption via SystemFunction032
- Modules 4-6 (Intermediate) — ROP concepts and NtContinue, the complete Ekko timer chain, and CONTEXT structure manipulation
- Modules 7-8 (Advanced) — Stack frame handling for stealth, detection engineering, and comparison with Cronos/DeathSleep/FOLIAGE
Knowledge Check
Q1: Approximately what percentage of its lifecycle does a typical C2 beacon spend sleeping?
Q2: What is the "self-encryption paradox" in sleep obfuscation?
Q3: How does Ekko solve the self-encryption paradox?