@[TOC]

介绍

VT,就是虚拟化技术(Virtualization Technology)的缩写。Intel VT就是指Intel的虚拟化技术。这种技术简单来说就是可以让一个CPU工作起来就像多个CPU并行运行,从而使得在一台电脑内可以同时运行多个操作系统。只有部分Intel的CPU才支持这种技术。vt是一个嵌套的过程,先启用vt技术的exe能获得更多的权限Intel VT技术,
主要由三部分技术组成:VTx、VTd和VTc。
VTx是处理器技术,提供内存以及虚拟机的硬件隔离,所涉及的技术有页表管理以及地址空间的保护。
VTd是处理有关芯片组的技术,VTc是针对网络提供的管理.
这里学习的是VTx技术
(中间代码见github)

环境配置

还是和之前windows内核的配置环境一样,在加上虚拟机里勾选上vt选项,并在虚拟机里安装好debugview和instdrv工具,方便驱动的加载和查看
在这里插入图片描述
安装wdk7600,下载后是个iso文件直接安装即可,安装好后可以在菜单栏看见命令框
http://www.microsoft.com/en-us/download/confirmation.aspx?id=11800
在这里插入图片描述

现在写一个简单的驱动程序,来测试环境有没有安装好
SOURCES文件

1
2
3
4
TARGETNAME=vt_x86
TARGETTYPE=DRIVER

SOURCES=entry.c \

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

VOID DriverUnload(PDRIVER_OBJECT driver)
{

DbgPrint("Driver is unloading...\r\n");
}



NTSTATUS
DriverEntry(
PDRIVER_OBJECT driver,
PUNICODE_STRING RegistryPath
)
{

DbgPrint("Driver Entered!\r\n");
driver->DriverUnload = DriverUnload;


return STATUS_SUCCESS;
}

然后用命令框build,build成功后会在当前路径生产一个文件夹,里面有一个sys文件,拖到虚拟机里
在这里插入图片描述先打开debugview,然后用驱动加载工具安装,启动,在debugview里观察到输出信息,代表环境安装成功
在这里插入图片描述

调试环境

在想要调试的源码上下入int 3断点

1
__asm int 3

在这里插入图片描述
build 语法高亮 重新build后拖入虚拟机,开启双击调试环境,加载驱动,就会发现windbg断下,并且可以在源码层进行调试

1
build /gw

在这里插入图片描述

汇编支持

对于有些汇编,如果不支持的话,可以嵌入硬编码

1
2
__emit 0xff
....

为了可读性,可以加载vtasm.asm文件,同时在SOURCES文件加入

1
I386_SOURCES=i386\vtasm.asm

在这里插入图片描述

一些名词解释
在这里插入图片描述

支持检测

在SOURCES文件里加入vtsystem.c \ 开始编辑vtsystem.c
检测电脑是否支持vt技术,一共需要3个方面检查cpuid 检查msr 检查 cr0 cr4

  1. cpuid
    检测电脑是否支持vt技术
    1 把eax设置为1
    2 汇编指令cpuid
    3观察ecx的第五位 如果为1 则支持vt技术
    1
    2
    3
    4
    5
    6
    7
    8
    Asm_CPUID(1, &uRet_EAX, &uRet_EBX, &uRet_ECX, &uRet_EDX);
    *((PULONG)&uCPUID) = uRet_ECX;

    if (uCPUID.VMX != 1)
    {
    Log("ERROR: 这个CPU不支持VT!",0);
    return FALSE;
    }
  2. 检测寄存器
    msr[3a]是否为1 则支持vt技术
    1
    2
    3
    4
    5
    6
    7
    8
    *((PULONG)&msr) = (ULONG)Asm_ReadMsr(MSR_IA32_FEATURE_CONTROL);
    if (msr.Lock != 1)
    {
    Log("ERROR:VT指令未被锁定!", 0);
    return FALSE;
    }
    Log("SUCCESS:这个CPU支持VT!", 0);
    return TRUE;
  3. 检测cr0 cr4
    cr4[13]为1 可以开启vt技术 这个在软件层次可以进行赋值
    cr0 [0] ,cr0[ 31] 开启保护模式和开启页保护模式的位
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    *((PULONG)&uCr0) = Asm_GetCr0();
    *((PULONG)&uCr4) = Asm_GetCr4();

    if (uCr0.PE != 1 || uCr0.PG!=1 || uCr0.NE!=1)
    {
    Log("ERROR:这个CPU没有开启VT!",0);
    return FALSE;
    }

    if (uCr4.VMXE == 1)
    {
    Log("ERROR:这个CPU已经开启了VT!",0);
    Log("可能是别的驱动已经占用了VT,你必须关闭它后才能开启。",0);
    return FALSE;
    }
    总一下就是BOOLEAN IsVTEnabled()函数的编写
    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
    BOOLEAN IsVTEnabled()
    {
    ULONG uRet_EAX, uRet_ECX, uRet_EDX, uRet_EBX;
    _CPUID_ECX uCPUID;
    _CR0 uCr0;
    _CR4 uCr4;
    IA32_FEATURE_CONTROL_MSR msr;

    //1. CPUID
    Asm_CPUID(1, &uRet_EAX, &uRet_EBX, &uRet_ECX, &uRet_EDX);
    *((PULONG)&uCPUID) = uRet_ECX;

    if (uCPUID.VMX != 1)
    {
    Log("ERROR: 这个CPU不支持VT!",0);
    return FALSE;
    }

    // 2. MSR
    *((PULONG)&msr) = (ULONG)Asm_ReadMsr(MSR_IA32_FEATURE_CONTROL);
    if (msr.Lock != 1)
    {
    Log("ERROR:VT指令未被锁定!", 0);
    return FALSE;
    }
    Log("SUCCESS:这个CPU支持VT!", 0);
    return TRUE;


    // 3. CR0 CR4
    *((PULONG)&uCr0) = Asm_GetCr0();
    *((PULONG)&uCr4) = Asm_GetCr4();

    if (uCr0.PE != 1 || uCr0.PG!=1 || uCr0.NE!=1)
    {
    Log("ERROR:这个CPU没有开启VT!",0);
    return FALSE;
    }

    if (uCr4.VMXE == 1)
    {
    Log("ERROR:这个CPU已经开启了VT!",0);
    Log("可能是别的驱动已经占用了VT,你必须关闭它后才能开启。",0);
    return FALSE;
    }


    }

    vmxon

    先看流程图
    在这里插入图片描述

开锁—检测支持(cr4)
开柜门—vmxon
拔电源–vmclear
选中机器—vmptrload
装机—vmcs
开机–vmlaunch
拔电源–vmclear
关柜门vmxoff
关锁—软件层取消
在进入vmxon之前首先需要把cr4[13]=1,这样才可以开启vt,这步相当于开锁,而vmxon参数需要申请一块内存,用来记录host相关的信息,最多为4kb,具体大小可以通过msr[480]读取出来

1
rdmsr 400

低四字节为系统的版本号,接下来的两字节为所要的大小,一般为0x1000
申请好以后,我们所拿到的是虚拟地址,而事实上我们需要的是一块物理地址,通过MmGetPhysicalAddress函数即可实现
结束后我们还需要检查cs标志位来观察是否成功

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

#include "vtsystem.h"
#include "vtasm.h"
#include "exithandler.h"

VMX_CPU g_VMXCPU;




NTSTATUS StartVirtualTechnology()
{
_CR4 uCr4;
_EFLAGS uEflags;
if (!IsVTEnabled()) {
return STATUS_UNSUCCESSFUL;
}

*((PULONG)&uCr4) = Asm_GetCr4();
uCr4.VMXE = 1;
Asm_SetCr4(*((PULONG)&uCr4)); //开锁

g_VMXCPU.pVMXONRegion = ExAllocatePoolWithTag(NonPagedPool, 0x1000, 'vmon');//申请虚拟地址
RtlZeroMemory(g_VMXCPU.pVMXONRegion, 0x1000);//清0
*(PULONG)g_VMXCPU.pVMXONRegion = 1;//设置版本号 和msr读出来的版本号一样
g_VMXCPU.pVMXONRegion_PA = MmGetPhysicalAddress(g_VMXCPU.pVMXONRegion);//转物理地址

Vmx_VmxOn(g_VMXCPU.pVMXONRegion_PA.LowPart, g_VMXCPU.pVMXONRegion_PA.HighPart);

*((PULONG)&uEflags) = Asm_GetEflags();//读标志位

if (uEflags.CF != 0)
{
Log("ERROR:VMXON指令调用失败!", 0);
ExFreePool(g_VMXCPU.pVMXONRegion);
return STATUS_UNSUCCESSFUL;
}


return STATUS_SUCCESS;
}

NTSTATUS StopVirtualTechnology()
{
_CR4 uCr4;

Vmx_VmxOff();

*((PULONG)&uCr4) = Asm_GetCr4();
uCr4.VMXE = 0;
Asm_SetCr4(*((PULONG)&uCr4));//关锁

ExFreePool(g_VMXCPU.pVMXONRegion);//释放内存空间

return STATUS_SUCCESS;
}

static BOOLEAN IsVTEnabled()
{
ULONG uRet_EAX, uRet_ECX, uRet_EDX, uRet_EBX;
_CPUID_ECX uCPUID;
_CR0 uCr0;
_CR4 uCr4;
IA32_FEATURE_CONTROL_MSR msr;

//1. CPUID
Asm_CPUID(1, &uRet_EAX, &uRet_EBX, &uRet_ECX, &uRet_EDX);
*((PULONG)&uCPUID) = uRet_ECX;

if (uCPUID.VMX != 1)
{
Log("ERROR: 这个CPU不支持VT!",0);
return FALSE;
}

// 2. MSR
*((PULONG)&msr) = (ULONG)Asm_ReadMsr(MSR_IA32_FEATURE_CONTROL);
if (msr.Lock != 1)
{
Log("ERROR:VT指令未被锁定!", 0);
return FALSE;
}
Log("SUCCESS:这个CPU支持VT!", 0);
return TRUE;


// 3. CR0 CR4
*((PULONG)&uCr0) = Asm_GetCr0();
*((PULONG)&uCr4) = Asm_GetCr4();

if (uCr0.PE != 1 || uCr0.PG!=1 || uCr0.NE!=1)
{
Log("ERROR:这个CPU没有开启VT!",0);
return FALSE;
}

if (uCr4.VMXE == 1)
{
Log("ERROR:这个CPU已经开启了VT!",0);
Log("可能是别的驱动已经占用了VT,你必须关闭它后才能开启。",0);
return FALSE;
}


}

vmcs

vmxon申请的4k是给vmm所用的,而vmcs也要申请4k大小,是给虚拟机使用的,每有一个虚拟机,vmcs就会申请申请一个4k
在vmcs之前还有两个操作,一个是vmclear,当有虚拟机创建后,vmclear进行初始化操作,另一个是vmptrload,它是负责虚拟机的选中,当有多个虚拟机时,通过vmptrload来选中一个虚拟机,进行操作命令

1
2
3
4
5
6
7
8
9
10
11
12
13
//vmcs申请内存
g_VMXCPU.pVMCSRegion = ExAllocatePoolWithTag(NonPagedPool, 0x1000, 'vmcs');//申请虚拟地址
RtlZeroMemory(g_VMXCPU.pVMCSRegion, 0x1000);//清0
*(PULONG)g_VMXCPU.pVMCSRegion = 1;//设置版本号 和msr读出来的版本号一样
g_VMXCPU.pVMCSRegion_PA = MmGetPhysicalAddress(g_VMXCPU.pVMCSRegion);//转物理地址

//vmclear vmptrload
Vmx_VmClear(g_VMXCPU.pVMCSRegion_PA.LowPart, g_VMXCPU.pVMCSRegion_PA.HighPart);
Vmx_VmPtrld(g_VMXCPU.pVMCSRegion_PA.LowPart, g_VMXCPU.pVMCSRegion_PA.HighPart);
//vmcs
SetupVMCS();
//vmlaunch
Vmx_VmLaunch();

具体的vmcs字段,对应一个虚拟机的大部分信息,包括五个部分,我们需要用Vmx_VmWrite来分别对他们写入
1.Guest State Area
2.Host State Area
3.虚拟机运行控制域
4.VMEntry运行控制域
5.VMExit运行控制域
先说VMEntry运行控制域,设置规则为msr的后两字节如果bit位为1 必须设置为1;而高两位字节如果bit位为0 必须设置为0;其他的位可0可1,所以设置规则需要我们对高位进行与操作,对低位进行或操作

