Skip to content
Lesson 13.1Physical Memory, Machines, and Hardware

DMA and Physical Memory

DMA gives devices a path to memory, while page tables and IOMMUs determine which physical bytes correspond to an address.

Lesson 13.1
Read or listenPrint chapter or book
About 40 minutes

Keyboard shortcuts

?
Open this guide
Alt + B
Show or hide the lesson list
Alt + O
Show or fold the lesson outline
Alt + N
Open or close notes
Alt + H
Toggle semantic colours
Alt + D
Toggle page brightness
Alt + C
Toggle code brightness
← →
Move through a focused code trace
Home End
First or final step in a focused trace
Escape
Close this guide or an open panel

Reader shortcuts pause while you type in a text field.

Cheatsheet for Lesson 13.1Key ideas, reference and questions

A one-page summary of “DMA and Physical Memory” you can keep beside your notes. Plain Markdown.

Lesson 13.1 — DMA and Physical Memory: cheatsheet

  • Remember: A process virtual address, a physical RAM address, and a device mapping belong to different address spaces.
  • Also useful: A legal address range can contain unmapped holes. The range tells you where an address could fit; the current page tables tell you whether an access can work.

Key terms

  • direct memory access: DMA means direct memory access.
  • DMA: Here DMA refers to device transfers.
  • noncanonical: An address that breaks this rule is noncanonical.
  • IOMMU: An IOMMU is an input/output memory management unit.

Formulas and patterns

  • translate(page-table root, virtual address, access type); -> physical address and permissions, or a fault

Review

  • Q: Why is a process virtual pointer not automatically an offset in a physical RAM capture? A: Page tables translate each address space's virtual pages to physical locations
  • Q: What does DMA stand for, and what does it do? A: Direct memory access: a device moves data without the CPU copying each byte
Download cheatsheet (.md)

The kernel boundary also determines how devices reach memory. A process sees virtual addresses; a device moves bytes through a physical-memory path with its own mappings and access permissions. Understanding both maps makes an offline capture interpretable without assuming that a game address is a RAM offset.

DMA means direct memory access. A capable device can transfer data without asking the CPU to copy each byte. Storage, networking, graphics, and other high-throughput devices use DMA because involving the CPU in every tiny move would waste time.

Here DMA refers to device transfers. It is different from dynamically allocating memory in a program, where the program asks its allocator for space and receives an address. The shared word memory does not make those the same operation.

DMA is not automatically a cheat or an attack. It is a hardware feature. Security problems appear when a device can reach memory it should not reach, or when software trusts a device too much.

This chapter uses ordinary offline capture files from an owned machine, virtual machine, or synthetic test. It does not configure DMA hardware, install a kernel driver, disable an IOMMU, access a live game, or bypass anti-cheat.

Physical and virtual addresses are different maps

Section titled “Physical and virtual addresses are different maps”

A CPU process usually uses virtual addresses. The memory-management unit translates them through page tables to physical addresses in RAM.

Treat translation as a function with an address-space input:

translate(page-table root, virtual address, access type)
-> physical address and permissions, or a fault

That describes the CPU’s complete decision. The small offline walker in Lesson 13.2 answers a narrower question: where do the recorded, present entries lead? It checks presence and file bounds but does not emulate every read, write, execute, and user/supervisor permission check the CPU would make. A physical result alone is therefore not proof that a live process could read or write the address.

Two processes may both use virtual address 0x1000 because they supply different page-table roots. The access type matters too: a mapping may permit a read but reject a write or instruction fetch. A virtual address by itself therefore does not name a location: the address space and the mapping’s permissions are part of its meaning.

The same virtual address in two processesBoth processes use the number 0x1000. Which memory that means depends on the page-table root in use.
  1. 1 Both processes use 0x1000

    Process A

    Virtual address
    0x1000

    Process B

    Virtual address
    0x1000

    The numbers are identical.

  2. 2 Each has its own page-table root

    Process A

    Virtual address
    0x1000
    Page-table root
    root A

    Process B

    Virtual address
    0x1000
    Page-table root
    root B

    The CPU translates each address with the root of the running process.

  3. 3 The same number reaches different physical memory

    Process A

    Virtual address
    0x1000
    Page-table root
    root A
    Physical memory
    physical page A

    Process B

    Virtual address
    0x1000
    Page-table root
    root B
    Physical memory
    physical page B

    Physical page A and physical page B are different places, labelled symbolically.

Both processes use the number 0x1000. Which memory that means depends on the page-table root in use.

It is like asking for apartment 1000 without naming the building. The apartment number is useful only after the building is chosen; likewise, 0x1000 is useful only with the correct page-table root. The analogy is limited: page tables can remap or remove a page while a program runs, and they separately record whether a read, write, or instruction fetch is allowed. The diagram below shows the same virtual number reaching two different physical places.

An offline physical capture contains bytes arranged by physical address. A debugger’s pointer such as 0x00007FF6_12341000 is virtual. To connect them, the reader needs the correct page-table root for that address space.

Process B, CR3 = root B
Process A, CR3 = root A
root A's page tables
root B's page tables
virtual 0x1000
virtual 0x1000
physical address A
physical address B

The idea transfers to a guest virtual machine in Lesson 13.3: a guest’s virtual address first needs its guest page tables, and the resulting guest physical address needs a second translation through the hypervisor’s map. Neither stage can be skipped just because both addresses are written as numbers.

The address-space root changes the answer. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

select the address-space root
split virtual page and byte offset
look up the page in that root’s map
reject if the entry is absent
physical = frame * 4096 + offset
report the mapping and its owner

Result: root A maps the virtual address to 0x701A. Choosing another valid root can give a different valid answer.

Final values: root = A; page = 1; offset = 26; virtual_address = 0x101A; virtual_page = 1; byte_offset = 26; physical_frame = 7; physical_address = 0x701A.

Read every step of the lesson’s example
  1. Step 1, line 1. Start with the selected inputs. Follow the highlighted operation and the changing state below.

    root = A; page = 1; offset = 26.

  2. Step 2, line 1. These four-entry maps illustrate context; they are not full x86 page tables.

    root = A; page = 1; offset = 26; virtual_address = 0x101A.

  3. Step 3, line 2. With 4 KiB pages, the offset must remain within 0..4095.

    root = A; page = 1; offset = 26; virtual_address = 0x101A; virtual_page = 1; byte_offset = 26.

  4. Step 4, line 3. Use the selected root rather than a global mapping.

    root = A; page = 1; offset = 26; virtual_address = 0x101A; virtual_page = 1; byte_offset = 26; physical_frame = 7.

  5. Step 5, line 5. Combine the mapped frame and unchanged offset.

    root = A; page = 1; offset = 26; virtual_address = 0x101A; virtual_page = 1; byte_offset = 26; physical_frame = 7; physical_address = 0x701A.

  6. Step 6, line 6. Result: root A maps the virtual address to 0x701A. Choosing another valid root can give a different valid answer.

    root = A; page = 1; offset = 26; virtual_address = 0x101A; virtual_page = 1; byte_offset = 26; physical_frame = 7; physical_address = 0x701A.

Place the virtual address inside a process map

Section titled “Place the virtual address inside a process map”

Before translating one page, identify the architecture and address-space configuration. Microsoft’s default 32-bit Windows map gives a 32-bit process on 32-bit Windows a lower 2 GiB user range (0x00000000–0x7FFFFFFF) and an upper 2 GiB system range (0x80000000–0xFFFFFFFF). Those are address ranges, not amounts of installed or resident RAM. Modules, heaps, stacks, mapped files, and free holes occupy parts of the user range rather than filling it uniformly.

Where a 32-bit Windows program’s addresses goHeights are shares of the 4 GiB (232 byte) address space, lowest address at the top. Point at a part, or tab to it, to read what it is.

The ordinary 32-bit Windows layout.

0x00000000
0x00010000

Placed inside it, positions illustrative:

0x7FFF0000
0x80000000
0xFFFFFFFF
Reserved low 64 KiB0x00000000 to 0x0000FFFF
The first 64 KiB are never usable. A null pointer, or a small offset from one, lands here and causes an access violation. Windows reports 0x00010000 as the lowest address a program may use. Size: 0x0000FFFF − 0x00000000 + 1 = 65,536 bytes (64 KiB).
User space0x00010000 to 0x7FFEFFFF
Everything the program itself can map: its image, DLLs, heaps, stacks and mapped files, with free holes between them. This is an address range, not an amount of RAM in use. Size: 0x7FFEFFFF − 0x00010000 + 1 = 2,147,352,576 bytes (2 GiB − 128 KiB).
Reserved top 64 KiB0x7FFF0000 to 0x7FFFFFFF
The last 64 KiB below the boundary stay inaccessible too. Windows reports the address just before it as the highest address the program may use. Size: 0x7FFFFFFF − 0x7FFF0000 + 1 = 65,536 bytes (64 KiB).
System space0x80000000 to 0xFFFFFFFF
Reserved for the operating system: the kernel, drivers and kernel data. User-mode code cannot read or write here, and a try is an access violation. Size: 0xFFFFFFFF − 0x80000000 + 1 = 2,147,483,648 bytes (2 GiB).
Program imageposition illustrative
The .exe is mapped here. 0x00400000 is the linker’s default preferred base for a 32-bit executable; an image built with /DYNAMICBASE can be placed elsewhere by ASLR.
Heapsposition illustrative
Memory from the process heap and from VirtualAlloc comes from whatever free addresses exist, so heaps are scattered and their addresses change from run to run.
Thread stacksposition illustrative
Every thread has its own stack, commonly 1 MiB reserved by default. It is placed when the thread starts and grows toward lower addresses.
DLLsposition illustrative
Each loaded DLL, such as kernel32.dll, is mapped into user space. With ASLR, Windows chooses the base, and it can differ between boots.
TEB and PEBposition illustrative
The Thread Environment Block and Process Environment Block are small system structures mapped into user space. On 32-bit x86 the FS segment register points at the current thread’s TEB.

The two big boundaries are real and drawn to scale; the 64 KiB reserved ends are drawn wider than their true share so they stay visible. Where the image, heaps, stacks and DLLs actually sit changes with ASLR and allocation history, so they are not placed. A native 64-bit process has a different map.

ASLR and allocation history can move those user regions; Microsoft describes the image-randomization behavior under DYNAMICBASE. The limits table also gives the exceptions: 4GT plus a large-address-aware image can extend a 32-bit process to 3 GiB on 32-bit Windows; on 64-bit Windows, a large-address-aware 32-bit process can have 4 GiB. A native 64-bit process has a different map. Do not apply the diagram’s split to this lesson’s x86-64 capture merely because both use Windows.

Segmentation and paging are different stages

Section titled “Segmentation and paging are different stages”

A simplified upward-growing segment has a base and an inclusive limit. For a one-byte access, check the offset against the limit, then add it to the base to form a linear address. Paging divides that linear address space into fixed-size pages and maps each page to a physical frame, with access permissions. Adjacent virtual pages need not reach adjacent physical frames.

Two ways to turn a program’s address into a real oneTwo independent examples, not consecutive steps of one address. Each one starts from a number the program uses and ends in a number the hardware uses, or in a fault.

Paging: equal 4 KiB pages, looked up by number

page number · 3 bits4
offset · 12 bits0x234

0x4234 in binary, 15 bits: 100 001000110100

Page table (8 pages). The present bit says whether the page has a frame.
PagePresentFrameFrame starts at
010x0A20x000A2000
110x1F00x001F0000
20nonenone
310x0110x00011000
410x1A00x001A0000
50nonenone
610x0B70x000B7000
710x05C0x0005C000
  1. Split 0x4234 into a 3-bit page number and a 12-bit offset: page 4 (the top bits), offset 0x234 (the low 12 bits).
  2. Row 4 of the page table: present bit 1, frame 0x1A0.
  3. Physical address = frame 0x1A0 × 0x1000 + offset 0x234 = 0x001A0234.

