查看原文
其他

四级分页下的页表自映射与基址随机化原理介绍

REPE 看雪学苑 2022-09-06


本文为看雪论坛精华文章

看雪论坛作者ID:REPE





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)。
kd> r idtridtr=fffff8037888e000kd> dq fffff8037888e000fffff803`7888e000 761e8e00`00107e00 00000000`fffff803fffff803`7888e010 761e8e04`00108140 00000000`fffff803fffff803`7888e020 761e8e03`00108600 00000000`fffff803fffff803`7888e030 761eee00`00108ac0 00000000`fffff803fffff803`7888e040 761eee00`00108e00 00000000`fffff803fffff803`7888e050 761e8e00`00109140 00000000`fffff803fffff803`7888e060 761e8e00`00109680 00000000`fffff803fffff803`7888e070 761e8e00`00109b80 00000000`fffff803

将虚拟地址按照 9-9-9-9-12 格式划分(注意低48位有效)
fffff803`7888e000 -> f803`7888e000 1 1111 0000 0x1f0 PML4I0 0000 1101 0xd PDPTI1 1100 0100 0x1c4 PTI0 1000 1110 0x8e PDI000000000000 0x0 Offset

访问 Cr3 + PML4I * 8 指向的物理地址得到 PDPTE 的物理地址
kd> r cr3cr3=0000000052c76000kd> !dq 52c76000+1f0*8#52c76f80 00000000`00c08063 00000000`00000000#52c76f90 00000000`00000000 00000000`00000000#52c76fa0 00000000`00000000 00000000`00000000#52c76fb0 0a000000`0bafc863 00000000`00000000#52c76fc0 00000000`00000000 00000000`00000000#52c76fd0 00000000`00000000 00000000`00000000#52c76fe0 00000000`00000000 00000000`00000000#52c76ff0 00000000`00000000 00000000`00ca8063

访问 PDPTE + PDPTI * 8 指向的物理地址得到 PTE 的物理地址
kd> !dq c08000+d*8# c08068 00000000`00c09063 00000000`00000000# c08078 00000000`00000000 00000000`00000000# c08088 00000000`00000000 00000000`00000000# c08098 00000000`00000000 00000000`00000000# c080a8 00000000`00000000 00000000`00000000# c080b8 00000000`00000000 00000000`00000000# c080c8 00000000`00000000 00000000`00000000# c080d8 00000000`00000000 00000000`00000000

访问 PTE + PTI * 8 指向的物理地址得到 PDE 的物理地址
kd> !dq c09000+1c4*8# c09e20 00000000`00ca7063 0a000000`03996863# c09e30 0a000000`0f5bc863 0a000000`0f5bd863# c09e40 0a000000`0f5be863 0a000000`0f5bf863# c09e50 0a000000`032c0863 0a000000`032c1863# c09e60 0a000000`040c3863 0a000000`02bc4863# c09e70 0a000000`02bc5863 0a000000`02bc6863# c09e80 0a000000`02bc7863 0a000000`02bc8863# c09e90 0a000000`02bc9863 0a000000`02bca863

访问 PDE + PDI * 8 指向的物理地址得到物理页面
kd> !dq ca7000+8e*8# ca7470 89000000`0588e121 89000000`0588f963# ca7480 89000000`05890963 89000000`05891963# ca7490 89000000`05892963 89000000`05893963# ca74a0 00000000`00000000 89000000`05895963# ca74b0 89000000`05896963 89000000`05897963# ca74c0 89000000`05898963 89000000`05899963# ca74d0 89000000`0589a963 89000000`0589b963# ca74e0 00000000`00000000 89000000`0589d963

访问 物理页面 + Offset 指向的物理地址得到内容
kd> !dq 0588e000# 588e000 761e8e00`00107e00 00000000`fffff803# 588e010 761e8e04`00108140 00000000`fffff803# 588e020 761e8e03`00108600 00000000`fffff803# 588e030 761eee00`00108ac0 00000000`fffff803# 588e040 761eee00`00108e00 00000000`fffff803# 588e050 761e8e00`00109140 00000000`fffff803# 588e060 761e8e00`00109680 00000000`fffff803# 588e070 761e8e00`00109b80 00000000`fffff803

与访问虚拟内存得到的结果一致。




页表自映射


1、介绍

