首页
社区
课程
招聘
[原创]VT虚拟化技术笔记(part 1)
2022-3-13 15:07 20372

[原创]VT虚拟化技术笔记(part 1)

2022-3-13 15:07
20372

目录

VT技术笔记(part 1)

  • 最近在学习VT技术,想把学习过程中记录的笔记分享出来。技术不精有不对的地方还望指正。代码会发到https://github.com/smallzhong/myvt这个仓库中,目前还在施工中,还没写完。欢迎star,预计这个月内完工。

VT概述

  • image-20220313143659687

    VT技术的大致含义如上。一个CPU上有vm monitor和guest。在guest中感受不到自己跑在虚拟环境中。执行普通的操作时在guest中的操作和未开启vt时的操作并没有什么不同,但是在执行一些特殊的指令或者进行一些特殊的操作,如执行cpuid指令、切换cr3指令、读写msr寄存器指令、触发异常时,会将控制权交还给vm monitor,通过vm monitor确定应该如何让该指令或者操作得到执行。在云服务器的搭建中可以使用该技术让一台实体机上可以跑多台虚拟机。而该技术也可以做到无痕对一些指令进行高权限的hook。甚至可以通过ept技术进行对指定地址的无痕hook。

VT执行的大体流程

  • 该流程的描述出自B站周壑的VT教学视频中,讲得非常通俗易懂。VT的开启与关闭大致分为开锁、开柜门、拔电源、选中机器、装机、开机、拔电源、关柜门、关锁9个步骤。这9个步骤的具体描述如下。此处为大体的描述,在后面会对每一个步骤的具体细节进行具体的说明。

image-20220307081330564

  1. 开锁:检测是否支持VT
  2. 开柜门:vmxon。需要申请一块内存然后把这块内存的lowpart和highpart作为参数调用vmxon。注意这块内存的开头要填入一个特定的msr寄存器中的值。注意这里vmxon修改cr4寄存器之后,cr0寄存器的页保护和段保护位都不能再置位,如果试图修改cr0的页保护或者段保护位,会触发异常蓝屏。
  3. 拔电源:vmclear
  4. 选中机器:vmptrload,相当于一个指针。需要通过vmptrload来指向某个机器。如果当前有多个机器的话,在各个机器之间的切换操作也需要用到vmptrload。
  5. 装机:设置vmcs。也是要申请一块内存,然后在这块内存里面填入开启虚拟机时虚拟机使用的一些寄存器。要注意这里不能直接操作这块内存区域,要用vmwrite来操作。因为随着版本的更新,原来在这块内存区域的东西可能后来就不在了。vmwrite保证兼容性。
  6. 开机vmlaunch
  7. 拔电源vmclear
  8. 关柜门vmoff

检测是否支持VT(开锁)

  • 在intel手册31.5章中详细描述了vmm的开启过程。

    image-20220313141119292

    1. 通过cpuid指令查询CPU是否支持VT

    2. 通过一些特定的MSR寄存器(在31.5.1中有介绍)来确认是否支持开启VT。

    3. 申请一块非分页内存,用来存放vmxon区域。其大小在IA32_VMX_BASIC这个寄存器中指定。该内存要4kb对齐(后12位全为0)。实际申请时申请一个页的大小的非分页内存即可。

    4. 初始化vmxon区域的版本编号(前4个字节)。版本编号通过IA32_VMX_BASIC这个寄存器的低4字节获取。实际实验时发现这个数为1,理论上把上一步申请到的内存前四字节置1即可,但为了兼容最好还是从寄存器中取值出来。

    5. 给cr0和cr4寄存器的特定位置置位,使得其满足如下要求

      • IA32_VMX_CR0_FIXED0 寄存器中为1的位,在cr0中必须为1
      • IA32_VMX_CR0_FIXED1 寄存器中为0的位,在cr0中必须为0

      • IA32_VMX_CR4_FIXED0 寄存器中为1的位,在cr4中必须为1

      • IA32_VMX_CR4_FIXED1 寄存器中为0的位,在cr4中必须为0
    6. 确认IA32_FEATURE_CONTROL寄存器被正确置位。该寄存器的具体细节可以通过《处理器虚拟化技术》2.3.2.1找到。

      image-20220313142937541

      也就是说,该寄存器的bit0是lock为,bit2是outside位。必须保证这两位均为1才可以开启VT。可以大致通过读取该寄存器后将其&5,判断是否两个位均为1。

    7. 执行vmxon指令,传进去的参数是一个指向存放vmxon区域的物理地址的指针。如果该指令成功,那么rflags.cf=0,否则rflags.cf=1。

  • 由上可写出检测是否支持VT的代码如下

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
//检测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());
    }
}

vmxon(开柜门)

  • 申请一块4KB对齐的非分页内存,作为vmxon的参数。
1
2
3
4
PHYSICAL_ADDRESS lowphys,heiPhy;
lowphys.QuadPart = 0;
heiPhy.QuadPart = -1;
pVcpu->VmxOnAddr = MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE, lowphys, heiPhy, lowphys, MmCached);
  • 设置vmxon区域。这一块的说明在intel白皮书24.11.5中。这一块内存是操作系统使用的。在申请区域之后要rtlzeromemory一下,防止没有挂页。其他位都置零即可,之后操作系统进行虚拟机的管理的时候会使用到这块内存。我们需要设置的只有头四个字节。要把msr中 IA32_VMX_BASIC 这个寄存器的低4字节填入vmxon的头4字节。否则vmxon的执行会出错。基本上这个寄存器的低4字节会是1。之后随着版本的更新可能会有变化。

    image-20220307110817566

    1
    2
    ULONG64 vmxBasic = __readmsr(IA32_VMX_BASIC);
    *(PULONG)pVcpu->VmxOnAddr = (ULONG)vmxBasic;
  • 在设置完vmxon区域之后,还需最后一步即可vmxon。需要根据msr寄存器中的指示进行cr0和cr4寄存器的设置。

    具体解释在附录A中。

    image-20220307112855414

    简单解释就是在进行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进行置位。具体代码如下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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);
  • 在进行以上步骤之后,可以通过vmxon打开柜门。

    1
    int error = __vmx_on(&pVcpu->VmxOnAddrPhys.QuadPart);

申请vmcs内存并vmclear(拔电源)

  • 首先申请vmcs内存。和vmxon内存类似,这一块内存的头4个字节也要填入 IA32_VMX_BASIC 的低4个字节。vmcs中保存的是虚拟机的各种寄存器和控制区域、vmexit控制区域等。在装机过程中要通过vmwrite进行vmcs的设置。

  • 申请vmcs内存并设置头4字节为 IA32_VMX_BASIC 的低4字节之后,通过vmclear指令进行初始化。传入的参数与vmxon的参数类似,是指向申请的内存的物理地址的一个指针。vmclear的用法如下

    1
    __vmx_vmclear(&pVcpu->VmxcsAddrPhys.QuadPart);

vmptrload(选中机器)

  • vmptrload 指令和vmxon、vmclear类似,传入的是指向vmcs结构的物理地址的一个指针。只有执行了这行指令之后才能通过 vmwrite 进行vmcs结构的填写。代码如下

    1
    __vmx_vmptrld(&pVcpu->VmxcsAddrPhys.QuadPart);

设置vmcs(装机)

  • image-20220307084341235

    24章中描述了需要设置的VMCS字段。大体包括如下几部分

    1. Guest state fields
    2. Host state fields
    3. vm-control fields
      1. vm execution control
      2. vm exit control
      3. vm entry control

​ 除这5个区域之外,vmcs还有一个区域是vm exit信息区域。这个区域是只读的,存储的是vmx指令失败后失败代码的编号。

  • VMCS字段的设置是VT中最为繁琐的一步。对于VMCS中一些具体字段应该如何进行设置,会在下一篇文章中进行说明。

参考文献

  1. intel白皮书
  2. 邓志《处理器虚拟化技术》
  3. B站周壑VT教学视频

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2022-3-13 15:27 被smallzhong_编辑 ,原因:
收藏
点赞12
打赏
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/03/28 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (2)
雪    币: 3350
活跃值: (3372)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
fengyunabc 1 2022-3-13 19:31
2
0
感谢分享!
雪    币: 4427
活跃值: (3454)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
木志本柯 2022-3-16 10:05
3
0
嘿嘿谢谢整理 我就白嫖了
游客
登录 | 注册 方可回帖
返回