x64分页 - 获取PTE基址

@lzeroyuee  October 4, 2020

分页

x64下分页相对于x86做了扩展,采用了4级页表,Intel与微软的定义上对于前两个页表的命名上有所区别

在Intel手册中,对于这四级页表的命名为:PML4E、PDPTE、PDE、PTE

1.png

在微软的定义上,这四级页表分别命名为:PXE、PPE、PDE、PTE,例如:

2.png

对于一个虚拟地址,其有效位有48位,其剩余的高16位为高位的符号扩展,对这48位分解成四个9位(四级页表索引),剩余12位表示物理页面的页内偏移,每个页表项占8字节

以4k页面为例,如图:

3.png

且,在虚拟内存空间中

  • 指向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自动完成了转换

4.png

获取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_BASEPPE_BASEPXE_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;
}

5.png

MmGetVirtualForPhysical函数实现可以从内核中找到,如下:

ULONG64 MmGetVirtualForPhysical(unsigned __int64 a1)
{
    return (a1 & 0xFFF) + (*(ULONG64 *)(48 * (a1 >> 12) - 0x57FFFFFFFF8i64) << 25 >> 16);
}

添加新评论