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:
| Protection | Constant | Hex | What it means |
|---|---|---|---|
| Read Only | PAGE_READONLY | 0x02 | Can read, but not write or execute |
| Read/Write | PAGE_READWRITE | 0x04 | Can read + write. Most heap memory. |
| Execute/Read | PAGE_EXECUTE_READ | 0x20 | Can read + execute code. Normal .text sections. |
| Execute/R/W | PAGE_EXECUTE_READWRITE | 0x40 | The 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)
AceLdr Beacon (Evasive)
Memory Scanners: The Enemy
AceLdr was built to evade 7 specific tools. Here's what each one looks for:
| Scanner | Detection Method | AceLdr's Counter |
|---|---|---|
| Moneta | Finds RWX private memory, unbacked executable regions | Toggles permissions; memory is RW or RX, never RWX |
| PE-sieve | Scans for PE headers and known signatures in memory | Encrypts all code during sleep with RC4 |
| BeaconEye | Pattern-matches Beacon config in heap memory | Separate heap + encryption = config invisible |
| Hunt-Sleeping-Beacons | Inspects call stacks of sleeping threads | Return address spoofing + context manipulation |
| BeaconHunter | Identifies Beacon by memory patterns and behavior | Full memory encryption + heap redirection |
| Patriot | Walks thread stacks looking for suspicious frames | JMP RBX trampoline gadget to spoof stack |
| MalMemDetect | Finds malicious artifacts in process memory | Everything 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?
Q2: AceLdr creates a private heap. Why?
Q3: What is the typical page size on Windows x64?