說到虛擬化、虛擬機等名詞,IT領域的小伙伴肯定不陌生,業內人士或多或少的都用過VMware或其他PC虛擬機重新搭建一個系統,用來做各種高危測試,避免影響物理機的OS;10年前熱炒的雲計算,也是基於虛擬化技術的;多年以前,早期的虛擬化還是靠虛擬機軟件模擬執行指令后返回結果。大約從2005年開始,intel推出了硬件輔助虛擬化技術Vanderpool(VT的前生),直接宣布從硬件層面開始支持虛擬化,個人認為有划時代的意義,這也直接促進了后來10多年雲計算的蓬勃發展(大名鼎鼎的KVM就是基於VT的);近些年新買的電腦都支持VT(當然cpu要求是intel的),安裝虛擬機的時候先在BIOS開啟VT支持即可;除了虛擬化,VT在安全領域用途也很廣,比如調試與反調試,無限硬件斷點,無痕Hook(不會觸發windwos的PG保護和被調試軟件自帶的CRC檢測),后續會逐個分享;本文先簡單介紹VT的基本概念和流程,為后續的代碼理解做准備;
凡是用過虛擬機的小伙伴都知道,需要先裝個虛擬機軟件,最常見的是vmware和virtual box,然后就可以在虛擬機裝OS了。這種場景下,虛擬機里面運行的os叫guestOS,處於VMX non-root狀態;物理機運行的os叫hostOS,出於VMX root狀態;虛擬機用來監控和管理guestOS的,叫VMM(virtual machine manager); 除此以外,VT還涉及到其他好些概念,諸如vmx\root\vmxon\vm entry\vmcs\vmexit\vmresume等等,先把這些概念之間的關系梳理如下:

上圖也是VMX執行的順序流程,嵌套了VMX所有重要的指令;現在來挨個解釋一下VM開頭的重要概念和指令:
- CPUID
用來檢查當前CPU是否支持VT,也是虛擬化開啟的第一步;近些年新買的電腦,只要cpu是intel,都支持VT。安裝虛擬機的時候先在BIOS開啟VT即可;這里要強調一下:逆向人員一般在開發的時候都在虛擬機做各種操作,藍屏后能快速利用鏡像恢復,這時需要在虛擬機開啟VT選項,如下圖,才能繼續在虛擬機測試VT功能;由於虛擬機本身已經虛擬化了一層,再開啟VT支持,相當於又虛擬化了一層;站在物理機的角度看,這時一共虛擬化了2層,像不像盜夢空間?

- VMXON
用於開啟VMX(virtual machine extension);執行VMXON指令的時候需要提供一個4KB對齊的內存區間,稱作VMXON region,該區域的物理地址作為VMXON指令的操作數。該內存區間用於支持host CPU的VMX功能,該區域在VMXON和VMXOFF之間一直都會被VMX硬件所使用,每個host cpu都需要一個4KB對齊的空間;至於這個4KB空間裝了什么數據,intel暫未公開(從其他開發人員的資料看,可能記錄了當前運行host正在運行哪個虛擬機)。代碼層面需要做的就是分配這么一個空間就好,其他都不用管了;
和虛擬化相關的內存不能分頁,換句話說不能被交換到磁盤,也就不能產生page fault,windows下再驅動層一般用ExAllocatePool函數分配,傳入NonPagedPool參數表示申請非分頁內存;
- VMCLEAR 和 VMPRTLOAD
VMCLEAR: 清空VMCS區域,為下一步初始化VMCS做准備;該指令在intel開發手冊的原話是:The instruction ensures that VMCS data for that VMCS (some of these data may be currently maintained on the processor) are copied to the VMCS region in memory. It also initializes parts of the VMCS region (for example, it sets the launch state of that VMCS to clear);大概意思是確保VMCS的數據能被cpoy到內存中VMCS region,同時初始化VMCS區域的部分內容(比如設置VMCS為launch狀態);
VMPRTLOAD: 加載或指定虛擬機。同一個物理CPU,可能會在不同的vCPU之間切換。再說直白一點,同一個物理核可能會在不同的虛擬機之間來回切換(就像進程、線程切換一樣),切換到哪個虛擬機運行了?可以通過該指令確定;一旦VMPRTLOAD指定了虛擬機,接下面所有的VM指令都是針對該虛擬機的了;切換虛擬機的本質就是切換VMCS(就像切換進程的本質就是切換CR3,也就是PCB一樣);
- 初始化VMCS
這是重點。VMX通過引入根運行模式(VMX root operation) 和非根模式(VMX non-root operation),直接讓vCPU運行在物理CPU上,在軟件上省去了對vCPU運行的模擬,大大提升了性能。 剩下的就是對vCPU狀態的記錄了,為此Intel引入了VMCS(Virtual Machine Control Structure)功能;VMCS詳細的數據作用如下:

注意:VMCS塊內數據的讀寫必須通過單獨的vmread和vmwrite指令;C語言常見內存操作如memset、memcopy、char * 等方式是不行的;為啥intel要單獨提供兩個指令了?個人理解:VMCS結構體內部各個字段的位置不是一成不變的。
(1)不同版本可能是不一樣的,如果直接用開發人員通過結構體把字段位置寫死,不利於代碼在不同版本之間適配和移植;
(2)VMWRITE指令可能還會影響某些寄存器
- VMLANUCH
這是一條進入新世界的指令。一旦執行,VMX隨即從root模式切換到non-root模式,直白點就是切換到guest OS繼續運行,避免影響guest OS在ring 3層的app;
- VMEXIT或VMCALL
上面切回到guest OS會一直運行,直到guest OS產生某些原因的異常,自身處理不了了,需要VMM幫忙處理,便會調用vmexit或vmcall退回到host;host處理這些異常的代碼就需要開發人員定制寫了!
- VMRESUME
host 處理完 guest產生的異常后,調用該指令回到guest OS繼續執行;
以上便是VMX整體的流程;總結一下:先在guest OS檢查一下當前CPU是否支持VT,然后通過VMXON開啟VMX,由此衍生出了MVX的root和non-root模式,同時進入root模式的VMM去初始化VMCS結構,再通過VMLAUNCH返回guestOS運行,直到產生部分異常,guestOS hold不住了,通過vmexit退回root模式的VMM,請求VMM幫忙處理。處理完成后再通過VMRESUME重新把物理CPU的執行機會還給guestOS運行,也就是運行vCPU;整個過程,物理CPU一直都在運行,無非是運行guestOS的指令(也就是vCPU),還是運行VMM的指令(也就是host CPU,有些地方也習慣稱為邏輯CPU);
用下圖總結一下以上內容:
- 不同的物理CPU可以來回在不同的vCPU之間切換,所以需要VMCS來保存host和guest的各個狀態(核心是各個寄存器、IDT/GDT表等),以便切回來的時候能恢復當初的狀態;
- 物理CPU和vCPU是多對多的關系,由VMM把物理CPU形成一個資源池(當然這是需要開發人員通過寫代碼實現的,硬件不直接支持),供vCPU使用;

最后附上VMX的指令集:
| 指令 |
作用 |
| VMPTRLD |
加載一個VMCS結構體指針作為當前操作對象 |
| VMPTRST |
保存當前VMCS結構體指針 |
| VMCLEAR |
清除當前VMCS結構體 |
| VMREAD |
讀VMCS結構體指定域 |
| VMWRITE |
寫VMCS結構體指定域 |
| VMCALL |
引發一個VMExit事件,返回到VMM |
| VMLAUNCH |
啟動一個虛擬機 |
| VMRESUME |
從VMM返回到虛擬機繼續運行 |
| VMXOFF |
退出VMX操作模式 |
| VMXON |
進入VMX操作模式 |
參考:
