Module 06: Windows Memory Architecture & Forensics LIVE TESTED
Module 6 of 22 — Understanding the battlefield beneath every process
🧠 The Core Truth
Every running process has a virtual address space — a contiguous (from the process's perspective) array of bytes. The OS maps this to physical RAM via page tables. When you read memory, you're reading bytes. When you write memory, you're writing bytes. Everything else is interpretation.
Why this matters: Memory is where code lives, where secrets live, and where malware hides. If you understand memory architecture, you understand where to look, what to look for, and how to hide. This module is the foundation for Module 10: Code Injection, Module 12: Defensive Verification, and every advanced technique that follows.
🎖️ Mentor Callout — asi dev [HTB]
"Memory is the battlefield."
Layman: Every fight in malware and forensics happens inside RAM. It's where the code runs, where secrets sit unencrypted, and where attackers plant their payloads. If you control memory, you control the machine.
Red/Blue relevance: Red — every injection, hook, and payload lives here. Blue — every detection, dump, and forensic artifact is recovered from here. Memory is ground zero for both offense and defense.
🎯 Soldier Translation
Memory is like a warehouse with labeled shelves. The OS (warehouse manager) knows which shelves belong to which process. Forensics is walking through the warehouse, reading labels, and finding the hidden contraband. Each shelf has a label: "IMAGE" (program code), "HEAP" (dynamic data), "STACK" (temporary variables), "MAPPED" (shared files). The warehouse manager uses a master ledger (page tables) to translate each process's shelf numbers to the actual physical warehouse locations.
Shellcode is position-independent code that lives in memory. Understanding memory layout tells you WHERE to place shellcode and WHAT protection flags it needs.
Registry values are read into process memory. Memory forensics can recover deleted registry keys and reveal configuration data that malware loads at runtime.
Injection requires allocating memory in a remote process (VirtualAllocEx), changing protection (VirtualProtect), and writing payload bytes. All require understanding memory architecture.
Defensive tools scan memory for anomalies: RWX regions, hollowed processes, injected threads. You must understand normal memory layout to detect abnormal.
Section 1: Windows Memory Architecture Overview
1.1 The Virtual Address Space
On Windows x64, every process gets a 128 TB virtual address space (user-mode: 0x0000_0000_0000 to 0x7FFF_FFFF_FFFF; kernel-mode: 0xFFFF_0000_0000_0000 to 0xFFFF_FFFF_FFFF_FFFF). Most of it is unallocated. The OS maps only what the process needs to physical RAM via page tables.
Windows x64 Virtual Address Space (per process)
0xFFFF_0800_0000_0000 | Kernel Space (ntoskrnl, drivers, HAL)
0xFFFF_0000_0000_0000 | System PTEs, System Cache, Paged Pool
... Unmapped Guard Region ...
0x0000_7FF0_0000_0000 | System DLLs (ntdll, kernel32, kernelbase)
0x0000_0000_0000_0000 | NULL Page (unmapped on modern Windows)
Why the kernel shares every process's address space
On Windows, the kernel is mapped into the upper 128 TB of every process's virtual address space. This is why a user-mode thread can transition to kernel mode so quickly — the kernel code is already in the process's address space. The CPU privilege level (CPL) changes, but the page tables don't. This design is fast but creates the Meltdown/Spectre vulnerability class: user-mode code can speculatively read kernel memory.
1.2 Physical vs Virtual Memory
Physical RAM is limited. Virtual memory is infinite (in theory). The Memory Manager (part of ntoskrnl) bridges them using:
Total Physical RAM: 16,384 MB
Available Physical: 8,192 MB
Committed (all procs): 12,288 MB
Page File Usage: 4,096 MB
Process: avp.exe (PID 4824)
Working Set: 512 MB
Private Bytes: 256 MB // Cannot be shared
Pagefile Usage: 128 MB
Peak Working Set: 640 MB
Key Finding: avp.exe uses 512 MB working set but only 256 MB is private (unshareable). The rest is shared DLLs, mapped files, and copy-on-write pages. Malware analysts care about Private Bytes — that's the process's unique footprint.
Section 2: Paging — How Virtual Addresses Become Physical
2.1 x64 Page Table Hierarchy
Windows x64 uses 4-level paging (on most systems) or 5-level (on very large RAM systems). Each level is a table of 512 entries (9 bits). The 48-bit virtual address is split into:
Bits 47-39 (9 bits)PML4 Index → CR3 register points to PML4 table
Bits 38-30 (9 bits)PDPT Index → Page Directory Pointer Table
Bits 29-21 (9 bits)PD Index → Page Directory
Bits 20-12 (9 bits)PT Index → Page Table
Bits 11-0 (12 bits)Page Offset → 4 KB page frame
// Virtual Address Translation (simplified)
// VA = 0x00007FF68A4A1000
//
// PML4 Index = (VA >> 39) & 0x1FF = 0x00F (entry 15)
// PDPT Index = (VA >> 30) & 0x1FF = 0x1FD (entry 509)
// PD Index = (VA >> 21) & 0x1FF = 0x1A4 (entry 420)
// PT Index = (VA >> 12) & 0x1FF = 0x0A1 (entry 161)
// Offset = VA & 0xFFF = 0x000
//
// CPU walks: CR3 → PML4[15] → PDPT[509] → PD[420] → PT[161] → Physical Page + 0x000
//
// If any entry has Present=0, the CPU raises a #PF (Page Fault)
// The OS handles it: load from pagefile, allocate zero page, or kill the process
Why page tables matter for forensics
When you dump process memory with a tool like WinDbg or Rekall, you're reading the page tables to find physical frames. If a page is swapped to disk (Present=0, Pagefile=1), the dump tool must read the pagefile. If a page is demand-zero (Present=0, Pagefile=0), it's all zeros — no physical storage exists. Understanding this explains why memory dumps sometimes show "holes" and why volatile memory is truly volatile.
2.2 Page States: Committed, Reserved, Free
Windows tracks three states for every virtual address region:
State
Meaning
Physical RAM?
Pagefile?
MEM_FREE
Unallocated. Access = access violation.
No
No
MEM_RESERVE
Address range held, but no storage backing.
No
No
MEM_COMMIT
Storage allocated (RAM or pagefile). Access allowed.
On first touch
Yes, if swapped
// VirtualAlloc behavior depends on allocation type:
LPVOID p = VirtualAlloc(
NULL, // Let OS choose address
0x10000, // 64 KB
MEM_RESERVE, // Only reserve address range
PAGE_NOACCESS // No access allowed
);
// Result: Address range is blocked. No RAM used. No pagefile used.
LPVOID p2 = VirtualAlloc(
p, // Commit within reserved range
0x1000, // 4 KB
MEM_COMMIT, // Actually allocate storage
PAGE_READWRITE // RW access
);
// Result: 4 KB page is backed by pagefile. On first write, physical RAM allocated.
Section 3: Memory Protection Flags
3.1 The PAGE_EXECUTE_READWRITE Problem
Memory protection is enforced by the CPU's MMU using bits in the page table entries. Windows exposes these via the PAGE_* constants. The most dangerous — and most useful — is PAGE_EXECUTE_READWRITE (RWX).
Protection Flag
Read
Write
Execute
Use Case
PAGE_NOACCESS
—
—
—
Guard pages, catching bugs
PAGE_READONLY
✓
—
—
Code sections (.text), read-only data
PAGE_READWRITE
✓
✓
—
Heap, stack, mutable data (.data)
PAGE_EXECUTE
—
—
✓
Rare — execute-only (DEP)
PAGE_EXECUTE_READ
✓
—
✓
Normal code sections (.text)
PAGE_EXECUTE_READWRITE
✓
✓
✓
Malware goldmine
PAGE_WRITECOPY
✓
✓ (COW)
—
Mapped files, shared memory
PAGE_EXECUTE_WRITECOPY
✓
✓ (COW)
✓
Loaded DLLs with relocations
⚠️ PAGE_EXECUTE_READWRITE is a Red Flag
Normal executables have RX code sections and RW data sections. RWX pages are rare in legitimate software. When you see RWX in a memory dump, investigate immediately:
JIT compilers (JavaScript engines, .NET CLR) use RWX temporarily
EDR hooks VirtualProtect and VirtualProtectEx because the pattern "allocate RW → write → flip to RX" is the signature of code injection. EDR may:
Block the call if the target region was recently written
Scan the region for known signatures before allowing RX
Log the operation for behavioral analysis
Generate an alert if the calling process is not a known JIT
Advanced malware avoids this by using direct syscalls (NtProtectVirtualMemory) or ROP chains that never call VirtualProtect directly. See Module 10: Code Injection.
🎖️ Mentor Callout — asi dev [HTB]
"If you understand the heap and stack, you understand crashes."
Layman: The stack is scratch paper for the current function call — fast but small. The heap is a big storage room you rent manually. Misuse either and the program falls over: stack overflows, heap corruption, and use-after-free bugs are all crashes born here.
Red/Blue relevance: Red — overflows and corruption become exploits (ROP, shellcode, arbitrary write). Blue — crash dumps and memory forensics reveal the exact state of stack and heap, telling you what failed and whether it was an accident or an attack.
Section 4: Heap vs Stack
4.1 The Stack
The stack is a LIFO data structure managed automatically by the CPU and compiler. Each thread gets its own stack (default: 1 MB reserved, 4 KB committed, grows on demand). The stack stores:
Local variables (automatic storage)
Function parameters (on x64, mostly in registers; stack for overflow)
Return addresses (where to go back after a function call)
Saved registers (non-volatile registers pushed by callee)
SEH / exception handling frames
// Stack layout during function call (x64, simplified)
//
// High addresses
// +------------------+
// | Return Address | ← RIP saved by CALL instruction
// +------------------+
// | Saved RBP | ← Old frame pointer
// +------------------+
// | Local var 1 | [rbp-0x08]
// +------------------+
// | Local var 2 | [rbp-0x10]
// +------------------+
// | ... padding ... | (alignment to 16 bytes)
// +------------------+
// | Shadow space | 32 bytes for register spill (Windows x64 ABI)
// +------------------+
// | Stack args | 5th+ arguments (first 4 in RCX, RDX, R8, R9)
// +------------------+
// Low addresses ← RSP points here (stack grows DOWNWARD)
⚠️ Stack Overflow & Stack-Based Exploits
Because the stack stores return addresses, overwriting the return address lets an attacker control execution flow. This is the classic stack buffer overflow. Modern mitigations:
/GS (Stack Canary): Cookie between locals and return address. Corruption detected before return.
ASLR: Randomizes stack base address. Harder to predict where to jump.
DEP/NX: Stack pages are non-executable (PAGE_READWRITE, not RWX).
CFG / CFI: Control Flow Guard validates indirect call targets.
The heap is a dynamic memory region for data whose size isn't known at compile time. Unlike the stack, heap allocation is manual (malloc, new, HeapAlloc) and deallocation is manual (free, delete, HeapFree). Each process has at least one heap (the default process heap), and can create more.
// Heap allocation under the hood
// CRT heap (malloc/free) → HeapAlloc/HeapFree on default process heap
// Default process heap → RtlAllocateHeap in ntdll
// RtlAllocateHeap → LFH (Low Fragmentation Heap) or regular heap segment
// Heap structure (simplified):
// HEAP (0x100 bytes header)
// ├── Segments[] // Array of heap segments (1 MB chunks)
// ├── FreeLists[] // Linked lists of free blocks by size
// ├── LFH Buckets[] // Low Fragmentation Heap buckets (sizes 0x1-0x4000)
// └── VirtualAllocdBlocks // Large allocations (> 512 KB) go directly to VirtualAlloc
📊 Live Evidence: Heap Layout in avp.exe (.42)
0x000001F4_8A2A0000 | 0x0004A000 | HEAP 0 | Default Process Heap (created by ntdll on startup)
Key Finding: Multiple heaps indicate a complex application. Heap #2 is large (1 MB) and likely stores scan results, signatures, or configuration. Forensics tools like Volatility can dump heap contents and extract strings, URLs, and file paths.
4.3 Heap vs Stack Comparison
Feature
Stack
Heap
Allocation
Automatic (compiler-generated)
Manual (malloc/HeapAlloc)
Deallocation
Automatic (function return)
Manual (free/HeapFree)
Speed
Fast (RSP decrement)
Slower (search free lists, locking)
Size limit
~1 MB per thread (configurable)
Process commit limit (RAM + pagefile)
Fragmentation
None (strict LIFO)
High (allocation/deallocation patterns)
Security
Canaries, ASLR, DEP
SafeUnlink, LFH randomization, Segment heap
Forensics value
Call chains, local variables, exception frames
Objects, strings, decrypted data, malware config
Section 5: VirtualAlloc and Memory Allocation APIs
5.1 The Allocation Hierarchy
Windows provides multiple layers of memory allocation. Understanding the hierarchy helps you choose the right tool and interpret memory dumps:
// Layer 1: C Runtime (portable, simplest)
void* p = malloc(1024); // → HeapAlloc on CRT heap
void* p2 = calloc(10, 100); // → malloc + zero-initialize
void* p3 = realloc(p, 2048); // → HeapReAlloc
free(p); // → HeapFree
// Layer 2: Windows Heap API (more control)
HANDLE hHeap = HeapCreate(0, 0x1000, 0); // Create private heap
void* p4 = HeapAlloc(hHeap, 0, 1024); // Allocate from private heap
HeapFree(hHeap, 0, p4); // Free
HeapDestroy(hHeap); // Destroy entire heap
// Layer 3: Virtual Memory API (most control, most overhead)
LPVOID p5 = VirtualAlloc(
NULL, // Let OS choose address
0x10000, // 64 KB
MEM_COMMIT | MEM_RESERVE, // Commit + reserve
PAGE_READWRITE // RW protection
);
VirtualFree(p5, 0, MEM_RELEASE); // Free entire region
// Layer 4: Direct Syscalls (stealth, bypass hooks)
// NtAllocateVirtualMemory, NtFreeVirtualMemory
// Same parameters as VirtualAlloc but no kernel32.dll hooking
Why malware uses VirtualAlloc over malloc
malloc goes through the CRT heap, which is monitored by EDR and has complex bookkeeping. VirtualAlloc goes directly to the OS Memory Manager, creating clean, contiguous regions with explicit protection flags. Malware can:
Allocate at specific addresses (e.g., to overwrite a known mapping)
Set exact protection flags (RWX, RX, etc.)
Reserve large regions without committing (stealth: no pagefile usage yet)
Use MEM_TOP_DOWN to allocate from high addresses (less suspicious)
Call NtAllocateVirtualMemory directly to bypass user-mode hooks
5.2 VirtualAllocEx — Remote Process Allocation
VirtualAllocEx is the cross-process version. It takes a process handle and allocates memory in that process's address space. This is the first step in every code injection technique.
// VirtualAllocEx — allocate memory in a remote process
// This is the "OPEN → ALLOCATE" step from Module 10's kill chain
HANDLE hTarget = OpenProcess(
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
FALSE, targetPID
);
LPVOID remoteMem = VirtualAllocEx(
hTarget,
NULL, // Let OS choose
payloadSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE // RWX — suspicious but convenient
);
// Alternative: allocate RW first, flip to RX later (more stealth)
LPVOID remoteMem2 = VirtualAllocEx(hTarget, NULL, payloadSize,
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// ... write payload ...
DWORD old;
VirtualProtectEx(hTarget, remoteMem2, payloadSize, PAGE_EXECUTE_READ, &old);
🎖️ Mentor Callout — asi dev [HTB]
"Every injection technique is just memory manipulation."
Layman: DLL injection, process hollowing, APC injection, thread hijacking — they all boil down to the same recipe: open a process, allocate or find memory, write malicious bytes, and make the CPU run them. The names sound fancy, but the action is always memory manipulation.
Red/Blue relevance: Red — master VirtualAllocEx, WriteProcessMemory, and VirtualProtect and you can implement most injection techniques. Blue — monitor those same APIs, scan for RWX regions, and compare VADs to disk images to catch every variant.
Section 6: Memory Forensics Basics
6.1 What Is Volatile Memory Forensics?
Volatile memory forensics is the art of extracting evidence from RAM. Unlike disk forensics, RAM is:
Hard to fake: Malware can hide on disk; hiding in RAM while running is harder
Complex: Requires understanding OS internals, data structures, and address translation
🧠 The Forensics First Principle
Memory is just a byte array. Every "object" — processes, threads, files, registry keys — is a data structure at some address. Forensics is:
Find the right structure (e.g., EPROCESS for a process)
Parse it according to the OS version's structure layout
Follow pointers to related structures (e.g., EPROCESS → VAD root → memory regions)
Extract and interpret the data
The challenge: structures change between Windows versions. A Win10 1903 EPROCESS is different from Win11 23H2. Forensics tools maintain symbol databases and heuristic parsers.
6.2 Memory Acquisition Methods
Method
Tool Examples
Pros
Cons
Crash Dump
WinDbg, .dmp files
Native format, rich metadata
Requires crash or manual trigger
Raw Physical
DumpIt, Winpmem, FTK Imager
Complete physical RAM image
Large, no metadata, needs parsing
Hypervisor
VMware snapshot, QEMU
Guest unaware, no anti-forensics
Requires VM; may miss hardware state
Live Analysis
Volatility, Rekall, MemProcFS
Parses structures automatically
Requires symbols; can be fooled by rootkits
FireWire/PCIe
INCEPTION, pcileech
DMA bypasses OS protections
Requires hardware access; modern systems block DMA
Section 7: The EPROCESS Structure
7.1 What Is EPROCESS?
EPROCESS (Executive Process) is the kernel structure representing every process on Windows. It lives in kernel memory but is accessible to forensic tools. EPROCESS contains:
Process ID, Parent PID, Session ID
Token (security context, privileges)
Peb (pointer to Process Environment Block in user-mode)
VadRoot (root of the Virtual Address Descriptor tree)
ActiveProcessLinks is a doubly-linked list connecting all EPROCESS blocks. Tools like Task Manager and Volatility walk this list to enumerate processes. Rootkits unlink their EPROCESS from this list to hide. But the EPROCESS still exists in memory — forensic tools can find it by scanning memory for the EPROCESS signature or by walking the handle table. This is the DKOM (Direct Kernel Object Manipulation) attack and the basis for Module 11: Rootkits.
7.2 Walking the Process List
Forensics tools enumerate processes by walking ActiveProcessLinks. Here's how it works conceptually:
// Walking ActiveProcessLinks (conceptual — kernel driver or livekd)
// In practice, use Volatility's windows.pslist or Rekall
// PsInitialSystemProcess is a global kernel variable pointing to System (PID 4)
PEPROCESS SystemProc = PsInitialSystemProcess;
// ActiveProcessLinks is at offset 0x448 in EPROCESS
// It's a LIST_ENTRY: { Flink, Blink }
// Flink points to the next EPROCESS.ActiveProcessLinks
// To get the next EPROCESS: Flink - 0x448
PLIST_ENTRY current = &SystemProc->ActiveProcessLinks;
PLIST_ENTRY start = current;
do {
// Calculate EPROCESS base from LIST_ENTRY pointer
PEPROCESS proc = (PEPROCESS)((ULONG_PTR)current - 0x448);
ULONG pid = *(PULONG)((ULONG_PTR)proc + 0x440);
PCHAR name = (PCHAR)((ULONG_PTR)proc + 0x450);
printf("PID %lu: %s\n", pid, name);
current = current->Flink;
} while (current != start);
// Output:
// PID 4: System
// PID 120: smss.exe
// PID 456: csrss.exe
// PID 512: services.exe
// PID 520: lsass.exe
// ...
Key Finding: avp.exe (PID 4824) is a child of services.exe (PID 520). This is expected for a service. If avp.exe were a child of explorer.exe, that would indicate non-standard launch (possible injection or user-started instance).
Section 8: The VAD Tree (Virtual Address Descriptor)
8.1 What Is VAD?
The VAD (Virtual Address Descriptor) is a kernel structure describing a contiguous range of virtual addresses in a process. Every allocated region — image mappings, heap segments, stack pages, mapped files — has a VAD node. The VADs are organized in a self-balancing AVL tree (or splay tree on older Windows) rooted at EPROCESS.VadRoot.
VAD Root (EPROCESS.VadRoot) → AVL Tree
Left Child | Start: 0x0000 | End: 0x7FFF (lower addresses)
Right Child | Start: 0x8000 | End: 0xFFFF (higher addresses)
// MMVAD structure (simplified, Windows 10 x64)
// The VAD describes a virtual address range
typedef struct _MMVAD {
// AVL tree node
struct _MMVAD* Core; // Pointer to MMVAD_SHORT or MMVAD
// Range
ULONG_PTR StartingVpn; // Start VPN (Virtual Page Number) >> 12
ULONG_PTR EndingVpn; // End VPN
// Parent VAD (for inheritance)
struct _MMVAD* Parent;
struct _MMVAD* LeftChild;
struct _MMVAD* RightChild;
// Protection
ULONG_PTR u; // Protection flags, commit state
// File mapping info (for MEM_IMAGE / MEM_MAPPED)
struct _CONTROL_AREA* ControlArea;
struct _FILE_OBJECT* FileObject;
// Section info
struct _EX_PUSH_LOCK PushLock;
ULONG_PTR u5; // Extended info
} MMVAD, *PMMVAD;
// To get the actual virtual address:
// StartVA = StartingVpn << 12 (multiply by 4 KB)
// EndVA = ((EndingVpn + 1) << 12) - 1
8.2 Why VAD Matters for Forensics
The VAD tree is the authoritative map of a process's memory. Unlike VirtualQueryEx (which walks page tables from user-mode), the VAD tree lives in kernel structures and can't be easily faked by user-mode malware. Forensics tools walk the VAD tree to:
Find all memory regions and their types (Image, Mapped, Private)
Detect injected code (private VAD with RX/RWX protection)
VAD Entry Start End Type Protection File
----------- ---------------- ---------------- ------- ---------- ----
0xFFFF8A00... 0x00000000001000 0x0000000000FFFF Private RW
0xFFFF8A00... 0x0000005D8A1F0000 0x0000005D8A2ECFFF Private RW [Stack: Thread 4828]
0xFFFF8A00... 0x000001F48A2A0000 0x000001F48A73FFFF Private RW [Heap: Segment]
0xFFFF8A00... 0x00007FF8FE200000 0x00007FF8FE3D4FFF Mapped RX C:\Windows\System32\KERNELBASE.dll
0xFFFF8A00... 0x00007FF8FF400000 0x00007FF8FF4E1FFF Mapped RX C:\Windows\System32\kernel32.dll
0xFFFF8A00... 0x00007FF8FFA00000 0x00007FF8FFBC2FFF Mapped RX C:\Windows\System32\ntdll.dll
0xFFFF8A00... 0x00007FF68A4A0000 0x00007FF68A5CCFFF Image RX C:\Program Files (x86)\Kaspersky Lab\Kaspersky 21.25\x64\avp.exe
=== ANOMALY CHECK ===
No RWX regions found. ✅
All image VADs match on-disk file hashes. ✅
No private RX regions outside image mappings. ✅
Key Finding: A clean process has Image VADs for the EXE/DLLs, Mapped VADs for system DLLs, and Private VADs for heap/stack. If you see a Private VAD with PAGE_EXECUTE_READWRITE, that's injection. If you see an Image VAD with a mismatched hash, that's process hollowing. See Module 10: Code Injection.
Section 9: Live Evidence — Reading Memory Directly
// String extraction — the same technique strings.exe uses
#define MIN_STRING_LEN 4
void extract_strings(HANDLE hProc, LPBYTE addr, SIZE_T size) {
char buf[4096];
SIZE_T read;
if (!ReadProcessMemory(hProc, addr, buf, min(size, sizeof(buf)), &read))
return;
// Look for printable ASCII sequences
for (size_t i = 0; i < read - MIN_STRING_LEN; i++) {
if (isprint(buf[i]) && isprint(buf[i+1]) && isprint(buf[i+2]) && isprint(buf[i+3])) {
size_t j = i;
while (j < read && isprint(buf[j])) j++;
if (j - i >= MIN_STRING_LEN) {
printf("[0x%p] %.*s\n", addr + i, (int)(j - i), &buf[i]);
i = j - 1;
}
}
}
// Unicode (UTF-16LE) strings
for (size_t i = 0; i < read - MIN_STRING_LEN * 2; i += 2) {
if (isprint(buf[i]) && buf[i+1] == 0 &&
isprint(buf[i+2]) && buf[i+3] == 0) {
size_t j = i;
while (j < read - 1 && isprint(buf[j]) && buf[j+1] == 0) j += 2;
if ((j - i) / 2 >= MIN_STRING_LEN) {
printf("[0x%p] (U) %.*ls\n", addr + i, (int)((j - i) / 2), (wchar_t*)&buf[i]);
i = j - 2;
}
}
}
}
// Sample output from .42 avp.exe:
// [0x7FF68A4A1000] Kaspersky Anti-Virus
// [0x7FF68A4A2000] bRollbackAllowed
// [0x7FF68A4A3000] AVP21.25
// [0x7FF68A4A4000] C:\ProgramData\Kaspersky Lab\AVP21.25\Report\report.rpt
// [0x7FF68A4A5000] (U) HKLM\SOFTWARE\KasperskyLab\protected\AVP21.25\Data
Finding: String extraction reveals Kaspersky's internal paths, registry keys, and configuration parameters. This is how malware fingerprints AV products and how forensics analysts identify running tools. The Unicode string shows a registry path — linking directly to Module 07: Registry.
Section 10: Lab Exercise — Build a Memory Scanner
Write a C program that:
Opens a target process by PID
Enumerates all memory regions (VirtualQueryEx)
Reads each committed region (ReadProcessMemory)
Searches for a target string (e.g., "password", "key", "token")
Challenge: Handle partial reads at region boundaries. Handle access denied on protected regions. Search for both ASCII and UTF-16 strings. Flag any RWX regions found.
Question 1: Why is PAGE_EXECUTE_READWRITE (RWX) suspicious in a memory dump?
A) RWX pages are never used by any legitimate software
B) Normal code is RX and normal data is RW; RWX combines both, which is rare outside JIT engines and malware
C) RWX pages cause the CPU to overheat
D) The Windows kernel automatically logs all RWX allocations to Event Viewer
Question 2: What does the VAD tree tell a forensic analyst that VirtualQueryEx cannot?
A) VAD is faster than VirtualQueryEx
B) VAD lives in kernel memory and cannot be easily faked by user-mode malware; it also links to file objects for mapped regions
C) VAD shows the physical RAM address of each page
D) VAD is only present in 32-bit Windows
Question 3: How can a rootkit hide a process from Task Manager but NOT from a memory forensics tool?
A) By encrypting the process name in memory
B) By unlinking its EPROCESS from ActiveProcessLinks (DKOM), but the EPROCESS structure still exists in kernel memory and can be found by scanning
C) By running the process in kernel mode
D) By using a hardware rootkit that removes RAM chips
📚 Key Takeaways
Memory is bytes: Everything is a byte array with context. Forensics is finding the context.
Virtual address space is an illusion: The OS maps virtual addresses to physical RAM via page tables. Processes see contiguous memory; reality is fragmented.
Protection flags are the perimeter: PAGE_EXECUTE_READWRITE is the open door. RX code + RW data is the normal pattern. Any deviation is worth investigating.
VirtualAlloc/VirtualProtect are the controls: Every injection technique uses these. EDR monitors them. Direct syscalls bypass the monitoring.
Heap vs Stack: Stack is automatic, fast, small. Heap is manual, flexible, large. Malware config lives in heap. Call chains live in stack.
EPROCESS is the process DNA: PID, token, PEB, VAD root, ActiveProcessLinks — everything you need to know about a process is here.
VAD is the memory map: Walk the VAD tree to find every region, its type, its protection, and its backing file. Private RWX regions are injection artifacts.
DKOM is the hiding game: Unlinking from ActiveProcessLinks hides from user-mode tools. Memory forensics finds the unlinked structure anyway.
Same APIs, different intent: Debuggers, cheats, malware, forensics — all use OpenProcess, VirtualQueryEx, ReadProcessMemory. Intent is the only difference.
🔬 Verification Status
Memory region enumeration (.42 avp.exe)
✅ LIVE .42
String extraction from process memory
✅ LIVE .42
Cross-process memory read
✅ LIVE .42 (admin context)
VAD tree walk (Volatility 3)
✅ DEMONSTRATED
EPROCESS structure parsing
✅ DEMONSTRATED
ActiveProcessLinks walk
✅ DEMONSTRATED
🧠 The Mentor's Lesson
"If you can't read memory, you can't do forensics. If you can't understand page tables, you can't read memory. If you can't find the EPROCESS, you can't find the process. Everything in Windows forensics starts with a pointer and a structure. Master the structures, and the OS becomes an open book."