1
2
3
4
5
6
7
8
static ULONG  VmxAdjustControls(ULONG Ctl, ULONG Msr)
{
LARGE_INTEGER MsrValue;
MsrValue.QuadPart = Asm_ReadMsr(Msr);
Ctl &= MsrValue.HighPart; /* bit == 0 in high word ==> must be zero */
Ctl |= MsrValue.LowPart; /* bit == 1 in low word ==> must be one */
return Ctl;
}//msr[481],msr[482]

这两个用Vmx_VmWrite写入

1
2
Vmx_VmWrite(PIN_BASED_VM_EXEC_CONTROL, VmxAdjustControls(0, MSR_IA32_VMX_PINBASED_CTLS));
Vmx_VmWrite(CPU_BASED_VM_EXEC_CONTROL, VmxAdjustControls(0, MSR_IA32_VMX_PROCBASED_CTLS));

VMEntry运行控制域和VMExit运行控制域也是如此

1
2
3
4
// 4.VMEntry运行控制域
Vmx_VmWrite(VM_ENTRY_CONTROLS, VmxAdjustControls(0, MSR_IA32_VMX_ENTRY_CTLS));
// 5.VMExit运行控制域
Vmx_VmWrite(VM_EXIT_CONTROLS, VmxAdjustControls(0, MSR_IA32_VMX_EXIT_CTLS));

接下来说Host State Area 它没有如上的01设置规则,类似于中断返回时宿主机应该做何操作,保存什么寄存器等

  1. 读取cr0 cr3 cr4到宿主机
    1
    2
    3
    Vmx_VmWrite(HOST_CR0, Asm_GetCr0());
    Vmx_VmWrite(HOST_CR3, Asm_GetCr3());
    Vmx_VmWrite(HOST_CR4, Asm_GetCr4());
  2. 返回到哪里,即eip应该如何设置,栈应该怎么构造
    1
    2
    3
    Vmx_VmWrite(HOST_RSP, ((ULONG)g_VMXCPU.pStack) + 0x2000);     //Host 临时栈
    Vmx_VmWrite(HOST_RIP, (ULONG)VMMEntryPoint); //这里定义我们的VMM处理程序入口

    入口程序需要另外一个文件exithandler.c来构造一些列的操作,对于栈的话,需要再申请一块内存进行操作
    1
    2
    g_VMXCPU.pStack = ExAllocatePoolWithTag(NonPagedPool, 0x1000, 'stck');//申请虚拟地址
    RtlZeroMemory(g_VMXCPU.pStack, 0x1000);//清0
  3. 读取段选择子
    1
    2
    3
    4
    5
    6
    7
    Vmx_VmWrite(HOST_ES_SELECTOR, Asm_GetEs() & 0xFFF8);
    Vmx_VmWrite(HOST_CS_SELECTOR, Asm_GetCs() & 0xFFF8);
    Vmx_VmWrite(HOST_DS_SELECTOR, Asm_GetDs() & 0xFFF8);
    Vmx_VmWrite(HOST_FS_SELECTOR, Asm_GetFs() & 0xFFF8);
    Vmx_VmWrite(HOST_GS_SELECTOR, Asm_GetGs() & 0xFFF8);
    Vmx_VmWrite(HOST_SS_SELECTOR, Asm_GetSs() & 0xFFF8);
    Vmx_VmWrite(HOST_TR_SELECTOR, Asm_GetTr() & 0xFFF8);
  4. fs,gs,tr,gdtr,idtr的基质
    fs和gs可以通过引用的方法更新基质,tr则通过查找搜索硬编码,gdtr,idtr通过汇编指令动态获取
1
2
3
4
5
6
7
8
9
10
11
12
       mov ax, fs
mov fs, ax

mov ax, gs
mov gs, ax

Vmx_VmWrite(HOST_TR_BASE, 0x80042000);

GdtBase = Asm_GetGdtBase();
IdtBase = Asm_GetIdtBase();
Vmx_VmWrite(HOST_GDTR_BASE, GdtBase);
Vmx_VmWrite(HOST_IDTR_BASE, IdtBase);

在这里插入图片描述

  1. KiFastCallEntry()函数的入口信息
    1
    2
    3
    Vmx_VmWrite(HOST_IA32_SYSENTER_CS, Asm_ReadMsr(MSR_IA32_SYSENTER_CS) & 0xFFFFFFFF);
    Vmx_VmWrite(HOST_IA32_SYSENTER_ESP, Asm_ReadMsr(MSR_IA32_SYSENTER_ESP) & 0xFFFFFFFF);
    Vmx_VmWrite(HOST_IA32_SYSENTER_EIP, Asm_ReadMsr(MSR_IA32_SYSENTER_EIP) & 0xFFFFFFFF);
    最后是Guest State Area和host设置类似

