初識虛擬化

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      }       }       }

相關文章
相關標籤/搜索