四级分页下的页表自映射与基址随机化原理介绍
2022-9-2 18:10:14 Author: 看雪学苑(查看原文) 阅读量:9 收藏


本文为看雪论坛精华文章

看雪论坛作者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协议漏洞挖掘实战

球分享

球点赞

球在看

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


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458467476&idx=2&sn=aff2cb20d617269e6c936e3d1a0fa20d&chksm=b18e0c1e86f9850844ab9ed32cd6c29f6a98ac2a51479cf6f68a528f8c15f925bc2e7b3651da#rd
如有侵权请联系:admin#unsafe.sh