分页
x64下分页相对于x86做了扩展,采用了4级页表,Intel与微软的定义上对于前两个页表的命名上有所区别
在Intel手册中,对于这四级页表的命名为:PML4E、PDPTE、PDE、PTE
在微软的定义上,这四级页表分别命名为:PXE、PPE、PDE、PTE,例如:
对于一个虚拟地址,其有效位有48位,其剩余的高16位为高位的符号扩展,对这48位分解成四个9位(四级页表索引),剩余12位表示物理页面的页内偏移,每个页表项占8字节
以4k页面为例,如图:
且,在虚拟内存空间中
- 指向PTE所在物理页面的PTE是其PDE
- 指向PDE所在物理页面的PTE是其PPE
- 指向PPE所在物理页面的PTE是其PXE
手动转换虚拟地址到物理地址
以IDT表所在虚拟地址为例
kd> dq idtr
fffff803`42672000 3c228e00`00102100 00000000`fffff803
fffff803`42672010 3c228e04`00102180 00000000`fffff803
fffff803`42672020 3c228e03`00102240 00000000`fffff803
fffff803`42672030 3c22ee00`001022c0 00000000`fffff803
fffff803`42672040 3c22ee00`00102340 00000000`fffff803
fffff803`42672050 3c228e00`001023c0 00000000`fffff803
fffff803`42672060 3c228e00`00102440 00000000`fffff803
fffff803`42672070 3c228e00`001024c0 00000000`fffff803
kd> .formats fffff803`42672000
Evaluate expression:
Hex: fffff803`42672000
Decimal: -8782094065664
Octal: 1777777600150231620000
Binary: 11111111 11111111 11111000 00000011 01000010 01100111 00100000 00000000
Chars: ....Bg .
Time: ***** Invalid FILETIME
Float: low 57.7812 high -1.#QNAN
Double: -1.#QNAN
kd> r cr3
cr3=00000000001ad002
拆分虚拟地址:
11111111 11111111 // 符号扩展
1 1111 0000 // 1f0 -> PXE
0 0000 1101 // d -> PPE
0 0001 0011 // 13 -> PDE
0 0111 0010 // 72 -> PTE
000000000000 // 0 -> offset
获取PXE
kd> !dq 00000000001ad002 + 1f0 * 8
# 1adf80 00000000`02c09063 00000000`00000000 // 其中63是页面的属性,紧接着4位保留
# 1adf90 00000000`00000000 0a000001`3ffff863
# 1adfa0 8a000000`00003863 8a000000`00003863
# 1adfb0 8a000000`00003863 8a000000`00003863
# 1adfc0 0a000001`3fffd863 0a000001`3fffb863
# 1adfd0 00000000`00000000 00000000`00000000
# 1adfe0 00000000`00000000 00000000`00000000
# 1adff0 00000000`00000000 00000000`02c2a063
需要注意的是,CR3寄存器的低12位上存在保留字段和两个标志位,而从第12位开始才是页目录基址,需要清除低12位,这里windbg自动完成了转换
获取PPE
kd> !dq 00000000`02c09000 + d * 8
# 2c09068 00000000`02c19063 00000000`00000000
# 2c09078 00000000`00000000 00000000`00000000
# 2c09088 00000000`00000000 00000000`00000000
# 2c09098 00000000`00000000 00000000`00000000
# 2c090a8 00000000`00000000 00000000`00000000
# 2c090b8 00000000`00000000 00000000`00000000
# 2c090c8 00000000`00000000 00000000`00000000
# 2c090d8 00000000`00000000 00000000`00000000
获取PDE
kd> !dq 00000000`02c19000 + 13 * 8
# 2c19098 00000000`02c29063 0a000000`82084863
# 2c190a8 0a000000`82085863 0a000000`82086863
# 2c190b8 0a000000`82087863 0a000000`82088863
# 2c190c8 0a000000`8ec3f863 0a000000`8ec40863
# 2c190d8 0a000000`8ec41863 0a000000`8ec42863
# 2c190e8 0a000000`8ec43863 00000000`00000000
# 2c190f8 00000000`00000000 00000000`00000000
# 2c19108 00000000`00000000 00000000`00000000
获取PTE
kd> !dq 00000000`02c29000 + 72 * 8
# 2c29390 89000000`07872021 89000000`07873863 // 最高位89,8是不可执行属性,9是protect key和SMAP有关
# 2c293a0 89000000`07874863 89000000`07875863
# 2c293b0 89000000`07876863 89000000`07877863
# 2c293c0 00000000`00000000 89000000`07879863
# 2c293d0 89000000`0787a863 89000000`0787b863
# 2c293e0 89000000`0787c863 89000000`0787d863
# 2c293f0 89000000`0787e863 89000000`0787f863
# 2c29400 00000000`00000000 89000000`07881863
最后加上物理页面偏移,得到物理地址
kd> !dq 07872000 + 0
# 7872000 3c228e00`00102100 00000000`fffff803
# 7872010 3c228e04`00102180 00000000`fffff803
# 7872020 3c228e03`00102240 00000000`fffff803
# 7872030 3c22ee00`001022c0 00000000`fffff803
# 7872040 3c22ee00`00102340 00000000`fffff803
# 7872050 3c228e00`001023c0 00000000`fffff803
# 7872060 3c228e00`00102440 00000000`fffff803
# 7872070 3c228e00`001024c0 00000000`fffff803
// 对比之前的一致
kd> dq idtr
fffff803`42672000 3c228e00`00102100 00000000`fffff803
fffff803`42672010 3c228e04`00102180 00000000`fffff803
fffff803`42672020 3c228e03`00102240 00000000`fffff803
fffff803`42672030 3c22ee00`001022c0 00000000`fffff803
fffff803`42672040 3c22ee00`00102340 00000000`fffff803
fffff803`42672050 3c228e00`001023c0 00000000`fffff803
fffff803`42672060 3c228e00`00102440 00000000`fffff803
fffff803`42672070 3c228e00`001024c0 00000000`fffff803
获取PTE基址
在现代操作系统中,PTE基址随机,不妨命名为PTE_BASE
,以此类推就有PDE_BASE
、PPE_BASE
、PXE_BASE
那么计算某一虚拟地址的PTE、PDE、PPE、PXE如下:
#define GET_PTE(x) ((((x) & 0xffffffffffff) >> 12) << 3) + PTE_BASE
#define GET_PDE(x) ((((x) & 0xffffffffffff) >> 21) << 3) + PDE_BASE
#define GET_PPE(x) ((((x) & 0xffffffffffff) >> 30) << 3) + PPE_BASE
#define GET_PXE(x) ((((x) & 0xffffffffffff) >> 39) << 3) + PXE_BASE
根据
指向PTE所在物理页面的PTE是其PDE指向PDE所在物理页面的PTE是其PPE
指向PPE所在物理页面的PTE是其PXE
可得
PDE_BASE = GET_PTE(PTE_BASE);
PPE_BASE = GET_PTE(PDE_BASE);
PXE_BASE = GET_PTE(PPE_BASE);
那么最关键的文件是如何获得PTE_BASE
,有好几种方法,推荐鹅厂是如何定位随机化的PTE_BASE一文
ULONG64 get_pte_base()
{
PHYSICAL_ADDRESS physical_address;
ULONG64 pte_base = 0;
physical_address.QuadPart = __readcr3() & 0xfffffffffffff000; // 获取CR3寄存器,清除低12位
PULONG64 pxe_ptr = MmGetVirtualForPhysical(physical_address); // 获取其所在的虚拟地址 - 页表自映射
ULONG64 index = 0;
// 遍历比较
while((pxe_ptr[index] & 0xfffffffff000) != physical_address.QuadPart) {
index++;
if(index >= 512) {
return 0;
}
}
// 计算pte基址
pte_base = ((index + 0x1fffe00) << 39);
return pte_base;
}
MmGetVirtualForPhysical
函数实现可以从内核中找到,如下:
ULONG64 MmGetVirtualForPhysical(unsigned __int64 a1)
{
return (a1 & 0xFFF) + (*(ULONG64 *)(48 * (a1 >> 12) - 0x57FFFFFFFF8i64) << 25 >> 16);
}