Hypervisor(四):Extended Page Table(EPT)

😄

1 EPT 介绍

Extended Page Table(EPT)技术, 是 Intel 实现 Secondary Level Address Translation(SLAT)的方法。

启用 EPT(“enable EPT” VM-execution control is 1)之后,VM 的地址转译如下:GVA—>GPA—>HPA。即 Guest 的页表将 Guest Virtual Address 转译为 Guest Physical Address,新增的 EPT 页表将 GPA 转换为 HPA,即真正的物理地址。

可以为每个 VM 建立一个 EPT 页表,也可以为每个 VM 建立多个 EPT 页表。两者的不同如下:

  • 一个 VM 中多个 VMCS 使用同一个 EPT 页表时,需要考虑多核同步与互斥,效率较低。
  • 一个 VM 中每个 VMCS 拥有一个 EPT 页表时,地址转译效率高,不用考虑多核同步与互斥。

44.png

如上图,每一个 GPA 都要作为 EPT 分页结构的输入,将其转化为 HPA,最终读取物理地址中的值。在一个 GVA 转换为最终的 HPA 时,要经历 5 次 EPT 分页结构转译。

  • EPT 分页结构需要开发者自己构建,然后将 EPT PLM4T 表基址填充到 VM-execution control fields—EPT Point 处。
  • EPTP Point 类似于 Guest 的 CR3,它是 EPT 分页结构 EPT PLM4T 表的基址,是一个 HPA 值。
  • EPT 分页结构类似于 Guest 使用的分页结构,支持 48 位 GPA 转译的 4 级分页,也支持 57 位 GPA 转译的 5 级分页。本文以 4 级分页为例,页面大小支持 4KB、2MB、1GB。

1.1 EPTP 结构

EPTP 指针结构如下:

45.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 24.6.11
typedef struct _EPTP_POINT
{
ULONG64 value;
union
{
ULONG64 MemoryType : 3; // 0-Uncacheable(UC), 6-Write-back(WB)
ULONG64 PageWalkLength : 3; // 页表级数-1,使用 4 级页表时该值为 3
ULONG64 EnableAccessAndDirtyFlags : 1; // 该值为 1 时,启用 EPT 各级分页结构中的 Access、Dirty 位
ULONG64 AR_enforcement_ssp : 1; // Supervisor shadow-stack acess right
ULONG64 Reservd0 : 4;
ULONG64 PageFrameNumber : 36;
ULONG64 Reservd1 : 16;
} bits;
} EPTP_POINT,*PEPTP_POINT;

EPTP 中的 EnableAccessAndDirtyFlags 是否可以启用,依赖于处理器是否支持,MSR IA32_VMX_EPT_VPID_CAP(0x48C),该 MSR 报告 VPIDs 和 EPT 相关信息。

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
typedef struct _MSR_VMX_EPT_VPID_CAP
{
ULONG64 value;
union
{
// RWX support
//
ULONG64 ept_xo_support : 1;
ULONG64 ept_wo_support : 1;
ULONG64 ept_wxo_support : 1;

// Guest address width support
//
ULONG64 gaw_21 : 1;
ULONG64 gaw_30 : 1;
ULONG64 gaw_39 : 1;
ULONG64 gaw_48 : 1;
ULONG64 gaw_57 : 1;

// Memory type support
ULONG64 uc_memory_type : 1;
ULONG64 wc_memory_type : 1;
ULONG64 rsvd0 : 2;
ULONG64 wt_memory_type : 1;
ULONG64 wp_memory_type : 1;
ULONG64 wb_memory_type : 1;
ULONG64 rsvd1 : 1;

// Page size support
ULONG64 pde_2mb_pages : 1;
ULONG64 pdpte_1gb_pages : 1;
ULONG64 pxe_512gb_page : 1;
ULONG64 pxe_1tb_page : 1;

// INVEPT support
ULONG64 invept_supported : 1;
ULONG64 ept_accessed_dirty_flags : 1;
ULONG64 ept_violation_advanced_information : 1;
ULONG64 supervisor_shadow_stack_control : 1;
ULONG64 individual_address_invept : 1;
ULONG64 single_context_invept : 1;
ULONG64 all_context_invept : 1;
ULONG64 rsvd2 : 5;

// INVVPID support
ULONG64 invvpid_supported : 1;
ULONG64 rsvd7 : 7;
ULONG64 individual_address_invvpid : 1;
ULONG64 single_context_invvpid : 1;
ULONG64 all_context_invvpid : 1;
ULONG64 single_context_global_invvpid : 1;
ULONG64 rsvd8 : 20;
} bits;
} MSR_VMX_EPT_VPID_CAP, *PMSR_VMX_EPT_VPID_CAP;

在构建 EPT 分页结构之前,当我们启用 secondary controls 的 “enable EPT”之前,需要调用以下函数检查是否具备开启 EPT 的条件。参考 MMU Virtualization via Intel EPT: Implementation – Part 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
BOOLEAN is_ept_available( void )
{
MSR_VMX_EPT_VPID_CAP cap_msr;
cap_msr.value = __readmsr(IA32_VMX_EPT_VPID_CAP_MSR_ADDRESS);

if( !cap_msr.bits.ept_xo_support ||
!cap_msr.bits.gaw_48 ||
!cap_msr.bits.wb_memory_type ||
!cap_msr.bits.pde_2mb_pages ||
!cap_msr.bits.pdpte_1gb_pages ||
!cap_msr.bits.invept_supported ||
!cap_msr.bits.single_context_invept ||
!cap_msr.bits.all_context_invept ||
!cap_msr.bits.invvpid_supported ||
!cap_msr.bits.individual_address_invvpid ||
!cap_msr.bits.single_context_invvpid ||
!cap_msr.bits.all_context_invvpid ||
!cap_msr.bits.single_context_global_invvpid )
{
return FALSE;
}

return TRUE;
}

1.2 VPID 和 PCID

我们知道,TLB 中保存虚拟地址和物理地址的映射关系。引入 VPID 之前,每一次 VMX operation transport (VM-exit、VM-entry)都会刷新 TLB,严重影响性能。

我们知道,TLB 中保存虚拟地址和物理地址的映射关系。引入 VPID 之前,每一次 VMX operation transport (VM-exit、VM-entry)都会刷新 TLB,严重影响性能。

VM-entry 和 VM-exit 发生地址空间改变时 (aka the reload of CR3.),由于 VPID 的存在,可以使 TLB 无需刷新。引入 VPID 之后,TLB 保存的映射关系有如下变化:

{虚拟地址 —> 物理地址} 变为 {VPID,虚拟地址 —> 物理地址}。Host 的 VPID为 0,Guest 的 VPID 非 0。

首先要理清楚下面几个概念:

  1. 在使用多个虚拟处理器的 VM 中,一个 VM 使用多个 VMCS,每个 VMCS 对应着一个虚拟处理器。大部分 VT 框架都会将 VMCS 做亲核性绑定,即一个 VMCS 绑定一个逻辑处理器(这些框架中一般都是一个虚拟处理器直接映射一个逻辑处理器,不让一个逻辑处理器在不同的 VMCS 切换的情况发生)。
  2. 如果没有做 VMCS 的亲核性绑定,则可以手动地在 VMM 中使用 VMPTRLD 指令使 VMCS 运行在不同的逻辑处理器上。
  3. VPID 的引入,对于我们这种虚拟化一个已经运行的系统(Type 2 Hypervisor)来说,在 VM-entry 和 VM-exit 时,VMM 和 VM 地址空间切换时,由于 VPID 的存在,TLB可以不刷新。VMM 的 VPID = 0,VM 的 VPID ≠ 0。并且每个 VMCS对应的 VPID 值不同。
  4. VPID 是和 VMCS 关联的(一个 VMCS 对应一个虚拟处理器),每个 VMCS 拥有一个非 0 值的 VPID(16 bits),由 VMM 填充在 VM-execution control fields 的 Virtual-Processor Identifier (VPID) 字段中。
  5. PCID(进程上下文 ID),是和进程 CR3 关联的,每个进程 CR3 低 12 位即为 PCID 值(12 bits)。有了 PCID 之后,不同进程切换时 TLB 无需刷新。使用 PCID 的 2 个条件如下:
    • 只能在 IA-32e 模式下使用(IA32_EFER_LMA = 1)。
    • 当 CPUID.01H:ECX[17] = 1 时,软件才可以置 CR4.PCIDE = 1,开启 PCID 功能。
  6. 在支持 VMX 的处理器中,PCID、VPID 可以同时使用。

1.3 MTRRs

本系列的第一篇文章简要讨论了内存类型范围寄存器(MTRR)。在最简单的意义上,这些寄存器用于将内存缓存类型与系统内存中的物理地址范围相关联。它们由BIOS(通常)初始化,旨在优化各种内存的访问。

2 EPT 分页结构

本节分两种风格展示 EPT 各级分页结构,分别是 Intel 模式和绘图模式,最后还将使用代码定义每一级分页结构。

2.0 汇总图

48.png

4KB 页面大小时的 EPT 分页结构。

46.png

1-GByte、2-MByte Page 大小时的分页结构。

47.png

当开启 EPT 后,Guest 的分页结构叫做 guest paging structures,HOST 的分页结构叫做 EPT paging structures

属性说明:

分类 说明
EPTP 1、MemoryType[bits 2:0]:EPT 分页结构内存类型(不是条目指向的物理内存的类型)。处理器支持的物理内存类型可通过 MSR IA32_VMX_EPT_VPID_CAP[8,14] 获取。目前处理器仅支持:0-Uncacheable(UC),6-WriteBack(WB)。
2、PageWalkLength[bit 5:3]:指示 EPT 页表的级数 - 1,如采用 4 级分页时为 3(不管是 1-GByte Page/2-MByte Page 都是 3)。
3、EnableAccessAndDirtyFlags[6]:是 EPT 页表项的 dirty 与 accessed 标志开启位。当 MSR IA32_VMX_EPT_VPID_CAP[21] = 1 将启用 EPT 的 dirty 以及 accessed 位。此时 EPT 页表项的 bit 8 与 bit 9 表示的 accessed与 dirty 标志位才会被启用。
4、PageFrameNumber[N-1:12]:为 EPT PLM4T 表的基地址(低 12 位填 0)。N 值等于 MAXPHYADDR,通过 CPUID.80000008H:EAX[bits 7:0-Physical,bits 15:8-Linear] 得到,目前处理器支持的最大 MAXPHYADDR = 52。目前一般只使用到 48 位。

Intel SDM 27.3.2, EPT Translation Mechanism:
The EPT translation mechanism uses only bits 47:0 of each guest-physical address. No processors supporting the Intel 64 architecture support more than 48 physical-address bits. Thus, no such processor can produce a guest-physical address with more than 48 bits.
提供 Page Frame 的条目 1、readable (可读,bit 0)、writable(可写,bit 1)、executable(可执行,bit 2):当 bits 2:0 为 0 值时,表示页面是 not-present(不存在),至少有一位为 1 才表示物理页面存在。同时还有制约关系:如果页面是 writable 的,那么该页面也必须是 readable 的,否则产生 EPT misconfiguration 故障。
2、MemoryType[bits 5:3]:1-GByte、2-MByte、4-KByte 内存页面的内存类型。
3、Accessed[8],Dirty[9]:在 EPTP[6] = 1 时,EPT paging structure 表项启用 accessed 与 dirty 标志位。同时,处理器对所有 guest paging structure 条目的访问都被认为是“写访问”。因此,对 guest paging structure 条目访问时的 GPA 转换需要有写访问的权限。When accessed and dirty flags for EPT are enabled, processor accesses to guest paging-structure entries are treated as writes (see Section 28.3.3.2).
Reference:
Intel SDM 28.3.5 Accessed and Dirty Flags for EPT.
处理器虚拟化技术 6.1.9 accessed 与 dirty 标志位。

2.1 EPTP

45.png

49.png

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Intel SDM 24.6.11
typedef struct _EPTP_POINT
{
ULONG64 value;
union
{
ULONG64 MemoryType : 3; // 0-Uncacheable(UC), 6-Write-back(WB)
ULONG64 PageWalkLength : 3; // 页表级数-1,使用 4 级页表时该值为 3
ULONG64 EnableAccessAndDirtyFlags : 1; // 该值为 1 时,启用 EPT 各级分页结构中的 Access、Dirty 位
ULONG64 AR_enforcement_ssp : 1; // Supervisor shadow-stack acess right
ULONG64 Reservd0 : 4;
ULONG64 PageFrameNumber : 36;
ULONG64 Reservd1 : 16;
} bits;
} EPTP_POINT,*PEPTP_POINT;

2.2 PLM4E

50.png

49.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef union _PEPT_PML4E
{
ULONG64 all;
struct {
ULONG64 read_access : 1; // [bit 0] 是否可读
ULONG64 write_access : 1; // [bit 1] 是否可写
ULONG64 execute_access : 1; // [bit 2] 是否可执行
ULONG64 reserved0_be_0 : 5; // [bits 7:3] 保留
ULONG64 accessed : 1; // [bit 8] 页面是否被访问
ULONG64 ignored0 : 1; // [bit 9]
ULONG64 user_mode_execute_access : 1; // [bit 10] Execute access for user-mode linear addresses.
ULONG64 ignored1 : 1; // [bit 11]
ULONG64 pdpt_address : 36; // [bits 47:12] EPT PDPT 的 PHY 地址 (N == 48)
ULONG64 reserved1_be_0 : 4; // [bits 51:48]
ULONG64 ignored2 : 12; // [bits 63:52]
}Bits;
} EPT_PML4, * PEPT_PML4E;

2.3 PDPTE

1-GByte Page

52.png

53.png

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
typedef union _EPT_PDPTE_1GB
{
ULONG64 all;
struct
{
ULONG64 read_access : 1; // [bit 0] 是否可读
ULONG64 write_access : 1; // [bit 1] 是否可写
ULONG64 execute_access : 1; // [bit 2] 是否可执行
ULONG64 memory_type : 3; // [bits 5:3]
ULONG64 ignore_pat : 1; // [bit 6] Ignore PAT memory type for this 1-GByte page.
ULONG64 large_page : 1; // [bit 7] 必须为1
ULONG64 accessed : 1; // [bit 8] 是否已经被访问过
ULONG64 dirty : 1; // [bit 9] 脏位,是否已经被写过
ULONG64 user_mode_execute_access : 1; // [bit 10]
ULONG64 ignored0 : 1; // [bit 11]
ULONG64 reserved0_be_0 : 18 // [bits 29:12]
ULONG64 physical_address : 18; // [bits 47:30]
ULONG64 reserved1_be_0 : 4; // [bits 48:51]
ULONG64 ignored1 : 5; // [bits 56:52]
ULONG64 verify_guest_paging : 1; // [bit 57]
ULONG64 paging_writed : 1; // [bit 58]
ULONG64 ignored2 : 1; // [bit 59]
ULONG64 supervisor_shadow_stack : 1; // [bit 60]
ULONG64 ignored3 : 2; // [bits 62:61]
ULONG64 suppress_VE : 1; // [63]
}Bits;
} EPT_PDPTE_1GB, *PEPT_PDPTE_1GB;

Others Page

54.png

55.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef union _EPT_PDPTE
{
ULONG64 all;
struct {
ULONG64 read_access : 1; // [bit 0] 是否可读
ULONG64 write_access : 1; // [bit 1] 是否可写
ULONG64 execute_access : 1; // [bit 2] 是否可执行
ULONG64 reserved0_be_0 : 5; // [bits 7:3]
ULONG64 accessed : 1; // [bit 8] 页面是否被访问
ULONG64 ignored0 : 1; // [bit 9]
ULONG64 user_mode_execute_access : 1; // [bit 10]
ULONG64 ignored1 : 1; // [bit 11]
ULONG64 pdt_address : 36; // [bits 47:12] EPT PDT 的 PHY 地址 (N == 48)
ULONG64 reserved1_be_0 : 4; // [bits 51:48]
ULONG64 ignored2 : 12; // [bits 63:52]
}Bits;
} EPT_PDPTE, *PEPT_PDPTE;

2.4 PDE

2-MByte page

56.png

57.png

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
typedef union _EPT_PDE_2MB
{
ULONG64 all;
struct
{
ULONG64 read_access : 1; // [bit 0] 是否可读
ULONG64 write_access : 1; // [bit 1] 是否可写
ULONG64 execute_access : 1; // [bit 2] 是否可执行
ULONG64 memory_type : 3; // [bits 5:3]
ULONG64 ignore_pat : 1; // [bit 6]
ULONG64 large_page : 1; // [bit 7] Must be 1 (this entry maps a 2-MByte page)
ULONG64 accessed : 1; // [bit 8] 是否被访问过
ULONG64 dirty : 1; // [bit 9] 是否已经被写过
ULONG64 user_mode_execute_access : 1; // [bit 10]
ULONG64 ignored0 : 1; // [bit 11]
ULONG64 reserved0_be_0 : 9; // [bits 20:12]
ULONG64 physical_address : 27; // [bit5 47:21]
ULONG64 reserved1_be_0 : 4; // [bit 51:48]
ULONG64 ignored1 : 5; // [bit 56:52]
ULONG64 verify_guest_paging : 1; // [bit 57]
ULONG64 paging_writed : 1; // [bit 58]
ULONG64 ignored2 : 1; // [bit 59]
ULONG64 supervisor_shadow_stack : 1; // [bit 60]
ULONG64 ignored3 : 2; // [bits 62:61]
ULONG64 suppress_VE : 1; // [bit 63]
}Bits;
} EPT_PDE_2MB, *PEPT_PDE_2MB;

Others Page

58.png

59.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef union _EPT_PDE
{
ULONG64 all;
struct {
ULONG64 read_access : 1; // [bit 0] 是否可读
ULONG64 write_access : 1; // [bit 1] 是否可写
ULONG64 execute_access : 1; // [bit 2] 是否可执行
ULONG64 memory_type : 4; // [bits 6:3]
ULONG64 must_be_0 : 1; // [bit 7] Must be 0 (otherwise, this entry maps a 2-MByte page)
ULONG64 accessed : 1; // [bit 8] 是否被访问过
ULONG64 ignored0 : 1; // [bit 9]
ULONG64 user_mode_execute_access : 1; // [bit 10]
ULONG64 ignored1 : 1; // [bit 11]
ULONG64 ptt_address : 36; // [bits 47:12]
ULONG64 reserved0_be_0 : 4; // [bit 51:48]
ULONG64 ignored2 : 12; // [bit 63:52]
}Bits;
} EPT_PDE, *PEPT_PDE;

2.5 PTE 4KB Page

60.png

61.png

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
typedef union _EPT_PTE
{
ULONG64 all;
struct
{
ULONG64 read_access : 1; // [bit 0] 是否可读
ULONG64 write_access : 1; // [bit 1] 是否可写
ULONG64 execute_access : 1; // [bit 2] 是否可执行
ULONG64 memory_type : 3; // [bits 5:3]
ULONG64 ignore_pat : 1; // [bit 6]
ULONG64 ignore0 : 1; // [bit 7]
ULONG64 accessed : 1; // [bit 8] 是否被访问过
ULONG64 dirty : 1; // [bit 9] 是否已经被写过
ULONG64 user_mode_execute_access : 1; // [bit 10]
ULONG64 ignored1 : 1; // [bit 11]
ULONG64 physical_address : 36; // [bits 47:12]
ULONG64 reserved0_be_0 : 4; // [bit 51:48]
ULONG64 ignored2 : 5; // [bit 56:52]
ULONG64 verify_guest_paging : 1; // [bit 57]
ULONG64 paging_writed : 1; // [bit 58]
ULONG64 ignored3 : 1; // [bit 59]
ULONG64 supervisor_shadow_stack : 1; // [bit 60]
ULONG64 ignored4 : 2; // [bits 62:61]
ULONG64 suppress_VE : 1; // [bit 63]
}Bits;
} EPT_PTE, *PEPT_PTE;

3 构建 EPT 分页结构

分类 说明
每个页表条目个数,大小 512 个条目, 每个 8 bytes。
每个分页条目 每个 PLM4E 管理 512-GByte 物理内存,每个 PDPTE 管理 1-GByte 物理内存,每个 PDE 管理 2-MByte 物理内存,每个 PTE 管理 4-KByte 物理内存。
PLM4E/512-GByte,PDPTE/1-GByte,PDE/2-MByte,PTE/4-KByte。
每个页表 每个 PLM4T 管理 512*512-GByte 物理内存,每个 PDPT 管理 512*1-GByte 物理内存,每个 PDT 管理 512*2-MByte 物理内存,每个 PTT 管理 512*4-KByte 物理内存。
PLM4T/512*512-GByte,PDPT/512*1-GByte,PDT/512*2-MByte,PTT/512*4-KBy。

本节分别构建两种页面大小的 EPT paging-structure,2-MByte Page Size。、4-KByte Page Size。

一个使用 32 GBytes 虚拟内存的 VMM,需要的 EPT 页表:

  • 4-KByte Page Size:共需要 1 PLM4E、32 PDPTE、32512 PDE、32512*512 PTE。即 1 PLM4T、1 PDPT、32 PDT、32*512 PTT。
  • 2-MByte Page Size:共需要 1 PLM4E、32 PDPTE、32*512 PDE。即 1 PLM4T、1 PDPT、32 PDT。

3.1 2-MByte Page Size

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
#define PAGE_SIZE 4096
#define EPT_PAGE_SIZE_2MB 2+32
#define EPT_PAGE_SIZE_4KB 2+32+32*512

// 每个 PLM4E 管理 512-GByte 物理内存,每个 PDPTE 管理 1-GByte 物理内存,每个 PDE 管理 2-MByte 物理内存,每个 PTE 管理 4-KByte 物理内存。
// PLM4E/512-GByte, PDPTE/1-GByte, PDE/2-MByte, PTE/4-KByte.

// 2-MByte Page Size:共需要 1 PLM4E、32 PDPTE、32*512 PDE。
// 即 1 PLM4T、1 PDPT、32 PDT。

BOOLEAN EptInitEptPagingStucture2MBPageSize()
{
PVOID EptPagingTableBase = 0;
PEPT_PLM4E pEpt_PLM4T = NULL;
PEPT_PDPTE pEpt_PDPT = NULL;
ULONG PDPT_Index = 0;
ULONG PDT_Index = 0;
ULONG Page_Index = 0;

EptPagingTableBase = ExAllocatePoolWithTag(NonPagedPool, EPT_PAGE_SIZE_2MB*PAGE_SIZE, 'EptP');

if(EptPagingTableBase = NULL)
{
DbgPrint("[+] Error: Alloc EPT Paging Structure failed.\t\r");
return FALSE;
}
RtlZeroMemory(EptPagingTableBase, EPT_PAGE_SIZE_2MB*PAGE_SIZE);

// 最后两页留给 PLM4T、PDPT 表
pEpt_PLM4T = (PEPT_PLM4E)((ULONG64)EptPagingTableBase + (EPT_PAGE_SIZE_2MB - 2)*PAGE_SIZE);
pEpt_PDPT = (PEPT_PDPTE)((ULONG64)EptPagingTableBase + (EPT_PAGE_SIZE_2MB - 1)*PAGE_SIZE);

// 初始化 EPTP
EptSetEptPoint(pEpt_PLM4T);

// 初始化那一个 PLM4E
pEpt_PLM4T[0].pdpt_address = (ULONG64)MmGetPhysicalAddress(pEpt_PDPT).QuadPart/PAGE_SIZE;
pEpt_PLM4T[0].read_access = TRUE;
pEpt_PLM4T[0].write_access = TRUE;
pEpt_PLM4T[0].execute_access = TRUE;

// 循环配置 32 PDPTE、32*512 PDE
for(PDPT_Index = 0; PDPT_Index < 32; PDPT_Index++)
{
PEPT_PDE_2MB pEpt_PDT_2MB = (PEPT_PDE_2MB)((ULONG64)EptPagingTableBase + (Page_Index++)*PAGE_AIZE);
pEpt_PDPT[PDPT_Index].pdt_address = (ULONG64)MmGetPhysicalAddress(pEpt_PDT_2MB).QuadPart/PAGE_SIZE;
pEpt_PDPT[PDPT_Index].read_access = TRUE;
pEpt_PDPT[PDPT_Index].write_access = TRUE;
pEpt_PDPT[PDPT_Index].execute_access = TRUE;

for(PDT_Index = 0; PDT_Index < 512; PDT_Index++)
{
pEpt_PDT_2MB[PDT_Index].physical_address = PDPT_Index * (1 << 9) + PDT_Index;
pEpt_PDT_2MB[PDT_Index].read_access = TRUE;
pEpt_PDT_2MB[PDT_Index].write_access = TRUE;
pEpt_PDT_2MB[PDT_Index].execute_access = TRUE;
pEpt_PDT_2MB[PDT_Index].large_page = TRUE;
pEpt_PDT_2MB[PDT_Index].memory_type = g_EptState.EPTP.MemoryType;
}
}

return TRUE;
}

BOOLEAN EptSetEptPoint(PEPT_PLM4E pEptPLM4T)
{
EPTP_POINT EPTP = NULL;
MSR_IA32_VMX_EPT_VPID_CAP MsrVmxEptVpidCapInfo = { 0 };
MsrVmxEptVpidCapInfo.value = __readmsr(IA32_VMX_EPT_VPID_CAP);

// PageWalkLength
if(MsrVmxEptVpidCapInfo.bits.gaw_48)
{
EPT.PageWalkLength = 3;
}

// MemoryType
if(MsrVmxEptVpidCapInfo.bits.wb_memory_type )
{
EPTP.MemoryType = MsrVmxEptVpidCapInfo.bits.wb_memory_type;
}else
{
EPTP.MemoryType = MsrVmxEptVpidCapInfo.bits.uc_memory_type ;
}

// EnableAccessAndDirtyFlags

// if(MsrVmxEptVpidCapInfo.bits.ept_accessed_dirty_flags)
// {
// EPTP.EnableAccessAndDirtyFlags = TRUE;
// }
// 暂时不启用该标志
EPTP.EnableAccessAndDirtyFlags = FALSE;

// 设置 PLM4T 的基址
EPTP.PageFrameNumber = (ULONG64)MmGetPhysicalAddress(pEptPLM4T).QuadPart/PAGE_SIZE;

return TRUE;
}

3.2 4-KByte Page Size

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
// 每个 PLM4E 管理 512-GByte 物理内存,每个 PDPTE 管理 1-GByte 物理内存,每个 PDE 管理 2-MByte 物理内存,每个 PTE 管理 4-KByte 物理内存。
// PLM4E/512-GByte, PDPTE/1-GByte, PDE/2-MByte, PTE/4-KByte.

// 4-KByte Page Size:共需要 1 PLM4E、32 PDPTE、32*512 PDE、32*512*512 PTE。
// 即 1 PLM4T、1 PDPT、32 PDT、32*512 PTT。
BOOLEAN EptInitEptPagingStucture4KBPageSize()
{
PVOID EptPagingTableBase = 0;
PEPT_PLM4E pEpt_PLM4T = NULL;
PEPT_PDPTE pEpt_PDPT = NULL;
ULONG PDPT_Index = 0;
ULONG PDT_Index = 0;
ULONG PTT_Index = 0;
ULONG Page_Index = 0;

EptPagingTableBase = ExAllocatePoolWithTag(NonPagedPool, EPT_PAGE_SIZE_4KB*PAGE_SIZE, 'EptP');

if(EptPagingTableBase = NULL)
{
DbgPrint("[+] Error: Alloc EPT Paging Structure failed.\t\r");
return FALSE;
}
RtlZeroMemory(EptPagingTableBase, EPT_PAGE_SIZE_4KB*PAGE_SIZE);

// 最后两页留给 PLM4T、PDPT 表
pEpt_PLM4T = (PEPT_PLM4E)((ULONG64)EptPagingTableBase + (EPT_PAGE_SIZE_4KB - 2)*PAGE_SIZE);
pEpt_PDPT = (PEPT_PDPTE)((ULONG64)EptPagingTableBase + (EPT_PAGE_SIZE_4KB - 1)*PAGE_SIZE);

// 初始化 EPTP
EptSetEptPoint(pEpt_PLM4T);

// 初始化那一个 PLM4E
pEpt_PLM4T[0].pdpt_address = (ULONG64)MmGetPhysicalAddress(pEpt_PDPT).QuadPart/PAGE_SIZE;
pEpt_PLM4T[0].read_access = TRUE;
pEpt_PLM4T[0].write_access = TRUE;
pEpt_PLM4T[0].execute_access = TRUE;

// 循环配置 32 PDPTE、32*512 PDE、32*512*512 PTE
for(PDPT_Index = 0; PDPT_Index < 32; PDPT_Index++)
{
PEPT_PDE pEpt_PDT = (PEPT_PDE)((ULONG64)EptPagingTableBase + (Page_Index++)*PAGE_AIZE);
pEpt_PDPT[PDPT_Index].pdt_address = (ULONG64)MmGetPhysicalAddress(pEpt_PDT).QuadPart/PAGE_SIZE;
pEpt_PDPT[PDPT_Index].read_access = TRUE;
pEpt_PDPT[PDPT_Index].write_access = TRUE;
pEpt_PDPT[PDPT_Index].execute_access = TRUE;

for(PDT_Index = 0; PDT_Index < 512; PDT_Index++)
{
PEPT_PTE pEpt_PTT = (PEPT_PTE)((ULONG64)EptPagingTableBase + (Page_Index++)*PAGE_AIZE);
pEpt_PDT[PDT_Index].ptt_address = (ULONG64)MmGetPhysicalAddress(pEpt_PTT).QuadPart/PAGE_SIZE;
pEpt_PDT[PDT_Index].read_access = TRUE;
pEpt_PDT[PDT_Index].write_access = TRUE;
pEpt_PDT[PDT_Index].execute_access = TRUE;

for(PTT_Index = 0; PTT_Index < 512; PTT_Index++)
{
pEpt_PTT[PTT_Index].physical_address = PDPT_Index*(1 << 18) + PDT_Index*(1 << 9) + PTT_Index ;
pEpt_PTT[PTT_Index].read_access = TRUE;
pEpt_PTT[PTT_Index].write_access = TRUE;
pEpt_PTT[PTT_Index].execute_access = TRUE;
pEpt_PTT[PTT_Index].memory_type = g_EptState.EPTP.MemoryType;
}
}
}

return TRUE;
}