Difficulty: Beginner

Module 1: Windows Memory Fundamentals

What happens in RAM stays in RAM... unless a scanner finds it.

Why This Module?

Before we reverse-engineer AceLdr (by kyleavery, presented at DEF CON 30), you need a mental model of how Windows manages memory. Every evasion technique in this project is basically a clever trick against these fundamentals. Think of it as learning the rules of chess before learning gambits.

Virtual Memory: Your Private Universe

Every Windows process gets its own virtual address space. On a 64-bit system, that's a theoretical 128 TB playground. The CPU's MMU (Memory Management Unit) translates virtual addresses to physical RAM addresses using page tables.

The smallest unit of memory Windows manages is a page (typically 4 KB = 4096 bytes). Each page has protection flags that determine what you can do with it:

ProtectionConstantHexWhat it means
Read OnlyPAGE_READONLY0x02Can read, but not write or execute
Read/WritePAGE_READWRITE0x04Can read + write. Most heap memory.
Execute/ReadPAGE_EXECUTE_READ0x20Can read + execute code. Normal .text sections.
Execute/R/WPAGE_EXECUTE_READWRITE0x40The red flag. Read + write + execute.

Why Defenders Care About RWX

Legitimate programs almost never need memory that is both writable AND executable at the same time. When a scanner like Moneta finds RWX pages, it's an immediate red flag. AceLdr avoids this by carefully toggling permissions: write when it needs to write, execute when it needs to execute, never both at once (except briefly during the sleep chain).

Key Memory APIs

Windows provides these functions to manage virtual memory. AceLdr uses the Nt* (native) versions because they bypass some userland monitoring:

C// High-level (kernel32)
LPVOID VirtualAlloc(LPVOID addr, SIZE_T size, DWORD type, DWORD protect);
BOOL   VirtualProtect(LPVOID addr, SIZE_T size, DWORD new, PDWORD old);

// Low-level (ntdll) - what AceLdr actually uses
NTSTATUS NtAllocateVirtualMemory(HANDLE proc, PVOID *base, ULONG_PTR zero,
                                  PSIZE_T size, ULONG type, ULONG protect);
NTSTATUS NtProtectVirtualMemory(HANDLE proc, PVOID *base,
                                 PSIZE_T size, ULONG newprot, PULONG old);

The Windows Heap

The heap is where dynamic allocations live (malloc, HeapAlloc, new). Each process has a default process heap accessible via GetProcessHeap(). Memory scanners inspect this heap looking for suspicious content.

AceLdr's trick: it creates a separate, private heap via RtlCreateHeap() and redirects Beacon's allocations there. Scanners looking at the default process heap find nothing.

C// AceLdr creates a private heap and stores the handle in the STUB structure
HANDLE privateHeap = RtlCreateHeap(
    HEAP_GROWABLE | HEAP_CREATE_ALIGN_16,  // Flags
    NULL,     // HeapBase - let the system choose
    0,        // ReserveSize
    0,        // CommitSize
    NULL,     // Lock
    NULL      // Parameters
);

// Later, when Beacon calls GetProcessHeap(), AceLdr's hook returns this instead:
SECTION( D ) HANDLE GetProcessHeap_Hook()
{
    return ( ( PSTUB )OFFSET( Stub ) )->Heap;  // Returns the private heap
}

Memory Layout: Normal vs AceLdr

Normal Beacon (Detectable)

Process Default Heap ← contains Beacon data
.text section RWX ← Beacon code (always executable)
Standard Thread Stack

AceLdr Beacon (Evasive)

Process Default Heap ← clean, nothing suspicious
Private Heap (encrypted during sleep)
.text RX (encrypted + non-exec during sleep)

Memory Scanners: The Enemy

AceLdr was built to evade 7 specific tools. Here's what each one looks for:

ScannerDetection MethodAceLdr's Counter
MonetaFinds RWX private memory, unbacked executable regionsToggles permissions; memory is RW or RX, never RWX
PE-sieveScans for PE headers and known signatures in memoryEncrypts all code during sleep with RC4
BeaconEyePattern-matches Beacon config in heap memorySeparate heap + encryption = config invisible
Hunt-Sleeping-BeaconsInspects call stacks of sleeping threadsReturn address spoofing + context manipulation
BeaconHunterIdentifies Beacon by memory patterns and behaviorFull memory encryption + heap redirection
PatriotWalks thread stacks looking for suspicious framesJMP RBX trampoline gadget to spoof stack
MalMemDetectFinds malicious artifacts in process memoryEverything encrypted and hidden during idle

Note on Hunt-Sleeping-Beacons

Hunt-Sleeping-Beacons was later updated with a detection specifically targeting FOLIAGE-style sleep obfuscation. The updated version checks for threads with a UserRequest wait reason that have KiUserApcDispatcher on their call stack. This combination is characteristic of the APC-based sleep chain that FOLIAGE employs. This means AceLdr's evasion against this scanner may no longer be fully effective against updated versions.

Pop Quiz: Memory Fundamentals

Q1: Why is PAGE_EXECUTE_READWRITE (RWX) suspicious to memory scanners?

Correct! Normal compiled code is mapped as RX (execute + read). Data is RW (read + write). Needing both at once strongly suggests runtime code generation or injection - exactly what shellcode does.

Q2: AceLdr creates a private heap. Why?

AceLdr hooks GetProcessHeap() to return a custom heap created with RtlCreateHeap(). Scanners like BeaconEye that walk the default process heap looking for Beacon config structures won't find anything.

Q3: What is the typical page size on Windows x64?

Windows uses 4 KB pages by default. The 64 KB value (65536) is the allocation granularity - the minimum size VirtualAlloc will reserve - but actual page granularity is 4 KB.