Intel VT学习笔记(八)—— 开启EPT
要点回顾
在上一篇中,我们学习了什么是EPT模式,以及EPT模式是如何在GPA和HPA之间进行转换的。
在本篇,我们即将学习的是32-bit分页模式(10-10-12分页)下如何开启EPT。
在讲解正题之前,周壑老师抛出了几个小问题。
问题一:为什么在退出VT模式后,使用PCHUNTER查看GDT表会蓝屏?
问题二:进入VT模式时通过EFLAGS关闭了外部中断(IF位清零),切换到HOST时需要再手动关闭一下吗?
这里我们就直接说答案了,大家也可以之后动手实验一下。
问题一答案:因为在退出VT模式时,GDT.Limit和IDT.Limit会被设置为0xFFFF,可以在退出时手动设置回去(可参考Intel开发手册卷3第27.5.2小节)。
问题二答案: 不需要,因为从Guest切换到Host时,RIP、RSP会从VMCS相应的字段读取,而EFLAGS除了部分关键的标志位必须置1以外,其余部分会被清零(可参考Intel开发手册卷3第27.5.2小节)。
32-bit分页模式开启EPT
描述:32-bit分页模式即10-10-12分页模式,接下来将学习如何在该模式下开启EPT
设置VMCS字段
IA32_VMX_PROCBASED_CTLS2 MSR字段
IA32_VMX_PROCBASED_CTLS2 MSR寄存器中包含了与开启VT相关的标志位(参考Intel开发手册卷3附录A.3.3和表24-7)
IA32_VMX_PROCBASED_CTLS MSR字段
只有当IA32_VMX_PROCBASED_CTLS MSR的第31位(最后一位)为1时,IA32_VMX_PROCBASED_CTLS2 MSR寄存器才会生效(参考Intel开发手册卷3表24-6)。
EPTP字段
最关键的是设置EPTP字段,格式如下(参考Intel开发手册卷3第24.6.11小节):
首先除了后12位比特位为属性位以外,剩下的部分为物理地址,直接写入即可。
属性位第2:0位表示是否开启缓存,但不能随意设置,需要参考IA32_VMX_EPT_VPID_CAP MSR寄存器的相关标志位(详见Intel开发手册卷3附录A.10),只有当它的第8位为1时,才能关闭缓存(UC),当14位为1时,才能开启缓存(WB)。
然后属性位的第5:3位表示当前页表的极数,值比实际页数少1(比如是4级页表,就这个字段写3),这个值也不是随便写的,同样要参考IA32_VMX_EPT_VPID_CAP MSR寄存器,如果它的第6位为1,那么一定是4级页表。
最后看一下笔者实验的环境中IA32_VMX_EPT_VPID_CAP MSR寄存器的值。
可以看到,第6位为1,说明当前是4级页表,第14位为1,说明必须开启缓存。
代码实现
//for EPT
EPTP = MmGetPhysicalAddress(MyEptInitialization()).QuadPart;
Vmx_VmWrite(CPU_BASED_VM_EXEC_CONTROL, VmxAdjustControls(0x80000000, MSR_IA32_VMX_PROCBASED_CTLS));
Vmx_VmWrite(SECONDARY_VM_EXEC_CONTROL, VmxAdjustControls(0x2, MSR_IA32_VMX_PROCBASED_CTLS2));
Vmx_VmWrite(EPT_POINTER, (EPTP | 6 | (3 << 3)) & 0xFFFFFFFF);
Vmx_VmWrite(EPT_POINTER_HIGH, (EPTP | 6 | (3 << 3)) >> 32);
运行结果:
此时产生了此前未处理过的退出码02,对应的含义为「Triple fault」。
结果分析
看一下当前EIP的位置:
不难发现,此时刚进入GuestEntry,但是执行第一句指令就导致了VM-Exit。
根据周壑老师的描述,三重错误是由于GPA转HPA的过程中出现了问题导致的。
细分来说:
- 第一次GPA转HPA失败时,产生第一次异常,尝试进入对应的中断处理程序
- 进入中断处理程序失败,产生第二次异常,尝试进入二次异常处理程序
- 进入二次异常处理程序依然失败,也就导致了三重错误。
具体GPA转HPA失败的原因嘛。。。大家检查下自己是不是忘记切换到32-bit分页模式了【大无语事件】。
知识点复习:修改boot.ini中的启动项配置以切换分页模式
//PAE模式
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional Kernel Debugging" /noexecute=optin /fastdetect /debug /debugport=com1 /baudrate=115200
//32-bit模式
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional Kernel Debugging" /execute=optin /fastdetect /debug /debugport=com1 /baudrate=115200
修改好后,重启生效,再次尝试开启EPT,结果如下:
那么这里直接成功开启了EPT模式。
本来根据周壑老师的进度,这里应该会产生一个0x30的退出码,这是由于周壑老师此时映射的物理内存比较少(只有1G),而系统尝试访问一个比较大的物理地址(视频里是APIC的地址,意为高级可编程中断控制器,位于内核的KfRaiseIrql函数,作用是当系统为多核时负责多核之间的调度),因此出现了这个问题,而我们在上一篇直接使用了修复后的代码(映射了4G),因此没有出现这个问题。
PAE分页模式开启EPT
描述:PAE分页模式即2-9-9-12分页模式,相比32-bit分页模式而言,想要开启EPT还需要在前者基础上做一些额外的事情。
设置PDPTE字段
描述:在PAE分页模式下,如果开启EPT,需要在VMCS中设置与PDPTE相关的寄存器字段,共4个(参考Intel开发手册卷3第24.4节)。
没错,PDPTE也是有相关的寄存器作为高速缓存的,它的作用于段寄存器的base类似,避免每次要使用时都进行索引。
PDPTE格式如下(参考Intel开发手册卷3第4.4节):
每当切换Cr3时,CPU会把四个PDPTE里的值(即PDE的地址)写到高速缓存寄存器中,提高运行效率。
思考:VMCS字段该如何设置,才能让进程访问到PDPTE每项的物理地址呢?
答案:通过虚拟地址0xC0600000。
在Windows操作系统中,PDE的基址为0xC0600000,我们就可以通过这个特性访问4个PDE。
代码实现
//for EPT with PAE
Vmx_VmWrite(GUEST_PDPTR0, MmGetPhysicalAddress((PVOID)0xc0600000).LowPart | 1); //最后一位为有效位
Vmx_VmWrite(GUEST_PDPTR0_HIGH, MmGetPhysicalAddress((PVOID)0xc0600000).HighPart);
Vmx_VmWrite(GUEST_PDPTR1, MmGetPhysicalAddress((PVOID)0xc0601000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR1_HIGH, MmGetPhysicalAddress((PVOID)0xc0601000).HighPart);
Vmx_VmWrite(GUEST_PDPTR2, MmGetPhysicalAddress((PVOID)0xc0602000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR2_HIGH, MmGetPhysicalAddress((PVOID)0xc0602000).HighPart);
Vmx_VmWrite(GUEST_PDPTR3, MmGetPhysicalAddress((PVOID)0xc0603000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR3_HIGH, MmGetPhysicalAddress((PVOID)0xc0603000).HighPart);
运行结果(记得切换回PAE模式):
结果分析
可以看到,驱动运行后能够成功开启了VT,但是在执行csrss.exe进程途中出现了错误(继续往下运行会走向蓝屏)。
产生这种现象的原因是由于在切换线程时,Cr3寄存器的值会被切换,那也就意味着PDPTE的值也会发生变化。但是由于我们在VMCS中设置的PDPTE字段的值是固定的,所以一旦发生线程切换,下一个线程就无法正常寻址了,因此就会出现这种错误。对应的解决方式是在处理Cr3访问时,切换到对应进程的Cr3,然后同步更新一下4个PDPTE的值。
void HandleCrAccess()
{
ULONG movcrControlRegister;
ULONG movcrAccessType;
ULONG movcrOperandType; //只是读了,没有使用,通常都是寄存器
ULONG movcrGeneralPurposeRegister;
ULONG movcrLMSWSourceData;
ULONG ExitQualification;
ExitQualification = Vmx_VmRead(EXIT_QUALIFICATION); //获取具体信息
movcrControlRegister = (ExitQualification & 0x0000000F); //控制寄存器下标
movcrAccessType = ((ExitQualification & 0x00000030) >> 4); //读/写
movcrOperandType = ((ExitQualification & 0x00000040) >> 6); //寄存器/内存
movcrGeneralPurposeRegister = ((ExitQualification & 0x00000F00) >> 8); //寄存器下标
if (movcrControlRegister != 3) { // not for cr3
__asm int 3
}
if (movcrAccessType == 0) { // CR3 <-- reg32
Vmx_VmWrite(GUEST_CR3, *(PULONG)((ULONG)&g_GuestRegs + 4 * movcrGeneralPurposeRegister));
Asm_SetCr3(Vmx_VmRead(GUEST_CR3)); //切换到对应进程的Cr3
Vmx_VmWrite(GUEST_PDPTR0, MmGetPhysicalAddress((PVOID)0xc0600000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR0_HIGH, MmGetPhysicalAddress((PVOID)0xc0600000).HighPart);
Vmx_VmWrite(GUEST_PDPTR1, MmGetPhysicalAddress((PVOID)0xc0601000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR1_HIGH, MmGetPhysicalAddress((PVOID)0xc0601000).HighPart);
Vmx_VmWrite(GUEST_PDPTR2, MmGetPhysicalAddress((PVOID)0xc0602000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR2_HIGH, MmGetPhysicalAddress((PVOID)0xc0602000).HighPart);
Vmx_VmWrite(GUEST_PDPTR3, MmGetPhysicalAddress((PVOID)0xc0603000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR3_HIGH, MmGetPhysicalAddress((PVOID)0xc0603000).HighPart);
}
else { // reg32 <-- CR3
*(PULONG)((ULONG)&g_GuestRegs + 4 * movcrGeneralPurposeRegister) = Vmx_VmRead(GUEST_CR3);
}
}
完整代码
//vtasm.h
#ifndef VTASM_H
#define VTASM_H
typedef union
{
struct
{
unsigned SSE3 : 1;
unsigned PCLMULQDQ : 1;
unsigned DTES64 : 1;
unsigned MONITOR : 1;
unsigned DS_CPL : 1;
unsigned VMX : 1;
unsigned SMX : 1;
unsigned EIST : 1;
unsigned TM2 : 1;
unsigned SSSE3 : 1;
unsigned Reserved : 22;
};
}_CPUID_ECX;
typedef struct _IA32_FEATURE_CONTROL_MSR
{
unsigned Lock : 1; // Bit 0 is the lock bit - cannot be modified once lock is set
unsigned Reserved1 : 1; // Undefined
unsigned EnableVmxon : 1; // Bit 2. If this bit is clear, VMXON causes a general protection exception
unsigned Reserved2 : 29; // Undefined
unsigned Reserved3 : 32; // Undefined
} IA32_FEATURE_CONTROL_MSR;
typedef struct _VMX_BASIC_MSR
{
unsigned RevId : 32;
unsigned szVmxOnRegion : 12;
unsigned ClearBit : 1;
unsigned Reserved : 3;
unsigned PhysicalWidth : 1;
unsigned DualMonitor : 1;
unsigned MemoryType : 4;
unsigned VmExitInformation : 1;
unsigned Reserved2 : 9;
} VMX_BASIC_MSR, * PVMX_BASIC_MSR;
typedef union
{
struct
{
unsigned PE : 1;
unsigned MP : 1;
unsigned EM : 1;
unsigned TS : 1;
unsigned ET : 1;
unsigned NE : 1;
unsigned Reserved_1 : 10;
unsigned WP : 1;
unsigned Reserved_2 : 1;
unsigned AM : 1;
unsigned Reserved_3 : 10;
unsigned NW : 1;
unsigned CD : 1;
unsigned PG : 1;
//unsigned Reserved_64:32;
};
}_CR0;
typedef union
{
struct {
unsigned VME : 1;
unsigned PVI : 1;
unsigned TSD : 1;
unsigned DE : 1;
unsigned PSE : 1;
unsigned PAE : 1;
unsigned MCE : 1;
unsigned PGE : 1;
unsigned PCE : 1;
unsigned OSFXSR : 1;
unsigned PSXMMEXCPT : 1;
unsigned UNKONOWN_1 : 1; //These are zero
unsigned UNKONOWN_2 : 1; //These are zero
unsigned VMXE : 1; //It's zero in normal
unsigned Reserved : 18; //These are zero
//unsigned Reserved_64:32;
};
}_CR4;
typedef union
{
struct
{
unsigned CF : 1;
unsigned Unknown_1 : 1; //Always 1
unsigned PF : 1;
unsigned Unknown_2 : 1; //Always 0
unsigned AF : 1;
unsigned Unknown_3 : 1; //Always 0
unsigned ZF : 1;
unsigned SF : 1;
unsigned TF : 1;
unsigned IF : 1;
unsigned DF : 1;
unsigned OF : 1;
unsigned TOPL : 2;
unsigned NT : 1;
unsigned Unknown_4 : 1;
unsigned RF : 1;
unsigned VM : 1;
unsigned AC : 1;
unsigned VIF : 1;
unsigned VIP : 1;
unsigned ID : 1;
unsigned Reserved : 10; //Always 0
//unsigned Reserved_64:32; //Always 0
};
}_EFLAGS;
void Asm_CPUID(ULONG uFn, PULONG uRet_EAX, PULONG uRet_EBX, PULONG uRet_ECX, PULONG uRet_EDX);
void Asm_IN(ULONG uRet_EAX, ULONG uPort);
ULONG64 Asm_ReadMsr(ULONG uIndex);
ULONG Asm_GetEflags();
ULONG Asm_GetCs();
ULONG Asm_GetDs();
ULONG Asm_GetEs();
ULONG Asm_GetFs();
ULONG Asm_GetGs();
ULONG Asm_GetSs();
ULONG Asm_GetTr();
ULONG Asm_GetGdtBase();
ULONG Asm_GetIdtBase();
ULONG Asm_GetGdtLimit();
ULONG Asm_GetIdtLimit();
void Asm_SetGdt(ULONG base, USHORT limit);
void Asm_SetIdt(ULONG base, USHORT limit);
ULONG Asm_GetCr0();
ULONG Asm_GetCr3();
ULONG Asm_GetCr4();
void Asm_SetCr3(ULONG uNewCr3);
void Asm_SetCr4(ULONG uNewCr4);
void Vmx_VmxOn(ULONG LowPart, ULONG HighPart);
void Vmx_VmxOff();
void Vmx_VmClear(ULONG LowPart, ULONG HighPart);
void Vmx_VmPtrld(ULONG LowPart, ULONG HighPart);
ULONG Vmx_VmRead(ULONG uField);
void Vmx_VmWrite(ULONG uField, ULONG uValue);
void Vmx_VmLaunch();
void Vmx_VmCall();
#endif
//vtasm.asm
.686p
.model flat, stdcall
option casemap:none
.data
.code
Asm_CPUID Proc uses ebx esi edi fn:dword, ret_eax:dword, ret_ebx:dword, ret_ecx:dword, ret_edx:dword
mov eax, fn
cpuid
mov esi, ret_eax
mov dword ptr [esi], eax
mov esi, ret_ebx
mov dword ptr [esi], ebx
mov esi, ret_ecx
mov dword ptr [esi], ecx
mov esi, ret_edx
mov dword ptr [esi], edx
ret
Asm_CPUID Endp
Asm_IN Proc uRet_EAX:dword, uPort:dword
mov eax, uRet_EAX
mov edx, uPort
in eax, dx
Asm_IN Endp
Asm_ReadMsr Proc Index:dword
mov ecx,Index
rdmsr
ret
Asm_ReadMsr Endp
Asm_GetCr0 Proc
mov eax, cr0
ret
Asm_GetCr0 Endp
Asm_GetCr3 Proc
mov eax, cr3
ret
Asm_GetCr3 Endp
Asm_GetCr4 Proc
mov eax, cr4
ret
Asm_GetCr4 Endp
Asm_SetCr3 Proc NewCr3:dword
mov eax, NewCr3
mov cr3, eax
ret
Asm_SetCr3 Endp
Asm_SetCr4 Proc NewCr4:dword
mov eax,NewCr4
mov cr4, eax
ret
Asm_SetCr4 Endp
Vmx_VmxOn Proc LowPart:dword,HighPart:dword
push HighPart
push LowPart
Vmxon qword ptr [esp]
add esp,8
ret
Vmx_VmxOn Endp
Vmx_VmxOff Proc
Vmxoff
ret
Vmx_VmxOff Endp
Asm_GetEflags PROC
pushfd
pop eax
ret
Asm_GetEflags ENDP
Vmx_VmClear Proc LowPart:dword,HighPart:dword
push HighPart
push LowPart
vmclear qword ptr [esp]
add esp,8
ret
Vmx_VmClear endp
Vmx_VmPtrld Proc LowPart:dword,HighPart:dword
push HighPart
push LowPart
vmptrld qword ptr [esp]
add esp,8
ret
Vmx_VmPtrld endp
Vmx_VmRead Proc uses ecx Field:dword
mov eax,Field
vmread ecx,eax
mov eax,ecx
ret
Vmx_VmRead endp
Vmx_VmWrite Proc uses ecx Field:dword,Value:dword
mov eax,Field
mov ecx,Value
vmwrite eax,ecx
ret
Vmx_VmWrite endp
Asm_GetCs PROC
mov eax, cs
ret
Asm_GetCs ENDP
Asm_GetDs PROC
mov eax, ds
ret
Asm_GetDs ENDP
Asm_GetEs PROC
mov eax, es
ret
Asm_GetEs ENDP
Asm_GetSs PROC
mov eax, ss
ret
Asm_GetSs ENDP
Asm_GetFs PROC
mov eax, fs
ret
Asm_GetFs ENDP
Asm_GetGs PROC
mov eax, gs
ret
Asm_GetGs ENDP
Asm_GetTr PROC
str eax
ret
Asm_GetTr ENDP
Asm_GetGdtBase PROC
LOCAL gdtr[10]:BYTE
sgdt gdtr
mov eax, dword PTR gdtr[2]
ret
Asm_GetGdtBase ENDP
Asm_GetGdtLimit PROC
LOCAL gdtr[10]:BYTE
sgdt gdtr
mov ax, WORD PTR gdtr[0]
ret
Asm_GetGdtLimit ENDP
Asm_GetIdtBase PROC
LOCAL idtr[10]:BYTE
sidt idtr
mov eax, dword PTR idtr[2]
ret
Asm_GetIdtBase ENDP
Asm_GetIdtLimit PROC
LOCAL idtr[10]:BYTE
sidt idtr
mov ax, WORD PTR idtr[0]
ret
Asm_GetIdtLimit ENDP
Asm_SetGdt PROC base:dword, limit:word
LOCAL gdt[6]:BYTE
mov ax, limit
mov word ptr gdt[0], ax
mov eax, base
mov dword ptr gdt[2], eax
lgdt fword ptr gdt
ret
Asm_SetGdt ENDP
Asm_SetIdt PROC base:dword, limit:word
LOCAL idt[6]:BYTE
mov ax, limit
mov word ptr idt[0], ax
mov eax, base
mov dword ptr idt[2], eax
lgdt fword ptr idt
ret
Asm_SetIdt ENDP
Vmx_VmLaunch Proc
vmlaunch
ret
Vmx_VmLaunch endp
Vmx_VmCall Proc
vmcall
ret
Vmx_VmCall endp
END
//vtsystem.h
#ifndef VTSYSTEM_H
#define VTSYSTEM_H
#include <ntddk.h>
/*MSR definition*/
#define MSR_IA32_FEATURE_CONTROL 0x03a
#define MSR_IA32_VMX_BASIC 0x480
#define MSR_IA32_VMX_PINBASED_CTLS 0x481
#define MSR_IA32_VMX_PROCBASED_CTLS 0x482
#define MSR_IA32_VMX_EXIT_CTLS 0x483
#define MSR_IA32_VMX_ENTRY_CTLS 0x484
#define MSR_IA32_VMX_PROCBASED_CTLS2 0x48b
#define MSR_IA32_SYSENTER_CS 0x174
#define MSR_IA32_SYSENTER_ESP 0x175
#define MSR_IA32_SYSENTER_EIP 0x176
#define MSR_IA32_DEBUGCTL 0x1d9
//VMX Exit Reasons
#define EXIT_REASON_CPUID 10
#define EXIT_REASON_VMCALL 18
#define EXIT_REASON_CR_ACCESS 28
typedef struct _VMX_CPU
{
PVOID pVMXONRegion;
PHYSICAL_ADDRESS pVMXONRegion_PA;
PVOID pVMCSRegion;
PHYSICAL_ADDRESS pVMCSRegion_PA;
PVOID pStack;
BOOLEAN bVTStartSuccess;
}VMX_CPU, * PVMX_CPU;
/* VMCS Encordings */
enum
{
VIRTUAL_PROCESSOR_ID = 0x00000000,
POSTED_INTR_NV = 0x00000002,
GUEST_ES_SELECTOR = 0x00000800,
GUEST_CS_SELECTOR = 0x00000802,
GUEST_SS_SELECTOR = 0x00000804,
GUEST_DS_SELECTOR = 0x00000806,
GUEST_FS_SELECTOR = 0x00000808,
GUEST_GS_SELECTOR = 0x0000080a,
GUEST_LDTR_SELECTOR = 0x0000080c,
GUEST_TR_SELECTOR = 0x0000080e,
GUEST_INTR_STATUS = 0x00000810,
HOST_ES_SELECTOR = 0x00000c00,
HOST_CS_SELECTOR = 0x00000c02,
HOST_SS_SELECTOR = 0x00000c04,
HOST_DS_SELECTOR = 0x00000c06,
HOST_FS_SELECTOR = 0x00000c08,
HOST_GS_SELECTOR = 0x00000c0a,
HOST_TR_SELECTOR = 0x00000c0c,
IO_BITMAP_A = 0x00002000,
IO_BITMAP_A_HIGH = 0x00002001,
IO_BITMAP_B = 0x00002002,
IO_BITMAP_B_HIGH = 0x00002003,
MSR_BITMAP = 0x00002004,
MSR_BITMAP_HIGH = 0x00002005,
VM_EXIT_MSR_STORE_ADDR = 0x00002006,
VM_EXIT_MSR_STORE_ADDR_HIGH = 0x00002007,
VM_EXIT_MSR_LOAD_ADDR = 0x00002008,
VM_EXIT_MSR_LOAD_ADDR_HIGH = 0x00002009,
VM_ENTRY_MSR_LOAD_ADDR = 0x0000200a,
VM_ENTRY_MSR_LOAD_ADDR_HIGH = 0x0000200b,
TSC_OFFSET = 0x00002010,
TSC_OFFSET_HIGH = 0x00002011,
VIRTUAL_APIC_PAGE_ADDR = 0x00002012,
VIRTUAL_APIC_PAGE_ADDR_HIGH = 0x00002013,
APIC_ACCESS_ADDR = 0x00002014,
APIC_ACCESS_ADDR_HIGH = 0x00002015,
POSTED_INTR_DESC_ADDR = 0x00002016,
POSTED_INTR_DESC_ADDR_HIGH = 0x00002017,
EPT_POINTER = 0x0000201a,
EPT_POINTER_HIGH = 0x0000201b,
EOI_EXIT_BITMAP0 = 0x0000201c,
EOI_EXIT_BITMAP0_HIGH = 0x0000201d,
EOI_EXIT_BITMAP1 = 0x0000201e,
EOI_EXIT_BITMAP1_HIGH = 0x0000201f,
EOI_EXIT_BITMAP2 = 0x00002020,
EOI_EXIT_BITMAP2_HIGH = 0x00002021,
EOI_EXIT_BITMAP3 = 0x00002022,
EOI_EXIT_BITMAP3_HIGH = 0x00002023,
VMREAD_BITMAP = 0x00002026,
VMWRITE_BITMAP = 0x00002028,
XSS_EXIT_BITMAP = 0x0000202C,
XSS_EXIT_BITMAP_HIGH = 0x0000202D,
GUEST_PHYSICAL_ADDRESS = 0x00002400,
GUEST_PHYSICAL_ADDRESS_HIGH = 0x00002401,
VMCS_LINK_POINTER = 0x00002800,
VMCS_LINK_POINTER_HIGH = 0x00002801,
GUEST_IA32_DEBUGCTL = 0x00002802,
GUEST_IA32_DEBUGCTL_HIGH = 0x00002803,
GUEST_IA32_PAT = 0x00002804,
GUEST_IA32_PAT_HIGH = 0x00002805,
GUEST_IA32_EFER = 0x00002806,
GUEST_IA32_EFER_HIGH = 0x00002807,
GUEST_IA32_PERF_GLOBAL_CTRL = 0x00002808,
GUEST_IA32_PERF_GLOBAL_CTRL_HIGH = 0x00002809,
GUEST_PDPTR0 = 0x0000280a,
GUEST_PDPTR0_HIGH = 0x0000280b,
GUEST_PDPTR1 = 0x0000280c,
GUEST_PDPTR1_HIGH = 0x0000280d,
GUEST_PDPTR2 = 0x0000280e,
GUEST_PDPTR2_HIGH = 0x0000280f,
GUEST_PDPTR3 = 0x00002810,
GUEST_PDPTR3_HIGH = 0x00002811,
GUEST_BNDCFGS = 0x00002812,
GUEST_BNDCFGS_HIGH = 0x00002813,
HOST_IA32_PAT = 0x00002c00,
HOST_IA32_PAT_HIGH = 0x00002c01,
HOST_IA32_EFER = 0x00002c02,
HOST_IA32_EFER_HIGH = 0x00002c03,
HOST_IA32_PERF_GLOBAL_CTRL = 0x00002c04,
HOST_IA32_PERF_GLOBAL_CTRL_HIGH = 0x00002c05,
PIN_BASED_VM_EXEC_CONTROL = 0x00004000,
CPU_BASED_VM_EXEC_CONTROL = 0x00004002,
EXCEPTION_BITMAP = 0x00004004,
PAGE_FAULT_ERROR_CODE_MASK = 0x00004006,
PAGE_FAULT_ERROR_CODE_MATCH = 0x00004008,
CR3_TARGET_COUNT = 0x0000400a,
VM_EXIT_CONTROLS = 0x0000400c,
VM_EXIT_MSR_STORE_COUNT = 0x0000400e,
VM_EXIT_MSR_LOAD_COUNT = 0x00004010,
VM_ENTRY_CONTROLS = 0x00004012,
VM_ENTRY_MSR_LOAD_COUNT = 0x00004014,
VM_ENTRY_INTR_INFO_FIELD = 0x00004016,
VM_ENTRY_EXCEPTION_ERROR_CODE = 0x00004018,
VM_ENTRY_INSTRUCTION_LEN = 0x0000401a,
TPR_THRESHOLD = 0x0000401c,
SECONDARY_VM_EXEC_CONTROL = 0x0000401e,
PLE_GAP = 0x00004020,
PLE_WINDOW = 0x00004022,
VM_INSTRUCTION_ERROR = 0x00004400,
VM_EXIT_REASON = 0x00004402,
VM_EXIT_INTR_INFO = 0x00004404,
VM_EXIT_INTR_ERROR_CODE = 0x00004406,
IDT_VECTORING_INFO_FIELD = 0x00004408,
IDT_VECTORING_ERROR_CODE = 0x0000440a,
VM_EXIT_INSTRUCTION_LEN = 0x0000440c,
VMX_INSTRUCTION_INFO = 0x0000440e,
GUEST_ES_LIMIT = 0x00004800,
GUEST_CS_LIMIT = 0x00004802,
GUEST_SS_LIMIT = 0x00004804,
GUEST_DS_LIMIT = 0x00004806,
GUEST_FS_LIMIT = 0x00004808,
GUEST_GS_LIMIT = 0x0000480a,
GUEST_LDTR_LIMIT = 0x0000480c,
GUEST_TR_LIMIT = 0x0000480e,
GUEST_GDTR_LIMIT = 0x00004810,
GUEST_IDTR_LIMIT = 0x00004812,
GUEST_ES_AR_BYTES = 0x00004814,
GUEST_CS_AR_BYTES = 0x00004816,
GUEST_SS_AR_BYTES = 0x00004818,
GUEST_DS_AR_BYTES = 0x0000481a,
GUEST_FS_AR_BYTES = 0x0000481c,
GUEST_GS_AR_BYTES = 0x0000481e,
GUEST_LDTR_AR_BYTES = 0x00004820,
GUEST_TR_AR_BYTES = 0x00004822,
GUEST_INTERRUPTIBILITY_INFO = 0x00004824,
GUEST_ACTIVITY_STATE = 0X00004826,
GUEST_SYSENTER_CS = 0x0000482A,
VMX_PREEMPTION_TIMER_VALUE = 0x0000482E,
HOST_IA32_SYSENTER_CS = 0x00004c00,
CR0_GUEST_HOST_MASK = 0x00006000,
CR4_GUEST_HOST_MASK = 0x00006002,
CR0_READ_SHADOW = 0x00006004,
CR4_READ_SHADOW = 0x00006006,
CR3_TARGET_VALUE0 = 0x00006008,
CR3_TARGET_VALUE1 = 0x0000600a,
CR3_TARGET_VALUE2 = 0x0000600c,
CR3_TARGET_VALUE3 = 0x0000600e,
EXIT_QUALIFICATION = 0x00006400,
GUEST_LINEAR_ADDRESS = 0x0000640a,
GUEST_CR0 = 0x00006800,
GUEST_CR3 = 0x00006802,
GUEST_CR4 = 0x00006804,
GUEST_ES_BASE = 0x00006806,
GUEST_CS_BASE = 0x00006808,
GUEST_SS_BASE = 0x0000680a,
GUEST_DS_BASE = 0x0000680c,
GUEST_FS_BASE = 0x0000680e,
GUEST_GS_BASE = 0x00006810,
GUEST_LDTR_BASE = 0x00006812,
GUEST_TR_BASE = 0x00006814,
GUEST_GDTR_BASE = 0x00006816,
GUEST_IDTR_BASE = 0x00006818,
GUEST_DR7 = 0x0000681a,
GUEST_RSP = 0x0000681c,
GUEST_RIP = 0x0000681e,
GUEST_RFLAGS = 0x00006820,
GUEST_PENDING_DBG_EXCEPTIONS = 0x00006822,
GUEST_SYSENTER_ESP = 0x00006824,
GUEST_SYSENTER_EIP = 0x00006826,
HOST_CR0 = 0x00006c00,
HOST_CR3 = 0x00006c02,
HOST_CR4 = 0x00006c04,
HOST_FS_BASE = 0x00006c06,
HOST_GS_BASE = 0x00006c08,
HOST_TR_BASE = 0x00006c0a,
HOST_GDTR_BASE = 0x00006c0c,
HOST_IDTR_BASE = 0x00006c0e,
HOST_IA32_SYSENTER_ESP = 0x00006c10,
HOST_IA32_SYSENTER_EIP = 0x00006c12,
HOST_RSP = 0x00006c14,
HOST_RIP = 0x00006c16,
};
typedef struct _GUEST_REGS
{
ULONG eax;
ULONG ecx;
ULONG edx;
ULONG ebx;
ULONG esp;
ULONG ebp;
ULONG esi;
ULONG edi;
ULONG eip;
ULONG eflags;
}GUEST_REGS, * PGUEST_REGS;
extern VMX_CPU g_VMXCPU;
//检查当前处理器是否支持VT
BOOLEAN IsVTEnabled();
//开启VT
NTSTATUS StartVirtualTechnology();
//关闭VT
NTSTATUS StopVirtualTechnology();
#define Log(message,value) {{KdPrint(("[MinVT] %-40s [%p]\n",message,value));}}
#endif
//vtsystem.c
#include "vtsystem.h"
#include "vtasm.h"
#include "exithandler.h"
extern ULONG g_back_esp;
extern ULONG g_back_eip;
VMX_CPU g_VMXCPU;
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;
}
// 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;
}
Log("SUCCESS:这个CPU支持VT!", 0);
return TRUE;
}
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 */
if (MSR_IA32_VMX_PINBASED_CTLS == Msr) //关闭外部中断信号
{
Ctl &= ~1;
}
if (MSR_IA32_VMX_PROCBASED_CTLS == Msr) //关闭I/O访问信号
{
Ctl &= ~0x03000000;
}
return Ctl;
}
static ULONG GetSegBase(ULONG Select)
{
ULONG GdtBase = Asm_GetGdtBase();
ULONGLONG Descriptor = *(PULONGLONG)(GdtBase + Select);
ULONG Base = 0;
Base |= (Descriptor & 0xff00000000000000) >> 32;
Base |= (Descriptor & 0x000000ff00000000) >> 16;
Base |= (Descriptor & 0x00000000ffff0000) >> 16;
return Base;
}
static ULONG GetSegLimit(ULONG Select)
{
ULONG GdtBase = Asm_GetGdtBase();
ULONGLONG Descriptor = *(PULONGLONG)(GdtBase + Select);
ULONG Limit = 0;
Limit |= (Descriptor & 0x000f000000000000) >> 32;
Limit |= Descriptor & 0x000000000000ffff;
//if Desc.G == 1 ? 4kb : byte
if (Descriptor & 0x0080000000000000)
{
Limit <<= 12;
Limit |= 0xfff;
}
return Limit;
}
static ULONG GetSegAR(ULONG Select)
{
ULONG GdtBase = Asm_GetGdtBase();
ULONGLONG Descriptor = *(PULONGLONG)(GdtBase + Select);
ULONG AR = 0;
AR |= (Descriptor & 0x00f0ff0000000000) >> 40;
return AR;
}
void __declspec(naked) GuestEntry()
{
__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
mov esp, g_back_esp
jmp g_back_eip
}
}
ULONG64 EPTP;
extern ULONG64* MyEptInitialization();
void SetupVMCS()
{
ULONG GdtBase, IdtBase;
GdtBase = Asm_GetGdtBase();
IdtBase = Asm_GetIdtBase();
//
// 1.Guest State Area
//
Vmx_VmWrite(GUEST_CR0, Asm_GetCr0());
Vmx_VmWrite(GUEST_CR3, Asm_GetCr3());
Vmx_VmWrite(GUEST_CR4, Asm_GetCr4());
Vmx_VmWrite(GUEST_DR7, 0x400);
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);
// 重要的段寄存器信息需要在进入Guest前加载
Vmx_VmWrite(GUEST_CS_AR_BYTES, GetSegAR(Asm_GetCs() & 0xFFF8));
Vmx_VmWrite(GUEST_CS_BASE, GetSegBase(Asm_GetCs() & 0xFFF8));
Vmx_VmWrite(GUEST_CS_LIMIT, GetSegLimit(Asm_GetCs() & 0xFFF8));
Vmx_VmWrite(GUEST_TR_AR_BYTES, GetSegAR(Asm_GetTr() & 0xFFF8));
Vmx_VmWrite(GUEST_TR_BASE, GetSegBase(Asm_GetTr() & 0xFFF8));
Vmx_VmWrite(GUEST_TR_LIMIT, GetSegLimit(Asm_GetTr() & 0xFFF8));
// 其他寄存器可在进入Guest后加载,先将属性的第16位置1,即不可用状态
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);
// CS、ESP、EIP
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
// GDTR
Vmx_VmWrite(GUEST_GDTR_BASE, GdtBase);
Vmx_VmWrite(GUEST_GDTR_LIMIT, Asm_GetGdtLimit());
// LDTR
Vmx_VmWrite(GUEST_IDTR_BASE, IdtBase);
Vmx_VmWrite(GUEST_IDTR_LIMIT, Asm_GetIdtLimit());
Vmx_VmWrite(GUEST_RSP, ((ULONG)g_VMXCPU.pStack) + 0x1000); // Guest 临时栈
Vmx_VmWrite(GUEST_RIP, (ULONG)GuestEntry); // 客户机的入口点
// Link Shadow VMCS
Vmx_VmWrite(VMCS_LINK_POINTER, 0xffffffff);
Vmx_VmWrite(VMCS_LINK_POINTER_HIGH, 0xffffffff);
//
// 2.Host State Area
//
Vmx_VmWrite(HOST_CR0, Asm_GetCr0());
Vmx_VmWrite(HOST_CR3, Asm_GetCr3());
Vmx_VmWrite(HOST_CR4, Asm_GetCr4());
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);
Vmx_VmWrite(HOST_TR_BASE, GetSegBase(Asm_GetTr() & 0xFFF8));
// GDTR
Vmx_VmWrite(HOST_GDTR_BASE, GdtBase);
// LDTR
Vmx_VmWrite(HOST_IDTR_BASE, IdtBase);
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); // KiFastCallEntry
Vmx_VmWrite(HOST_RSP, ((ULONG)g_VMXCPU.pStack) + 0x2000); //Host 临时栈
Vmx_VmWrite(HOST_RIP, (ULONG)VMMEntryPoint); //这里定义我们的VMM处理程序入口
//
// 3.虚拟机运行控制域
//
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));
//for EPT
EPTP = MmGetPhysicalAddress(MyEptInitialization()).QuadPart;
Vmx_VmWrite(CPU_BASED_VM_EXEC_CONTROL, VmxAdjustControls(0x80000000, MSR_IA32_VMX_PROCBASED_CTLS));
Vmx_VmWrite(SECONDARY_VM_EXEC_CONTROL, VmxAdjustControls(0x2, MSR_IA32_VMX_PROCBASED_CTLS2));
Vmx_VmWrite(EPT_POINTER, (EPTP | 6 | (3 << 3)) & 0xFFFFFFFF);
Vmx_VmWrite(EPT_POINTER_HIGH, (EPTP | 6 | (3 << 3)) >> 32);
//for EPT with PAE
Vmx_VmWrite(GUEST_PDPTR0, MmGetPhysicalAddress((PVOID)0xc0600000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR0_HIGH, MmGetPhysicalAddress((PVOID)0xc0600000).HighPart);
Vmx_VmWrite(GUEST_PDPTR1, MmGetPhysicalAddress((PVOID)0xc0601000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR1_HIGH, MmGetPhysicalAddress((PVOID)0xc0601000).HighPart);
Vmx_VmWrite(GUEST_PDPTR2, MmGetPhysicalAddress((PVOID)0xc0602000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR2_HIGH, MmGetPhysicalAddress((PVOID)0xc0602000).HighPart);
Vmx_VmWrite(GUEST_PDPTR3, MmGetPhysicalAddress((PVOID)0xc0603000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR3_HIGH, MmGetPhysicalAddress((PVOID)0xc0603000).HighPart);
//
// 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));
}
NTSTATUS StartVirtualTechnology()
{
PVOID pVMXONRegion;
PVOID pVMCSRegion;
PVOID pStack;
VMX_BASIC_MSR Msr;
ULONG uRevId;
_CR4 uCr4;
_EFLAGS uEflags;
if (!IsVTEnabled())
return STATUS_NOT_SUPPORTED;
//VMXE
*((PULONG)&uCr4) = Asm_GetCr4();
uCr4.VMXE = 1;
Asm_SetCr4(*((PULONG)&uCr4));
//VMX version
*((PULONG)&Msr) = (ULONG)Asm_ReadMsr(MSR_IA32_VMX_BASIC);
uRevId = Msr.RevId;
//VMXON region
pVMXONRegion = ExAllocatePoolWithTag(NonPagedPool, 0x1000, 'vmon'); //4KB
if (!pVMXONRegion)
{
Log("ERROR:申请VMXON内存区域失败!", 0);
return STATUS_MEMORY_NOT_ALLOCATED;
}
RtlZeroMemory(pVMXONRegion, 0x1000);
g_VMXCPU.pVMXONRegion = pVMXONRegion;
g_VMXCPU.pVMXONRegion_PA = MmGetPhysicalAddress(pVMXONRegion);
*((PULONG)g_VMXCPU.pVMXONRegion) = uRevId;
//VMXON
Vmx_VmxOn(g_VMXCPU.pVMXONRegion_PA.LowPart, g_VMXCPU.pVMXONRegion_PA.HighPart);
// if CF = 0
*((PULONG)&uEflags) = Asm_GetEflags();
if (uEflags.CF != 0)
{
Log("ERROR:开启VT失败!", 0);
return STATUS_UNSUCCESSFUL;
}
Log("SUCCESS:开启VT成功!", 0);
//VMCS
pVMCSRegion = ExAllocatePoolWithTag(NonPagedPool, 0x1000, 'vmon'); //4KB
if (!pVMCSRegion)
{
Log("ERROR:申请VMCS内存区域失败!", 0);
return STATUS_MEMORY_NOT_ALLOCATED;
}
RtlZeroMemory(pVMCSRegion, 0x1000);
*((PULONG)pVMCSRegion) = uRevId;
g_VMXCPU.pVMCSRegion = pVMCSRegion;
g_VMXCPU.pVMCSRegion_PA = MmGetPhysicalAddress(pVMCSRegion);
Vmx_VmClear(g_VMXCPU.pVMCSRegion_PA.LowPart, g_VMXCPU.pVMCSRegion_PA.HighPart);
Vmx_VmPtrld(g_VMXCPU.pVMCSRegion_PA.LowPart, g_VMXCPU.pVMCSRegion_PA.HighPart);
//Stack,一半给Guest用,一半给Host用
pStack = ExAllocatePoolWithTag(NonPagedPool, 0x2000, 'stck');
if (!pStack)
{
Log("ERROR:申请Stack内存区域失败!", 0);
return STATUS_MEMORY_NOT_ALLOCATED;
}
RtlZeroMemory(pStack, 0x2000);
g_VMXCPU.pStack = pStack;
//Setup
SetupVMCS(); //设置VMCS字段
//Launch
Vmx_VmLaunch();
//===================================================
//正常情况下,VMLAUNCH执行后,CPU会进入虚拟机中
//如果走到这里,说明执行失败
//===================================================
Log("ERROR:VmLaunch指令调用失败!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", Vmx_VmRead(VM_INSTRUCTION_ERROR))
return STATUS_SUCCESS;
}
extern ULONG g_vmcall_arg;
extern ULONG g_stop_esp;
extern ULONG g_stop_eip;
extern void MyEptFree();
NTSTATUS StopVirtualTechnology()
{
_CR4 uCr4;
UCHAR GDT[6];
g_vmcall_arg = 'STOP';
__asm {
pushad
pushfd
mov g_stop_esp, esp
mov g_stop_eip, offset STOPVT
}
Vmx_VmCall();
STOPVT:
__asm {
popfd
popad
}
// Set VMXE with 0
*((PULONG)&uCr4) = Asm_GetCr4();
uCr4.VMXE = 0;
Asm_SetCr4(*((PULONG)&uCr4));
// Repair Gdt&Idt Limit
Asm_SetGdt(Asm_GetGdtBase(), 0x3ff);
Asm_SetIdt(Asm_GetGdtBase(), 0x7ff);
ExFreePool(g_VMXCPU.pVMXONRegion);
ExFreePool(g_VMXCPU.pVMCSRegion);
ExFreePool(g_VMXCPU.pStack);
MyEptFree();
Log("SUCCESS:关闭VT成功!", 0);
return STATUS_SUCCESS;
}
//exithandler.h
#ifndef EXITHANDLER_H
#define EXITHANDLER_H
void VMMEntryPoint(void);
#endif
//exithandler.c
#include "exithandler.h"
#include "vtsystem.h"
#include "vtasm.h"
GUEST_REGS g_GuestRegs;
void HandleCPUID()
{
//特殊处理,用于测试VT是否开启,注意保证参数的独立性。
if (g_GuestRegs.eax == 'Mini')
{
g_GuestRegs.ebx = 0x88888888;
g_GuestRegs.ecx = 0x11111111;
g_GuestRegs.edx = 0x12345678;
}
//正常情况,替Guest模拟执行CPUID
else Asm_CPUID(g_GuestRegs.eax, &g_GuestRegs.eax, &g_GuestRegs.ebx, &g_GuestRegs.ecx, &g_GuestRegs.edx);
}
ULONG g_vmcall_arg;
ULONG g_stop_esp, g_stop_eip;
void HandleVmCall()
{
if (g_vmcall_arg == 'STOP')
{
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
}
}
void HandleCrAccess()
{
ULONG movcrControlRegister;
ULONG movcrAccessType;
ULONG movcrOperandType; //只是读了,没有使用,通常都是寄存器
ULONG movcrGeneralPurposeRegister;
ULONG movcrLMSWSourceData;
ULONG ExitQualification;
ExitQualification = Vmx_VmRead(EXIT_QUALIFICATION); //获取具体信息
movcrControlRegister = (ExitQualification & 0x0000000F); //控制寄存器下标
movcrAccessType = ((ExitQualification & 0x00000030) >> 4); //读/写
movcrOperandType = ((ExitQualification & 0x00000040) >> 6); //寄存器/内存
movcrGeneralPurposeRegister = ((ExitQualification & 0x00000F00) >> 8); //寄存器下标
if (movcrControlRegister != 3) { // not for cr3
__asm int 3
}
if (movcrAccessType == 0) { // CR3 <-- reg32
Vmx_VmWrite(GUEST_CR3, *(PULONG)((ULONG)&g_GuestRegs + 4 * movcrGeneralPurposeRegister));
Asm_SetCr3(Vmx_VmRead(GUEST_CR3)); //切换到对应进程的Cr3
Vmx_VmWrite(GUEST_PDPTR0, MmGetPhysicalAddress((PVOID)0xc0600000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR0_HIGH, MmGetPhysicalAddress((PVOID)0xc0600000).HighPart);
Vmx_VmWrite(GUEST_PDPTR1, MmGetPhysicalAddress((PVOID)0xc0601000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR1_HIGH, MmGetPhysicalAddress((PVOID)0xc0601000).HighPart);
Vmx_VmWrite(GUEST_PDPTR2, MmGetPhysicalAddress((PVOID)0xc0602000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR2_HIGH, MmGetPhysicalAddress((PVOID)0xc0602000).HighPart);
Vmx_VmWrite(GUEST_PDPTR3, MmGetPhysicalAddress((PVOID)0xc0603000).LowPart | 1);
Vmx_VmWrite(GUEST_PDPTR3_HIGH, MmGetPhysicalAddress((PVOID)0xc0603000).HighPart);
}
else { // reg32 <-- CR3
*(PULONG)((ULONG)&g_GuestRegs + 4 * movcrGeneralPurposeRegister) = Vmx_VmRead(GUEST_CR3);
}
}
static void VMMEntryPointEbd(void)
{
ULONG ExitReason;
ULONG ExitInstructionLength;
ULONG GuestResumeEIP;
ExitReason = Vmx_VmRead(VM_EXIT_REASON);
ExitInstructionLength = Vmx_VmRead(VM_EXIT_INSTRUCTION_LEN);
g_GuestRegs.eflags = Vmx_VmRead(GUEST_RFLAGS);
g_GuestRegs.esp = Vmx_VmRead(GUEST_RSP);
g_GuestRegs.eip = Vmx_VmRead(GUEST_RIP);
//Log("ExitReason", ExitReason);
//Log("g_GuestRegs.eip", g_GuestRegs.eip);
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);
Log("g_GuestRegs.eip", g_GuestRegs.eip);
__asm int 3;
break;
}
//Resume:
GuestResumeEIP = g_GuestRegs.eip + ExitInstructionLength;
Vmx_VmWrite(GUEST_RIP, GuestResumeEIP);
Vmx_VmWrite(GUEST_RSP, g_GuestRegs.esp);
Vmx_VmWrite(GUEST_RFLAGS, g_GuestRegs.eflags);
}
void __declspec(naked) VMMEntryPoint(void)
{
__asm
{
mov g_GuestRegs.eax, eax
mov g_GuestRegs.ecx, ecx
mov g_GuestRegs.edx, edx
mov g_GuestRegs.ebx, ebx
mov g_GuestRegs.esp, esp
mov g_GuestRegs.ebp, ebp
mov g_GuestRegs.esi, esi
mov g_GuestRegs.edi, edi
pushfd
pop eax
mov g_GuestRegs.eflags, eax
//需要设置fs和gs,否则无法正常运行
mov ax, fs
mov fs, ax
mov ax, gs
mov gs, ax
}
//尽量不要在裸函数中定义局部变量,或实现太多功能,最好封装成函数
VMMEntryPointEbd();
__asm {
mov eax, g_GuestRegs.eax
mov ecx, g_GuestRegs.ecx
mov edx, g_GuestRegs.edx
mov ebx, g_GuestRegs.ebx
mov esp, g_GuestRegs.esp
mov ebp, g_GuestRegs.ebp
mov esi, g_GuestRegs.esi
mov edi, g_GuestRegs.edi
//vmresume
__emit 0x0f
__emit 0x01
__emit 0xc3
}
}
//ept.c
#include <ntddk.h>
#define PAGE_SIZE 0x1000
ULONG64* ept_PML4T;
static PVOID *pagesToFree;
static int index = 0;
void InitEptPagesPool()
{
pagesToFree = ExAllocatePoolWithTag(NonPagedPool, 12*1024*1024, 'ept');
if(!pagesToFree)
__asm int 3
RtlZeroMemory(pagesToFree, 12*1024*1024);
}
static ULONG64* AllocateOnePage()
{
PVOID page;
page = ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, 'ept');
if(!page)
__asm int 3
RtlZeroMemory(page, PAGE_SIZE);
pagesToFree[index] = page;
index++;
return (ULONG64 *)page;
}
ULONG64* MyEptInitialization()
{
ULONG64 *ept_PDPT, *ept_PDT, *ept_PT;
PHYSICAL_ADDRESS FirstPtePA, FirstPdePA, FirstPdptePA;
int a, b, c;
InitEptPagesPool();
ept_PML4T = AllocateOnePage();
ept_PDPT = AllocateOnePage();
FirstPdptePA = MmGetPhysicalAddress(ept_PDPT);
*ept_PML4T = (FirstPdptePA.QuadPart) + 7;
for (a = 0; a < 4; a++)
{
ept_PDT = AllocateOnePage();
FirstPdePA = MmGetPhysicalAddress(ept_PDT);
*ept_PDPT = (FirstPdePA.QuadPart) + 7;
ept_PDPT++;
for (b = 0; b < 512; b++)
{
ept_PT = AllocateOnePage();
FirstPtePA = MmGetPhysicalAddress(ept_PT);
*ept_PDT = (FirstPtePA.QuadPart) + 7;
ept_PDT++;
for (c = 0; c < 512; c++)
{
*ept_PT = ((a << 30) | (b << 21) | (c << 12) | 0x37) & 0xFFFFFFFF;
ept_PT++;
}
}
}
return ept_PML4T;
}
void MyEptFree()
{
int i;
for (i = 0; i < index; i++) {
ExFreePool(pagesToFree[i]);
}
ExFreePool(pagesToFree);
index = 0;
}
//driver.c
#include <ntddk.h>
#include "vtasm.h"
#include "vtsystem.h"
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
StopVirtualTechnology();
DbgPrint("Driver unload. \r\n");
}
ULONG g_back_esp;
ULONG g_back_eip;
extern void InitEptPagesPool();
extern ULONG64* MyEptInitialization();
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING reg_path)
{
DbgPrint("Driver load. \r\n");
pDriver->DriverUnload = DriverUnload;
/*
InitEptPagesPool();
ULONG64* ept_PML4T = MyEptInitialization();
Log("ept_PML4T", ept_PML4T);
__asm int 3;
*/
__asm
{
pushad
pushfd
mov g_back_esp, esp
mov g_back_eip, offset BACK
}
StartVirtualTechnology(); //开启VT
BACK:
__asm
{
popfd
popad
}
//Log("Return To DriverEntry", 0);
return STATUS_SUCCESS;
}
运行结果:
现在,已经能够成功在PAE分页模式和32-bit分页模式下开启EPT模式了,这份代码是两者通用的,切换到32-bit分页模式时,系统不会去检查与PAE相关的字段,因此不会产生错误。
参考资料
- VT虚拟化架构编写视频教程①~⑥课
- 周壑《VT技术入门》系列视频教程
- github项目:VT_Learn
- github项目: HyperPlatform
- Intel开发手册 卷3:Chapter 23 ~ Chapter 33
- x86内部函数列表
- 科普下VT EPT