# 前言

昨天在看周哥讲 x64 内核的时候,得知了 windows10 的某个版本开始,页表基址不再固定了。
今天晚上突然有了个思路,就动手把他敲出来了,这种思路是不是已经烂大街了我也不确定,就当发出来学习交流吧。

# 随机页表基址原理

这里用 x86 的 10-10-12 分页举例吧,比较好理解,理解了 x64 的也差不多的

启用了保护模式和分页机制后,咱就不能直接访问物理地址了,都会被 MMU 当作虚拟地址进行转换。

但是 cr3 存的又是物理地址,不能直接操作页表了,那咋办呢?

聪明的前辈们,选择在页目录表中选择一项 (共 1024 项,每一项 4 字节),使其存储的物理地址与页目录表的基址 (cr3) 相同,这样子就可以构造一个访问页表的虚拟地址了,大概就是让 cpu 在地址转换的过程中绕圈圈

  • 画个图吧

# 定位思路

  • x64 也必然是随机选择 PXT 的其中一项 PXE 来存储 PXT 的物理地址,实现的随机页表基址
  • 那么就可以通过构造所有可能指向 PXT 的虚拟地址 (共 512 项),转换为物理地址,再与 cr3 比较,最终得到正确的 PXT 基址。
  • 只要知道了存储 PXT 的物理地址的 PXE 的 PXI,构造 PPT/PDT/PTT 基址都很简单了

# 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PVOID GetPXTBase() {
UINT64 cr3 = __readcr3();
KdPrint(("yuyu:cr3:%p\n", cr3));
for (UINT64 i = 0; i < 512; i++) {
// 通过PXI构造虚拟地址
PVOID pxtBase = (PVOID)(0xffff000000000000 | (i << 12) | (i << 21) | (i << 30) | (i << 39));
PHYSICAL_ADDRESS physical = MmGetPhysicalAddress(pxtBase);
KdPrint(("yuyu:i:%d 物理:%p 虚拟:%p\n", i, physical.QuadPart, pxtBase));
if (cr3 == physical.QuadPart) {
return pxtBase;
}
}
return NULL;
}

# 结尾

理解有限,如有错误,还请指正

# 2022/5/14 补充

  • 其实除开这个之外,我先想到的是另一个思路,即构造所有可能存储 PXT 的物理地址的 PXE 的虚拟地址
    • 依旧是 512 项,探测地址是否可访问,读取 8 字节;
    • 将其当作 PXE,取出物理页面基址,再与 cr3 进行比较。
  • 但是这种方法有概率出现问题,即存放的数据可能正好与 cr3 相同,但所在页面并不是 PXT。
    • 因此我就没有写出来,不过我想了一下,还是贴出来了,也算是一种思路嘛。