1.读取cr0 cr3 cr4到宿主机

1
2
3
Vmx_VmWrite(GUEST_CR0, Asm_GetCr0());
Vmx_VmWrite(GUEST_CR3, Asm_GetCr3());
Vmx_VmWrite(GUEST_CR4, Asm_GetCr4());
  1. 写dr7为0x400
    1
    Vmx_VmWrite(GUEST_DR7, 0x400);
  2. 栈和eip
    1
    2
    Vmx_VmWrite(GUEST_RSP, ((ULONG)g_VMXCPU.pStack) + 0x1000);     //Guest 临时栈
    Vmx_VmWrite(GUEST_RIP, (ULONG)GuestEntry); // 客户机的入口点
    同样的我们需要补充guestentry函数来处理客户机的东西
    1
    2
    3
    4
    void __declspec(naked) GuestEntry()
    {
    __asm{
    }
  3. 读取eflags,段选择子,以及他们的基质 属性,界限
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Vmx_VmWrite(GUEST_RFLAGS, Asm_GetEflags() & ~0x200);//cli关中断 

    Vmx_VmWrite(GUEST_ES_SELECTOR, Asm_GetEs() & 0xFFF8);
    Vmx_VmWrite(GUEST_CS_SELECTOR, Asm_GetCs() & 0xFFF8);
    Vmx_VmWrite(GUEST_DS_SELECTOR, Asm_GetDs() & 0xFFF8);
    Vmx_VmWrite(GUEST_FS_SELECTOR, Asm_GetFs() & 0xFFF8);
    Vmx_VmWrite(GUEST_GS_SELECTOR, Asm_GetGs() & 0xFFF8);
    Vmx_VmWrite(GUEST_SS_SELECTOR, Asm_GetSs() & 0xFFF8);
    Vmx_VmWrite(GUEST_TR_SELECTOR, Asm_GetTr() & 0xFFF8);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Vmx_VmWrite(GUEST_ES_AR_BYTES, 0x10000);//不可用状态
    Vmx_VmWrite(GUEST_FS_AR_BYTES, 0x10000);
    Vmx_VmWrite(GUEST_DS_AR_BYTES, 0x10000);
    Vmx_VmWrite(GUEST_SS_AR_BYTES, 0x10000);
    Vmx_VmWrite(GUEST_GS_AR_BYTES, 0x10000);
    Vmx_VmWrite(GUEST_LDTR_AR_BYTES, 0x10000);

    Vmx_VmWrite(GUEST_CS_AR_BYTES, 0xc09b);//属性
    Vmx_VmWrite(GUEST_CS_BASE, 0);//基质
    Vmx_VmWrite(GUEST_CS_LIMIT, 0xffffffff);//界限

    Vmx_VmWrite(GUEST_TR_AR_BYTES, 0x008b);
    Vmx_VmWrite(GUEST_TR_BASE, 0x80042000);
    Vmx_VmWrite(GUEST_TR_LIMIT, 0x20ab);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    __asm {
    mov ax, es
    mov es, ax

    mov ax, ds
    mov ds, ax

    mov ax, fs
    mov fs, ax

    mov ax, gs
    mov gs, ax

    mov ax, ss
    mov ss, ax
    }
  4. gdtr idtr
    1
    2
    3
    4
    Vmx_VmWrite(GUEST_GDTR_BASE, GdtBase);
    Vmx_VmWrite(GUEST_GDTR_LIMIT, Asm_GetGdtLimit());
    Vmx_VmWrite(GUEST_IDTR_BASE, IdtBase);
    Vmx_VmWrite(GUEST_IDTR_LIMIT, Asm_GetIdtLimit());
  5. 其他的一些设置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Vmx_VmWrite(GUEST_IA32_DEBUGCTL, Asm_ReadMsr(MSR_IA32_DEBUGCTL) & 0xFFFFFFFF);
    Vmx_VmWrite(GUEST_IA32_DEBUGCTL_HIGH, Asm_ReadMsr(MSR_IA32_DEBUGCTL) >> 32);

    Vmx_VmWrite(GUEST_SYSENTER_CS, Asm_ReadMsr(MSR_IA32_SYSENTER_CS) & 0xFFFFFFFF);
    Vmx_VmWrite(GUEST_SYSENTER_ESP, Asm_ReadMsr(MSR_IA32_SYSENTER_ESP) & 0xFFFFFFFF);
    Vmx_VmWrite(GUEST_SYSENTER_EIP, Asm_ReadMsr(MSR_IA32_SYSENTER_EIP) & 0xFFFFFFFF); // KiFastCallEntry

    Vmx_VmWrite(VMCS_LINK_POINTER, 0xffffffff);//物理地址不用设置为0xffffffff
    Vmx_VmWrite(VMCS_LINK_POINTER_HIGH, 0xffffffff);

    vm-exit

    在entry.c文件里的StartVirtualTechnology()函数上下保存上下文环境,让其执行完后可以正常退出
    在这里插入图片描述
    在这里插入图片描述
    对于exithander我们需要加入一些特有的异常处理函数,来解决常见的问题,包括cpuid,读写,和vmcall等等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
switch (ExitReason)
{
case EXIT_REASON_CPUID:
HandleCPUID();
Log("EXIT_REASON_CPUID", 0)
break;

case EXIT_REASON_VMCALL:
HandleVmCall();
Log("EXIT_REASON_VMCALL", 0)
break;

case EXIT_REASON_CR_ACCESS:
HandleCrAccess();
//Log("EXIT_REASON_CR_ACCESS", 0)
break;

default:
Log("not handled reason: %p", ExitReason);
__asm int 3
}

vmcall其中的一部分用来执行StopVirtualTechnology(),因为guest没有权限执行vmxoff

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void HandleVmCall()
{
if (g_vmcall_arg == 'SVT')
{
Vmx_VmClear(g_VMXCPU.pVMCSRegion_PA.LowPart, g_VMXCPU.pVMCSRegion_PA.HighPart);
Vmx_VmxOff();
__asm {
mov esp, g_stop_esp
jmp g_stop_eip
}
}
else {
__asm int 3
}
}
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
if (g_VMXCPU.bVTStartSuccess)
{
g_vmcall_arg = 'SVT';
__asm {
pushad
pushfd
mov g_stop_esp, esp
mov g_stop_eip, offset LLL
}
Vmx_VmCall();
LLL:
__asm {
popfd
popad
}
g_VMXCPU.bVTStartSuccess = FALSE;
*((PULONG)&uCr4) = Asm_GetCr4();
uCr4.VMXE = 0;
Asm_SetCr4(*((PULONG)&uCr4));

ExFreePool(g_VMXCPU.pVMXONRegion);
ExFreePool(g_VMXCPU.pVMCSRegion);
ExFreePool(g_VMXCPU.pStack);

Log("SUCCESS:关闭VT成功!", 0);
Log("SUCCESS:现在这个CPU退出了VMX模式.", 0);
}

到此为止,基本的代码框架已经写完了。

EPT

EPT用于实现内存的虚拟化,它是把guest的物理地址转换为host的物理地址
先解释几个名词
主机物理地址 (Host Physical Address):HPA
主机虚拟地址 (Host Virtual Address): HVA
客户物理地址 (Guest Physical Address):GPA
客户虚拟地址(Guest Virtual Address):GVA
PDBR:页目录表物理基地址寄存器,X86上叫CR3
硬件层面引入EPTP寄存器,来指向EPT页表基地址。Guest运行时,Guest页表被载入PDBR,而 EPT 页表被载入专门的EPT 页表指针寄存器 EPTP。
在这里插入图片描述
当Guest中进程访问GVA时,CPU首先就要通过PDBR寄存器去找页目录,但是PDBR中存储的地址是GPA,所以要到EPT中进行GPA->HPA的转换,这个转换过程和物理MMU的工作流程相同。

找到了页目录的HPA基地址,再通过GVA中的Directory offset段,就找到页表的VGA了,这个页表VGA再去EPT中进行GPA->HPA的转换,就找到页表VGA的HPA了。

重复上述过程,依次查找各级页表,最终获得该GVA对应的HPA。如果是三级或者四级页表,也是同样的原理。
流程为:虚拟地址>一级页表>(ept转换)>二级页表>(ept转换)>三级页表>(ept转换)>客户端物理地址
对应上述的图表就是3*4次转换
……..