Physical address 0x001A0234

Segmentation: variable-size pieces with a base and a limit

segment numberS0
offset0x0234
Segment table. The limit is the last valid offset, so the segment holds limit + 1 bytes.
SegmentBaseLimitSize
S0 code0x004000000x1FFF8,192 bytes
S1 data0x005000000x0FFF4,096 bytes
S2 stack0x007000000x2FFF12,288 bytes
S3 unusednonenonenot in use
  1. Row 0 of the segment table: base 0x00400000, limit 0x1FFF (the last valid offset).
  2. Check: offset 0x0234 is not greater than limit 0x1FFF, so the check passes.
  3. Linear address = base 0x00400000 + offset 0x0234 = 0x00400234.

Linear address 0x00400234

Four worked translations

MethodInputResult
Paging0x4234Physical address 0x001A0234
Paging0x2FFFPage fault: page 2 is not present
SegmentationS0 : 0x0234Linear address 0x00400234
SegmentationS0 : 0x2000Trap: offset 0x2000 is past the limit 0x1FFF
Piece size
Paging: every page is the same size (here 4 KiB), so any free frame fits any page. Segmentation: each segment has its own size, which is why every row has its own limit.
What is checked
Paging: the page’s present bit (and, on real CPUs, access permissions). Segmentation: that the offset is not past the limit.
What goes wrong
Paging: a page fault when the page has no frame. Segmentation: a trap when the offset is past the limit or the segment is not in use.
Windows today
Windows normally uses flat segments (one big segment starting at zero) and does the real work with paging, so the page tables decide what each address means.
Optional exploration

Compare a segment with a page mapping

Use the same small number as a segment offset and, independently, a virtual address. The segment has base 0x00400000 and an inclusive limit. Paging models one selected 4 KiB page entry; changing its frame moves that page. Large pages and read/write privilege checks are outside this model.

4 / 4: form each result

Segment: 0x00401234. Paging: 0x001A0234. These are independent address examples.

number supplied to each example0x00001234

Conceptual segment

inclusive limit 0x00001FFFoffset fits
base 0x00400000 + offset0x00401234

4 KiB paging

page | byte offset1 | 0x00000234
selected page-table entryframe base 0x001A0000
frame base + byte offset0x001A0234
Segment offset / virtual address
0x00001234
Virtual page and offset
1 / 0x00000234
Segment result
0x00401234
Physical result
0x001A0234
Complete default worked example and listing

This reference keeps the default values. The live picture above follows your input and selected step.

ActionWorked state
choose the inputUse 0x00001234 as a segment offset in one example and a virtual address in the other.
check the limit and split the pageOffset 0x00001234 passes limit 0x00001FFF. Paging splits it into page 1 and offset 0x00000234.
read the selected page entryThe entry for virtual page 1 is present and selects frame 416.
form each resultSegment: 0x00401234. Paging: 0x001A0234. These are independent address examples.
segment: if offset > inclusive_limit, fault
         otherwise linear = base + offset
paging:  virtual_page = floor(address / 4096)
         byte_offset = address % 4096
         if selected entry is absent, fault
         otherwise physical = frame * 4096 + byte_offset

The conceptual segment result is a linear address; the paging result is a physical address. Segment-limit failure and a not-present page are separate decisions. Modern Windows normally uses flat application segments plus paging.

Intel’s memory-management guide describes how the mechanisms combine. Windows normally uses a flat application address model rather than separate variable-size application segments; Microsoft’s x86 overview states that model. Page tables supply the process mappings and page permissions. In 64-bit mode, most traditional segment bases are ignored, while FS/GS still have special uses. A segment register is therefore not an alternative process page table. The next section follows the paging structures used by the capture.

On ordinary x86-64 systems using four-level paging, the CPU’s CR3 register identifies the physical base of the top table. The virtual address supplies four nine-bit indices and a twelve-bit offset:

Virtual-address bits Use
47–39 PML4 index
38–30 page-directory-pointer index
29–21 page-directory index
20–12 page-table index
11–0 byte offset inside a 4 KiB page
sign ext.bits 63–48
PML4bits 47–39
PDPTbits 38–30
PDbits 29–21
PTbits 20–12
offsetbits 11–0
A 64-bit virtual address in this x86-64 four-level paging model. Four 9-bit table indices and a 12-bit page offset use bits 47–0; the top 16 bits must copy bit 47 to make a canonical address. Five-level paging uses a different split.

Each table has 512 eight-byte entries. A normal translation reads one entry at each level, checks its present bit, takes the next table’s physical address, and finally adds the page offset.

Large pages can finish earlier: a page-directory-pointer entry may map 1 GiB, and a page-directory entry may map 2 MiB.

With the common 48-bit virtual-address form, bits 63–48 must repeat bit 47. An address that breaks this rule is noncanonical. The lab rejects it before reading the capture:

Optional: practise the sign-extension check for this four-level, 48-bit address model.

fn ensure_canonical(address: VirtualAddress) -> Result<(), MemoryError> {
let upper = address.0 >> 48;
let sign_bit = (address.0 >> 47) & 1;
let expected = if sign_bit == 0 { 0 } else { 0xFFFF };
if upper == expected {
Ok(())
} else {
Err(MemoryError::NonCanonical(address))
}
}

Rejecting impossible input gives a clearer error than following nonsense offsets into a file.

An IOMMU is an input/output memory management unit. It does for devices what the page tables above do for processes: it sits between a device and physical memory and translates the addresses the device asks for, so the operating system can limit a device to approved physical regions instead of letting it reach all RAM.

requests an address
approved region
outside approved region
DMA-capable device
IOMMU
physical RAM
blocked

Windows exposes protections including Kernel DMA Protection on supported hardware. Microsoft explains that it uses the IOMMU to isolate capable peripherals and protect against malicious DMA devices. Read the current Microsoft overview for requirements and behavior.

If a protected machine blocks DMA, that is the system working correctly. Keep the protections enabled and use the synthetic capture created by the tests or an offline hypervisor capture.

A capture is a snapshot, not a live process

Section titled “A capture is a snapshot, not a live process”

An offline image has important limits:

  • threads do not run while you inspect it;
  • pages may have changed immediately after capture;
  • some virtual pages may be absent, swapped, compressed, or intentionally omitted;
  • you need the correct CR3 and architecture details;
  • device memory and CPU caches may not be represented;
  • the capture may contain private data unrelated to the lesson.

A capture holds whatever was in RAM, including private data unrelated to the lesson, so it is stored securely and deleted when the lab no longer needs it. It also cannot be interpreted on its own. Its origin, capture time, build, architecture, capture method, and a cryptographic hash travel with the file, because a CR3 value or a structure layout means something only for the machine state it came from, and the hash is what shows the bytes have not changed since.

The useful computer-science problem is translating and validating captured memory. The complete implementation:

  1. opens a normal file read-only;
  2. accepts a physical CR3 value and virtual address;
  3. walks x86-64 page tables inside that file;
  4. checks present bits and bounds;
  5. supports 4 KiB, 2 MiB, and 1 GiB mappings while leaving live CPU access permissions as a separate question;
  6. prints a bounded hexdump.

No hardware-specific SDK is needed. This keeps the lesson reproducible and focuses attention on address translation rather than stealth, firmware, or bypasses.

For the exact paging bit definitions, use the current Intel Software Developer Manuals as the primary architecture reference.

Knowledge check

Choose the right map

Why is a process virtual pointer not automatically an offset in a physical RAM capture?

Finished Lesson 13.1?

Mark it done and the lesson list shows a beside it. A chapter whose lessons are all done is marked done too. Progress is saved in this browser only.

Chapter 13: 10 lessons

This book grew out of the original Game Hacking Academy by attilathedud and has been rewritten as a beginner-first course. Like the original repository, its source is published under the Apache License 2.0.