In this module
MF0.4 Physical Memory, Virtual Memory, and Why It Matters for Forensics
From MF0.1-0.3 you know what memory contains, how it's categorised, and the six-phase workflow. What you've been taking for granted is how "an address" works — when a plugin reports 0x7ff8a2100000 in a process, what is that, and where does it live in the image? This sub gives you the model underneath every plugin output you've seen so far.
Every plugin you run, every structure you walk, every piece of evidence you extract depends on one concept most IR practitioners have never formally studied: the distinction between physical memory and virtual memory, and the page tables that translate between them.
An investigator who treats memory as one flat address space produces incorrect analyses. Mistakes reading plugin output. Mistakes under cross-examination. "What address did you examine?" is a question every memory forensics practitioner will face, and "virtual 0x7ff8a2100000 in PID 4872, translating to physical 0x2f8a3000 in the image" is a different quality of answer from "what the plugin reported."
This sub introduces paging at the level a practitioner needs — enough that every subsequent reference to "virtual address," "physical offset," "page tables," "kernel space," and "ASLR" lands rather than gets glossed over. The deeper structure walk belongs in MF2.
Deliverable: Working understanding of physical RAM vs per-process virtual address spaces, the role of the page table in translation, the kernel/user address split that identifies privileged memory at a glance, why ASLR randomises starting addresses without affecting structure relationships, and the confidence to read plugin output and raw memory dumps using standard address-space terminology.
Figure 0.4.1 — Physical RAM is one contiguous address space containing all process and kernel memory. Each process has its own virtual view, stitched together from page-table mappings. Kernel mappings are identical across processes (same virtual address, same physical location). User-space mappings differ (same virtual address in different processes resolves to different physical locations). The memory image captures physical RAM; every analysis translates back through page tables.
Physical memory is what the image captures
Physical memory is the raw contiguous address space your acquisition tool dumps. Every forensic analysis starts from this flat image, regardless of what the plugin output looks like.
When you acquire memory — WinPmem, DumpIt, LiME, hypervisor .vmem extraction, whichever tool — what you end up with is a file that represents physical RAM as one contiguous address space. Byte 0 of the file corresponds to physical address 0x0. The image for a system with 4 GB of RAM is roughly 4 GB long; 32 GB gives 32 GB. Flat, linear, entirely about physical hardware addresses.
Physical memory is undiscriminating. Every page in RAM — kernel code, driver pages, process stacks, the clipboard, injected shellcode — sits somewhere in the image at a specific physical offset. The image doesn't know what's a process and what isn't; it records what the RAM chips held at acquisition time. Physical memory isn't contiguous within any process either — a process's pages are scattered across RAM in 4 KB allocations wherever the OS had space. Virtual space is contiguous by design; physical space is whatever the allocator assigned. Finding structure in this undifferentiated space is the forensic tool's job.
Virtual memory is what processes see
Each process has its own virtual address space — its own private view of memory where addresses start at zero and run up to an architecture-defined maximum. Every process sees the same kernel addresses mapped to the same physical pages; user-space addresses differ per process.
Modern operating systems give every process its own virtual address space. From the process's perspective, it owns an entire address range — 0x0 at the bottom to something like 0xFFFFFFFFFFFFFFFF at the top on 64-bit systems. The process's code runs as if it has all of this, reading and writing virtual addresses without knowing what's happening in physical RAM. The illusion is maintained by the CPU's MMU and the kernel's page tables — every memory access goes through a translation from virtual address to physical address before RAM is touched.
The virtual address space is split into two halves by the OS. The upper half (starting with 0xFFFF... on x86_64) is kernel space — shared across all processes, mapped identically everywhere, accessible only in kernel mode. The lower half (starting with 0x00007F... and lower) is user space — private to the process, different physical pages for different processes, accessible from user mode. The canonical split on x86_64 is kernel above 0xFFFF800000000000, user below 0x00007FFFFFFFFFFF. Addresses in between are non-canonical — accessing one faults. You can classify an address's territory in under a second by looking at its first few hex digits; practitioners do this reflexively.
# The distinction is visible in every plugin output that includes addresses.
# From a windows.malfind report (PID 4872, a PowerShell process on NE-FIN-014):
#
# VAD Start Protection Interpretation
# 0x00007ff8a2100000 PAGE_EXECUTE_READWRITE User-space (0x00007F... prefix)
# 0xfffff80614c13710 - Kernel-space (0xFFFF8... prefix)
#
# The first is a VAD in the process's user-mode memory — phase 4 investigation
# territory, probably an injected region. The second is a kernel structure
# pointer (from windows.info output earlier) — leave alone in most analyses
# unless you're doing rootkit work.Page tables translate virtual to physical
Page tables are the kernel-maintained data structure that maps virtual addresses to physical offsets. Every forensic tool you use walks these tables. Understanding the translation is why two plugins sometimes report different addresses for the same thing.
The page table is the mechanism. Every process has its own page tables — a hierarchical tree of structures kept in kernel memory that the MMU walks every time the process touches a virtual address. On x86_64 the tree has four levels (PML4 → PDPT → Page Directory → Page Table → final physical page), and each level narrows the search by using a slice of the virtual address as an index. The CPU walks the tree in hardware billions of times per second; your forensic tool walks it in software when you ask it to translate. MF2 covers the tree structure in depth; the practitioner-level point for this sub is what the walk means for plugin output.
Translation is stateful per process, and every plugin that reports a virtual address has done a translation to arrive at it. When windows.malfind tells you a suspicious region starts at 0x7ff8a2100000 in PID 4872, Volatility 3 walked PID 4872's page tables to find that region in physical RAM. If you ran the same plugin on PID 5120 and got 0x7ff8a2100000, it's not the same physical memory — it's a different virtual address in a different process that the page tables happen to map to a different physical offset. Virtual addresses are meaningful only in the context of the owning process.
Figure 0.4.2 — Process SVG. One virtual-to-physical translation walked end to end. The virtual address is sliced into four 9-bit index fields plus a 12-bit offset; each index selects an entry in the next table down; the final entry gives the physical page frame number, which combines with the offset to produce the physical address in the image. Every forensic plugin that accepts or emits a virtual address performs this walk internally.
ASLR — address space layout randomisation
ASLR randomises where things land in virtual memory. Every time a process starts, its libraries, heap, and stack sit at different addresses. That matters for analysis because plugin output across two images of the same process will not show the same addresses, but the structure relationships stay identical.
Modern operating systems randomise memory layout on every process start. A process launched at 10:00 has its ntdll.dll at virtual address 0x7ff8a2100000. Launch it again at 10:01 and ntdll lands at 0x7ff8b4600000. Same binary, same process, same system — different address. The randomisation is the security feature; for memory forensics it's a fact that changes how you interpret addresses across investigations.
What ASLR randomises is where in the virtual address space each loaded module, heap, and stack lands. What it doesn't change is the internal structure of each — the fields within a loaded DLL, the layout of a heap allocation, the thread stack format. If you capture memory from the same process twice, the process's ntdll might be at two different addresses, but ntdll's contents are identical. An injected payload at 0x7ff8a2100000 on one capture and 0x7ff8b4600000 on another is the same evidence found in different locations. ASLR has a kernel variant, KASLR, that randomises the kernel's load address itself; Volatility 3 derandomises by pattern-matching known kernel signatures (MF2 covers the mechanism).
One operational consequence: if you're comparing two captures of the same system, you can't just diff addresses. Baseline has ntdll at one address; post-attack has it at another because of reboots or re-randomisation. What you compare is which modules are loaded, which VADs exist, and what content lives at which logical location — not raw virtual addresses. Addresses are the name of the evidence, not the evidence itself.
Given: virtual address 0x00007ff8a2100000 in PID 4872 of a memory image from NE-FIN-014. The page tables for PID 4872 live starting at CR3 = 0x1aa000. Walk the translation step by step. Result to check against: physical offset 0x2f8a3000.
You've now walked a translation the same way a forensic tool walks thousands per second. "The plugin couldn't resolve this address" now has concrete meaning — you know which step in the walk produced the failure.
The situation. You're writing the phase 4 section of an investigation report. The finding is an RWX region in PID 4872 at virtual address 0x7ff8a2100000. You need to cite the address. The senior investigator reviewing your draft asks whether you should cite the virtual address, the physical offset in the image, or both.
The choice. Use the virtual address only (what the plugin reported), the physical offset only (what the image actually contains), or cite both with an explanation of the translation.
The correct call. Cite both with translation context. The virtual address is what the attacker's payload sees and what a reproduction attempt would target; the physical offset is what the image contains and what a second investigator verifying your work would read directly. A report citing only the virtual address can be challenged on "how did you know that address corresponded to real data in the image?" A report citing only the physical offset makes the finding hard for a reader to connect to the attacker's activity. Citing both — "virtual 0x7ff8a2100000 in PID 4872, translating to physical 0x2f8a3000 in the image" — ties the finding to both frames of reference.
The operational lesson. Address context is part of the evidence. A practitioner who treats the two address systems as interchangeable gets caught out when an opposing expert asks the right question. Cite both forms for any address that appears in a finding.
The myth. A memory image is a snapshot of RAM. When I look at the image, I'm looking at what was running on the system. The addresses I read from the image are the addresses the attacker's code was using.
The reality. The image contains physical addresses — the raw offsets where the RAM chips stored the data. The attacker's code saw virtual addresses — its own private view, mediated by page tables. These aren't the same, and confusing them produces reports that can't be defended.
An attacker's PowerShell injecting a DLL at virtual address 0x7ff8a2100000 in its own process isn't putting the DLL at physical offset 0x7ff8a2100000 in RAM. It's asking the kernel to allocate physical pages somewhere and map them into its virtual address space at 0x7ff8a2100000. The physical pages could be anywhere. The virtual address is a label; the physical offset is the location.
Reports that conflate the two fail under scrutiny. "The payload was located at 0x7ff8a2100000" means nothing without process context, and is false across systems — ASLR and per-process page tables guarantee the payload will be somewhere else on any other system. The correct claim language is virtual-address-in-process plus physical-offset-in-image: "virtual 0x7ff8a2100000 in PID 4872, translating to physical 0x2f8a3000 in the image." That claim is defensible. "Address 0x7ff8a2100000" alone is not.
VAD Start VAD End Protection Filename
0x00007ff6f5a10000 0x00007ff6f5a73000 PAGE_EXECUTE_READ C:\Windows\System32\powershell.exe
0x00007ff8a2100000 0x00007ff8a2118000 PAGE_EXECUTE_READWRITE (private)
0x000001a8b2400000 0x000001a8b2490000 PAGE_READWRITE (private heap)
0xfffff80614c13710 0xfffff80614c14000 PAGE_EXECUTE_READ ntoskrnl.exe
0x000001a8b1cf0000 0x000001a8b1cf2000 PAGE_EXECUTE_READWRITE (private)Try it — Classify VAD output by address space and permission pattern
Setup. Below is synthetic but realistic windows.vadinfo output from PID 4872 of a PowerShell process on NE-FIN-014. The output shows five VAD entries. You don't need any tools; read the output inline and make the calls on paper.
Task. For each of the five entries, determine (a) whether the address is kernel space or user space, (b) whether the region is file-backed or anonymous, (c) whether the permission pattern is expected or suspicious for a user-space process. Write one sentence per entry stating your classification and the specific field(s) that support it.
Expected result. Entry 1 — user-space, file-backed (powershell.exe), PAGE_EXECUTE_READ — expected for a loaded module. Entry 2 — user-space, anonymous, PAGE_EXECUTE_READWRITE — suspicious (RWX with no file backing is the classical injected-code pattern). Entry 3 — user-space, anonymous, PAGE_READWRITE — expected for a heap allocation. Entry 4 — kernel-space (address starts 0xfffff8...), file-backed (ntoskrnl.exe), PAGE_EXECUTE_READ — expected for the kernel itself. Entry 5 — user-space, anonymous, PAGE_EXECUTE_READWRITE — suspicious, same pattern as entry 2 but smaller (possibly shellcode rather than a loaded payload).
If your result doesn't match. If you classified entry 4 as user-space, check the first hex digits — 0xfffff8... is the kernel half of x86_64 address space. If you classified entries 2 or 5 as expected, re-read the "Physical memory is what the image captures" section on the RWX-no-file-backing pattern. If you got all five right, you're reading plugin output at the level the rest of the course assumes.
You should be able to do the following without referring back to this sub. If you can't, the sections to re-read are noted.
You've set up the lab and captured your first clean baselines.
MF0 built the three-VM lab and established the memory forensics landscape. MF1 taught acquisition with WinPmem and LiME, integrity verification, and chain of custody. From here, you execute attacks and investigate what they leave behind.
- 8 attack modules (MF2–MF9) — process injection, credential theft, fileless malware, persistence, kernel drivers, Linux rootkits, timeline construction, and a multi-stage capstone
- You run every attack yourself — from Kali against your target VMs, then capture memory and investigate your own attack's artifacts with Volatility 3
- MF9 Capstone — multi-stage chain (initial access → privilege escalation → credential theft → persistence → data staging), three checkpoint captures, complete investigation report
- The lab pack — PoC kernel driver and LKM rootkit source code, setup scripts, 21 exercises, 7 verification scripts, investigation report templates
- Cross-platform coverage — Windows and Linux memory analysis in one course, with the timeline module integrating evidence from both
Cancel anytime