初识虚拟化

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之间的交互了。大致的流程是这样子的:

  1. software执行VMXON指令进入VMX operation
  2. 经过VM entries,VMM就能够进入VM的guest中(VMM经过VMLANCH和VMRESUME来触发VM entry,并经过VM exits从新得到控制权)
  3. VM exits将控制权转移到由VMM定义的entry point(VMM能够采起适当的动做来触发VM exit,而后再使用一个VM entry就能够返回到VM中)。
  4. 最后,VMM经过VMXOFF指令关闭自身并退出VMX operation

 

 

 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的相关位以下:

  • Bit 0 is the lock bit. 若该位被清零,VMXON就会触发一个general-protection exception;若该位被置1,向此MSR进行WRMSR也会触发general-protection exception,直到a power up reset condition发生时该MSR才能被修改。系统BIOS能够经过该比特位来禁用VMX,若要打开VMX支持,BIOS必须设置该MSR的bit 0, bit 1, bit 2。
  • Bit 1 enables VMXON in SMX operation. 若该位被清零,SMX operation中的VMXON将触发一个general-protection exception。若在不一样时支持VMX和SMX的logical processors上试图设置该位,将会触发general-protection exceptions。(若一个logical processor自从最后一次执行GETSEC[SENTER]为止GETSEC[SEXIT]还未被执行,则称其in SMX operation)
  • Bit 2 enables VMXON outside SMX operation. 若该位被清零,在SMX operation外部进行VMXON将触发一个general-protection exception。在不支持VMX的logical processors上试图设置该位将触发general-protection exceptions。(若一个logical processor还未执行GETSEC[SENTER]或最后一次执行过GETSEC[SENTER]后又执行了GETSEC[SEXIT],则称其outside SMX operation)

在执行VMXON以前,software应该分配出(保留)一块4KB对齐的内存区域供logical processor用以支持VMX operation。这个内存区域就被称为VMXON region。

最后,简单说说VMX operation上的限制:VMX operation对processor operation做了一些限制,诸如,

  • 在VMX operation中,处理器将对CR0和CR4的某些具体位填充固定值(CR0.PE, CR0.NE, CR0.PG, cR4.VMXE的值都必须为1)。若是这些位中任何一位的值并非它应该的值,VMXON就会fail。在VMX operation中任何试图使用CLTS, LMSW, MOV CR等指令改变这些位的值都将致使general-protection exception。VM entry或VM exit都没法将这些位的值设置为其不该该的值。(CR0.PE和CR0.PG的限制就说明了VMX operation必须处于paged protected mode,这也使得guest software没法运行于unpaged protected mode或real-address mode中)
  • 若logical processor处于A20M mode,VMXON会fail。一旦处理器进入VMX operation, A20M的中断就会被block,这样VMX operation中A20M mode固然是不可能的了。
  • 只要logical processor处于VMX root operation,INIT signal就会被block;在VMX non-root operation中它是不会被block的,此时INITs将触发VM exits。


从源码级别,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      }       }       }

相关文章
相关标签/搜索