x64 分页管理(二)页表自映射

😄

1 遍历页表

1.1 使用物理地址遍历页表

有 2 种方法使用物理地址来遍历页表结构:

  1. 使用如下图方法。

    68.png

  2. 使用 CR3 逐级遍历。具体过程见上一篇文章 2.5 章节。

1.2 使用虚拟地址遍历页表

实际在代码中,由于分页模式的存在,我们无法直接操作物理地址。但是 Windows 会将对应的页表结构映射到虚拟地址中,如 x86 下将PTE 页表基址映射到 0xC0000000,将 PDE 映射到 0xC0300000。Drunkmars 在文章《x64下隐藏可执行内存》指出在 Windows 7 x64 PTE_BASE = fffff68000000000 固定映射,但是从 Windows 10 1607 开始页表采用随机地址映射。

这一块主要涉及两种获取 PTE_BASE 的方法和解析 PTE 的方式:

获取 PTE_BASE 两种常用方法:

  1. 动态获取 PTE_BASE 对应的虚拟地址,可以通过特征码搜索。
  2. 利用页表自映射(Self-Referencing Page Tables)首先获取 PLM4T 的虚拟基地址,然后再获取 PTE_BASE。

解析 PTE 的方式的方式:

PTE 表在物理内存中是一块 4KB 大小的物理页,但是所有物理页面的 PTE 映射到虚拟地址后,这些虚拟地址都是连续的。这就有一个比较有趣的现象,表示 PTE 的虚拟地址是连续的(不止4KB)。

示例1:

  1. 先拿到一个虚拟地址,地址 0x0。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    kd> !process 0 0 notepad.exe
    PROCESS ffff9386ffaa4080
    SessionId: 1 Cid: 0858 Peb: 555dab6000 ParentCid: 0c5c
    DirBase: 3cb63002 ObjectTable: ffffe68e661f4d80 HandleCount: 239.
    Image: notepad.exe

    kd> .process /i ffff9386ffaa4080

    kd> !pte 0
    VA 0000000000000000
    PXE at FFFF8FC7E3F1F000 PPE at FFFF8FC7E3E00000 PDE at FFFF8FC7C0000000 PTE at FFFF8F8000000000
    contains 8A0000001FE61867 contains 0000000000000000
    pfn 1fe61 ---DA--UW-V contains 0000000000000000
    not valid

    可以看到,线性地址 0x0 对应的 PTE 为 0xFFFF8F8000000000。

  2. 解析地址 0x20DEAC52A40 对应的 PTE。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    kd> dq FFFF8F8000000000+((0x20D`EAC52A40>>0xC)<<3)
    ffff8f81`06f56290 81000000`3d28a867 00002000`45bb6880
    ffff8f81`06f562a0 81000000`6cb79847 00002000`1bd78880
    ffff8f81`06f562b0 81000000`24545805 0005a817`00002094

    kd> !vtop 3cb63000 0x20DEAC52A40
    Amd64VtoP: Virt 0000020deac52a40, pagedir 000000003cb63000
    Amd64VtoP: PML4E 000000003cb63020
    Amd64VtoP: PDPE 0000000035a051b8
    Amd64VtoP: PDE 0000000025786ab0
    Amd64VtoP: PTE 0000000070e29290
    Amd64VtoP: Mapped phys 000000003d28aa40
    Virtual address 20deac52a40 translates to physical address 3d28aa40.

    kd> !du 3d28aa40
    #3d28aa40 "20221110"

    可以看到地址 0x20DEAC52A40 对应的 PTE 为 ffff8f81'06f56290。

从上面这个示例可以总结几点:

  • 为什么选定 0x0 的 PTE 作为转换其他线性地址对应的基础地址,也就是 PTE_BASE?是因为在线性地址中,所有的 PTE 都是从 0x0 对应的 PTE 进行线性排列的。

    • 先右移 12 位,目的就是拿到 PTi 索引。每一项为 8 字节,所以还要左移 3 位。
    • 然后加上基地址 FFFF8F8000000000 即可得到目标地址对应的 PTE。

    76.png

  • 计算 PTE 的公式:PTE = PTE_BASE + PLM4i*512GB + PDPTi*1GB + PDi*2MB + PTi*8,实际上右移 12 位后低 9 位就是 PTi,左移 3 位就是 *8,只不过使用移位更方便一些。

2 页表自映射

微软管理页表时,使用了页表自映射的技术(Self-Referencing Page Tables,或 Self-ref entry)。

其核心就是:在某一级的页表中存在一个条目,该条目指向当前级页表基址。

  • 32-bit paging、PAE 分页下,该条目位于 PDE 中。
  • 4-level 分页下,该条目位于 PLM4T 中。

注意:Cr3 寄存器中存储了最高级页表(PML4)的表基物理地址。

在 4-level 分页下:

该自映射的条目位于 PLM4T 中,同时该条目指向 PLM4T 表。因为 CR3 也指向 PLM4T 表,所以:这个 PLM4E entry 保存的物理地址 = CR3,这种自映射也叫做最高级页表自映射。

参考以下文章,对 Microsoft 的页表自映射进行分析:

2.1 推导自映射页表条目

假设 PML4 表中索引位置 0x100 的元素用于内存管理且满足自映射,那么此时用于内存管理的虚拟地址空间为:

1
0xFFFF8000`00000000 ~ 0xFFFF807F`FFFFF000

查询流程:

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] = 物理页面
  1. 推指向 PLM4T 表的虚拟基址。假设在 PLM4T 中,这个自映射页表条目的虚拟地址对应的 4 级分页索引分别为:$x,y,z,r$。页表解析规则:

    1
    2
    3
    4
    ![CR3   + x * 8] = PDPTE = Cr3
    ![PDPTE + y * 8] = PDE
    ![PDE + z * 8] = PTE
    ![PTE + r * 8] = PLM4T基址 = Cr3

    当且仅当 $x = y = z = r$ 时,满足上述条件。所以指向 PLM4T 表基址的虚拟地址的特点为:PML4i == PDTi == PDi == PTi == Index。

  2. 推导指向 PDPT 表的虚拟基址。假设这个虚拟地址对应的 4 级分页索引分别为:$a,b,c,d$。

    1
    2
    3
    4
    ![CR3   + a * 8] = PDPTE = Cr3
    ![PDPTE + b * 8] = PDE
    ![PDE + c * 8] = PTE
    ![PTE + d * 8] = X

    注意 ![PTE + d * 8] != Cr3,不再指向 PLM4T 表,而是 d = 0。

经过最后的推导,所有指向每级页表的虚拟地址有如下特征:

  • PML4 表的虚拟地址:PML4i == PDTi == PDi == PTi == Index,页内偏移为0。
  • PDPT 表的虚拟地址:PML4i == PDTi == PDi == Index && PTi == 0,页内偏移为0。
  • PDT 表的虚拟地址:PML4i == PDTi == Index && PDi == 0 && PTi == 0,页内偏移为0。
  • PTT 表的虚拟地址:PML4i == Index && PDTi == 0 && PDi == 0 && PTi == 0,页内偏移为0。

这个 Index 就是自映射表项在 PML4 表中的索引。

所以只要得到 PLM4T 表的虚拟地址,就得到了Index,就可以推导PDPTE、PDT、PTT:

1
2
3
PDPT_BASE = (PLM4T_BASE >> (9+12)) << 21	PML4i == PDTi == PDi == Index && PTi == 0;
PDT_BASE = (PLM4T_BASE >> (9*2+12)) << 30 PML4i == PDTi == Index && PDi == 0 && PTi == 0;
PTT_BASE = (PLM4T_BASE >> (9*3+12) << 39) PML4i == Index && PDTi == 0 && PDi == 0 && PTi == 0;

使用PLM4T_BASE推导PTE_BASE

利用页表自映射的一些结论来获取 PML4 表基址:

PML4 表基址的内容为 Cr3 的值,并且位于 PML4 表所在的页面内。因为 Cr3 里保存了 PML4 的表基物理地址,所以可以通过映射 Cr3 物理地址的虚拟地址(该地址指向PLM4T),遍历这个虚拟地址页面的 512 个 8 字节的地址,哪个地址符合上述条件,哪个地址就是 PML4 表基址。下面给出驱动代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ULONG64 GetPml4Base()
{
PHYSICAL_ADDRESS pCr3 = { 0 };
pCr3.QuadPart = __readcr3() & 0xfffffffffffff000;
PULONG64 Plm4t_Base = MmGetVirtualForPhysical(pCr3); //得到CR3映射的虚拟地址

ULONG64 index = 0;
while ((Plm4t_Base[index] & 0xFFFFFFFFF000) != pCr3.QuadPart)//注意需要屏蔽掉高16位属性,否则是错误的
{
index++;
if (index >= 512)
{
return -1;
}
}
// ULONG64 pte_base = 0;
// pte_base = ((index + 0x1fffe00) << 39);
// return pte_base;
return ((ULONG64)Plm4t_Base & 0xFFFFFFFFFFFFF000);
}

注意:虽然一开始就得到了Cr3映射的虚拟地址 VirtualAddress_1,该地址虽然指向 PLM4T 表,但不是自映射条目的虚拟地址。 循环中找到的虚拟地址 VirtualAddress_2 也指向PLM4T表,此时的虚拟地址才是自映射条目的虚拟地址。即数值上:VirtualAddress_1 != VirtualAddress_2,但是两个虚拟地址指向的内容相等,都是 PLM4T 表的基址。

得到 PLM4T 表的虚拟地址,就得到了Index,就可以推导PDPTE、PDT、PTT:

1
2
3
PDPT_BASE = (PLM4T_BASE >> (9+12)) << 21	PML4i == PDTi == PDi == Index && PTi == 0;
PDT_BASE = (PLM4T_BASE >> (9*2+12)) << 30 PML4i == PDTi == Index && PDi == 0 && PTi == 0;
PTT_BASE = (PLM4T_BASE >> (9*3+12) << 39) PML4i == Index && PDTi == 0 && PDi == 0 && PTi == 0;

如上面实验的 0x0 地址,其 4 级页表的虚拟地址满足上述的特征:

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
kd> !pte 0
VA 0000000000000000
PXE at FFFF8FC7E3F1F000 PPE at FFFF8FC7E3E00000 PDE at FFFF8FC7C0000000 PTE at FFFF8F8000000000
contains 8A0000001FE61867 contains 0000000000000000
pfn 1fe61 ---DA--UW-V contains 0000000000000000
not valid

kd> !vtop 3cb63000 FFFF8FC7E3F1F000
Amd64VtoP: Virt ffff8fc7e3f1f000, pagedir 000000003cb63000
Amd64VtoP: PML4E 000000003cb638f8
Amd64VtoP: PDPE 000000003cb638f8
Amd64VtoP: PDE 000000003cb638f8
Amd64VtoP: PTE 000000003cb638f8
Amd64VtoP: Mapped phys 000000003cb63000
Virtual address ffff8fc7e3f1f000 translates to physical address 3cb63000.

kd> !dq 3cb63000
#3cb63000 8a000000`241fe867 00000000`00000000
#3cb63010 00000000`00000000 00000000`00000000
#3cb63020 8a000000`35a05867 00000000`00000000
#3cb63030 00000000`00000000 00000000`00000000
#3cb63040 00000000`00000000 00000000`00000000
#3cb63050 00000000`00000000 00000000`00000000
#3cb63060 00000000`00000000 00000000`00000000
#3cb63070 00000000`00000000 00000000`00000000

2.2 推导PTE公式

为什么推导 PTE 虚拟地址要使用 PTE = PTE_BASE + (PLM4i*2^(9*3) + PDPTi*2^(9*2) + PDi*2^(9) + PTi)*8 ?

是因为 $PLM4i*2^{9*3} + PDPTi*2^{9*2} + PDi*2^{9} + PTi$ 是基于 PTE_BASE 的偏移量,是一个偏移量。

同理:

1
2
3
4
PTE	= PTE_BASE    + (PLM4i*2^(9*3)  + PDPTi*2^(9*2)  + PDi*2^(9) + PTi)*8
PDE = PDE_BASE + (PLM4i*2^(9*2) + PDPTi*2^(9) + PDi)*8
PDPTE = PDPTE_BASE + (PLM4i*2^(9) + PDPTi)*8
PLM4E = PLM4E_BASE + PLM4i*8

即:

1
2
3
4
PLM4E	=  ((VA & 0x0000FFFF`FFFFFFFF) >> 39) << 3 + PLM4E_BASE
PDPTE = ((VA & 0x0000FFFF`FFFFFFFF) >> 30) << 3 + PDPTE_BASE
PDE = ((VA & 0x0000FFFF`FFFFFFFF) >> 21) << 3 + PDE_BASE
PTE = ((VA & 0x0000FFFF`FFFFFFFF) >> 12) << 3 + PTE_BASE

使用 !pte 0 可以得到自 0 地址的 PLM4T、PDPT、PDT、PTT 表虚拟地址基址。 同时可以根据得到的虚拟地址解析出自映射的 Index。

是因为每级页表起始虚拟地址地总都是从 0 映射的位置开始的。如地址 0 的 PTE = 0xFFFF0808'00000000开始,所以所有虚拟地址的 PTE 都是从 0xFFFF0808'00000000 开始。

2.3 推导公式总结

  1. 根据页表自映射机制,获取 PLM4T/PTT 基址。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    ULONG64 GetPml4Base()
    {
    PHYSICAL_ADDRESS pCr3 = { 0 };
    pCr3.QuadPart = __readcr3() & 0xfffffffffffff000;
    PULONG64 Plm4t_Base = MmGetVirtualForPhysical(pCr3); //得到CR3映射的虚拟地址

    ULONG64 index = 0;
    while ((Plm4t_Base[index] & 0xFFFFFFFFF000) != pCr3.QuadPart)//注意需要屏蔽掉高16位属性,否则是错误的
    {
    index++;
    if (index >= 512)
    {
    return -1;
    }
    }
    // ULONG64 Pte_Base = 0;
    // Pte_Base = ((index + 0x1fffe00) << 39);
    // return Pte_Base;
    return ((ULONG64)Plm4t_Base & 0xFFFFFFFFFFFFF000);
    }
  2. 根据 PLM4T_BASE、页表自映射原理,推导各级页表基址。

    1
    2
    3
    PDPT_BASE = (PLM4T_BASE >> (9+12)) << 21	PML4i == PDPi == PDi == Index && PTi == 0;
    PDT_BASE = (PLM4T_BASE >> (9*2+12)) << 30 PML4i == PDPi == Index && PDi == 0 && PTi == 0;
    PTT_BASE = (PLM4T_BASE >> (9*3+12) << 39) PML4i == Index && PDPi == 0 && PDi == 0 && PTi == 0;
  3. 根据 VA、各级页表基址,推导该 VA 对应的页表条目。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    参考《处理器虚拟化技术 6.1.5 p422》
    IA-32e 4 级分页模式下,每级页表占 1 页(4KB)大小,每个页表条目为 8 bytes,则每级页表共有 512 个条目。

    1、每个 PLM4E 管理 512-GByte 物理内存,[bits 47:39] 是 512-GByte Page 的 Page Number。则 [bits 47:39] = PLM4i = PageFrameNumber。
    2、每个 PDPTE 管理 1-GByte 物理内存,[bits 47:30] 是 1-GByte Page 的 Page Number。
    也就是将物理内存看成是 1-GByte 分页。则 [bits 47:30] = PDPi = PageFrameNumber。
    3、每个 PDE 管理 2-MByte 物理内存,[bits 47:21] 是 2-MByte Page的 Page Number。
    也就是将物理内存看成是 2-MByte 分页。则 [bits 47:21] = PDi = PageFrameNumber。
    4、每个 PTE 管理 4-KByte 物理内存,[bits 47:12] 是 4-KByte Page的 Page Number。
    也就是将物理内存看成是 4-KByte 分页。则 [bits 47:12] = PTi = PageFrameNumber。
    */
    PLM4E = PLM4E_BASE + ((VA & 0x0000FFFF`FFFFFFFF) >> 39) << 3;

    PDPTE = PDPTE_BASE + ((VA & 0x0000FFFF`FFFFFFFF) >> 30) << 3;
    PDE = PDE_BASE + ((VA & 0x0000FFFF`FFFFFFFF) >> 21) << 3;
    PTE = PTE_BASE + ((VA & 0x0000FFFF`FFFFFFFF) >> 12) << 3;

    注意:

    1
    PTE	=  PTE_BASE + ((VA & 0x0000FFFF`FFFFFFFF) >> 12) << 3;

    这里的 PTE_BASE 是虚拟地址,不是物理地址,该公式等价于如下:

    1
    Phy(PTE) = Phy(PTE_BASE) + ((VA >> 12) & 0x1FF) << 3;
  4. 根据 PTE 反推 PDE、PDPTE、PLM4E。

    另外一个疑问,已知一个地址的 PTE(虚拟地址),如何得到该虚拟地址对应的 PDE、PDPTE、PLM4E 对应的虚拟地址。

    答案:只需要将当前得到的 PTE 虚拟地址当做一个普通地址,然后解析该地址的 PTE。因为新的 PTE 指向的物理地址保存的内容就是原来的 PTE。

    原理:先获取虚拟地址 VA 的 PTE ,该 PTE 指向该地址。如果将这个 PTE 当成是一个普通地址 VAx,通过这个 PTE 得到的 PTEx,即为这个 PTE 的 PDE。

    1
    2
    3
    4
    5
    6
    7
    // 已知 PTE_BASE
    // 已知公式:PTE = PTE_BASE + ((VA & 0x0000FFFF`FFFFFFFF) >> 12) << 3;
    // 有如下关系:
    PTE = PTE_BASE + ((VA & 0x0000FFFF`FFFFFFFF) >> 12) << 3;
    PDE = PTE_BASE + (PTE & 0x0000FFFF`FFFFFFFF >> 0xC) << 3;
    PDPTE = PTE_BASE + (PDE & 0x0000FFFF`FFFFFFFF >> 0xC) << 3;
    PLM4E = PTE_BASE + (PDPTE & 0x0000FFFF`FFFFFFFF >> 0xC) << 3;

    举例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // VA = 0xFFFF020D`EAC52A40
    // PTE_BASE = FFFF8F8000000000, 求得 PTE = 0xFFFF8f81`06f56290
    kd> dq FFFF8F8000000000+((0x20D`EAC52A40>>0xC)<<3)
    ffff8f81`06f56290 81000000`3d28a867 00002000`45bb6880
    ffff8f81`06f562a0 81000000`6cb79847 00002000`1bd78880
    ffff8f81`06f562b0 81000000`24545805 0005a817`00002094

    // 将 PTE(0xFFFF8f81`06f56290) 当成一个 VA,然后求这个 VA 的 PTE
    // 求得 0xffff8fc7`c0837ab0 即为这个 VA 的 PTEx
    // 即:VA = 0xFFFF020D`EAC52A40, PTE = 0xFFFF8f81`06f56290, PDE = 0xffff8fc7`c0837ab0
    // 然后还可以利用该 PDE 继续求得 PDPTE、PLM4E。
    kd> dq FFFF8F8000000000+((0x8f8106f56290>>0xC)<<3)
    ffff8fc7`c0837ab0 0a000000`70e29867 0a000000`182ba867
    ffff8fc7`c0837ac0 0a000000`13925867 00000000`00000000
    ffff8fc7`c0837ad0 00000000`00000000 00000000`00000000

3 分析 MmIsAddressValidEx

该函数对输入的参数 VirtualAddress 进行校验是否是一个有效的线性地址。该函数中给出了PTE_BASE 后推导该虚拟地址 PDE_BASE、PDPTE_BASE、PLM4E_BASE 的方法:

1
2
3
4
PTE	=  ((VA & 0x0000FFFF`FFFFFFFF) >> 12) << 3 + PTE_BASE
PDE = ((VA & 0x0000FFFF`FFFFFFFF) >> 21) << 3 + PDE_BASE
PDPTE = ((VA & 0x0000FFFF`FFFFFFFF) >> 30) << 3 + PDPTE_BASE
PLM4E = ((VA & 0x0000FFFF`FFFFFFFF) >> 39) << 3 + PLM4E_BASE

pCmpArr = FFFFA8542A150000 pCr3.QuadPart = 1AD000

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
.text:00000001402EDF10 // =============== S U B R O U T I N E =======================================
.text:00000001402EDF10
.text:00000001402EDF10
.text:00000001402EDF10 MmIsAddressValidEx proc near // CODE XREF: MiCommitExistingVad+69E↑p
.text:00000001402EDF10 // MiCommitExistingVad+DE2↑p ...
.text:00000001402EDF10
.text:00000001402EDF10 var_30 = qword ptr -30h
.text:00000001402EDF10 var_28_PTE = qword ptr -28h
.text:00000001402EDF10 var_20_PDE = qword ptr -20h
.text:00000001402EDF10 var_18_PDPTE = qword ptr -18h
.text:00000001402EDF10 var_10_PLM4E = qword ptr -10h
.text:00000001402EDF10 arg_0 = qword ptr 8
.text:00000001402EDF10
.text:00000001402EDF10 // FUNCTION CHUNK AT .text:000000014045F898 SIZE 00000056 BYTES
.text:00000001402EDF10
.text:00000001402EDF10 mov [rsp+arg_0], rbx
.text:00000001402EDF15 push rdi
.text:00000001402EDF16 sub rsp, 20h
.text:00000001402EDF1A mov rax, rcx // 保存 VirtualAddress
.text:00000001402EDF1D mov r10, rcx
.text:00000001402EDF20 sar rax, 2Fh // 算术右移 47 位(高位补符号位)
.text:00000001402EDF20 // 判断线性地址是用户模式地址还是内核地址:
.text:00000001402EDF20 // - 第48位 = 1:内核模式地址
.text:00000001402EDF20 // - 第48位 = 0:用户模式地址
.text:00000001402EDF24 inc rax
.text:00000001402EDF27 cmp rax, 1
.text:00000001402EDF2B ja loc_1402EE034
.text:00000001402EDF31 mov rdx, rcx
.text:00000001402EDF34 mov rdi, 0FFFFF68000000000h // PTE_BASE
.text:00000001402EDF3E shr rdx, 9
.text:00000001402EDF42 mov rcx, 7FFFFFFFF8h
.text:00000001402EDF4C and rdx, rcx
.text:00000001402EDF4F mov rax, rdi // (VA >> 9) & 0x7FFFFFFFF8
.text:00000001402EDF52 add rdx, rax // rdx = PTE = (VA >> 9) & 0x7FFFFFFFF8 + PTE_BASE
.text:00000001402EDF55 mov [rsp+28h+var_28_PTE], rdx
.text:00000001402EDF59 shr rdx, 9
.text:00000001402EDF5D and rdx, rcx
.text:00000001402EDF60 mov rax, rdi
.text:00000001402EDF63 add rdx, rax // rdx = PDE = (PTE >> 9) & 0x7FFFFFFFF8 + PTE_BASE
.text:00000001402EDF66 mov [rsp+28h+var_20_PDE], rdx
.text:00000001402EDF6B shr rdx, 9
.text:00000001402EDF6F and rdx, rcx
.text:00000001402EDF72 mov rax, rdi
.text:00000001402EDF75 add rdx, rax // rdx = PDPTE = (PDE >> 9) & 0x7FFFFFFFF8 + PTE_BASE
.text:00000001402EDF78 mov [rsp+28h+var_18_PDPTE], rdx
.text:00000001402EDF7D shr rdx, 9
.text:00000001402EDF81 and rdx, rcx
.text:00000001402EDF84 mov rax, rdi
.text:00000001402EDF87 add rdx, rax // rdx = PLM4E = (PDPTE >> 9) & 0x7FFFFFFFF8 + PTE_BASE
.text:00000001402EDF8A mov r11, 0FFFFF6FB7DBED000h //映射 PLM4T 表的基地址
.text:00000001402EDF94 mov [rsp+28h+var_10_PLM4E], rdx
.text:00000001402EDF99 mov edx, 4 // 4表示正在处理4-level分页的PLM4i
.text:00000001402EDF9E mov rbx, 0FFFFF6FB7DBED7F8h
.text:00000001402EDFA8
.text:00000001402EDFA8 loc_1402EDFA8: // CODE XREF: MmIsAddressValidEx+BF↓j
.text:00000001402EDFA8 mov r9, [rsp+rdx*8+28h+var_30]// r9 = PLM4E
.text:00000001402EDFAD dec rdx
.text:00000001402EDFB0 mov rcx, [r9] // rcx = PDPTE 的物理地址
.text:00000001402EDFB3 mov rax, r11
.text:00000001402EDFB6 cmp r9, rax
.text:00000001402EDFB9 jb short loc_1402EDFC3
.text:00000001402EDFBB mov rax, rbx // 没有开启1GB大页
.text:00000001402EDFBE cmp r9, rax
.text:00000001402EDFC1 jbe short loc_1402EDFDF // PLM4E <= 0FFFFF6FB7DBED7F8h
.text:00000001402EDFC3
.text:00000001402EDFC3 loc_1402EDFC3: // CODE XREF: MmIsAddressValidEx+A9↑j
.text:00000001402EDFC3 // MmIsAddressValidEx+D9↓j ...
.text:00000001402EDFC3 test cl, 1 // PDPTE_P
.text:00000001402EDFC6 jz short loc_1402EE034 // PDPTE_P = 0
.text:00000001402EDFC8 test cl, cl // 测试 bit7(PDPTE_PS)是否为1
.text:00000001402EDFCA js short loc_1402EE01D // PDPTE_PS = 1,开启1GB大页
.text:00000001402EDFCC test rdx, rdx
.text:00000001402EDFCF jnz short loc_1402EDFA8
.text:00000001402EDFD1
.text:00000001402EDFD1 loc_1402EDFD1: // CODE XREF: MmIsAddressValidEx+113↓j
.text:00000001402EDFD1 // MmIsAddressValidEx+122↓j
.text:00000001402EDFD1 mov al, 1 //地址有效
.text:00000001402EDFD3
.text:00000001402EDFD3 loc_1402EDFD3: // CODE XREF: MmIsAddressValidEx+126↓j
.text:00000001402EDFD3 mov rbx, [rsp+28h+arg_0] // rbx = 返回地址
.text:00000001402EDFD8 add rsp, 20h
.text:00000001402EDFDC pop rdi
.text:00000001402EDFDD retn
.text:00000001402EDFDD // ---------------------------------------------------------------------------
.text:00000001402EDFDE db 0CCh
.text:00000001402EDFDF // ---------------------------------------------------------------------------
.text:00000001402EDFDF
.text:00000001402EDFDF loc_1402EDFDF: // CODE XREF: MmIsAddressValidEx+B1↑j
.text:00000001402EDFDF test cs:MiFlags, 0C00000h
.text:00000001402EDFE9 jz short loc_1402EDFC3
.text:00000001402EDFEB mov rax, gs:188h
.text:00000001402EDFF4 mov r8, [rax+_KTHREAD.___u25.ApcState.Process]
.text:00000001402EDFFB cmp byte ptr [r8+390h], 1 // AddressPolicy = 1?
.text:00000001402EE003 jz short loc_1402EDFC3
.text:00000001402EE005 test cl, 1
.text:00000001402EE008 jz short loc_1402EE034
.text:00000001402EE00A test cl, 20h //A位,访问位
.text:00000001402EE00D jz loc_14045F898
.text:00000001402EE013 test cl, 42h //D、R/W
.text:00000001402EE016 jnz short loc_1402EDFC3
.text:00000001402EE018 jmp loc_14045F898
.text:00000001402EE01D // ---------------------------------------------------------------------------
.text:00000001402EE01D
.text:00000001402EE01D loc_1402EE01D: // CODE XREF: MmIsAddressValidEx+BA↑j
.text:00000001402EE01D mov rax, rdi // PTE_BASE
.text:00000001402EE020 cmp r10, rax // r10 = VirtualAddress
.text:00000001402EE023 jb short loc_1402EDFD1 // VirtualAddress < PTE_BASE
.text:00000001402EE025 mov r,ax, 0FFFFF6FFFFFFFFFFh
.text:00000001402EE02F cmp r10 rax
.text:00000001402EE032 ja short loc_1402EDFD1 //地址有效
.text:00000001402EE034
.text:00000001402EE034 loc_1402EE034: // CODE XREF: MmIsAddressValidEx+1B↑j
.text:00000001402EE034 // MmIsAddressValidEx+B6↑j ...
.text:00000001402EE034 xor al, al
.text:00000001402EE036 jmp short loc_1402EDFD3
.text:00000001402EE036 MmIsAddressValidEx endp

3.1 获取PLM4E

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
41
42
43
44
45
46
47
48
49
50
51
52
#include <ntddk.h>
//http://www.bytekits.com/blog/blog-ntifs-confilict.html
//wdm.h一般用于即插即用驱动。
//ntddk.h 一般用于非即插即用驱动,如我们常说的NT架构驱动。这类驱动一般用于无硬件支持的系统内核服务。
//Ntifs.h 包含 Ntddk.h,而 Ntddk.h 又包含 Wdm.h。


VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
UNREFERENCED_PARAMETER(pDriver);
DbgPrint("Goodbye~\n");
}

ULONG64 GetPml4Base()
{
PHYSICAL_ADDRESS pCr3 = { 0 };
pCr3.QuadPart = __readcr3() & 0xfffffffffffff000;
PULONG64 pCmpArr = MmGetVirtualForPhysical(pCr3); //得到CR3映射的虚拟地址

ULONG64 count = 0;
while ((pCmpArr[count] & 0xFFFFFFFFF000) != pCr3.QuadPart)//注意需要屏蔽掉高16位属性,否则是错误的
{
count++;
if (count >= 512)
{
return -1;
}
}
return ((ULONG64)pCmpArr & 0xFFFFFFFFFFFFF000);
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath)
{
UNREFERENCED_PARAMETER(pRegPath);
pDriver->DriverUnload = DriverUnload;
DbgPrint("Hello Driver!\n");
//DbgBreakPoint();
ULONG64 MmIsAddressValid_Addr = (ULONG64)&MmIsAddressValid;
// 注意下面的偏移是一个有符号数
ULONG64 MmIsAddressValidEx = (ULONG64)((INT32) * (PULONG32)(MmIsAddressValid_Addr + 5) + MmIsAddressValid_Addr + 9);
ULONG64 PTE_BASE = (ULONG64)*(PULONG64)(MmIsAddressValidEx + 0x26);
ULONG64 What_Fuck = (ULONG64) * (PULONG64)(MmIsAddressValidEx + 0x7C);

DbgPrint("MmIsAddressValid_Addr = %llX\n", MmIsAddressValid_Addr);
DbgPrint("MmIsAddressValidEx = %llX\n", MmIsAddressValidEx);
DbgPrint("PTE_BASE = %llX\n", PTE_BASE);
DbgPrint("What_Fuck = %llX\n", What_Fuck);

ULONG64 PLM4T_BASE = GetPml4Base();
DbgPrint("PLM4T_BASE = %llX\n", PLM4T_BASE);
return STATUS_SUCCESS;
}

输出如下:

1
2
3
4
5
MmIsAddressValid_Addr = FFFFF80066F44CC0
MmIsAddressValidEx = FFFFF80066D00F10
PTE_BASE = FFFFA80000000000
What_Fuck = FFFFA8542A150000
PLM4T_BASE = FFFFA8542A150000

查看一下地址 FFFFF80066F44CC0 是否是函数 MmIsAddressValid:

1
2
3
4
5
6
kd> u FFFFF80066F44CC0
fffff800`66f44cc0 4883ec28 sub rsp,28h
fffff800`66f44cc4 e847c2dbff call fffff800`66d00f10
fffff800`66f44cc9 4883c428 add rsp,28h
fffff800`66f44ccd c3 ret
fffff800`66f44cce cc int 3