Module 1: Memory Scanning Threat Model
What memory scanners look for, how they catch sleeping implants, and why ShellcodeFluctuation exists.
Module Objective
Understand the detection capabilities of memory scanning tools like Moneta, pe-sieve, and BeaconEye. Learn why idle shellcode is maximally exposed, how scanners exploit the sleep window, and what specific indicators of compromise (IOCs) these tools flag.
1. The Memory Scanner Landscape
Post-exploitation implants (Cobalt Strike Beacon, Meterpreter, Sliver, etc.) must reside in memory to execute. Once loaded, they become targets for a class of defensive tools that scan process address spaces for anomalies. Three tools are particularly relevant to understanding ShellcodeFluctuation:
| Tool | Author | Primary Detection Method | Key Strength |
|---|---|---|---|
| Moneta | forrest-orr | Virtual memory anomaly detection | Identifies unbacked executable memory, RWX pages, modified mapped images |
| pe-sieve | hasherezade | PE image integrity scanning | Compares in-memory modules against on-disk copies, detects hollowing and hooks |
| BeaconEye | CCob | Cobalt Strike config signature scanning | Scans for Beacon configuration structures in process memory |
Each tool targets different evidence, but they share a common assumption: the shellcode or implant is present in cleartext in executable memory at the time of scanning.
2. What Moneta Detects
Moneta walks every process's virtual address space using VirtualQueryEx and flags memory regions exhibiting suspicious properties. Its detection categories are critical to understand because ShellcodeFluctuation is designed specifically to evade them.
Moneta IOC Categories
| IOC | Description | Why Shellcode Triggers It |
|---|---|---|
| Abnormal private executable memory | Private (not file-backed) memory with PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE | Shellcode is allocated with VirtualAlloc, creating private executable regions with no backing file |
| RWX memory | Pages with simultaneous read, write, and execute permissions | Lazy protection: some loaders set PAGE_EXECUTE_READWRITE and never tighten permissions |
| Modified code | Mapped image sections where in-memory bytes differ from the on-disk file | Inline hooks modify mapped DLL code (e.g., hooking kernel32!Sleep changes bytes in kernel32's .text section) |
| Abnormal thread start | Threads whose start address points to non-image memory | CreateThread targeting a VirtualAlloc region instead of a DLL function |
When Moneta scans an unprotected Cobalt Strike Beacon, it immediately flags the shellcode allocation as "Abnormal private executable memory" — this is the primary IOC that ShellcodeFluctuation targets.
3. What pe-sieve Detects
pe-sieve focuses on PE image integrity. It enumerates loaded modules in a process and compares them byte-for-byte against their on-disk counterparts. Key detection capabilities:
pe-sieve Detection Vectors
- Module overwriting (hollowing) — the in-memory image of a DLL or EXE has been replaced with different code
- Inline hooks — specific functions within a mapped module have been patched (JMP instructions at function entry points)
- Implant scanning — pe-sieve can also scan non-image memory regions, finding shellcode through heuristics
- IAT modification — Import Address Table entries pointing to unexpected locations
For ShellcodeFluctuation, pe-sieve is relevant in two ways: it can detect the shellcode itself (if present in cleartext), and it can detect the inline hook on kernel32!Sleep that the fluctuation mechanism installs.
4. What BeaconEye Detects
BeaconEye takes a targeted approach, specifically hunting for Cobalt Strike Beacon configurations in memory. Beacon stores its configuration (C2 URLs, sleep time, watermark, etc.) in a predictable structure that can be identified through pattern matching.
// Simplified Beacon config structure in memory
// BeaconEye scans for these characteristic patterns:
struct BeaconConfig {
uint16_t setting_id; // e.g., 0x0001 = BeaconType
uint16_t data_type; // 0x0001=short, 0x0002=int, 0x0003=data
uint16_t data_length;
char data[]; // the actual configuration value
};
// The config block is XOR'd with a single byte key in memory
// BeaconEye tries all 256 possible XOR keys to decode it
BeaconEye scans all private memory regions in every process, attempting single-byte XOR decryption with each of the 256 possible keys. If the decoded data matches the Beacon configuration signature, detection occurs. ShellcodeFluctuation's multi-byte XOR encryption defeats this brute-force approach because 256 keys is insufficient to crack a 32-bit XOR key.
5. The Idle-Time Detection Window
C2 implants follow a predictable lifecycle: execute briefly, then sleep for an extended period. This creates a massive asymmetry that defenders exploit:
Time Distribution of a 60-Second Beacon
~100ms
0.17% of cycle
~59,900ms
99.83% of cycle
~100ms
0.17% of cycle
During the sleep window, the shellcode memory region has these properties that make it trivially detectable:
| Property | During Sleep (unprotected) | What Scanner Sees |
|---|---|---|
| Protection | PAGE_EXECUTE_READ (0x20) | Executable private memory with no backing file |
| Content | Cleartext shellcode | Known signatures, MZ headers, config structures |
| Type | MEM_PRIVATE | Not mapped from any DLL — allocated via VirtualAlloc |
| Thread | Suspended on Sleep/WaitForSingleObject | Thread start address in private memory |
The Core Problem
An unprotected implant is detectable during 99.83% of its runtime. Memory scanners do not need to be fast or lucky — they just need to scan at any point during the sleep window. Even periodic scans at 30-second intervals will catch a 60-second beacon with near-certainty.
6. How Scanners Enumerate Memory
Understanding the API surface that scanners use helps explain why certain evasion strategies work:
// Core scanning loop used by tools like Moneta
MEMORY_BASIC_INFORMATION mbi;
LPVOID address = 0;
while (VirtualQueryEx(hProcess, address, &mbi, sizeof(mbi))) {
// Check for suspicious attributes
if (mbi.Type == MEM_PRIVATE &&
(mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ |
PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))) {
// FLAG: Private executable memory - potential shellcode
ScanRegion(hProcess, mbi.BaseAddress, mbi.RegionSize);
}
if (mbi.Protect == PAGE_EXECUTE_READWRITE) {
// FLAG: RWX memory - highly suspicious
FlagRWX(hProcess, mbi.BaseAddress, mbi.RegionSize);
}
// Advance to next region
address = (LPBYTE)mbi.BaseAddress + mbi.RegionSize;
}
The key insight: scanners use VirtualQueryEx to read the current page protection. If the memory is marked PAGE_READWRITE (no execute), it does not match the "private executable memory" heuristic. This is the foundation of ShellcodeFluctuation's evasion strategy.
7. The ShellcodeFluctuation Strategy
ShellcodeFluctuation (by mgeeky / Mariusz Banach) addresses the idle-time detection window with a straightforward but effective approach:
The Fluctuation Concept
- Before sleep: Change shellcode memory from
PAGE_EXECUTE_READtoPAGE_READWRITE - Before sleep: XOR-encrypt the entire shellcode region in-place
- During sleep: Memory is non-executable and contains encrypted gibberish
- After wake: XOR-decrypt the shellcode region
- After wake: Change memory back to
PAGE_EXECUTE_READ
This transforms the scanner's view during the 99.83% sleep window from "executable shellcode" to "non-executable random data" — eliminating every IOC in the table above.
The mechanism is called "fluctuation" because the memory protection constantly oscillates between RW (encrypted) and RX (executable), and the content constantly toggles between ciphertext and plaintext.
8. Detection Evasion Summary
| Scanner | IOC | Without Fluctuation | With Fluctuation (during sleep) |
|---|---|---|---|
| Moneta | Private executable memory | Detected | Evaded — memory is RW, not executable |
| Moneta | RWX memory | Detected (if RWX used) | Evaded — never uses RWX |
| pe-sieve | Shellcode signatures | Detected | Evaded — content is XOR-encrypted |
| BeaconEye | Beacon config pattern | Detected | Evaded — 32-bit XOR key defeats brute-force |
| Any scanner | Known byte patterns | Detected | Evaded — encrypted content has no recognizable patterns |
What Remains Detectable
ShellcodeFluctuation does not eliminate all IOCs. The inline hook on kernel32!Sleep creates a "Modified code" IOC in Moneta because patching a mapped module's code causes a private copy of the affected page (copy-on-write). This is addressed in Module 5 by temporarily unhooking Sleep before the actual sleep call.
Knowledge Check
Q1: What is the primary Moneta IOC that ShellcodeFluctuation eliminates during the sleep window?
Q2: Why does single-byte XOR (as used by Beacon config obfuscation) fail against BeaconEye?
Q3: Approximately what percentage of its runtime does a 60-second beacon spend sleeping (and thus vulnerable to scanning)?