在64位模式下,高等级页表项都指向低等级页表项的物理地址,依次类推,直到最低级别页表项,即可获取物理页面进而读取内容。在此过程中 Cr3 寄存器中存储了最高级页表(PML4)的表基物理地址。为了更好的管理这些页表,微软采取了最高级页表基址自映射的方式实现仅仅利用8字节物理内存,就可以在每次访问分页管理相关的内存时,少做一次页表查询操作来优化速度。


2、原理

在四级页表的最高级 PML4 页表中存在一项,里面保存了 PML4 页表的表基物理地址,即 Cr3 。假设这一项在 PML4 表中的索引为 0x100,如下图所示:
 
此时满足:( ![物理地址] 表示读取物理地址的内容)
![Cr3 + 0x100 * 8] = Cr3

用于分页管理的物理页面大小总计 512 512 512 * 4KB = 512GB,而一个 PML4 表项恰好可以管理512GB内存。
 
PML4 表中索引位置0x100的元素用于内存管理且满足上述关系,那么此时用于内存管理的虚拟地址空间为:
0xFFFF8000`00000000 ~ 0xFFFF807F`FFFFF000

按照 9-9-9-9-12 分页方式去拆分上述边界物理地址:(只使用低48位)
// 起始地址0x8000`00000000 1 0000 0000 0x1000 0000 0000 0x00 0000 0000 0x00 0000 0000 0x00000 0000 0000 0x0 // 结束地址0x807F`FFFFF0001 0000 0000 0x1001 1111 1111 0x1FF1 1111 1111 0x1FF1 1111 1111 0x1FF0000 0000 0000 0x0

常规查询流程:
// 起始地址![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,所以查询流程变为:
// 起始地址![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 页表基址有两个特点:
  • 属于虚拟地址

  • 虚拟地址的内容是Cr3


假设该虚拟地址按照 9-9-9-9-12 分页规则拆分得到的索引依次为 x、y、z、r,根据页表解析规则:
![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,根据页表解析规则:
![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 基址:
0xFB7DBEDF60001 1111 0110 0x1F6 PML41 1111 0110 0x1F6 PDPT1 1111 0110 0x1F6 PD1 1111 0110 0x1F6 PT000000000000 0x0 Index为:0x1F6

系统重启后的PML4基址:
0x8D46A351A0001 0001 1010 0x11A1 0001 1010 0x11A1 0001 1010 0x11A1 0001 1010 0x11A000000000000 0 Index为:0x11A


2、定位


页表基址随机化导致写代码读写页表属性变得不方便,但可以利用页表自映射的一些结论来获取 PML4 表基址。PML4 表基址的内容为Cr3的值,并且位于 PML4 表所在的页面内。因为Cr3里保存了 PML4 的表基物理地址,所以可以通过映射Cr3物理地址的虚拟地址,遍历这个虚拟地址页面的512个地址,哪个地址符合上述条件,哪个地址就是 PML4 表基址。下面给出驱动代码实现:
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 索引值,其他各级页表基址也就都可以得到了。
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)https://www.coresecurity.com/core-labs/articles/getting-physical-extreme-abuse-of-intel-based-paging-systems-part-2-windows
 
关于WIndows内核自映射方案的通俗解释 - SivilTaram - 博客园 (cnblogs.com)https://www.cnblogs.com/SivilTaram/p/WindowsKernelMapping.html
 
[原创]逆向TesSafe.sys有感:鹅厂是如何定位随机化的PTE_BASE  https://bbs.pediy.com/thread-254276.htm
 
x64内核研究04分页 https://www.bilibili.com/video/BV1yJ411T7dz?spm_id_from=333.999.0.0




看雪ID:REPE

https://bbs.pediy.com/user-home-894067.htm

*本文由看雪论坛 REPE 原创,转载请注明来自看雪社区



# 往期推荐

1.Android4.4和8.0 DexClassLoader加载流程分析之寻找脱壳点

2.逆向篇:解决tiktok切换前后置虚拟摄像头卡住问题

3.House of cat新型glibc中IO利用手法解析 & 第六届强网杯House of cat详解

4.对一个随身WIFI设备的漏洞挖掘尝试

5.[安全运维向]模拟搭建小型企业内网

6.某设备CoAP协议漏洞挖掘实战






球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存