内存分页笔记

Paging

32-Bit Paging

启用条件:CR0.PG = 1 and CR4.PAE = 0.

寻址范围:40-bit physical addresses, linear addresses are limited to 32 bits.

寻址方式:

32bit

32bit2

PAE Paging

启用条件:CR0.PG = 1, CR4.PAE = 1, and IA32_EFER.LME = 0.

寻址范围:52-bit physical addresses, linear addresses are limited to 32 bits.

寻址方式:

PAE

PAE2

PAE3

4-Level Paging

启用条件:CR0.PG = 1, CR4.PAE = 1, and IA32_EFER.LME = 1.

寻址范围:52-bit physical addresses, linear addresses are limited to 48 bits.

寻址方式:

4level

4level2

4level3

4level4

其中,某些entry的bit7决定页面大小,PTE的bit9与copy-on-write技术有关。

代码测试(以x64 4-Level Paging为例)

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
#define GetPml4Base(cr3) ((ULONG64)(cr3) & 0xFFFFFFFFF000)
#define GetPdptBase(pml4e) ((ULONG64)(pml4e) & 0xFFFFFFFFF000)
#define GetPdBase(pdpte) ((ULONG64)(pdpte) & 0xFFFFFFFFF000)
#define GetPtBase(pde) ((ULONG64)(pde) & 0xFFFFFFFFF000)
#define GetPageBase(pte) ((ULONG64)(pte) & 0xFFFFFFFFF000)

#define GetPml4eIndex(va) ((((ULONG64)(va)) >> 39) & 0x1FF)
#define GetPdpteIndex(va) ((((ULONG64)(va)) >> 30) & 0x1FF)
#define GetPdeIndex(va) ((((ULONG64)(va)) >> 21) & 0x1FF)
#define GetPteIndex(va) ((((ULONG64)(va)) >> 12) & 0x1FF)
#define GetPageOffset(va) ((ULONG64)(va) & 0xFFF)

#define GetPml4eAddress(pml4base, index) ((ULONG64)(pml4base) + ((index) << 3))
#define GetPdpteAddress(pdptbase, index) ((ULONG64)(pdptbase) + ((index) << 3))
#define GetPdeAddress(pdebase, index) ((ULONG64)(pdebase) + ((index) << 3))
#define GetPteAddress(ptebase, index) ((ULONG64)(ptebase) + ((index) << 3))
#define GetPhysicalAddress(pte, offset) ((ULONG64)(pte) + offset)

BOOLEAN ReadPhysicalAddress(ULONG64 addr, SIZE_T len, PVOID buf)
{
PHYSICAL_ADDRESS pa;
PVOID MapAddr;

pa.QuadPart = addr;
MapAddr = MmMapIoSpace(pa, len, MmCached);
if (MapAddr != NULL)
{
memcpy(buf, MapAddr, len);
MmUnmapIoSpace(MapAddr, len);
return TRUE;
}
else
{
return FALSE;
}
}

ULONG64 MyVtop(ULONG64 cr3, ULONG64 va)
{
ULONG64 Pml4eAddr, PdpteAddr, PdeAddr, PteAddr;
ULONG64 pml4e, pdpte, pde, pte;
ULONG64 pa;

Pml4eAddr = GetPml4eAddress(GetPml4Base(cr3), GetPml4eIndex(va));
KdPrint(("Pml4eAddr:%p\n", Pml4eAddr));
if (ReadPhysicalAddress(Pml4eAddr, sizeof(pml4e), &pml4e))
{
if (((PHARDWARE_PTEX64)&pml4e)->valid == 0)
{
KdPrint(("pml4e is not valid, va:%p, pml4e:%p\n", va, pml4e));
return NULL;
}
}
else
{
KdPrint(("read pml4e failed, va:%p\n", va));
return NULL;
}

PdpteAddr = GetPdpteAddress(GetPdptBase(pml4e), GetPdpteIndex(va));
KdPrint(("PdpteAddr:%p\n", PdpteAddr));
if (ReadPhysicalAddress(PdpteAddr, sizeof(pdpte), &pdpte))
{
if (((PHARDWARE_PTEX64)&pdpte)->valid == 0)
{
KdPrint(("pdpte is not valid, va:%p, pdpte:%p\n", va, pdpte));
return NULL;
}
}
else
{
KdPrint(("read pdpte failed, va:%p\n", va));
return NULL;
}

PdeAddr = GetPdeAddress(GetPdBase(pdpte), GetPdeIndex(va));
KdPrint(("PdeAddr:%p\n", PdeAddr));
if (ReadPhysicalAddress(PdeAddr, sizeof(pde), &pde))
{
if (((PHARDWARE_PTEX64)&pde)->valid == 0)
{
KdPrint(("pde is not valid, va:%p pde:%p\n", va, pde));
return NULL;
}
}
else
{
KdPrint(("read pde failed, va:%p\n", va));
return NULL;
}

PteAddr = GetPteAddress(GetPtBase(pde), GetPdpteIndex(va));
KdPrint(("PteAddr:%p\n", PteAddr));
if (ReadPhysicalAddress(PteAddr, sizeof(pte), &pte))
{
if (((PHARDWARE_PTEX64)&pte)->valid == 0)
{
KdPrint(("pte is not valid, va:%p pte:%p\n", va, pte));
return NULL;
}
}
else
{
KdPrint(("read pte failed, va:%p\n", va));
return NULL;
}

pa = GetPhysicalAddress(GetPageBase(pte), GetPageOffset(va));
return pa;
}

