Difficulty: Beginner

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:

ToolAuthorPrimary Detection MethodKey Strength
Monetaforrest-orrVirtual memory anomaly detectionIdentifies unbacked executable memory, RWX pages, modified mapped images
pe-sievehasherezadePE image integrity scanningCompares in-memory modules against on-disk copies, detects hollowing and hooks
BeaconEyeCCobCobalt Strike config signature scanningScans 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

IOCDescriptionWhy Shellcode Triggers It
Abnormal private executable memoryPrivate (not file-backed) memory with PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITEShellcode is allocated with VirtualAlloc, creating private executable regions with no backing file
RWX memoryPages with simultaneous read, write, and execute permissionsLazy protection: some loaders set PAGE_EXECUTE_READWRITE and never tighten permissions
Modified codeMapped image sections where in-memory bytes differ from the on-disk fileInline hooks modify mapped DLL code (e.g., hooking kernel32!Sleep changes bytes in kernel32's .text section)
Abnormal thread startThreads whose start address points to non-image memoryCreateThread 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

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

ACTIVE
~100ms
0.17% of cycle
SLEEPING
~59,900ms
99.83% of cycle
ACTIVE
~100ms
0.17% of cycle

During the sleep window, the shellcode memory region has these properties that make it trivially detectable:

PropertyDuring Sleep (unprotected)What Scanner Sees
ProtectionPAGE_EXECUTE_READ (0x20)Executable private memory with no backing file
ContentCleartext shellcodeKnown signatures, MZ headers, config structures
TypeMEM_PRIVATENot mapped from any DLL — allocated via VirtualAlloc
ThreadSuspended on Sleep/WaitForSingleObjectThread 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

  1. Before sleep: Change shellcode memory from PAGE_EXECUTE_READ to PAGE_READWRITE
  2. Before sleep: XOR-encrypt the entire shellcode region in-place
  3. During sleep: Memory is non-executable and contains encrypted gibberish
  4. After wake: XOR-decrypt the shellcode region
  5. 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

ScannerIOCWithout FluctuationWith Fluctuation (during sleep)
MonetaPrivate executable memoryDetectedEvaded — memory is RW, not executable
MonetaRWX memoryDetected (if RWX used)Evaded — never uses RWX
pe-sieveShellcode signaturesDetectedEvaded — content is XOR-encrypted
BeaconEyeBeacon config patternDetectedEvaded — 32-bit XOR key defeats brute-force
Any scannerKnown byte patternsDetectedEvaded — 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?

A) Modified code in kernel32.dll
B) Abnormal private executable memory
C) Thread running from ntdll.dll
D) Signed module with invalid certificate

Q2: Why does single-byte XOR (as used by Beacon config obfuscation) fail against BeaconEye?

A) Single-byte XOR is too slow
B) BeaconEye uses hardware-accelerated decryption
C) BeaconEye can brute-force all 256 possible single-byte keys
D) Single-byte XOR corrupts the configuration data

Q3: Approximately what percentage of its runtime does a 60-second beacon spend sleeping (and thus vulnerable to scanning)?

A) ~99.8%
B) ~50%
C) ~75%
D) ~90%