Documentation & Tools →
Sign In
In this section

Physical vs Virtual Memory in Memory Forensics

Module 0

The one concept everything else rests on

Every plugin you run, every structure you walk, every byte you extract depends on a distinction most incident responders have never formally studied: physical memory versus virtual memory, and the page tables that translate between them. Treat memory as one flat address space and you produce analyses that are subtly wrong, misread plugin output, and stumble on the question every memory analyst eventually faces in cross-examination: "what address did you examine?"

There are two qualities of answer to that question. One is "what the plugin reported." The other is "virtual address 0x7ff8a2100000 in process 4872, which my translation resolves to physical offset 0x2f8a3000 in the image, and here are the bytes at that offset." The second answer is the one that holds. This sub teaches paging at the level a practitioner needs, enough that every later reference to virtual address, physical offset, page table, kernel space, and ASLR lands rather than gets skated over. The deep structure walk comes in MF2; this is the frame it requires.

Physical memory is what the image captures

Physical memory is the actual RAM in the machine: a single contiguous range of addresses from zero up to the installed size, each address naming one byte of one chip. When an acquisition tool captures memory, this is what it captures, a linear copy of physical RAM, byte zero of the image being physical address zero. A 4 GB system produces a roughly 4 GB image, and offset 0x2f8a3000 in that file is physical address 0x2f8a3000 in the machine.

That is the only address space that exists in hardware. One practical consequence falls out immediately: the image is not laid out the way a process sees its memory. A process believes its code, heap, and stack sit in tidy contiguous ranges, but in the physical image those ranges are scattered across the file wherever the operating system happened to place each page, interleaved with pages from every other process and from the kernel. You never read a process's memory by reading a contiguous span of the image; you read it by following the translation that tells you where each of its pages physically landed. Everything else, every "address" a process or the kernel talks about, is a fiction the CPU and operating system maintain on top of it. Holding that straight is the foundation: the image is physical, flat, and real; the addresses in your plugin output are virtual, per-context, and translated.

There is one more reason the physical view matters on its own terms. Some evidence is best understood physically rather than per-process: a pool-tag scan that hunts kernel object signatures, or a raw carve for a known byte pattern, works across the whole physical image without reference to any single process's address space. Those techniques, which later modules use to find objects an attacker unlinked from every per-process structure, only make sense once you hold the physical layer clearly in mind as a thing that exists independently of the virtual views built on top of it.

One physical RAM, many virtual views PROCESS A (virtual) 0x7ff... code 0x2a0... heap kernel (shared) PHYSICAL RAM (the image) 0x2f8a3000 kernel pages PROCESS B (virtual) 0x7ff... code 0x180... data kernel (shared) The image holds the physical centre. Page tables reconstruct each process's scattered virtual view from it.

Two processes can both name an address `0x7ff...` for their code, and those names point at completely different physical pages. The same physical kernel pages are mapped into both. This is why an address alone is meaningless without the process context that gives it a translation.

Virtual memory is what processes see

Every process runs inside its own private virtual address space: a full range of addresses that looks, from inside the process, like a contiguous expanse of memory all to itself. Process 4872 believes it owns address 0x7ff8a2100000; so does every other process, and none of them collide, because each virtual address space is translated independently to different physical pages. The process never sees physical memory and never needs to. It asks for a virtual address; the hardware delivers the byte from wherever in physical RAM that virtual address currently maps.

This is the source of the single most important rule in reading memory output: an address is meaningless without its context. "I examined 0x7ff8a2100000" is an incomplete statement, because that virtual address resolves to different physical bytes in every process. The complete statement names the process: "0x7ff8a2100000 in PID 4872." Volatility output always carries that context, which is why plugins are scoped with --pid, and why a finding copied out of its process context is a finding you can no longer locate.

Page tables translate virtual to physical

The translation from a virtual address to a physical one is done by the page table, a per-process tree of tables the CPU walks on every memory access. The root of that tree is a single physical address called the directory table base, the DTB (on x86-64, the value the CPU holds in the CR3 register while the process runs). Each process has its own DTB, and that one value is what makes its virtual address space distinct: point the hardware at process A's DTB and 0x7ff... resolves one way, point it at process B's and the same virtual address resolves another.

Translating a virtual address to a physical offset virtual address 0x7ff8a2100000 DTB (from EPROCESS) per-process root walk page tables level by level physical offset 0x2f8a3000 Volatility does this same walk in software to read any process's memory from the image.

The directory table base makes each process's address space distinct: point the walk at process A's DTB and an address resolves one way, at process B's and the same address resolves elsewhere. A wrong DTB, from a profile mismatch, translates against the wrong tree and reads plausible nonsense.