Windows页表自映射

介绍

Windows为了方便自己管理内存于是采用了页表自映射(Self-mapping page tables)的方法以便于用VA操作。

以Win7 x64 7601为例子

已知定义

1
2
3
4
5
6
7
8
#define VIRTUAL_ADDRESS_BITS 48
#define VIRTUAL_ADDRESS_MASK ((((ULONG_PTR)1) << VIRTUAL_ADDRESS_BITS) - 1)
#define PTI_SHIFT 12
#define PTE_SHIFT 3
#define PTE_BASE 0xFFFFF68000000000UI64

#define MiGetPteAddress(va) \
((PMMPTE)(((((ULONG_PTR)(va) & VIRTUAL_ADDRESS_MASK) >> PTI_SHIFT) << PTE_SHIFT) + PTE_BASE))

利用windbg先转换0x400000

1
2
3
4
5
6
7
8
kd> !vtop 0 0x400000
Amd64VtoP: Virt 0000000000400000, pagedir 00000000bb8f7000
Amd64VtoP: PML4E 00000000bb8f7000
Amd64VtoP: PDPE 00000000ba746000
Amd64VtoP: PDE 00000000bbec7010
Amd64VtoP: PTE 00000000bb2c8000
Amd64VtoP: Mapped phys 00000000bb656000
Virtual address 400000 translates to physical address bb656000.

将0x400000根据MiGetPteAddress计算得到0xFFFFF68000002000,再次利用windbg转换

1
2
3
4
5
6
7
8
kd> !vtop 0 0xFFFFF68000002000
Amd64VtoP: Virt fffff68000002000, pagedir 00000000bb8f7000
Amd64VtoP: PML4E 00000000bb8f7f68
Amd64VtoP: PDPE 00000000bb8f7000
Amd64VtoP: PDE 00000000ba746000
Amd64VtoP: PTE 00000000bbec7010
Amd64VtoP: Mapped phys 00000000bb2c8000
Virtual address fffff68000002000 translates to physical address bb2c8000.

实际上就是根据这个特定的PTE_BASE计算出pml4中的某一项的索引并让这一项再指向pml4(或者应该反过来说通过特定的索引计算出PTE_BASE)。在这里这个索引等于0x1ED,将这个索引左移39位(va中pml4e索引的位置),并将bit63:48填充为1即可得到PTE_BASE=0xFFFFF68000000000。

某种意义上来说就是让虚拟地址到物理地址的翻译过程中少了一次查表的过程。

PTE_BASE随机化

从Win10 14316开始,PTE_BASE不再是WRK中给出的0xFFFFF68000000000,而是随机初始化的。

引用大表哥hzqst逆向鹅厂定位PTE_BASE的例子。

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
char __fastcall InitializePteBase(char a1)
{
char v1; // bl
PHYSICAL_ADDRESS pml4t; // rdi
__int64 *pml4t_va; // r11
int slot; // edx
__int64 index; // rcx
__int64 v6; // r8

v1 = 0;
if ( a1 )
{
pml4t.QuadPart = __readcr3();
pml4t_va = (__int64 *)MmMapIoSpace(pml4t, 0x1000ui64, MmCached);
if ( pml4t_va )
{
slot = 0;
index = 0i64;
while ( (pml4t_va[index] & 0xFFFFFFFFF000i64) != pml4t.QuadPart )
{
++index;
++slot;
if ( index >= 512 )
goto LABEL_8;
}
v1 = 1;
v6 = (slot + 0x1FFFE00i64) << 39;
g_pte_base = (slot + 0x1FFFE00i64) << 39;
g_pxe_selfmapping_index = slot;
g_pde_base = v6 + ((__int64)slot << 30);
g_ppe_base = v6 + ((__int64)slot << 30) + ((__int64)slot << 21);
g_pxe_base = (void *)(g_ppe_base + ((__int64)slot << 12));
g_pxe_end = (__int64)g_pxe_base + 4096;
g_pte_end = v6 + 0x8000000000i64;
LABEL_8:
MmUnmapIoSpace(pml4t_va, 0x1000ui64);
}
}
else
{
g_pxe_selfmapping_index = 493i64;
v1 = 1;
g_pte_base = 0xFFFFF68000000000i64;
g_pde_base = 0xFFFFF6FB40000000i64;
g_ppe_base = 0xFFFFF6FB7DA00000i64;
g_pxe_base = (void *)0xFFFFF6FB7DBED000i64;
g_pxe_end = 0xFFFFF6FB7DBEE000i64;
g_pte_end = 0xFFFFF70000000000i64;
}
return v1;
}

这段代码的意思就是在pml4中找到指向自己的那一项的索引,然后把这个索引左移39位再把bit63:48补上1就能得到PTE_BASE。而PDE_BASE就是把这个索引左移到VA中pdpte索引的位置再加上PTE_BASE就能得到,PPE_BASE、PXE_BASE同理,某种意义上相当于少了几次查表过程。

注:win10 1803之后MmMapIoSpace不再允许映射页表相关的物理地址,因此只能用MmGetVirtualForPhysical代替,或者自己实现映射物理地址。