0x01 硬件虚拟化数据结构
早期的虚拟机都是进程级虚拟机,也就是做为已有操做系统的一个进程,彻底经过软件的手段来模拟硬件,软件再翻译内存地址的方法实现物理机器的模拟,这样虚拟效率较低,资源利用率低。以后Intel和AMD相继推出了支持硬件虚拟化的处理器,所谓硬件虚拟化,也就是在硬件抽象层对虚拟技术提供直接支持,提升虚拟效率。咱们以前提到过在进程级虚拟机中的客户机访问的物理地址须要通过软件的再次转化成真实机器的物理地址,并且也须要给不一样的客户机操做系统编写不一样的虚拟设备驱动程序从而能够共享同一真实的硬件资源。而硬件虚拟化则是实现了内存地址甚至与I/O设备的直接映射,无需通过再一次的转换。而硬件虚拟化技术中引出了一个重要的概念——VMM(Virtual Machine Monitor),使硬件虚拟化技术产生的一个新的特权级,用来处理虚拟硬件和真实硬件的通讯和一些事件的处理,所以其系统权限在操做系统之上,产生了一个新的特权级“Ring -1”。能够简单理解为利用硬件虚拟化技术可让咱们的代码运行在操做系统之下,监视整个操做系统的运行。app
Intel VIrtual Techonlogy , intel 硬件虚拟化技术 ,在硬件级别上完成计算机的虚拟化
为实现硬件虚拟化 ,VT增长了 12条新的 VMX指令:
[VMCS控制 5 条]
VMPTRLD
VMPTRST
VMCLEAR
VMREAD
VMWRITE
[VMX命令 5条]
VMLAUNCH
VMCALL
VMXON
VMXOFF
VMRESUME
[Guest software 2条]
INVEPT
INVVPID
12条指令对应的机器码(xen-3.4.1\xen\include\asm-x86\hvm\vmx\vmx.h)
代码中使用 _emit
#define VMCALL_OPCODE ".byte 0x0f,0x01,0xc1\n"
#define VMCLEAR_OPCODE ".byte 0x66,0x0f,0xc7\n" /* reg/opcode: /6 */
#define VMLAUNCH_OPCODE ".byte 0x0f,0x01,0xc2\n"
#define VMPTRLD_OPCODE ".byte 0x0f,0xc7\n" /* reg/opcode: /6 */
#define VMPTRST_OPCODE ".byte 0x0f,0xc7\n" /* reg/opcode: /7 */
#define VMREAD_OPCODE ".byte 0x0f,0x78\n"
#define VMRESUME_OPCODE ".byte 0x0f,0x01,0xc3\n"
#define VMWRITE_OPCODE ".byte 0x0f,0x79\n"
#define INVEPT_OPCODE ".byte 0x66,0x0f,0x38,0x80\n" /* m128,r64/32 */
#define INVVPID_OPCODE ".byte 0x66,0x0f,0x38,0x81\n" /* m128,r64/32 */
#define VMXOFF_OPCODE ".byte 0x0f,0x01,0xc4\n"
#define VMXON_OPCODE ".byte 0xf3,0x0f,0xc7\n"
驱动初在始化一个VMCS (Virtual Machine Control Structures)内存区域后,启动VMM (Virtual Machine Monitor) ,
获得最高权限从而管理硬件资源, 操做系统是运行于ring0下
VMM要监控管理整个系统的资源,于是VMM的权限是大于操做系统,它处于一个全新的级别ring -1ide
0x02 虚拟机的体系结构函数
Intel提供了处理器级的VMX(Virtual-Machine Extensions),从硬件层面支持VT技术。Intel VMX的体系结构可划分为两层:VMM和VM。 ui
VMM(Virtual-Machine Monitors)做为host,具备对processor(s)和平台硬件的彻底控制权限。它为guest提供VCPU(virtual processor)的抽象,并容许guest直接运行在逻辑处理器(logical processor)上。VMM具备对处理器资源、物理内存、中断和IO的选择控制的权利。(举例:Xen就是一种VMM。)spa
VM(Virtual-Machine)相应地做为guest,实际上是提供了一种guest软件环境:它维护一个栈,其中包含了OS和application software。其每一个操做都独立于其余的VM,而且使用由同一个物理平台所提供的对处理器、内存、硬盘、显卡、IO访问的统一接口。此外,这个栈并不知道VMM的存在。运行于VM中的软件其权限是受限的,这样才能保障VMM对整个平台资源的彻底控制。(举相应的例子:Xen上跑的Guest OS就是VM。)操作系统
0x03 VMX operation翻译
支持虚拟化的处理器其虚拟化相关的操做被称为VMX operation 。它分为两类:VMX root operation 和 VMX non-root operation 。一般来讲,VMM 运行于 VMX root operation 而 guest 运行于 VMX non-root operation 。两种operation之间的转换被称为VMX transitions:从root到non-root被称为VM entries,而从non-root到root则称为VM exits。设计
处于VMX root operation的CPU其行为与在VMX operation以外是基本同样的,最根本的不一样之处在于其增长了一套新的VMX指令集,且能存储到特定控制寄存器的值是有限的。而处于VMX non-root operation的CPU起行为是受限的,且通过了修改以帮助实现virtualization。与其普通的operation不一样,特定的指令(包括新增的VMCALL指令)和事件将致使VM exits从而进入VMM:因为这些VM exits代替了之前正常的行为,因此在VMX non-root operation中的软件的功能是受限的(也正是这种限制保证了VMM可以始终具备控制处理器资源的能力)。
从guest的角度,没有任何一个Guest可见的位来指示一个逻辑处理器是否处于VMX non-root operation,这样VMX就能保证guest并不知道其正在运行于一个VM中。即使是CPL(current privilege level)为0,VMX operation也给guest加了限制,这样guest software就能够彻底没必要改变其原始的设计,这也简化了VMM的开发。调试
看一下VMM与Guest之间的交互了。大致的流程是这样子的:
0x04 VMCS(Virtual-Machine Control Structure)
VMCS(Virtual-Machine Control Structure)是一个很是重要的数据结构,这个数据结构在下文中还要很是详细地介绍。对VMCS的访问是由一组被称做VMCS pointer(每一个logical processor有一个VMCS pointer)的处理器状态来管理的。VMCS pointer是一个64位的VMCS地址,可经过VMPTRST和VMPTRLD指令对其进行读写;VMM可使用VMREAD、VMWRITE、VMCLEAR指令对VMCS进行配置。对VMM所管理的每一个VM,VMM可使用不一样的VMCS;且对VM中的每一个logical processor(or vcpu),VMM也能够为每一个vcpu使用不一样的VMCS。
VMX operation须要处理器支持,那么如何从软件层面判定一个处理器是否支持VMX operation:经过CPUID --- 若是CPUID.1:ECX.VMX[bit 5] = 1,那说明该CPU支持VMX operation。现有的VMX体系结构的设计具备良好的可扩展性,software可使用一个VMX capability MSRs集来得到VMX新增的扩展特性。
如今再来看看如何使能和进入VMX operation:进入VMX operation以前,system software经过设置CR4.VMXE[bit 13] = 1 来使能VMX,以后就能够经过VMXON指令来进入VMX operation。若是CR4.VMXE = 0,VMXON将致使一个invalid-opcode异常(#UD),并且一旦进入VMX operation,CR4.VMXE就没法在此中被清零;system software经过VMXOFF离开VMX operation,只有在VMX operation外部CR4.VMXE才能被清零。
VMXON由IA32_FEATURE_CONTROL MSR (MSR address 3AH)所控制,当一个logical processor被rest时,该MSR被清零。此MSR的相关位以下:
在执行VMXON以前,software应该分配出(保留)一块4KB对齐的内存区域供logical processor用以支持VMX operation。这个内存区域就被称为VMXON region。
最后,简单说说VMX operation上的限制:VMX operation对processor operation做了一些限制,诸如,
从源码级别,Xen里面的vmx.c描述的就是Intel VMX体系结构相关的VM Exits支持:对照着Intel的Manual能够找到Xen对其的具体实现。
0x05 初识VMM的创建过程
首先经过CPUID检查CPU是否支持VT
硬件环境的检测,经过指令CPUID检测CPU是否支持VT
ECX的第5个bit标志表明对 VT 的支持与否
初始化主要的内存区域
使用VMXON 进入 VMX (虚拟机指令扩展指令集)操做
执行VMXON指令 , EFLAGS.CF 可判断执行是否成功
__asm
{
PUSH 0
PUSH PhysicalVMXONRegionPtr.LowPart
_emit 0xF3 // VMXON [ESP]
_emit 0x0F
_emit 0xC7
_emit 0x34
_emit 0x24
PUSHFD
POP eFlags
ADD ESP, 8
}
接下来开始初始化VMCS区域,(至关于部署军队,准备起义)
这个结构只可以被VMCLEAR, VMPTRLD, VMREAD,和VMWRITE操做。
VMM运行后guest 执行这些指令时会引起#VMExit事件
VMCS 是一个4K的内存区域
在逻辑上,虚拟机控制结构被划分为 6 部分:
1) GUEST-STATE 域:虚拟机从根操做模式进入非根操做模式时,处理器所处的状态;
2) HOST-STATE 域:虚拟机从非根操做模式退出到根操做模式时,处理器所处的状态;
3) VM 执行控制域:虚拟机在非根操做模式运行的时候,控制处理器非根操做模式退出到根操做模式;
4) VM 退出控制域:虚拟机从非根操做模式下退出时,须要保存的信息;
5) VM 进入控制域:虚拟机从根操做模式进入非根操做模式时,须要读取的信息;
6) VM 退出信息域:虚拟机从非根操做模式退出到根操做模式时,将退出的缘由保存到该域中。
在这里初始化的代码比较多,要初始化 host 与 guest 的 CR0,CR3,CR4,IDTR,GDTR,LDTR,Rflag,SYSENTER_CS,SYSENTER_EIP,SYSENTER_ESP等
初始化时使用VMWRITE 指令设置 , 设置时主要的宏是
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,
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,
…
使用 VMWRITE 指令集成的函数
VOID WriteVMCS( ULONG encoding, ULONG value )
{
__asm
{
PUSHAD
PUSH value
MOV EAX, encoding
_emit 0x0F
_emit 0x79
_emit 0x04
_emit 0x24
POP EAX
POPAD
}
}
例如初始化 GDT , IDT 使用的是如下代码
__asm
{
SGDT gdt_reg
}
temp32 = 0;
temp32 = gdt_reg.BaseHi;
temp32 <<= 16;
temp32 |= gdt_reg.BaseLo;
Log( "Setting Host GDTR Base" , temp32 );
WriteVMCS( HOST_GDTR_BASE, temp32 );
__asm
{
SIDT idt_reg
}
temp32 = 0;
temp32 = idt_reg.BaseHi;
temp32 <<= 16;
temp32 |= idt_reg.BaseLo;
Log( "Setting Host IDTR Base" , temp32 );
WriteVMCS( HOST_IDTR_BASE, temp32 );
比较重要的是设置 #VMExit 事件处理入口 HOST_RIP
WriteVMCS( HOST_RIP, (ULONG)VMMEntryPoint ); //0x6C16
最后执行 VMLAUNCH 指令 ,正式启动虚拟机(创建了新的政权)
__asm
{
_emit 0x0F
_emit 0x01
_emit 0xC2
}
至此,整个VMM的帝国已经运行起来,帝国将会管理整个系统资源
接下来的就是#VMExit的事件循环处理(相似在调试器中下断点后,等待事件发生)
运行过程当中Guest OS 遇到要监控的指令,会发生 #VMExit 事件 (至关于被调试程序执行到中断事件)
此时由VMM 处理,例如cpuid ,
处理完后由 VMRESUME 继续执行 (至关于在 OD 中 F9 继续运行)
#VMExit事件的类型
#VMExit事件分为二种
1 无条件事件CPUID GETSEC INVD XSETBV 全部 VMX指令
2 有条件事件I/O访问,中断事件, MSR寄存器访问, HTL 等 (要设置VMCS相应部分触发)
VMM使用VMREAD读取虚拟机状态,主要的一些变量有
VM_EXIT_REASON = 0x00004402, //退出代码
VM_EXIT_INTR_INFO = 0x00004404, //中断信息
VM_EXIT_INTR_ERROR_CODE = 0x00004406,
IDT_VECTORING_INFO = 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,
…
其中最重要的就是VM_EXIT_REASON , 能够看做是消息类型
主要的类型有
#define EXIT_REASON_EXCEPTION_NMI 0
#define EXIT_REASON_EXTERNAL_INTERRUPT 1
#define EXIT_REASON_TRIPLE_FAULT 2
#define EXIT_REASON_INIT 3
#define EXIT_REASON_SIPI 4
#define EXIT_REASON_IO_SMI 5
#define EXIT_REASON_OTHER_SMI 6
#define EXIT_REASON_PENDING_VIRT_INTR 7
#define EXIT_REASON_PENDING_VIRT_NMI 8
#define EXIT_REASON_TASK_SWITCH 9
#define EXIT_REASON_CPUID 10
#define EXIT_REASON_HLT 12
#define EXIT_REASON_INVD 13
#define EXIT_REASON_INVLPG 14
#define EXIT_REASON_RDPMC 15
#define EXIT_REASON_RDTSC 16
#define EXIT_REASON_RSM 17
#define EXIT_REASON_VMCALL 18
#define EXIT_REASON_VMCLEAR 19
#define EXIT_REASON_VMLAUNCH 20
#define EXIT_REASON_VMPTRLD 21
#define EXIT_REASON_VMPTRST 22
#define EXIT_REASON_VMREAD 23
#define EXIT_REASON_VMRESUME 24
#define EXIT_REASON_VMWRITE 25
#define EXIT_REASON_VMXOFF 26
#define EXIT_REASON_VMXON 27
…
DWORD VmxRead(DWORD in_code)
{
DWORD m_vmread = 0;
__asm
{
PUSHAD
MOV EAX, in_code
_emit 0x0F // VMREAD EBX, EAX
_emit 0x78
_emit 0xC3
MOV m_vmread, EBX
POPAD
}
return m_vmread;
}
VOID VMMReadGuestState( )
{
HandlerLogging = 0;
ExitReason = VmxRead(VM_EXIT_REASON); //0x4402
ExitInterruptionInformation = VmxRead( VM_EXIT_INTR_INFO); //0x4404
ExitInstructionLength = VmxRead(VM_EXIT_INSTRUCTION_LEN); //0x440c
ExitQualification = VmxRead(EXIT_QUALIFICATION) ; //0x6400
ExitInterruptionInformation = VmxRead(VM_EXIT_INTR_INFO); //0x4404
ExitInterruptionErrorCode = VmxRead(VM_EXIT_INTR_ERROR_CODE); //0x4406
IDTVectoringInformationField = VmxRead(IDT_VECTORING_INFO); //0X00004408 // IDT-Vectoring Information Field
IDTVectoringErrorCode = VmxRead(IDT_VECTORING_ERROR_CODE); //0X0000440A // IDT-Vectoring Error Code
ExitInstructionLength = VmxRead(VM_EXIT_INSTRUCTION_LEN); //0x0000440C // VM-Exit Instruction Length
ExitInstructionInformation = VmxRead(VMX_INSTRUCTION_INFO) ; //0x0000440E //VM-Exit Instruction Information
GuestEIP = VmxRead(GUEST_RIP); //0x0000681E; //GuestEIP
GuestESP = VmxRead(GUEST_RSP); //0x0000681c //esp
GuestCR3 = VmxRead(GUEST_CR3); //0X6802 GuestCR3
}
CPUID指令的拦截与修改
当 guest OS 执行 cupid 时,会获得这样一个错误号 10 (VMX_EXIT_CPUID )
当执行到CPUID这条指令的时候,响应,而后就能够修改cpuid的返回值 if( ExitReason == VMX_EXIT_CPUID ) { if( GuestEAX == 0 ) { DbgPrint("CPUID EIP == %08X \n" , GuestEIP ); //0x34EC2B __asm { POPAD MOV EAX, 0 CPUID //修改CPUID返回值 MOV EBX, 0x80808080 MOV ECX, 0x90909090 MOV EDX, 0x10101010 JMP Resume } } else { __asm { POPAD MOV EAX, GuestEAX CPUID JMP Resume } } }