VT技术的大致含义如上。一个CPU上有vm monitor和guest。在guest中感受不到自己跑在虚拟环境中。执行普通的操作时在guest中的操作和未开启vt时的操作并没有什么不同,但是在执行一些特殊的指令或者进行一些特殊的操作,如执行cpuid指令、切换cr3指令、读写msr寄存器指令、触发异常时,会将控制权交还给vm monitor,通过vm monitor确定应该如何让该指令或者操作得到执行。在云服务器的搭建中可以使用该技术让一台实体机上可以跑多台虚拟机。而该技术也可以做到无痕对一些指令进行高权限的hook。甚至可以通过ept技术进行对指定地址的无痕hook。
在intel手册31.5章中详细描述了vmm的开启过程。
通过cpuid指令查询CPU是否支持VT
通过一些特定的MSR寄存器(在31.5.1中有介绍)来确认是否支持开启VT。
申请一块非分页内存,用来存放vmxon区域。其大小在IA32_VMX_BASIC这个寄存器中指定。该内存要4kb对齐(后12位全为0)。实际申请时申请一个页的大小的非分页内存即可。
初始化vmxon区域的版本编号(前4个字节)。版本编号通过IA32_VMX_BASIC这个寄存器的低4字节获取。实际实验时发现这个数为1,理论上把上一步申请到的内存前四字节置1即可,但为了兼容最好还是从寄存器中取值出来。
给cr0和cr4寄存器的特定位置置位,使得其满足如下要求
IA32_VMX_CR0_FIXED1
寄存器中为0的位,在cr0中必须为0
IA32_VMX_CR4_FIXED0
寄存器中为1的位,在cr4中必须为1
确认IA32_FEATURE_CONTROL寄存器被正确置位。该寄存器的具体细节可以通过《处理器虚拟化技术》2.3.2.1找到。
也就是说,该寄存器的bit0是lock为,bit2是outside位。必须保证这两位均为1才可以开启VT。可以大致通过读取该寄存器后将其&5,判断是否两个位均为1。
执行vmxon指令,传进去的参数是一个指向存放vmxon区域的物理地址的指针。如果该指令成功,那么rflags.cf=0,否则rflags.cf=1。
由上可写出检测是否支持VT的代码如下
设置vmxon区域。这一块的说明在intel白皮书24.11.5中。这一块内存是操作系统使用的。在申请区域之后要rtlzeromemory一下,防止没有挂页。其他位都置零即可,之后操作系统进行虚拟机的管理的时候会使用到这块内存。我们需要设置的只有头四个字节。要把msr中 IA32_VMX_BASIC
这个寄存器的低4字节填入vmxon的头4字节。否则vmxon的执行会出错。基本上这个寄存器的低4字节会是1。之后随着版本的更新可能会有变化。
在设置完vmxon区域之后,还需最后一步即可vmxon。需要根据msr寄存器中的指示进行cr0和cr4寄存器的设置。
具体解释在附录A中。
简单解释就是在进行vmxon之前, IA32_VMX_CR0_FIXED0
寄存器中为1的值,在cr0中必须为1、 IA32_VMX_CR0_FIXED1
寄存器中为0的值,在cr0中必须为0。cr4同理。那么可以通过 cr0 = cr0 | IA32_VMX_CR0_FIXED0 & IA32_VMX_CR0_FIXED1
操作将cr0和cr4进行置位。具体代码如下。
在进行以上步骤之后,可以通过vmxon打开柜门。
首先申请vmcs内存。和vmxon内存类似,这一块内存的头4个字节也要填入 IA32_VMX_BASIC
的低4个字节。vmcs中保存的是虚拟机的各种寄存器和控制区域、vmexit控制区域等。在装机过程中要通过vmwrite进行vmcs的设置。
申请vmcs内存并设置头4字节为 IA32_VMX_BASIC
的低4字节之后,通过vmclear指令进行初始化。传入的参数与vmxon的参数类似,是指向申请的内存的物理地址的一个指针。vmclear的用法如下
vmptrload
指令和vmxon、vmclear类似,传入的是指向vmcs结构的物理地址的一个指针。只有执行了这行指令之后才能通过 vmwrite
进行vmcs结构的填写。代码如下
24章中描述了需要设置的VMCS字段。大体包括如下几部分
除这5个区域之外,vmcs还有一个区域是vm exit信息区域。这个区域是只读的,存储的是vmx指令失败后失败代码的编号。
/
/
检测Bios是否开启VT
BOOLEAN VmxIsCheckSupportVTBIOS()
{
ULONG64 value
=
__readmsr(IA32_FEATURE_CONTROL);
return
(value &
0x5
)
=
=
0x5
;
}
/
/
检测CPU是否支持VT
BOOLEAN VmxIsCheckSupportVTCPUID()
{
int
cpuidinfo[
4
];
__cpuidex(cpuidinfo,
1
,
0
);
/
/
CPUID 是否支持VT ecx.vmx第
6
位 如果为
1
,支持VT,否则不支持
return
(cpuidinfo[
2
] >>
5
) &
1
;
}
/
/
检测CR4VT是否开启,如果为
1
代表已经开启过了,否则没有开启
BOOLEAN VmxIsCheckSupportVTCr4()
{
ULONG64 mcr4
=
__readcr4();
/
/
检测CR4 VT是否开启,cr4.vmxe如果第
14
位为
1
,那么VT已经被开启,否则可以开启
return
((mcr4 >>
13
) &
1
)
=
=
0
;
}
void checkVT()
{
if
(VmxIsCheckSupportVTCPUID())
{
DbgPrintEx(
77
,
0
,
"[db]:VmxIsCheckSupportVTCPUID number = %d\r\n"
, KeGetCurrentProcessorNumber());
}
if
(VmxIsCheckSupportVTBIOS())
{
DbgPrintEx(
77
,
0
,
"[db]:VmxIsCheckSupportVTBIOS number = %d\r\n"
, KeGetCurrentProcessorNumber());
}
if
(VmxIsCheckSupportVTCr4())
{
DbgPrintEx(
77
,
0
,
"[db]:VmxIsCheckSupportVTCr4 number = %d\r\n"
, KeGetCurrentProcessorNumber());
}
}
/
/
检测Bios是否开启VT
BOOLEAN VmxIsCheckSupportVTBIOS()
{
ULONG64 value
=
__readmsr(IA32_FEATURE_CONTROL);
return
(value &
0x5
)
=
=
0x5
;
}
/
/
检测CPU是否支持VT
BOOLEAN VmxIsCheckSupportVTCPUID()
{
int
cpuidinfo[
4
];
__cpuidex(cpuidinfo,
1
,
0
);
/
/
CPUID 是否支持VT ecx.vmx第
6
位 如果为
1
,支持VT,否则不支持
return
(cpuidinfo[
2
] >>
5
) &
1
;
}
/
/
检测CR4VT是否开启,如果为
1
代表已经开启过了,否则没有开启
BOOLEAN VmxIsCheckSupportVTCr4()
{
ULONG64 mcr4
=
__readcr4();
/
/
检测CR4 VT是否开启,cr4.vmxe如果第
14
位为
1
,那么VT已经被开启,否则可以开启
return
((mcr4 >>
13
) &
1
)
=
=
0
;
}
void checkVT()
{
if
(VmxIsCheckSupportVTCPUID())
{
DbgPrintEx(
77
,
0
,
"[db]:VmxIsCheckSupportVTCPUID number = %d\r\n"
, KeGetCurrentProcessorNumber());
}
if
(VmxIsCheckSupportVTBIOS())
{
DbgPrintEx(
77
,
0
,
"[db]:VmxIsCheckSupportVTBIOS number = %d\r\n"
, KeGetCurrentProcessorNumber());
}
if
(VmxIsCheckSupportVTCr4())
{
DbgPrintEx(
77
,
0
,
"[db]:VmxIsCheckSupportVTCr4 number = %d\r\n"
, KeGetCurrentProcessorNumber());
}
}
PHYSICAL_ADDRESS lowphys,heiPhy;
lowphys.QuadPart
=
0
;
heiPhy.QuadPart
=
-
1
;
pVcpu
-
>VmxOnAddr
=
MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE, lowphys, heiPhy, lowphys, MmCached);
PHYSICAL_ADDRESS lowphys,heiPhy;
lowphys.QuadPart
=
0
;
heiPhy.QuadPart
=
-
1
;
pVcpu
-
>VmxOnAddr
=
MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE, lowphys, heiPhy, lowphys, MmCached);
ULONG64 vmxBasic
=
__readmsr(IA32_VMX_BASIC);
*
(PULONG)pVcpu
-
>VmxOnAddr
=
(ULONG)vmxBasic;
ULONG64 vmxBasic
=
__readmsr(IA32_VMX_BASIC);
*
(PULONG)pVcpu
-
>VmxOnAddr
=
(ULONG)vmxBasic;
ULONG64 vcr00
=
__readmsr(IA32_VMX_CR0_FIXED0);
ULONG64 vcr01
=
__readmsr(IA32_VMX_CR0_FIXED1);
ULONG64 vcr04
=
__readmsr(IA32_VMX_CR4_FIXED0);
ULONG64 vcr14
=
__readmsr(IA32_VMX_CR4_FIXED1);
ULONG64 mcr4
=
__readcr4();
ULONG64 mcr0
=
__readcr0();
mcr4 |
=
vcr04;
mcr4 &
=
vcr14;
mcr0 |
=
vcr00;
mcr0 &
=
vcr01;
__writecr0(mcr0);
__writecr4(mcr4);
ULONG64 vcr00
=
__readmsr(IA32_VMX_CR0_FIXED0);
ULONG64 vcr01
=
__readmsr(IA32_VMX_CR0_FIXED1);
ULONG64 vcr04
=
__readmsr(IA32_VMX_CR4_FIXED0);
ULONG64 vcr14
=
__readmsr(IA32_VMX_CR4_FIXED1);
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2022-3-13 15:27
被smallzhong_编辑
,原因: