DMA and Physical Memory
DMA gives devices a path to memory, while page tables and IOMMUs determine which physical bytes correspond to an address.
Cheatsheet for Lesson 13.1
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
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 is a way devices move data
Section titled “DMA is a way devices move data”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 faultThat 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.
1 Both processes use 0x1000
Process A
Virtual address0x1000Process B
Virtual address0x1000The numbers are identical.
2 Each has its own page-table root
Process A
Virtual address0x1000translated with
Page-table rootroot AProcess B
Virtual address0x1000translated with
Page-table rootroot BThe CPU translates each address with the root of the running process.
3 The same number reaches different physical memory
Process A
Virtual address0x1000translated with
Page-table rootroot Alands in
Physical memoryphysical page AProcess B
Virtual address0x1000translated with
Page-table rootroot Blands in
Physical memoryphysical page BPhysical 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.
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 ownerResult: 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
Step 1, line 1. Start with the selected inputs. Follow the highlighted operation and the changing state below.
root = A; page = 1; offset = 26.
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.
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.
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.
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.
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.
The ordinary 32-bit Windows layout.
- 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.
On 32-bit Windows booted with the 4-gigabyte-tuning option, a program built as large-address-aware may use 3 GiB.
- 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 0xBFFEFFFF
- 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: 0xBFFEFFFF − 0x00010000 + 1 = 3,221,094,400 bytes (3 GiB − 128 KiB).
- Reserved top 64 KiB0xBFFF0000 to 0xBFFFFFFF
- 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: 0xBFFFFFFF − 0xBFFF0000 + 1 = 65,536 bytes (64 KiB).
- System space0xC0000000 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 − 0xC0000000 + 1 = 1,073,741,824 bytes (1 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 32-bit program sees all 4 GiB; the 64-bit kernel lives elsewhere, outside this 32-bit view.
- 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 0xFFFEFFFF
- 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: 0xFFFEFFFF − 0x00010000 + 1 = 4,294,836,224 bytes (4 GiB − 128 KiB).
- Reserved top 64 KiB0xFFFF0000 to 0xFFFFFFFF
- 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: 0xFFFFFFFF − 0xFFFF0000 + 1 = 65,536 bytes (64 KiB).
- 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.
Paging: equal 4 KiB pages, looked up by number
0x4234 in binary, 15 bits: 100 001000110100
| Page | Present | Frame | Frame starts at |
|---|---|---|---|
| 0 | 1 | 0x0A2 | 0x000A2000 |
| 1 | 1 | 0x1F0 | 0x001F0000 |
| 2 | 0 | none | none |
| 3 | 1 | 0x011 | 0x00011000 |
| 4 | 1 | 0x1A0 | 0x001A0000 |
| 5 | 0 | none | none |
| 6 | 1 | 0x0B7 | 0x000B7000 |
| 7 | 1 | 0x05C | 0x0005C000 |
- Split 0x4234 into a 3-bit page number and a 12-bit offset: page 4 (the top bits), offset 0x234 (the low 12 bits).
- Row 4 of the page table: present bit 1, frame 0x1A0.
- Physical address = frame 0x1A0 × 0x1000 + offset 0x234 = 0x001A0234.
Physical address 0x001A0234
Segmentation: variable-size pieces with a base and a limit
| Segment | Base | Limit | Size |
|---|---|---|---|
| S0 code | 0x00400000 | 0x1FFF | 8,192 bytes |
| S1 data | 0x00500000 | 0x0FFF | 4,096 bytes |
| S2 stack | 0x00700000 | 0x2FFF | 12,288 bytes |
| S3 unused | none | none | not in use |
- Row 0 of the segment table: base 0x00400000, limit 0x1FFF (the last valid offset).
- Check: offset 0x0234 is not greater than limit 0x1FFF, so the check passes.
- Linear address = base 0x00400000 + offset 0x0234 = 0x00400234.
Linear address 0x00400234
Four worked translations
| Method | Input | Result |
|---|---|---|
| Paging | 0x4234 | Physical address 0x001A0234 |
| Paging | 0x2FFF | Page fault: page 2 is not present |
| Segmentation | S0 : 0x0234 | Linear address 0x00400234 |
| Segmentation | S0 : 0x2000 | Trap: 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.
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.
Play starts only when chosen. Step works one action at a time.
4 / 4: form each result
Segment: 0x00401234. Paging: 0x001A0234. These are independent address examples.
Conceptual segment
4 KiB paging
- 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.
| Action | Worked state |
|---|---|
choose the input | Use 0x00001234 as a segment offset in one example and a virtual address in the other. |
check the limit and split the page | Offset 0x00001234 passes limit 0x00001FFF. Paging splits it into page 1 and offset 0x00000234. |
read the selected page entry | The entry for virtual page 1 is present and selects frame 416. |
form each result | Segment: 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_offsetThe 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.
CR3 points to the first translation table
Section titled “CR3 points to the first translation table”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 |
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.
Canonical addresses catch bad input early
Section titled “Canonical addresses catch bad input early”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 gives devices their own map
Section titled “An IOMMU gives devices their own map”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.
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.
What “using DMA” means in this book
Section titled “What “using DMA” means in this book”The useful computer-science problem is translating and validating captured memory. The complete implementation:
- opens a normal file read-only;
- accepts a physical CR3 value and virtual address;
- walks x86-64 page tables inside that file;
- checks present bits and bounds;
- supports 4 KiB, 2 MiB, and 1 GiB mappings while leaving live CPU access permissions as a separate question;
- 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.
Choose the right map
Why is a process virtual pointer not automatically an offset in a physical RAM capture?
The capture is organized by physical address. The matching CR3 and page tables connect a process's virtual address to that physical location.
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.
Plain Markdown, saved in this browser.
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.