VT 技術 (編寫一個 VT 框架)#
1.VT 技術介紹#
1. 技術介紹#
1.VT 技術#
VT 技術是 Intel 提供的虛擬化技術,全稱為 Intel Virtualization Technology。它是一套硬體和軟體的解決方案,旨在增強虛擬化環境的性能、可靠性和安全性。VT 技術允許在一台物理計算機上同時運行多個虛擬機,每個虛擬機都可以運行不同的操作系統和應用程序。
Intel VT(Intel Virtualization Technology)可以使單個 CPU 在虛擬化環境下模擬多個邏輯處理器(Virtual CPU),從而實現多個操作系統同時運行的能力。
VT 技術分為軟體虛擬化、容器虛擬化、虛擬化層翻譯
- 軟體虛擬化(Software Virtualization):這種虛擬化技術是基於軟體實現的,它在一個宿主操作系統上運行虛擬化軟體(如 Vmware Workstation、Virtual PC),通過模擬硬體環境來創建和管理虛擬機。每個虛擬機運行的操作系統和應用程序都不需要進行修改。
- 容器虛擬化(Container Virtualization):容器虛擬化是一種輕量級的虛擬化技術,它將操作系統內核的特性,如 Linux 容器(LXC)或 Docker 容器,用於隔離應用程序和它們的運行環境。容器共享宿主操作系統的內核,因此不需要進行完全的操作系統虛擬化。
2. 抽象的 "Ring-1 層"#
在傳統的計算機體系結構中,沒有明確定義的 "Ring -1" 層。通常,計算機體系結構中的 "Ring" 層級是指特權級別或權限級別,用於控制對系統資源的訪問。常見的層級包括 Ring 0(內核態)和 Ring 3(用戶態)。
然而,"VT 技術"Intel 的虛擬化技術(Virtualization Technology),它允許在一台物理計算機上同時運行多個虛擬機。虛擬化技術引入了新的軟體和硬體層級,以管理虛擬機的創建、運行和資源分配。
在虛擬化技術中,可以將主機操作系統視為 Ring 0,並將虛擬機的操作系統視為 Ring 3。在這種情況下,可以認為虛擬化技術引入了一種抽象的 "Ring -1" 層,用於管理虛擬機的創建和資源分配。這個抽象的層級位於主機操作系統和虛擬機操作系統之間,可以看作是一種虛擬化管理的層級。
需要注意的是,"Ring -1" 只是一種抽象概念,用於解釋虛擬化技術中的層級關係,並不是傳統計算機體系結構中的標準術語。實際上,不同的虛擬化技術可能採用不同的層級結構和術語,具體情況取決於所使用的虛擬化平台和技術。
2. 關鍵詞介紹#
1.VMM(Virtual Machine Monitor)#
虛擬機監視器,是指在電腦上的軟體,固件,或者是硬體,用來建立與執行虛擬機
2.VMX(Virtual Machine Extensions)#
處理器對虛擬化的處理器支持由一種稱為 VMX Opreation 的處理器操作形式提供,VMX Opreation 有 2 種,VMX Root Opreation 以及 VMX Non-root Opreation,一般來說,一個 VMM 將在 VMX Root Opreation 中運行,而客戶軟體運行在 VMX Non-root Opreation
3.VM(Virtual Machine)#
VM 指的是 Virtual Machine 虛擬機
4.VMCS(Virtual Machine Control Structures)#
邏輯處理器在執行 VMX 操作時,會使用虛擬機控制數據結構(VMCSs)。這些操作可以管理進出 VMX Non-root Opreation 的轉換(VM 條目和 VM 退出)的轉換,以及 VMX 非根操作中的處理器行為。該結構由新的指令 VMCLEAR、VMPTRLD、VMREAD 和 VMWRITE 操作。
5.VMX Root Opreation#
通常 VMM 將會在這種模式下運行
6.VMX Non-root Opreation#
通常客戶軟體(虛擬機)將在這種環境下運行。兩種類型的操作之間的轉換稱作 VMX 轉換,從根操作模式轉換到非根操作模式稱作 VMX 進入(VMX Entry),相反從非根操作模式轉換到根操作模式稱作 VMX 退出(VMX Exit)
7.Guest software#
每個虛擬機(VM)就是一個客戶軟體運行環境。
3.VMM 軟體的生命周期#
以上圖來描述 VMM 軟體的生命周期
- 啟用 VMX:VMX 操作的生命周期始於啟用 VMX 擴展。在計算機啟動時,虛擬機監視器(VMM)通過設置處理器的控制寄存器來啟用 VMX 擴展。這個過程通常在操作系統引導期間或虛擬化軟體加載時完成。
- 進入 VMX 操作模式:啟用 VMX 後,處理器進入 VMX 操作模式。在這個模式下,處理器支持運行虛擬機和虛擬機監視器之間的切換。VMM 負責管理虛擬機的創建、配置和執行。
- 虛擬機的創建和運行:在 VMX 操作模式下,VMM 可以創建虛擬機實例並配置虛擬機的資源。虛擬機監視器通過 VMCS(Virtual Machine Control Structure)來管理虛擬機的狀態和控制信息。VMM 在虛擬機和虛擬機監視器之間進行切換,使虛擬機可以在物理處理器上運行。
- 虛擬機監控和控制:在虛擬機運行期間,虛擬機監視器監控虛擬機的行為並提供必要的控制。它負責處理虛擬機的中斷、異常和特權指令,並確保虛擬機之間和虛擬機與物理機之間的資源隔離。
- 退出 VMX 操作模式:當虛擬機執行完成或發生特定事件時,虛擬機監視器將控制權從虛擬機切換回虛擬機監視器。這個過程稱為 VMX 退出。在退出過程中,虛擬機監視器可以讀取和更新虛擬機的狀態信息,並進行必要的處理。
- 禁用 VMX:VMX 操作的生命周期在虛擬化環境不再需要時結束。在關閉虛擬化軟體或關閉計算機時,處理器的 VMX 擴展將被禁用,處理器將恢復到普通的非虛擬化模式。
總的來說,VMX Operation 的生命周期涵蓋了啟用 VMX 擴展、進入 VMX 操作模式、虛擬機的創建和運行、虛擬機監控和控制、退出 VMX 操作模式以及禁用 VMX 擴展等關鍵階段,以實現硬體虛擬化的支持和管理。
2.VT 技術 (二) 檢測 CPU 支持#
1. 使用 CPUID 指令檢測 CPU 是否支持虛擬化#
在進入 VMX Opreation 之前需要對 CPU 進行一系列檢測,用於判斷 CPU 硬體是否支持虛擬化技術。
CPUID 指令在 Ring3 也可以使用,不需要進入 Ring0 層進行檢測,以下為 CPUID 指令的使用詳細說明
1.CPUID 指令使用說明#
- 語法
CPUID 在執行時需要將所需要的查詢編號傳入 eax 寄存器當中
xor rax,rax
cpuid
- 功能
CPUID 指令返回處理器的信息和功能支持,包括處理器廠商、處理器系列、功能位、快取配置、支持的擴展功能等。
- 寄存器使用
- 輸入:EAX 寄存器用於傳遞查詢的功能編號。
- 輸出:EAX、EBX、ECX、EDX 寄存器用於返回處理器的信息和功能。
2.CPUID 指令參數說明#
- 查詢處理器廠商信息:通過將 EAX 設置為 0,執行 CPUID 指令,處理器廠商的標識符將返回在 EBX、EDX 和 ECX 寄存器中,以 ASCII 編碼的形式表示。
- 查詢擴展功能支持:通過將 EAX 設置為 7,執行 CPUID 指令,處理器支持的擴展功能信息將返回在 EBX、ECX 和 EDX 寄存器中。
- 查詢快取配置:通過將 EAX 設置為 2,執行 CPUID 指令,處理器的快取配置信息將返回在 EAX、EBX、ECX 和 EDX 寄存器中。
- 查詢 CPUID 最大支持功能編號:通過將 EAX 設置為 0x80000000,執行 CPUID 指令,最大支持的功能編號將返回在 EAX 寄存器中。
以上簡單列出常用的幾個指令參數,如果需要查詢詳細參數,可參考 Intel 白皮書卷二第三章 (指令 A-L) 中的 CPUID 章節 (書中詳細介紹)
3. 查詢 CPU 是否支持 VT 技術#
由於本文基於的系統是 x64 位操作系統,VS 編譯器中的編譯器默認支持內聯匯編 (當然可以寫 asm 文件),但是考慮到代碼的兼容性,在代碼中使用的是 VS 自帶的內建函數,下方放出 VS 的內建函數查詢表
在匯編中執行的指令應該是
mov rax,1
cpuid
執行結束之後查詢 ECX 第五位是否被置為 1,如果被置為 1,表示當前的 CPU 支持虛擬化技術
代碼
EXTERN_C BOOLEAN Check_CPUID() {
int Ecx[4];
__cpuid(Ecx,1);
return (Ecx[2] >> 5) & 1; //Ecx 第六位是否為1
}
2. 讀取 MSR 字段檢查 BIOS 是否打開 VT 技術#
MSR(Model Specific Register)是一種特殊類型的寄存器,它包含了處理器的模型特定信息和配置參數。MSR 寄存器是處理器架構的一部分,由處理器製造商定義和實現。
每個處理器都可能具有不同的 MSR 寄存器集合,這些寄存器對應於特定的功能和配置選項。MSR 寄存器通常用於控制處理器的某些特性、性能調整、電源管理和虛擬化支持等。
與通用寄存器(如通用目的寄存器)不同,MSR 寄存器不可直接訪問,而是通過特殊的指令(如 RDMSR 和 WRMSR)進行讀取和寫入。這些指令用於將 MSR 的地址加載到指定寄存器(如 ECX)中,然後執行相應的操作。
通過讀取MSR_IA32_FEATURE_CONTROL
字段,對得到的值進行檢測,檢測第 0 位是否為 0,如果為 0,VMX 進入保護異常,無法直接執行指令進入 VMX Opreation,需要在 bios 中設置打開 VT 技術
1.RDMSR/WRMSR 指令#
該 2 條指令用於操作 MSR 寄存器,對 MSR 寄存器進行讀寫
RDMSR
mov eax,msr_index
rdmsr
該指令用於讀取 MSR 的值,將 MSR 索引號放入 eax 寄存器,執行指令之後,會將 MSR 的高 32 位值存儲在 EDX 寄存器中,低 32 位值存儲在 EAX 寄存器中
WRMSR
mov eax,values
mov ecx,msr_index
wrmsr
該指令負責對 MSR 寄存器進行寫入操作,將需要寫入的數據放入 eax,將 msr 寄存器的索引號放入 ecx,執行指令即可寫入
C 語言代碼
考慮到需要驗證的結果是二進制數字,我們知道奇數的第 0 位一遍位 1,偶數的第 0 位一遍位 0,因此只需要判斷奇偶性即可
BOOLEAN Check_CPUID() {
int Ecx[4];
__cpuid(Ecx,1);
return (Ecx[2] >> 5) & 1; //Ecx 第六位是否為1
}
3. 讀取 CR0 與 CR4 檢查是否已經成功進入 VMX#
以上工作做好之後,代表從硬體層面,已經支持進入 VMX 了,但是還需要打開 CR4 寄存器的字段鎖,允許運行 VT,且該鎖在進入 VMX 之後無法更改,否則直接藍屏,直到關閉 VMX。
此處先介紹一下六個 CR (Control Register) 寄存器的作用,但其實只有五個,CR1 寄存器在真實情況中不採用
- CR0:控制與保護模式、實模式、分頁以及其他系統操作相關的設置。
- CR1:保留不採用
- CR2:存儲引發頁錯誤異常的線性地址
- CR3:存儲頁表的物理地址,用於地址轉換和分頁機制
- CR4:控制處理器的特定功能和擴展,如分頁擴展、物理地址擴展等
- CR8:用於控制中斷和異常處理的優先級(僅在 64 位操作系統下使用)
如果對 CR 寄存器感興趣的師傅可以移步到這位大佬的這篇文章CR 寄存器的位介紹
事實上,CR 寄存器的每一位都有自己的名稱,而控制是否成功進入 VMX 的位則是 CR4 寄存器的正是 CR4 的 VMXE 位,在運行 VT 驅動代碼之前,可以先檢測是否已經進入了 VT,如果進入了 VT,則需要避免一些不必要的操作,否則導致主機藍屏
3.VMXON 進入 VMX#
在完成了 2 中所說的對 CPU 進行支持檢查之後,就可以正式開始寫進入 VMX 的代碼了
1.VMXON 與 VMXOFF 指令#
進入 VMX Opreation 模式的方式是執行 VMXON 指令,退出在的指令則是 VMXOFF
VMXON
在執行該指令之前需要初始化申請一段內存,該段內存大小為自然對齊,為 1KB 大小,被稱之為 "VMX_Region"
mov ecx,VMX_Region_Physical
vmxon ecx
該指令執行的要求是,需要將 VMX_Region 對應的物理內存地址放入寄存器中,再執行指令
PVOID VMX_Region = ExAllocatePoolWithTag(NonePagePool,0x1000,'VMX');
ULONG_PTR VMX_Region_Phy = MmGetPhysicalAddress(VMX_Region).QuadPart;
//需要設置VMX Region
__vmx_on(&VMX_Region_Phy);
VMXOFF
VMXON 指令直接在退出 VMX 的時候執行即可
以上 2 條指令在 VS 編譯器中,使用__vmx_off () 、__vmx_on () 即可
2. 設置 VMX_Region#
該段內存需要分配為 NonePagePool 類型的內存
該段內存的前四個字節需要寫入 VMCS_ID (MSR index 0x480)
通過__readmsr () 讀取並將其寫入至該內存
* (ULONG*)VMX_Region = __readmsr(0x480);
檢查錯誤位置#
在執行__vmx_on () 之後,需要對 Eflags 寄存器的相關位進行檢測,檢測 eflags 寄存器 CF 字段是否被置為 1,如果被置為 1,則進入 VMX 失敗
*(ULONG_PTR*)(&eflags) = __readeflags();
if (eflags.fields.cf != 0) {
DbgPrint("[CPU:%d]VMXON ON Failed", index);
}
4. 設置 VMCS 字段#
在閱讀本章之前,請先閱讀第五章進入虛擬化
在成功進入 VMX 環境之後,還需要一段 VMCS (Virtual Machine Control Structure) 內存,稱之為 "VMCS_Region",該內存主要的作用是存儲和管理控制虛擬機的執行,主要用於 VMCS 的相關信息
1.vmwrite 與 vmread 指令#
vmwrite 指令
mov ecx,VMCS_Fields
mov eax,VMCS_Data
vmwrite ecx,eax
該指令將需要寫入的 VMCS_Fields 放入 ecx,將需要寫入進字段的數據放入 eax,執行指令,成功將需要寫入的數據放入 VMCS_Fields
vmread 指令
mov ecx,VMCS_Fields
vmread eax,ecx
將需要讀取的 VMCS_Fields 放入 ecx,執行指令,將讀取到的數據放入 ecx 中
VMCS 中主要需要寫入的有 Guest_State
Host_State
VM Execute State
1.VM Execute State 設置#
- Pin Base
設置虛擬機的 IA32_VMX_PINBASED_CTLS,這個寄存器控制了大多數基於引腳的虛擬機執行控制的允許設置,寄存器的位 31:0 指示這些控制的允許的 0 設置。如果 MSR 中的位 X 被清除為 0,VM entry 允許控制 X(基於引腳的虛擬機執行控制的第 X 位)為 0;如果 MSR 中的位 X 設置為 1,如果控制 X 為 0,VM entry 將失敗。
- CPU Base
用於設置 IA32_VMX_PROCBASED_CTLS,這個寄存器控制大多數基於主處理器的虛擬機執行控件的允許設置,讀者如果想具體查看每一位的控制,詳見 Intel 白皮書卷三 24 章第 6 節第 2 點
- VM Exit Controls
設置 IA32_VMX_EXIT_CTLS 寄存器,控制大部分 VM Exit 允許的控制,詳見卷三 24 節第七節第一點
- VM Entry Controls
用於設置 IA32_VMX_ENTRY_CTLS,記錄大部分 VM Entry 的允許設置,詳見卷三 24 節第 8 節第 1 點
- Secondar Processor-Based
用於設置 IA32_VMX_PROCBASED_CTLS2,主要用於設置次級處理器,配置 APIC EPT 等,詳見卷三 24 節第 6 節第 2 點
2.Guest_State 設置#
-
寄存器部分
需要設置的有段寄存器,段選擇子,段限制,AR,段基址,分別為 (ES、CS、DS、FS、GS、FS、TR、GDTR、IDTR、LDTR、RIP,RSP) 等等,文本會給一個文檔具體的設置參數
-
非寄存器
VMCS Link pointer
3.Host_State 設置#
詳情請看文末的文檔
對於以上提到的字段對應的索引號,通過查找 Intel 白皮書卷三附錄 B 中查到
5. 進入虛擬化#
在設置完以上 VMCS 內容之前,執行 vmclear vmptrld 這兩條指令大概流程以代碼表示為
__vmx_vmclear(&VMCS_Phy);
__vmx_vmptrld(&VMCS_Phy);
SetupVmcs(); //設置VMCS
__vmx_vmlaunch(); //進入虛擬化
DbgPrint(”Vmlaunch Failed“); //如果成功執行vmlaunch,不會執行dbgprint函數
在執行__vmx_vmlaunch 之後,如果程序沒有發生意外,會直接進入 GUEST 模式,RIP/RSP 變化為 GUEST_RIP/GUEST_RSP 地址,在發生 VMX-Exit 事件後,產生異常,RIP/RSP 變為 HOST_RIP/HOST_RSP 指向的位置
如果成功執行了 DbgPrint 函數,請檢查 VM_INSTRUCTION_ERROR 的錯誤號可以在 Intel 白皮書的卷三第 30 章第 4 節查到
6. 處理 VM Exit#
在成功進入 GUEST 之後,RIP 跳到 GUEST_RIP 位置,繼續執行代碼
期間會產生大量 VMX-Exit 事件,在產生 VM-Exit 之後,虛擬機跳到 HOST_RIP 位置,因此 Host_RIP 處需要設置一個回調函數稱之為 VMExithandler
在 VMExithandler 函數中設置通過檢測 VM_EXIT_REASON 的錯誤號,來判斷產生錯誤的代碼,以及讀取 GUEST_RIP 以確定代碼執行錯誤的位置,聯繫代碼上下文對錯誤進行排查檢測
void ExitHandler(){
DWORD64 ExitReason = __readmsr(VM_EXIT_REASON);
DWORD64 GUEST_RIP = __readmsr(GUEST_RIP);
__DebugBreak();
}
通過斷點斷下程序,獲取 ExitReason 根據錯誤號進行排查 (ExitReason 錯誤號說明詳見卷三附錄 B)
完整的流程則為
將 hostrip 的函數用 asm 文件寫出將寄存器信息保存進入堆棧,將首地址指針通過 call 指令傳入 Exithandler
EXTERN_C ExitHandler:PROC //從其他文件導入函數
PUSHAQ MACRO
push rax
push rcx
push rdx
push rbx
push -1
push rbp
push rsi
push rdi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
ENDM
POPAQ MACRO
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rbp
add rsp, 8
pop rbx
pop rdx
pop rcx
pop rax
ENDM
ExithandlerEntry PROC
pushaq
mov rcx,rsp
sub rsp,50h
call ExitHandler
····//後續處理rax通過rax控制接下來的流程
popaq
resume //返回GUEST
ExithandlerEntry ENDP
通過 switch case 程序命中 Exitreason 再設置函數在 host 模式中執行 guest 模式中無法執行的指令,
再通過讀取 VM_EXIT_INSTRUCTION_LEN,獲取 GUEST_RIP 指向的當前指令長度通過 GUEST_RIP+VM_EXIT_INSTRUCTION_LEN 重新設置 GUESTRIP
void CpuidError(//接受GUEST寄存器信息){
//在host模式中處理GUEST無法執行或產生異常的指令
}
void NextCode(){
DWORD64 GUESTrip = __readmsr(GUEST_RIP);
DWORD64 VM_EXIT_INSTRUCTION = __readmsr(VM_EXIT_INSTRUCTION_LEN);
DWORD64 NextCode = GUESTrip+VM_EXIT_INSTRUCTION;
__vmx_vmwrite(GUEST_RIP,NextCode);
}
EXTERN_C BOOLEAN ExitHandler(//設置一個結構體獲取GUEST寄存器信息){
DWORD64 ExitReason = __readmsr(VM_EXIT_REASON);
DWORD64 GUESTrip = __readmsr(GUEST_RIP);
switch(ExitReason):
case CPUIDError: //命中函數
{
CpuidError();
NextCode();
break;
}
default:
DbgPrint("Exit Reason %p\n",ExitReason); // 輸出未處理的Exit事件
__DebugBreak()// int 斷點
break;
return TURE; // 通過返回布爾值以確定rax是否為0
}
通過以上流程設置 VMXExitHandler,處理 vmexit 事件
7. 退出虛擬化#
考慮到 VT 代碼是以驅動加載至 Windows 當中的,在需要關閉 VT 時需要執行卸載驅動的函數,因此必須保證程序正常執行到 DriverLoad 函數的 Return 部分,因此,在進入 Guest 之前需要保存堆棧信息以及寄存器信息以便在成功進入 GUEST 之後恢復堆棧以及寄存器信息,使程序正常執行下去。以保證正常執行卸載驅動的函數
GUEST 允許執行 VMCALL 指令,直接退出 GUEST 模式,在 UnloadVT 函數中執行該代碼,通過 Exithandler 命中 vmcall 錯誤,vmcall 可以提供參數執行,也可以不提供參數執行,通過回調函數檢測 rax 是否為設置的特殊參數,如果是直接執行 vmxon 指令關閉 vt
EXTERN_C void __fastcall AsmVmcall(ULONG_PTR num, ULONG_PTR param);
void UnloadVt(){
asmcall(vmxexit,0)
}
在 asm 中對 ExitHandler 函數的返回值進行檢測
ExithandlerEntry PROC
pushaq
mov rcx,rsp
sub rsp,50h
call ExitHandler
····//後續處理rax通過rax控制接下來的流程
test al,al //檢測返回值是否為0
jz ExitVT:
popaq //將修改後的寄存器回彈
resume //返回GUEST
jmp Error
ExitVT:
popaq
vmxon
jz Error
jc Error
push rax
popfq // 恢復堆棧
mov rsp, rdx
push rcx
ret
Error:
int 3
ExithandlerEntry ENDP
在 ExitHandler 中設置
VT CloseVT(){
//恢復一些段屬性 限制 基址等
}
BOOLEAN vmcallhandler(GUEST寄存器信息){
//如果rax為設定的預定值
CloseVt();
return FALSE;
//如果不是 正常處理錯誤
return TRUE;
}
EXTERN_C BOOLEAN ExitHandler(//設置一個結構體獲取GUEST寄存器信息){
DWORD64 ExitReason = __readmsr(VM_EXIT_REASON);
DWORD64 GUESTrip = __readmsr(GUEST_RIP);
ret = TRUE;
switch(ExitReason):
case vmcall: //命中函數
{
ret = vmcallhandler();
break;
}
default:
DbgPrint("Exit Reason %p\n",ExitReason); // 輸出未處理的Exit事件
__DebugBreak()// int 斷點
break;
return ret; // 通過返回布爾值以確定rax是否為0
}
總結#
代碼已經上傳到了 Github 倉庫
歡迎各位大佬批評,覺得不錯的師傅可以給個 star
在文章中的代碼可能會有一定的問題,請以 github 項目源碼參考
文章中需要的文檔在這裡!!!
Intel 白皮書全卷 & VMCS 設置參考文檔::
鏈接:https://pan.baidu.com/s/1cmTCIKwaT_eGlnmpO178ZQ
提取碼:yftx