This matters to you directly, because Volatility does exactly what the CPU does. To read a process's memory from the image, it finds that process's DTB in the EPROCESS structure, then walks the page tables itself to translate each virtual address the process used into the physical offset in the image where those bytes actually sit. When you saw windows.info report a DTB in the last sub, that was the kernel's own translation root. Every per-process plugin uses the per-process DTB the same way. A wrong DTB, from a profile mismatch, is precisely why bad symbols produce output that looks plausible and reads the wrong bytes: the translation runs, it just translates against the wrong tree.

Volatility 3: a virtual address resolved to a physical offset
$ vol -f NE-FIN-014-mem.raw windows.pslist --pid 4872
PID   ImageFileName    Offset(V)            Offset(P)
4872  powershell.exe   0xe40a8b3c1080      0x2f8a3080
# Offset(V) is the EPROCESS virtual address in kernel space.
# Offset(P) is where those bytes physically sit in the image.
# The page-table walk from V to P is what every plugin does for you.

When you can read both columns and explain the relationship, "the virtual offset is the kernel's address for the structure; the physical offset is where I can find it in the raw image", you can answer the cross-examination question with the second-quality answer instead of the first.

The kernel/user split tells you privilege at a glance

A 64-bit address space is divided in two. The lower half belongs to the running user process; the upper half is kernel space, shared and identical across every process, and inaccessible to user code. On 64-bit Windows the practical tell is the high bits: a kernel-space address starts in the 0xfffff... range, while user-space addresses sit far lower, like 0x7ff... and below. You saw this already, the kernel base in windows.info was 0xfffff80614400000, unmistakably kernel space, and the EPROCESS offset above sits in kernel space too because the structure is kernel-maintained.

This is a fast, free check you will use constantly. A pointer that should be in kernel space but sits in the user range, or a "kernel" structure reporting a low address, is a sign that something is wrong, a bad translation, a corrupted structure, or an attacker's manipulation. Knowing the split lets you sanity-check any address the instant you see it, without running another command. It also explains a structural fact you will lean on constantly: because the kernel half is shared and identical across every process, kernel structures like EPROCESS have one address that is valid in any process context, while user-space addresses are only meaningful inside the specific process that owns them. That is why process-listing plugins report the EPROCESS at a kernel-space offset, it lives in the shared upper half, reachable the same way from anywhere, whereas a string of interest inside a process's heap is a low user-space address that means nothing without naming the process it belongs to.

Where analysts get it wrong
Comparing a virtual address from one process against a virtual address from another and concluding they "point to the same place" because the numbers match. They do not. 0x7ff8a2100000 in PID 4872 and the identical value in PID 1816 are two different physical pages, because each process has its own page tables. Addresses are only comparable within the same address space, or after both have been translated to physical offsets. The trap shows up most often when correlating an injected region across processes: the right comparison is the physical bytes or the structure content, never the virtual address, and an analyst who matches on the virtual number alone will claim a relationship that does not exist.

ASLR randomises the address, not the relationships

Address Space Layout Randomisation places a process's modules, stack, and heap at different base addresses on every boot, so the same binary loads at 0x7ff8a2100000 today and somewhere else tomorrow. This defeats exploits that hardcode addresses, and it has one consequence for you: you cannot memorise where things live. The base address of a DLL is not a constant, and a finding that says "the payload was at 0x7ff8a2100000" is true only for that one capture of that one process.

What ASLR does not change is the relationships between structures. A process still points to its threads, a thread still points back to its process, the VAD tree still describes the same regions in the same order, regardless of where ASLR placed the base. Memory forensics works by walking those pointers: from the kernel's process list to an EPROCESS, from there to its threads, its handle table, its VAD tree, and onward, each step following an address one structure stores about another. Memory forensics works precisely because it follows those relationships, walking from one structure to the next by the pointers they hold, rather than by absolute address. ASLR randomises the starting point; the map of how structures connect is invariant, and that map is what every analysis traverses. This is worth stating as a working principle, because it is easy to assume ASLR makes memory analysis harder when it does the opposite for you. The attacker uses ASLR to make their payload's address unpredictable to an exploit; it does nothing to hide the payload from an analyst who arrives after the fact with the whole image, because you are not guessing addresses, you are reading the structures that record where everything actually ended up. The VAD tree still lists the injected region wherever ASLR put it, the EPROCESS still links to it, and your translation resolves it to a physical offset regardless. ASLR is a runtime defense against prediction; forensics is retrospective reading, and the two simply do not collide.

This is the conceptual floor for the rest of the course. When MF2 walks the VAD tree of an injected process, you will be translating virtual VAD addresses to physical offsets, sanity-checking them against the kernel/user split, and following structure pointers that ASLR moved but did not rewire. The next sub puts a tool in your hands to do it: Volatility 3 and MemProcFS, what each one is, and which to reach for on a given question.

Next section
The two mainstream open-source tools of 2026: how Volatility 3's symbol-driven model differs from the Volatility 2 most tutorials still teach, what MemProcFS's filesystem view is faster at, and the decision logic for choosing between them.