x64页表映射
微软设计了一种页表自映射的方式,提高了cpu工作的效率。正常情况下一个线性地址需要进行4次拆分.

而在表映射的作用下,可实现3次拆分即可。
原理
微软在PML4表中的某一项保存着表地址(实际上也是CR3),假设这个地址在PML4+0x100的位置,那么根据下图所示:

有这么一个关系:![cr3+i*8]=cr3
。由于一个PML4E可以管理的内存大小为512G
,因此0x100位置的PML4E可以管理的内存范围如下:
1
| 0xFFFF8000`00000000 ~ 0xFFFF807F`FFFFF000
|
按照 9-9-9-9-12 分页方式去拆分上述边界物理地址:
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 。下图展示了优化后的查询过程:

推导PML4基地址
PML4 页表基址有两个特点:
cr3=[pml4_base]
物理寻址如下:
1 2 3 4 5 6
| //!为读物理地址 cr3 =![x + pte] =![x + ![y + pde]] =![x + ![y + ![z + pdpte]]] =![x + ![y + ![z + ![r + pml4]]]] =![x + ![y + ![z + ![r + ![n + cr3]]]]]
|
其中cr3=pml4_base
- x:page_offset,页内偏移
- y:pti
- z:pdi
- r:pdti
- n:自映射的索引。
当x=y=z=r=0时,可得:
由此可知在cr3中的某项保存的物理地址指向了自身
,因此可以将cr3映射到虚拟地址后,通过遍历来获取自身。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| #include<ntifs.h> #include <intrin.h>
VOID UnloadDrv(PDRIVER_OBJECT pDrv) { return; }
NTSTATUS DriverEntry(PDRIVER_OBJECT pDrv, PUNICODE_STRING pReg) { NTSTATUS status = STATUS_SUCCESS; do { pDrv->DriverUnload = UnloadDrv; PHYSICAL_ADDRESS pCr3 = {0}; pCr3.QuadPart = __readcr3(); PULONG64 tmp = MmGetVirtualForPhysical(pCr3); if (tmp == NULL) { DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "get cr3 virtual address failed!\r\n"); break; } DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "cr3 virtual address = %p\r\n", tmp);
ULONG_PTR pml4_base = NULL; for (int i = 0; i < 512; i++) { if (pCr3.QuadPart == (tmp[i] & 0xFFFFFFFFF000)) { pml4_base = tmp[i] & 0xFFFFFFFFF000; DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "&tmp[i] = %p\rpml4_base = %p\r\n",&tmp[i], pml4_base); break; } } } while (FALSE); return status; }
|

有了PML4基址,其他基址就可以推出了。
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; }
|

参考
[原创]四级分页下的页表自映射与基址随机化原理介绍https://bbs.kanxue.com/thread-274152.htm
[关于WIndows内核自映射方案的通俗解释]]https://www.cnblogs.com/SivilTaram/p/WindowsKernelMapping.html