1.Windows内核系列:页表自映射原理与页表基址的逆推结合你之前掌握的 x86-64 四级分页、VA↔PA 转换,以及游戏内核驱动、逆向、反对抗的实战场景,下面系统讲解 页表自映射 和 页表基址逆推 两大核心知识点。
这两块是 Windows x64 内核驱动的高频刚需:
页表自映射:让内核可以用固定虚拟地址直接读写各级页表,省去反复手动 VA→PA 遍历,大幅简化页表遍历、权限修改、内存扫描代码;
页表基址逆推:从已知虚拟地址/页表项,反向算出各级页表物理基址、甚至进程 CR3,用于溯源、内存排查、漏洞分析、对抗隐藏内存。
全程基于 x86-64、4KB 标准页、Windows 64 位默认规则,并穿插游戏驱动实战用法。
## 一、前置回顾(快速对齐)
### 1. 64位虚拟地址结构(有效低48位)
x86-64 四级分页(4KB页),虚拟地址 VA 低48位划分为 4 级索引 + 页内偏移:
位范围 位数 名称 符号缩写 取值范围
47 ~ 39 9 PML4 索引 I4 0~511
38 ~ 30 9 PDPT 索引 I3 0~511
29 ~ 21 9 PD 索引 I2 0~511
20 ~ 12 9 PT 索引 I1 0~511
11 ~ 0 12 页内偏移 Offset 0~4095
单级页表大小:512项 × 8字节 = 4KB,和页尺寸一致;
页表项通用规则:物理基址 = 页表项值 & 0xFFFFFFFFFF000(清空低12位标志位)。
### 2. 正向流程(复习)
CR3(PM4基址) → I4 → PML4E → I3 → PDPTE → I2 → PDE → I1 → PTE → 物理页基址 + Offset → 物理地址。
## 二、页表自映射(Page Table Self-Mapping)
### 2.1 什么是页表自映射?
自映射:操作系统主动把页表本身的物理内存,映射到一段固定内核虚拟地址区间,形成「页表自己映射自己」的闭环。
### 核心价值
正常访问页表内容,必须走:CR3 → 四级索引 → 物理地址,代码繁琐、重复度高;
开启自映射后,直接用虚拟地址就能读写 PML4/PDPT/PD/PT 所有页表项,不需要手动做 VA→PA 转换。
关键限制:自映射区域属于内核高地址空间,用户态程序无法访问(会触发访问违规/蓝屏),仅内核驱动可用。
### 2.2 Windows x64 固定规则(全网通用)
Windows 10/11 x64 系统有一个全局固定配置:
1. PML4 表一共 512 项(索引 0 ~ 511);
2. 将 PML4[511](索引 = 0x1FF)这一项,指向 PML4 自身的物理基址(即 CR3 & 0xFFFFFFFFFF000);
3. 这个索引 0x1FF 称为自映射索引,是整个机制的核心。
简写:
SELF_MAP_INDEX = 0x1FF // 固定 511
PML4[0x1FF] = PML4 物理基址
因为 PML4 第 511 项指向自己,四级页表形成闭环,所有层级页表都能通过一段连续虚拟地址访问。
### 2.3 各级页表的自映射虚拟地址推导
我们利用 I4 = 0x1FF 这个固定值,分别构造访问 PML4、PDPT、PD、PT 的虚拟地址。
约定:
任意索引:idx4(PML4项索引)、idx3(PDPT项索引)、idx2(PD项索引)、idx1(PT项索引)
页表项在页内偏移:off = 索引 × 8(每项8字节)
#### 1)访问 PML4 页表本身(最高级页表)
要读取 PML4 中第 idx4 个表项(PML4E):
固定 I4 = 0x1FF(走自映射项),I3=idx4,其余索引置0,偏移 = idx4 × 8
地址拼接(位运算):
VA_PML4 = (0x1FF << 39) | (idx4 << 30) | (0 << 21) | (0 << 12) | (idx4 * 8)
作用:直接通过 VA_PML4 虚拟地址,读写当前进程 PML4 的任意表项。
#### 2)访问任意 PDPT 页表
PML4 第 idx4 项指向某一个 PDPT,读取该 PDPT 内第 idx3 项(PDPTE):
VA_PDPT = (0x1FF << 39) | (idx4 << 30) | (idx3 << 21) | (0 << 12) | (idx3 * 8)
#### 3)访问任意 PD 页表
PDPT 第 idx3 项指向某一个 PD,读取该 PD 内第 idx2 项(PDE):
VA_PD = (0x1FF << 39) | (idx4 << 30) | (idx3 << 21) | (idx2 << 12) | (idx2 * 8)
#### 4)访问任意 PT 页表(最常用)
PD 第 idx2 项指向某一个 PT,读取该 PT 内第 idx1 项(PTE):
VA_PT = (0x1FF << 39) | (idx4 << 30) | (idx3 << 21) | (idx2 << 12) | (idx1 * 8)
极简记忆
所有自映射地址的最高9位(I4)永远是 0x1FF,后面三段索引依次对应下一级页表。
### 2.4 举个实例(直观理解)
读取 PML4[0] 表项:
idx4 = 0
VA = (0x1FF << 39) | (0 << 30) | 0 | 0 | 0
在内核驱动中直接读取这个虚拟地址,拿到的就是 PML4 第 0 项的原始值,无需 VA→PA 转换。
### 2.5 游戏驱动中的实战用途
1. 快速遍历进程所有页表
传统方式:遍历每一个虚拟地址 → 手动四级VA→PA,效率极低;
自映射方式:直接循环自映射虚拟地址区间,批量读取 PTE/PDE,扫描游戏所有内存页。
2. 修改页表权限(对抗反作弊)
游戏/反作弊会把关键内存设为 只读(RW=0)、不可执行(XD=1);
利用自映射直接定位 PTE,修改标志位,解除保护、实现代码注入/内存修改。
3. 检测页表篡改(反外挂)
反作弊会监控 PML4[0x1FF] 自映射项,一旦发现被篡改,判定存在恶意驱动。
4. WinDbg 调试底层依赖
!pte、!process、dq 内核地址 等调试命令,底层全部依赖页表自映射。
## 三、页表基址逆推(反向推导)
正向:CR3 → 各级索引 → 物理地址
逆推:已知「虚拟地址 / 页表项 / 物理地址」,反向算出 PT/PD/PDPT/PML4 物理基址、进程CR3。
这部分是溯源分析、漏洞利用、内存隐藏排查的核心,分三大实战场景,同时兼容 4KB普通页、2MB大页、1GB大页(Unity/UE游戏高频大页,必须处理)。
### 通用前置规则
1. 页表物理基址:Base = Entry
...(已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-291414.htm
[讨论]Windbg学习使用2
218 浏览
0 回复
暂无回复,快来抢沙发吧!