四级分页下的页表自映射与基址随机化原理介绍
一、x64分页基础
1.介绍
IA-32e模式下,虚拟地址宽度为64位,但只有低48位有效,最多可以寻址256TB,高16位用作符号拓展(全0或全1)。CPU 分页机制变为4级,分别对应 PML4、PDPT、PD、PT,并将48位虚拟地址按 9-9-9-9-12 索引格式划分。
其中,Cr3 寄存器中的物理地址指向 PML4 表的首地址。上图中表项均占8个字节,物理页面大小仍然为4KB。
2.实例
Windbg 中手动拆分64位虚拟地址,并按照上面的分页规则计算出物理地址。实验选用 idt 表首地址进行拆分。(在计算物理地址时,需要对页表项的属性位清0。)
1 2 3 4 5 6 7 8 9 10 11 | kd> r idtr
idtr = fffff8037888e000
kd> dq fffff8037888e000
fffff803` 7888e000 761e8e00 ` 00107e00 00000000 `fffff803
fffff803` 7888e010 761e8e04 ` 00108140 00000000 `fffff803
fffff803` 7888e020 761e8e03 ` 00108600 00000000 `fffff803
fffff803` 7888e030 761eee00 ` 00108ac0 00000000 `fffff803
fffff803` 7888e040 761eee00 ` 00108e00 00000000 `fffff803
fffff803` 7888e050 761e8e00 ` 00109140 00000000 `fffff803
fffff803` 7888e060 761e8e00 ` 00109680 00000000 `fffff803
fffff803` 7888e070 761e8e00 ` 00109b80 00000000 `fffff803
|
将虚拟地址按照 9-9-9-9-12 格式划分(注意低48位有效)
1 2 3 4 5 6 7 | fffff803` 7888e000 - > f803` 7888e000
1 1111 0000 0x1f0 PML4I
0 0000 1101 0xd PDPTI
1 1100 0100 0x1c4 PTI
0 1000 1110 0x8e PDI
000000000000 0x0 Offset
|
访问 Cr3 + PML4I * 8 指向的物理地址得到 PDPTE 的物理地址
1 2 3 4 5 6 7 8 9 10 11 | kd> r cr3
cr3 = 0000000052c76000
kd> !dq 52c76000 + 1f0 * 8
|
访问 PDPTE + PDPTI * 8 指向的物理地址得到 PTE 的物理地址
访问 PTE + PTI * 8 指向的物理地址得到 PDE 的物理地址
访问 PDE + PDI * 8 指向的物理地址得到物理页面
访问 物理页面 + Offset 指向的物理地址得到内容
与访问虚拟内存得到的结果一致。
二、页表自映射
1.介绍
在64位模式下,高等级页表项都指向低等级页表项的物理地址,依次类推,直到最低级别页表项,即可获取物理页面进而读取内容。在此过程中 Cr3 寄存器中存储了最高级页表(PML4)的表基物理地址。为了更好的管理这些页表,微软采取了最高级页表基址自映射的方式实现仅仅利用8字节物理内存,就可以在每次访问分页管理相关的内存时,少做一次页表查询操作来优化速度。
2.原理
在四级页表的最高级 PML4 页表中存在一项,里面保存了 PML4 页表的表基物理地址,即 Cr3 。假设这一项在 PML4 表中的索引为 0x100,如下图所示:
此时满足:( ![物理地址] 表示读取物理地址的内容)
用于分页管理的物理页面大小总计 512 512 512 * 4KB = 512GB,而一个 PML4 表项恰好可以管理512GB内存。
PML4 表中索引位置0x100的元素用于内存管理且满足上述关系,那么此时用于内存管理的虚拟地址空间为:
1 | 0xFFFF8000 ` 00000000 ~ 0xFFFF807F `FFFFF000
|
按照 9-9-9-9-12 分页方式去拆分上述边界物理地址:(只使用低48位)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | / / 起始地址
0x8000 ` 00000000
1 0000 0000 0x100
0 0000 0000 0x0
0 0000 0000 0x0
0 0000 0000 0x0
0000 0000 0000 0x0
/ / 结束地址
0x807F `FFFFF000
1 0000 0000 0x100
1 1111 1111 0x1FF
1 1111 1111 0x1FF
1 1111 1111 0x1FF
0000 0000 0000 0x0
|
常规查询流程:
1 2 3 4 5 6 7 8 9 10 11 | / / 起始地址
![Cr3 + 0x100 * 8 ] = PDPTE
![PDPTE + 0x0 * 8 ] = PDE
![PDE + 0x0 * 8 ] = PTE
![PTE + 0x0 * 8 ] = 物理页面
/ / 结束地址
![Cr3 + 0x100 * 8 ] = PDPTE
![PDPTE + 0x1FF * 8 ] = PDE
![PDE + 0x1FF * 8 ] = PTE
![PTE + 0x0 * 8 ] = 物理页面
|
根据上述等式,![Cr3 + 0x100 * 8] = Cr3,所以查询流程变为:
1 2 3 4 5 6 7 8 9 | / / 起始地址
![Cr3 + 0x0 * 8 ] = PDE
![PDE + 0x0 * 8 ] = PTE
![PTE + 0x0 * 8 ] = 物理页面
/ / 结束地址
![Cr3 + 0x1FF * 8 ] = PDE
![PDE + 0x1FF * 8 ] = PTE
![PTE + 0x0 * 8 ] = 物理页面
|
很神奇,查询页表操作由四次变成了三次,效率大大提升。而且只是使用了8字节的物理地址空间来保存 Cr3 。下图展示了优化后的查询过程:
3.规律
为了写代码方便读写页表属性,四级页表都应该有自己的表基虚拟地址,以便访问其中的元素。
3.1 推导最高级页表 PML4 的基址
PML4 页表基址有两个特点:
假设该虚拟地址按照 9-9-9-9-12 分页规则拆分得到的索引依次为 x、y、z、r,根据页表解析规则:
1 2 3 4 | ![Cr3 + x * 8 ] = PDPTE
![PDPTE + y * 8 ] = PDE
![PDE + z * 8 ] = PTE
![PTE + r * 8 ] = 物理页面 = Cr3
|
还需要满足 ![Cr3 + x * 8] = Cr3,所以当 x = y = z = r 的时候上述条件均满足。
3.2 推导 PDPT 表的基址
PDPT 页表基址有两个特点:
- 属于虚拟地址
- 虚拟地址的内容不再是Cr3,而是 ![Cr3 + 0 * 8] 指向的物理地址。
假设该虚拟地址按照 9-9-9-9-12 分页规则拆分得到的索引依次为 x、y、z、r,根据页表解析规则:
1 2 3 4 | ![Cr3 + x * 8 ] = PDPTE
![PDPTE + y * 8 ] = PDE
![PDE + z * 8 ] = PTE
![PTE + r * 8 ] = ![Cr3]
|
还需要满足 ![Cr3 + x * 8] = Cr3,所以当 x = y = z 且 r = 0 的时候上述条件均满足。
3.3 推导 PD、PT 表的基址
方法同理。
3.4 结论
页内偏移均为0
- PML4:PML4i == PDTi == PDi == PTi == Index
- PDPT:PML4i == PDTi == PDi == Index && PTi == 0
- PD:PML4i == PDTi == Index && PDi == 0 && PTi == 0
- PT:PML4i == Index && PDTi == 0 && PDi == 0 && PTi == 0
三、基址随机化
1.原理
上面得到结论中的 Index 就是自映射表项在 PML4 表中的索引,这个值的变化就是造成各级页表基址变化的原因。
系统重启前的 PML4 基址:
1 2 3 4 5 6 7 8 | 0xFB7DBEDF6000
1 1111 0110 0x1F6 PML4
1 1111 0110 0x1F6 PDPT
1 1111 0110 0x1F6 PD
1 1111 0110 0x1F6 PT
000000000000 0x0
Index为: 0x1F6
|
系统重启后的PML4基址:
1 2 3 4 5 6 7 8 | 0x8D46A351A000
1 0001 1010 0x11A
1 0001 1010 0x11A
1 0001 1010 0x11A
1 0001 1010 0x11A
000000000000 0
Index为: 0x11A
|
2.定位
页表基址随机化导致写代码读写页表属性变得不方便,但可以利用页表自映射的一些结论来获取 PML4 表基址。PML4 表基址的内容为Cr3的值,并且位于 PML4 表所在的页面内。因为Cr3里保存了 PML4 的表基物理地址,所以可以通过映射Cr3物理地址的虚拟地址,遍历这个虚拟地址页面的512个地址,哪个地址符合上述条件,哪个地址就是 PML4 表基址。下面给出驱动代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | ULONG64 GetPml4Base()
{
PHYSICAL_ADDRESS pCr3 = { 0 };
pCr3.QuadPart = __readcr3();
PULONG64 pCmpArr = MmGetVirtualForPhysical(pCr3);
int count = 0 ;
while (( * pCmpArr & 0xFFFFFFFFF000 ) ! = pCr3.QuadPart)
{
if ( + + count > = 512 )
{
return - 1 ;
}
pCmpArr + + ;
}
return (ULONG64)pCmpArr & 0xFFFFFFFFFFFFF000 ;
}
|
得到了 PML4 表基址,就可以得到 Index 索引值,其他各级页表基址也就都可以得到了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ULONG64 GetPdptBase(ULONG64 ulPml4Base)
{
return (ulPml4Base >> 21 ) << 21 ;
}
ULONG64 GetPdBase(ULONG64 ulPml4Base)
{
return (ulPml4Base >> 30 ) << 30 ;
}
ULONG64 GetPtBase(ULONG64 ulPml4Base)
{
return (ulPml4Base >> 39 ) << 39 ;
}
|
得到了 PML4 表基址,就可以得到 Index 索引值,其他各级页表基址也就都可以得到了。
四、参考文档
Getting Physical: Extreme abuse of Intel based Paging Systems - Part 2 - Windows (coresecurity.com)
关于WIndows内核自映射方案的通俗解释 - SivilTaram - 博客园 (cnblogs.com)
[原创]逆向TesSafe.sys有感:鹅厂是如何定位随机化的PTE_BASE-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com
x64内核研究04分页哔哩哔哩_bilibili
五、结语
有错误欢迎指出,一起交流进步。
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法
最后于 2022-8-24 20:31
被REPE编辑
,原因: