Hypervisor(一):VMX 架构与 VMXON region

😄

1 Hypervisor

Hypervisor 虚拟机监视器(VMM),由它来实现对物理资源虚拟化。并控制着多个虚拟机(VM)对物理硬件资源的访问。

  • CPU
  • 内存
  • I/O
  • 网络

1960s,Hypervisor 是在大型机上用来开发和调试操作系统的。1990s,小型机发展起来后也开发出新的 Hypervisor,Hypervisor 可以运行在操作系统之上,也可以不依赖操作系统直接运行在硬件之上。有两种类型的 Hypervisor:

  1. 👆Type 1:也叫做裸机型(Bare-metal),直接运行在硬件服务器之上,其下没有操作系统。比 Type 2 型的 Hypervisor 更高效、更安全。
  2. ✌️Type 2:也叫做主机型(Hosted),Hypervisor 只是主机操作系统上的一个应用程序。这种类型的 Hypervisor 能适应更多类型的硬件,因为操作系统在之下已经进行了适配。这种类型的 Hypervisor 也比 Type 1 Hypervisor 安装方便。但是 VM 执行效率较 Type 1 型的低,比如 VM 发起的 I/O 请求会先交给 Hypervisor,然后Hypervisor 再请求操作系统处理,操作系统处理完成后再逐步传回 VM。

Hypervisor 的三个作用:

  1. 提供与物理环境相同的环境。Provide an environment identical to the physical environment.
  2. 以最小的性能成本提供该环境。Provide that environment with minimal performance cost.
  3. 保留对系统资源的完全控制。Retain complete control of the system resources.

1.png

2 Type 1 hypervisor

2.1 VMware ESX

2001 年,VMware 发布了 ESX 1.0 和 GSX 1.0。 ESX 是 Type 1 hypervisor,而 GSX 是 Type 2 hypervisor。 ESX 今天仍然存在,并且仍在得到增强和更新。 不过,GSX 已更名为 VMware Server,但不再可用。

ESX 的原始架构由两部分组成,hypervisor + 基于 Linux 的服务控制台模块(充当管理接口)。管理接口往往非常大,如 hypervisor(32MB),服务控制台约 900 MB。服务控制台的安全性也会影响着 hypervisor 的安全性。所以后来基于相同的 hypervisor 开发出 ESXi,但是不再有控制台。2011 年后只有 ESXi 架构可用。

2.png

2.2 Citrix Hypervisor (Xen)

开源的,开源的解决方案!但是目前商业市场份额还不到 5%。

Xen 管理程序始于 1990 年代末剑桥大学的一个研究项目,其目标是为分布式计算创建一个高效的平台。 2002 年,该代码成为一个开源项目,允许任何人为改进特性和功能做出贡献。XenSource 成立于 2004 年,旨在将 Xen 虚拟机管理程序推向市场,该开源项目至今仍保持开放状态。Citrix Systems 收购了 XenSource 以补充其应用程序交付解决方案。 2013 年,Citrix Systems 宣布 Xen 的开发将成为 Linux Foundation Collaborative Project,将开发回归到开源社区。

Xen 模型有一个称为 Domain 0 的特殊 Guest OS,也称为 Dom0。 这个 Guest 在 hypervisor 启动时被启动,并且它具有不同于其他 Guest 的管理权限,可以直接访问物理硬件资源。Type 1 Hypervisor。

如下图,当其他 Guest OS 请求访问物理硬件资源时,这些请求首先会传递给 Hypervisor,然后接着传递到 Dom0 Guest OS,然后去访问资源。当资源访问到之后再原路返回。

3.png

Dom0 的重启将中断所有其他 Guest OS。 因为 Dom0 也是 Guest,它会消耗资源并与系统中的其他 Guest 争夺资源。

2.3 Microsoft Hyper-V

微软在几年前从 Connectix 收购了该解决方案后,于 2005 年通过 Virtual Server 开始涉足虚拟化领域。 与 GSX 一样,Virtual Server 是 Type 2 虚拟机管理程序,但已停产以支持 Hyper-V(Type 1 Hypervisor)。 Microsoft Hyper-V 于 2008 年作为 Windows Server 2008 操作系统的可安装部分发布。 图 2.10 显示了 Hyper-V 的体系结构。

4.png

Hyper-V 是 Type 1 Hypervisor,Hypervisor 代码直接存在于硬件上。 不过,命名法略有不同——虚拟化工作负载称为分区,而不是 Guest。 与 Xen 模型类似,它需要一个可以直接访问硬件资源的特殊根分区。 与 Dom0 一样,根分区运行操作系统——在本例中为 Windows Server。 该分区创建和管理子分区并处理系统管理功能和设备驱动程序。 由于它使用类似于 XenServer 的模型,因此在修补和争用方面也存在相同的可用性漏洞。目前 Hyper-V 拥有大约 20% 的市场份额。

其他一些基本都是在开源的 Xen 进行修改发展起来的,Red Hat 是另一种解决方案,随着时间的推移经历了一些不同的排列。 最初,它还使用开源 Xen 代码,因为它非常适合其开源解决方案的业务模型。 2008 年,红帽收购了 Qumranet 及其基于内核的虚拟机 (KVM) 解决方案。 KVM 与 Linux 本身一样,也是基于同名的开源项目。 有一段时间,Red Hat Enterprise Linux (RHEL) 版本同时支持 KVM 和 Xen 虚拟化技术。 2012年,Red Hat表示KVM是其未来方向,不再支持Xen。 KVM 作为 Linux 内核模块提供,允许您利用 Linux 的许多功能,例如调度程序和内存管理,而无需将它们作为代码的一部分包含在内。 对于 Red Hat 本身的现有用户来说,KVM 的使用随着时间的推移而增长。 然而,开源云计算项目 OpenStack 的兴起有所帮助,因为 KVM 可以用作虚拟机管理程序的选择之一。

参考资料:

3 硬件辅助的虚拟化

3.1 硬件辅助的虚拟化

在虚拟化技术早期,主要有两种在软件层面来将物理 CPU 虚拟成虚拟 vCPU 的技术:

  • 全虚拟化(Full Virtualization):以 VMware 为代表,动态处理非特权指令“运行时监控,捕捉后在 VMM 中模拟”。(无需修改,直接运行)
  • 半虚拟化(Para Virtualization):以 Xen 为代表,静态处理非特权指令“将 VM 中全部非特权指令进行替换处理”。提高效率。

后来 Intel、AMD 硬件厂商均推出了在硬件级支持虚拟化的 CPU——硬件辅助的虚拟化,本文主要关注 Intel 的 CPU 硬件虚拟化。

Intel 推出了以下三个层面的虚拟化技术:

  1. 基于处理器的虚拟化技术(Intel VT-x), 全称为 Virtualization Technology for x86。

  2. 基于PCI总线域设备实现的 I/O 虚拟化技术(Intel VT-d) ,全称为 Virtualization Technology for Directed I/O。

  3. 基于网络的虚拟化技术(Intel VT-c),主要在服务器平台提供, 全称为 Virtualization Technology for Connectify。

    在处理器虚拟化方面,Intel 也为 Itanium(奔腾) 平台提供了虚拟化,即 Intel VT-i

以下几个概念需要弄清楚:

  1. Intel 处理器级的虚拟化技术叫做 Intel VT-x,用来实现虚拟化的功能叫做 VMX(Virtual Machine Extension,虚拟机扩展),也可以将 Intel VT-x 叫做 VMX,也可以叫做硬件虚拟化技术(Hardware Enabled Virtualization,HEV)。虚拟机扩展定义了在 IA-32 处理器上对虚拟机的处理器级支持。
  2. 虚拟机扩展架构(VMX)下定义了两个角色、两种模式、一组指令:
    • 两个角色:虚拟机管理器(Virtual Machine Monitor,VMM)和客户机(Guest OS)。
    • 两种模式:VMX root operation(ring -1),VMX non-root operation。VMM 运行在 VMX root 模式,Guest OS 运行在 VMX non-root 模式。
    • 一组专门用在 VMM 中的指令,叫做 VMX 指令,VMX 指令只能在 VMX root 模式下执行。

注意:处理器(Processor)和 CPU 的差别,处理器一般指逻辑处理器(Logical Processor)。在多核 CPU 中,一个 CPU 上可能会有多个核, 而在操作系统视角中,一个核才是一个逻辑处理器,因此通过操作系统查看的逻辑处理器数量往往大于真实 CPU 的数量,并且逻辑处理器才是能够运行 Hypervisor 的基础

5.png

3.2 虚拟机扩展架构(VMX)

Intel 实现了 VMX 架构来支持 CPU 端的虚拟化技术(Intel VT-x 技术),引入了一种新的处理器操作模式,被称为 VMX operation。也加入了一系列的 VMX 指令支持 VMX operation 模式。

处理器提供 VMX operation 使得处理器支持虚拟化。在硬件虚拟化中,有时也称 VMM 为 “Hypervisor” 专指在使用 VT 技术时创建的特权层,也就是 Ring 0 之 下的 Ring -1。处在这个层次的代码具有“上帝视角”,能监控计算机的各种行为,例如特权指令、内存访问、IO 访问等。该层提供给虚拟机开发者,Hypervisor 的权限级别要大于或等于操作系统权限

在 VT 技术中,VMX root operation 和 VMX non-root operation 模式切换过程如下:

6.png

  1. 对于 VMM 层,进入此层则代表进入了 VMX root 模式。
    • 为每个虚拟机提供虚拟处理器,并且可以在恰当的时候把它放在真正的物理处理器上,从而使得这个虚拟处理器可以处理指令。
    • VMM 通过虚拟机控制结构(Virtual-Machine Control Strueture,VMCS)设置需要截获的硬件访问,例如指令、中断、内存访问和异常等。如果 Guest OS 在执行时触发了这些条件设置,将引起 VM 退出(#VMExit),此时 CPU 会从 non-Root 模式切换到 Root 模式,VMM 会获得系统控制杈并执行相应的处理程序。
  2. 对于 Guest(VM)层,进入此层则代表进入了 VMX non-root 模式。
    • 每个虚拟机使用相同的接口来使用虚拟处理器、内存、存储设备等资源。
    • 每个虚拟机可以独立地不受干扰地运行,虚拟机间都是相互独立的。
    • 对于虚拟机来说,VMM 层像是完全透明的。
  3. 软件通过执行 VMXON 指令进入 VMX Root 模式下,开启了虚拟机管理器的运行环境。然后通过使用 VMLAUNCH 指令使得目标系统正式运行在虚拟机中(开启 VM)。 当 VM 中某条指令产生了 #VMEXIT 事件后,会陷入虚拟机管理器中,待其处理完这个事件, 可以通过 VMRESUME 指令将控制权移交回发生 #VMEXIT 事件的虚拟机。直到某个时刻,在 Hypervisor 中显式地调用了 VMXOFF 指令,Hypervisor 才会被关闭。

创建一个典型的 VT 技术 Hypervisor 例子主要有以下几个步骤:

  1. 先检测当前处理器是否支持 VMX(VT-x)。
  2. 分配 4KB(0x1000)对齐的 VMXON 区域和 VMCS 控制块。
  3. 填写 VMCS 控制块。VMCS 控制块用于控制要监控什么特权指令、是否开启 EPT 机制、是否监控 I/O 访问等。
  4. 调用 VMLAUNCH 指令启动虚拟机。
  5. 当产生 #VMExit 事件时调用 VMExitProc 函数,并处理各种感兴趣的虚拟机陷入消息。

关于 VMX 指令,将在后面章节进行介绍。

3.3 检测 CPU 是否支持 VMX

在使用 VMXON 进入到 VMX operation 模式之前,先要判断当前 CPU 是否是 Intel,是否支持 VMX。通过以下两个步骤:

  1. 判断是否是 Intel 处理器。CPUID.00H, RBX+RDX+RCX = Genuntelinel
  2. 判断是否支持 VMX。CPUID.01H:ECX.VMX[bit 5] = 1

–Test_Support_VMX.asm–

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
option casemap :none

EXTERN R_EBX:dword
EXTERN R_ECX:dword
EXTERN R_EDX:dword

.data
;Var_1 qword ?

.CODE
Test_Support_Intel PROC
xor eax, eax;
xor ebx, ebx;
xor ecx, ecx;
xor edx, edx;

mov eax, 0;
cpuid;

mov dword ptr R_EBX, ebx;
mov dword ptr R_ECX, ecx;
mov dword ptr R_EDX, edx;

ret;
Test_Support_Intel ENDP

DetectVMXSupport PROC
xor eax, eax;
inc eax;
cpuid

xor eax, eax;
and ecx, 020h;
mov eax, ecx;

ret;
DetectVMXSupport ENDP

END

–main.c–

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
#include <ntddk.h>

EXTERN_C VOID _fastcall Test_Support_Intel(void);
EXTERN_C ULONG _fastcall DetectVMXSupport(void);

ULONG R_EBX, R_ECX, R_EDX = 0;

VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
UNREFERENCED_PARAMETER(pDriver);

DbgPrint("Goodbye~\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath)
{
UNREFERENCED_PARAMETER(pRegPath);
pDriver->DriverUnload = DriverUnload;

Test_Support_Intel();

if (R_EBX == 0x756E6547 && R_ECX == 0x6C65746E && R_EDX == 0x49656E69)
{
// Parallels Desktop for Mac 需要开启嵌套虚拟化才支持 VMX(VT-x)
if (DetectVMXSupport() == 0x20)
{
DbgPrint("This CPU SUPPORT VMX!!!\n");
}
}

return STATUS_SUCCESS;
}

输出结果如下:

8.png

3.4 启用 VMX

在进入 VMX operation 之前,需要通过设置 CR4.VMXE[bit 13] = 1 来启用 VMX。然后通过执行 VMXON 指令进入 VMX operation。其他的 VMX 指令只有在进入到 VMX operation 模式后才能使用后。

如果在 CR4.VMXE = 0 的情况下执行,执行 VMXON 会导致无效操作码异常(#UD)。一旦进入 VMX operation,就不可清除 CR4.VMXE。 系统软件通过执行 VMXOFF 指令退出 VMX 操作, 执行 VMXOFF 后,才可以在 VMX operation 之外清除 CR4.VMXE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
AsmEnableVmxOperation PROC PUBLIC

PUSH RAX ; Save the state

XOR RAX, RAX ; Clear the RAX
MOV RAX, CR4

OR RAX,02000h ; Set the 14th bit
MOV CR4, RAX

POP RAX ; Restore the state
RET

AsmEnableVmxOperation ENDP

3.5 禁用 VMX

VMXON 指令由 IA32_FEATURE_CONTROL(0x3A) MSR 控制。当逻辑处理器重置时,该 MSR 的值会被重置为零。

这里主要关注 IA32_FEATURE_CONTROL[bit 0]

  • 该位是 locked 位。当 locked 位为 0 时,执行 VMXON 指令将产生 #GP 异常(VMX support was disabled in the BIOS)。因此,执行 VMXON 指令前必须确保 locked 位为 1 值,也就是锁上 IA32_FEATURE_CONTROL 寄存器。上锁后如果对 IA32_FEATURE_CONTROL 寄存器进行写操作(WRMSR),将产生 #GP 异常。
  • 一旦该位置位(set 1)后,就不可以再使用 WRMSR 来修改该 MSR 寄存器的值,否则会产生通用保护(#GP)异常。

这是什么意思?这意味着我们可以禁用 VMX 功能。只有在系统重置后,我们才能启用 VMX。

如下,一般 CPU 默认启用该位。可以在系统 BIOS 中可以将该位清零,以禁用对 VMX 的支持。但是不能使用 WRMSR 来将该位清零。

1
2
kd> rdmsr 0x3A
msr[3a] = 00000000`00000005

3.6 进入 VMX operation 条件

进入到 VMX operation 模式使用 VMXON 指令,使用该指令前,以下条件必须得到满足才可以。

  1. VMXON 指令由 IA32_FEATURE_CONTROL(0x3A) MSR 控制才能执行。

    • locked 位,bit 0 = 1
    • CPUID.01H:ECX[bit 6] == 1 表示 CPU 支持 SMX 模式。然后查看 CR4.SMXE[bit 14] 是否已启用 SMX 模式。
      • CR4.SMXE == 1,表示 CPU 运行在 SMX 模式中,此时必须先置位 IA32_FEATURE_CONTROL[bit 1] = 1,且 IA32_FEATURE_CONTROL[bit 0] = 1 才能使用 VMXON 指令。
      • CR4.SMXE == 0,表示 CPU 没有运行在 SMX 模式,此时必须先置位 IA32_FEATURE_CONTROL[bit 2] = 1,且 IA32_FEATURE_CONTROL[bit 0] = 1 才能使用 VMXON 指令。

    10.png

    11.png

    (Intel 手册 2 Chapter 6)

  2. CR0, CR4 寄存器和 4 个 MSR 寄存器。

    IA32_VMX_CR0_FIXED0(0x486), IA32_VMX_CR0_FIXED1(0x487)

    IA32_VMX_CR4_FIXED0(0x488), IA32_VMX_CR4_FIXED1(0x489)

    • FIXED0 寄存器的位为 1 值时,CR0 和 CR4 寄存器对应的位必须为 1 值。
    • FIXED1 寄存器的位为 0 值时,CR0 和 CR4 寄存器对应的位必须为 0 值。
    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
    // IA32_VMX_CR0_FIXED0
    kd> rdmsr 0x486
    msr[486] = 00000000`80000021 // PE(bit0), NE(bit5), PG(bit31)
    kd> .formats 0x00000000`80000021
    Evaluate expression:
    Hex: 00000000`80000021
    Decimal: 2147483681
    Octal: 0000000000020000000041
    Binary: 00000000 00000000 00000000 00000000 10000000 00000000 00000000 00100001

    // IA32_VMX_CR0_FIXED1
    kd> rdmsr 0x487
    msr[487] = 00000000`ffffffff
    kd> .formats 0x00000000`ffffffff
    Evaluate expression:
    Hex: 00000000`ffffffff
    Decimal: 4294967295
    Octal: 0000000000037777777777
    Binary: 00000000 00000000 00000000 00000000 11111111 11111111 11111111 11111111

    // IA32_VMX_CR4_FIXED0
    kd> rdmsr 0x488
    msr[488] = 00000000`00002000 // VMXE(bit13)
    kd> .formats 0x00000000`00002000
    Evaluate expression:
    Hex: 00000000`00002000
    Decimal: 8192
    Octal: 0000000000000000020000
    Binary: 00000000 00000000 00000000 00000000 00000000 00000000 00100000 00000000

    // IA32_VMX_CR4_FIXED1
    kd> rdmsr 0x489
    msr[489] = 00000000`003727ff
    kd> .formats 0x00000000`003727ff
    Evaluate expression:
    Hex: 00000000`003727ff
    Decimal: 3614719
    Octal: 0000000000000015623777
    Binary: 00000000 00000000 00000000 00000000 00000000 00110111 00100111 11111111

    CR0 置位:PE(bit0) 保护模式,NE(bit5) x87 FPU,PG(bit31)分页。

    CR4 置位:VMXE(bit13)。

  3. A20M 模式。

    当处理器处于 A20M 模式(即 A20 线 mask 模式),也不允许进入 VMX operation 模式,执行 VMXON 指令将引发 #GP 异常。A20M 模式会屏蔽 A20 地址线产生所谓的 “wrapping” 现象,从而模拟 8086 处理器的 IM地址内访问行为。A20M 模式主要由旧操作系统使用,而不是现代操作系统使用。在较新的英特尔 64 处理器上,A20M# 可能不存在。

3.7 总结

综上所述关于 CPU 是否支持 VMX,支持的话就启用 VMX。

使用 VMXON 指令前对 CPU 状态进行检查,符合要求才可以使用该指令。封装成以下两个函数。

因为逻辑判断较多,用汇编写这部分函数反而效率低,还是用 C 写更快一些。

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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// https://learn.microsoft.com/zh-cn/cpp/intrinsics/cpuid-cpuidex?view=msvc-170

#include <ntddk.h>

#define MSR_IA32_FEATURE_CONTROL 0x3A
#define IA32_VMX_CR0_FIXED0 0x486
#define IA32_VMX_CR0_FIXED1 0x487
#define IA32_VMX_CR4_FIXED0 0x488
#define IA32_VMX_CR4_FIXED1 0x489

typedef struct _CPUID
{
ULONG EAX;
ULONG EBX;
ULONG ECX;
ULONG EDX;
} CPUID, *PCPUID;

typedef union _IA32_FEATURE_CONTROL_MSR
{
ULONG64 All;
struct
{
ULONG64 Lock : 1; // [0]
ULONG64 EnableSMX : 1; // [1]
ULONG64 EnableVmxon : 1; // [2]
ULONG64 Reserved1 : 5; // [3-7]
ULONG64 EnableLocalSENTER : 7; // [8-14]
ULONG64 EnableGlobalSENTER : 1; // [15]
ULONG64 Reserved2 : 1; // [16]
ULONG64 EnableSGXLanch : 1; // [17]
ULONG64 EnableSGXGlobal : 1; // [18]
ULONG64 Reserved3 : 1; // [19]
ULONG64 LMCEOn : 1; // [20]
ULONG64 Reserved4 : 43; // [21-63]
} Fields;
} IA32_FEATURE_CONTROL_MSR, *PIA32_FEATURE_CONTROL_MSR;

//
// 1. Detect this CPU is Intel or not.
// 2. If CPU support VME: CPUID.01H:ECX.VMX[bit5] == 1, set CR4.VMXE(bit13) to 1.
//

BOOLEAN DetectVMXSupportAndEnableCr4()
{
CPUID Cpuid = { 0 };
ULONG64 Cr4 = 0;

// detect cpu is intel or not.
__cpuid(&Cpuid, 0x0);

// Intel: Genuntelinel
// test CPUID.00H, RBX+RDX+RCX = Genuntelinel ?
if (Cpuid.EBX == 0x756E6547 && Cpuid.ECX == 0x6C65746E && Cpuid.EDX == 0x49656E69)
{
RtlSecureZeroMemory((PVOID)&Cpuid, sizeof(CPUID));
__cpuid(&Cpuid, 0x1);

// test CPUID.01H:ECX.VMX[bit 5] = 1 ?
if(_bittest(&Cpuid.ECX, 5) == 1)
{
DbgPrint("[*] This CPU support VMX!\n");

// Enable cr4 register, set cr4.VMXE(bit 13) = 1
Cr4 = __readcr4();
_bittestandset((PLONG)&Cr4, 13);
__writecr4(Cr4);
return TRUE;
}else DbgPrint("[*] This CPU Not support VMX!\n");
}else DbgPrint("[*] This CPU Not Intel!\n");

return FALSE;
}

//
// 1. Detect IA32_FEATURE_CONTROL(0x3A) lock(bit0), EnableSMX(bit1), EnableVmxon(bit2) and set.
// 2. If bit X is 1 in FIXED0, then set that bits of CR0 and CR4 to 1.
// 3. If bit X is 0 in FIXED1, then set that bits of CR0 and CR4 to 0.
//
BOOLEAN SetEntryVMXoperationCondition()
{
IA32_FEATURE_CONTROL_MSR MsrControl = { 0 };
CPUID Cpuid = { 0 };
ULONG64 Cr0, Cr4 = 0;
BOOLEAN InSMXmode = FALSE;
ULONG64 Cr0Fixed0, Cr0Fixed1, Cr4Fixed0, Cr4Fixed1 = 0;

// detect CPU in SMX mode or not.
__cpuid(&Cpuid, 0x1);

if(_bittest((PLONG)&Cpuid.ECX, 6) == 1) // support SMX mode
{
Cr4 = __readcr4();
if(_bittest((PLONG)&Cr4, 14) == 1)
{
InSMXmode = TRUE;
DbgPrint("[*] This CPU in SMX mode.\n");
}
else
{
InSMXmode = FALSE;
DbgPrint("[*] This CPU NOT in SMX mode.\n");
}
}

// read MSR_IA32_FEATURE_CONTROL MSR register.
// set Lock, EnableSMX, EnableVmxon.
MsrControl.All = __readmsr(MSR_IA32_FEATURE_CONTROL);
if(MsrControl.Fields.Lock == 0)
{
if(InSMXmode)
{
MsrControl.Fields.Lock = 1;
MsrControl.Fields.EnableSMX = 1;
__writemsr(MSR_IA32_FEATURE_CONTROL, MsrControl.All);
DbgPrint("[*] Set entry VMX operation condition inside SMX mode OK.");
}else
{
MsrControl.Fields.Lock = 1;
MsrControl.Fields.EnableVmxon = 1;
__writemsr(MSR_IA32_FEATURE_CONTROL, MsrControl.All);
DbgPrint("[*] Set entry VMX operation condition outside SMX mode OK.");
}
}else if (MsrControl.Fields.EnableVmxon == FALSE) // lock = 1, but EnableVmxon = 0
{
DbgPrint("[*] VMX locked off in BIOS");
return FALSE;
}

// set CR0, CR4 according to FIXED0 and FIXED1.
Cr0Fixed0 = __readmsr(IA32_VMX_CR0_FIXED0);
Cr0Fixed1 = __readmsr(IA32_VMX_CR0_FIXED1);
Cr4Fixed0 = __readmsr(IA32_VMX_CR4_FIXED0);
Cr4Fixed1 = __readmsr(IA32_VMX_CR4_FIXED1);
Cr0 = __readcr0();
Cr4 = __readcr4();

Cr0 |= Cr0Fixed0;
Cr0 &= Cr0Fixed1;
Cr4 |= Cr4Fixed0;
Cr4 &= Cr4Fixed1;

__writecr0(Cr0);
__writecr4(Cr4);

DbgPrint("[*] Now you can entry VMX operation.");

return TRUE;
}

4 VMXON region

4.1 概述

在 VMX 架构下,至少需要实现一个被称为 VMXON region,以及一个被称为 VMCS region 的物理区域。在使用 VMXON 指令进入 VMX root operation 前,需要为逻辑处理器分配一块物理内存区域作为 VMXON 区域。

VMM 需要用一个被称为 VMIXON region 的区域来管理整个 VMX operation 模式,VMXON 区域大小及所支持的 cache 类型与 VMCS 区域是一致的。指向 VMXON 区域的指针被称为 VMXON-point(指向物理地址)。VMXON 区域的物理指针需要作为操作数提供给 VMXON 指令。 VMXON 指令的操作数是物理地址。指令格式:VMXON VMXON-point

  • VMXON region,对应于 VMM。对 VMM 进行一些记录或者维护工作。
  • VMCS region,对应于 VM 中的一个虚拟处理器(逻辑处理器)。

关于 VMXON region 有以下几点需要注意:

  1. 存放 VMXON region 区域的物理内存需要对齐在 4KB 字节边界上(bits 11:0 为 0)。
  2. VMXON region 和 VMCS region 的大小内存 Cache 类型都来自 IA32_VMX_BASIC(0x480) MSR 寄存器。

4.2 IA32_VMX_BASIC

IA32_VMX_BASIC(0x480) MSR 寄存器如下:

13.png

Intel 卷3 Table A-1, 24.2 指出当前支持 VMX operation 模式的 CPU 使用的都是 write-back 类型,可使用 MmAllocateContiguousMemorySpecifyCache 函数来申请。

1
Buffer = MmAllocateContiguousMemorySpecifyCache(VMXONSize, Lowest, Highest, Lowest, MmNonCached);
  • Write-through:直写或透写。CPU 向 Cache 写入数据时,同时向内存也写一份,使 Cache 和内存的数据保持一致。
  • Write-back:回写。CPU 只将数据写入 Cache 不写入内存。只在数据被替换出缓存时,被修改的缓存数据才会被写到内存(flush)。

22.png

1
2
3
4
5
6
7
8
kd> rdmsr 0x480
msr[480] = 00da1000`00000004
kd> .formats 0x00da1000`00000004
Evaluate expression:
Hex: 00da1000`00000004
Decimal: 61379137108967428
Octal: 0003320400000000000004
Binary: 00000000 11011010 00010000 00000000 00000000 00000000 00000000 00000100

4.3 申请 VMXON region

在使用 VMXON 指令进入 VMX root operation 模式前,除了上述章节 2 中支持 VMX 的条件得到满足外,还需要准备好一块物理内存,填充部分字段进行初始化后,才能使用 VMXON 指令。VMXON 指令执行完成后,处理器会将 VMM 相关状态信息保存在 VMXON region 中,Intel 并没有公开 VMXON region 的具体内容。

按照 Intel 卷3 24.11.5 指出,初始化 VMXON region 时,只需要将 31 bits 的 VMCS revision identifier 写入 VMXON region 前 4 字节即可,然后 VMXON region 的 bit 31 = 0,接着就可以使用 VMXON 指令了。

注意:在执行 VMXON 和 VMXOFF 的整个期间都不能通过软件的方式修改 VMXON region 的值

申请步骤如下:

  1. 通过 IA32_VMX_BASIC[44:32] 得到 VMXON region 所需空间大小
  2. 通过 IA32_VMX_BASIC[53:50] 得到 VMXON region 所需内存类型,如 write-back。
  3. 调用 MmAllocateContiguousMemorySpecifyCache 函数申请连续指定大小和类型的物理内存。
  4. 判断返回地址是否在边界对齐 4KB(bit 11:0 为 0)。
  5. IA32_VMX_BASIC[31:0] 的 VMCS revision identifier 写入 VMXON region 前 4 字节。
  6. 将 VMXON region 的 bit 31 = 0
  7. 使用 VMXON 进入 VMX root operation(使用 __vmx_on 函数)。如果 VMXON 指令执行失败,则有 Rflags.CF = 1
  8. 判断 __vmx_on 函数的返回值是否是 TRUE
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#include <ntddk.h>

#define MSR_IA32_VMX_BASIC 0x480
#define PAGE_SIZE 0x1000

typedef struct _VIRTUAL_MACHINE_STATE
{
// BOOLEAN IsOnVmxRootMode; // Detects whether the current logical core is on Executing on VMX Root Mode
// BOOLEAN IncrementRip; // Checks whether it has to redo the previous instruction or not (it used mainly in Ept routines)
// BOOLEAN HasLaunched; // Indicate whether the core is virtualized or not
ULONG64 VmxonRegionPhysicalAddress; // VMXON region physical address
ULONG64 VmxonRegionVirtualAddress; // VMXON region virtual address
// ULONG64 VmcsRegionPhysicalAddress; // VMCS region physical address
// ULONG64 VmcsRegionVirtualAddress; // VMCS region virtual address
// ULONG64 VmmStack; // Stack for VMM in VM-Exit State
// ULONG64 MsrBitmapVirtualAddress; // Msr Bitmap Virtual Address
// ULONG64 MsrBitmapPhysicalAddress; // Msr Bitmap Physical Address
// VMX_VMXOFF_STATE VmxoffState; // Shows the vmxoff state of the guest
// PEPT_HOOKED_PAGE_DETAIL MtfEptHookRestorePoint; // It shows the detail of the hooked paged that should be restore in MTF vm-exit
} VIRTUAL_MACHINE_STATE, * PVIRTUAL_MACHINE_STATE;

typedef union _IA32_VMX_BASIC_MSR
{
ULONG64 All;
struct
{
ULONG64 RevisionIdentifier : 31; // [0-30]
ULONG64 Reserved1 : 1; // [31]
ULONG64 RegionSize : 13; // [32-44]
ULONG64 Reserved2 : 3; // [45-47]
ULONG64 SupportedIA64 : 1; // [48]
ULONG64 SupportedDualMoniter : 1; // [49]
ULONG64 MemoryType : 4; // [50-53]
ULONG64 VmExitReport : 1; // [54]
ULONG64 VmxCapabilityHint : 1; // [55]
ULONG64 VmxDeliverExceptionToVmm : 1; // [56]
ULONG64 Reserved3 : 7; // [57-63]
} Fields;
} IA32_VMX_BASIC_MSR, * PIA32_VMX_BASIC_MSR;

PVIRTUAL_MACHINE_STATE g_pGuestState = { 0 };

//
// 1. 通过 IA32_VMX_BASIC[44:32] 得到 VMXON region 所需空间大小。
// 2. 通过 IA32_VMX_BASIC[53:50] 得到 VMXON region 所需内存类型,如 write-back。
// 3. 调用 MmAllocateContiguousMemorySpecifyCache 函数申请连续指定大小和类型的物理内存。
// 4. 判断返回地址是否在边界对齐 4KB(bit 11:0 为 0)。
// 5. 将 IA32_VMX_BASIC[31:0] 的 VMCS revision identifier 写入 VMXON region 前 4 字节。
// 6. 将 VMXON region 的 bit 31 = 0。
// 5. Write VMXON region lowest 4 bytes from IA32_VMX_BASIC[31:0] that VMCS revision identifier.
// 6. Set VMXON region bit 31 to 0.
// 7. 使用 VMXON 进入 VMX root operation(使用 __vmx_on 函数)。
// 8. 判断 __vmx_on 函数的返回值是否是 TRUE。
//
BOOLEAN VmxAllocateVmxonRegion(VIRTUAL_MACHINE_STATE * CurrentGuestState)
{
ULONG VmxRegionSize = 0;
UCHAR VmxRegionType = 0;
PVOID pVmxRegion_VA = 0;
PHYSICAL_ADDRESS pVmxRegion_Phy = { 0 };
PHYSICAL_ADDRESS Lowphys = { 0 }, Highphys = { 0 };
ULONG64 uAlignVirtualAddress = 0;
ULONG64 uAlignPhysicalAddress = 0;
IA32_FEATURE_CONTROL_MSR MsrControl = { 0 };


// get the Vmx region size and type.
IA32_VMX_BASIC_MSR MsrVmxBasic = { 0 };
MsrVmxBasic.All = __readmsr(MSR_IA32_VMX_BASIC);
if(MsrVmxBasic.All)
{
VmxRegionSize = (MsrVmxBasic.All >> 32) & 0x1FFF;
VmxRegionType = (MsrVmxBasic.All >> 50) & 0xF;
}

if(VmxRegionSize != PAGE_SIZE)
{
DbgPrint("[*] Error : The VMX region size is error getting from IA32_VMX_BASIC[44:32].");
return FALSE;
}

do
{

// PVOID MmAllocateContiguousMemorySpecifyCache(
// [in] SIZE_T NumberOfBytes,
// [in] PHYSICAL_ADDRESS LowestAcceptableAddress,
// [in] PHYSICAL_ADDRESS HighestAcceptableAddress,
// [in, optional] PHYSICAL_ADDRESS BoundaryAddressMultiple,
// [in] MEMORY_CACHING_TYPE CacheType
// );
Lowphys.QuadPart = 0;
Highphys.QuadPart = -1;

switch(VmxRegionType)
{
case 0: // uncache
pVmxRegion_VA = MmAllocateContiguousMemorySpecifyCache(VmxRegionSize*2, Lowphys, Highphys, Lowphys, MmNonCached);
break;
case 6: // write-back
pVmxRegion_VA = MmAllocateContiguousMemorySpecifyCache(VmxRegionSize*2, Lowphys, Highphys, Lowphys, MmCached);
break;
}

if(pVmxRegion_VA == NULL)
{
DbgPrint("[*] Error : Couldn't Allocate Buffer for VMXON Region.");
return FALSE;
}

RtlSecureZeroMemory(pVmxRegion_VA, VmxRegionSize*2);
pVmxRegion_Phy = MmGetPhysicalAddress(pVmxRegion_VA);
uAlignVirtualAddress = (ULONG64)pVmxRegion_VA;
uAlignPhysicalAddress = (ULONG64)pVmxRegion_Phy.QuadPart;

// align the physical address to 0x1000(bit 11:0 to 0)
// the address align formula: if align to n bytes,(address + n-1) & (~(n-1))
// https://catecat.top/post/WinXP-Struct/
uAlignPhysicalAddress = ((ULONG64)uAlignPhysicalAddress + PAGE_SIZE - 1) & (~(PAGE_SIZE - 1));

while((uAlignPhysicalAddress & 0xFFF != 0) && ((ULONG64)uAlignPhysicalAddress - (ULONG64)pVmxRegion_Phy.QuadPart <= PAGE_SIZE))
{
uAlignPhysicalAddress++;
uAlignPhysicalAddress = ((ULONG64)uAlignPhysicalAddress + PAGE_SIZE - 1) & (~(PAGE_SIZE - 1));
}
}while(uAlignPhysicalAddress & 0xFFF != 0);


if(uAlignVirtualAddress == 0)
{
DbgPrint("[*] Error : MmGetVirtualForPhysical have a fault in VmxAllocateVmxonRegion function.\n");
return FALSE;
}

pVmxRegion_Phy.QuadPart = uAlignPhysicalAddress;
uAlignVirtualAddress = (ULONG64)MmGetVirtualForPhysical(pVmxRegion_Phy);

CurrentGuestState->VmxonRegionVirtualAddress = uAlignVirtualAddress;
CurrentGuestState->VmxonRegionPhysicalAddress = uAlignPhysicalAddress;

DbgPrint("[*] Virtual allocated buffer for VMXON at %llx\n", pVmxRegion_VA);
DbgPrint("[*] Virtual aligned allocated buffer for VMXON at %llx\n", uAlignVirtualAddress);
DbgPrint("[*] Aligned physical buffer allocated for VMXON at %llx\n", uAlignPhysicalAddress);

*(PULONG64)uAlignVirtualAddress = MsrVmxBasic.Fields.RevisionIdentifier;
_bittestandreset64((PULONG64)uAlignVirtualAddress, 31);

return TRUE;
}

//
// 1. Call DetectVMXSupportAndEnableCr4, SetEntryVMXoperationCondition functions.
// 2. Check MSR_IA32_FEATURE_CONTROL lock, EnableSMX, EnableVmxon bits.
// 3. Check FIXED0 and FIXED1 correspondingly bits in CR0, CR4.
// 4. Call VmxAllocateVmxonRegion function.
// 5. Use VMXON instruction into VMX root operation(use the __vmx_on function)。
// 6. Check the return value is TRUE about __vmx_on function or not.
//
BOOLEAN EnableVmxon(IN VIRTUAL_MACHINE_STATE * CurrentGuestState)
{
ULONG64 Cr0, Cr4 = 0;
ULONG64 Cr0Fixed0 = 0, Cr0Fixed1 = 0, Cr4Fixed0 = 0, Cr4Fixed1 = 0;
IA32_FEATURE_CONTROL_MSR MsrFeatureControl = { 0 };


if(DetectVMXSupportAndEnableCr4() == FALSE)
{
DbgPrint("[*] Error : DetectVMXSupportAndEnableCr4 have fault.\n");
return FALSE;
}
if(SetEntryVMXoperationCondition() == FALSE)
{
DbgPrint("[*] Error : SetEntryVMXoperationCondition have fault.\n");
return FALSE;
}

MsrFeatureControl.All = __readmsr(MSR_IA32_FEATURE_CONTROL);
if(MsrFeatureControl.All != 3 || MsrFeatureControl.All != 5)
{
DbgPrint("[*] Error : The MSR_IA32_FEATURE_CONTROL MSR lock, EnableSMX, EnableVmxon bits have fault.\n");
return FALSE;
}

Cr0Fixed0 = __readmsr(MSR_IA32_VMX_CR0_FIXED0);
Cr0Fixed1 = __readmsr(MSR_IA32_VMX_CR0_FIXED1);
Cr4Fixed0 = __readmsr(MSR_IA32_VMX_CR4_FIXED0);
Cr4Fixed1 = __readmsr(MSR_IA32_VMX_CR4_FIXED1);
Cr0 = __readcr0();
Cr4 = __readcr4();
if((Cr0Fixed0 & Cr0 != Cr0Fixed0) || (Cr4Fixed0 & Cr4 != Cr4Fixed0) || (Cr0Fixed1 | Cr0 != Cr0Fixed1) || (Cr4Fixed1 | Cr4 != Cr4Fixed1))
{
DbgPrint("[*] Error : FIXED0 and FIXED1 correspondingly bits in CR0, CR4.\n");
return FALSE;
}

if(VmxAllocateVmxonRegion(g_pGuestState) == FALSE)
{
DbgPrint("[*] Error : VmxAllocateVmxonRegion have fault.\n");
return FALSE;
}

if (__vmx_on((PULONG)&g_pGuestState->VmxonRegionPhysicalAddress) != 0)
{
DbgPrint("[*] Error : VMXON launch failed.\n");
return FALSE;
}

return TRUE;
}

__vmx_ on 是执行 VMXON 的函数。返回值显示不同的含义。

解释
0 操作成功。
1 操作失败,当前VMCS的VM-instructionerrorfield中提供了扩展状态。
2 操作失败,状态不可用。

参考资料:

5 逻辑处理器

  • 处理器(Processor):物理处理器,一个处理器上可以有多个核心(Core),常见的就是一个处理器上有两核心。

  • 核心(Core):每个核心都有一套自己的寄存器,各个核心可以并行执行指令(同一时间同时执行指令)。

  • 逻辑处理器(Logical Processor,LP):是机器可以同时处理的线程数,实际上 1 core == 1 logical processor(LP)

  • 在虚拟化中:一个虚拟处理器( Virtual Processor ,VP)也叫做 Virtual CPU(vCPU),就是一个 Physical Core(PC)。

    所以有以下等式:

    1 VP == 1 vCPU == 1 core(PC) == 1 LP

    • LP:Logical Processor,逻辑处理器。
    • VP:Virtual Processor,虚拟处理器。
    • vCPU:Virtual CPU,虚拟 CPU。
    • PC:Physical Core,物理核心。

我们使用的一个 VMX region、VMCS region 对应一个逻辑处理器

Processor

When I say processor I’m referring to the physical hardware. The component responsible for all processing operations. A machine can have more than one processor, just as some servers have a bi-processor setup. This is also referred to as a socket, CPU, or package.

Core

Within a physical processor there is an operation unit called a core. It’s often said that a core is like a processor, so a single processor with 2 cores means it has a single physical piece of hardware with two independent processing units that can fetch and execute instructions. Most processors today are multi-core.

Logical Processor

A logical processor is the number of threads a machine can handle at the same time. This is an abstraction of a processor, at least on Windows. It’s able to fetch and execute its own stream of instructions in the same processor time slot. The number of logical processors on your machine can be found by multiplying the number of physical cores by the thread count.

In virtualization literature you may see the term virtual processor (VP) used, however, by today’s standards a virtual processor is equivalent to a physical core. That means if you need to allocate a number of virtual processors for your virtual machine you determine the number of cores in your physical processor and assign however many that is. If I have a quad core processor, I allocate 4 virtual processors to the virtual machine. In this series, we start with a single processor setup, and then move to support a multi-processor environment – and to do that we must have control structures allocated for each core so that we can manage the virtual processor state.

——Day 2: Entering VMX Operation, Explaining Implementation Requirements

虚拟地址(VA): 指 Guest OS 提供给其应用程序使用的线性地址空间。
物理地址(PA): 经 VMM 抽象的、虚拟机看到的伪物理地址。
机器地址(MA):真实的机器地址,即地址总线上出现的地址信号。

这样的话,会存在两张页表:

  • VMM 维护一张页表,负责 PA 到 MA 的映射。$VMM:MA = g(PA)$。
  • Guest OS 维护一张页表,负责 VA 到 PA 的映射。$Guest\ OS:PA = f(VA)$。
  1. 交大课件
  2. 恶意样本分析手册-虚拟机检测篇(上)
  3. Hypervisor From Scratch
  4. 从头开始了解和使用Hypervisor(第1部分)
  5. 加密与解密第四版
  6. Windows Internals 7
  7. Virtualization Essentials 3(Matthew Portnoy)
  8. New Blue Pill
  9. MMU Virtualization Via Intel EPT: Technical Details
  10. N. Sahgal, D. Rodgers, Unclerstanding Intel Virtualization Technology