banner
yfsec

yfsec

一个会一点点渗透,会一点点开发,会一点点Windows内核的Fw
github

VT驱动开发

VT 技术 (编写一个 VT 框架)#

1.VT 技术介绍#

1. 技术介绍#

1.VT 技术#

VT 技术是 Intel 提供的虚拟化技术,全称为 Intel Virtualization Technology。它是一套硬件和软件的解决方案,旨在增强虚拟化环境的性能、可靠性和安全性。VT 技术允许在一台物理计算机上同时运行多个虚拟机,每个虚拟机都可以运行不同的操作系统和应用程序。

Intel VT(Intel Virtualization Technology)可以使单个 CPU 在虚拟化环境下模拟多个逻辑处理器(Virtual CPU),从而实现多个操作系统同时运行的能力。

VT 技术分为软件虚拟化、容器虚拟化、虚拟化层翻译

  1. 软件虚拟化(Software Virtualization):这种虚拟化技术是基于软件实现的,它在一个宿主操作系统上运行虚拟化软件(如 Vmware Workstation、Virtual PC),通过模拟硬件环境来创建和管理虚拟机。每个虚拟机运行的操作系统和应用程序都不需要进行修改。
  2. 容器虚拟化(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 Machin 虚拟机

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 软件的生命周期#

img

以上图来描述 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 指令使用说明#

  1. 语法

CPUID 在执行时需要将所需要的查询编号传入 eax 寄存器当中

xor rax,rax 
cpuid
  1. 功能

CPUID 指令返回处理器的信息和功能支持,包括处理器厂商、处理器系列、功能位、缓存配置、支持的扩展功能等。

  1. 寄存器使用
  • 输入: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 的内建函数查询表

VS x64 内建函数大全

VMX 指令函数介绍

在汇编中执行的指令应该是

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 寄存器在真实情况中不采用

  1. CR0:控制与保护模式、实模式、分页以及其他系统操作相关的设置。
  2. CR1:保留不采用
  3. CR2:存储引发页错误异常的线性地址
  4. CR3:存储页表的物理地址,用于地址转换和分页机制
  5. CR4:控制处理器的特定功能和扩展,如分页扩展、物理地址扩展等
  6. 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 设置#
  1. Pin Base

设置虚拟机的 IA32_VMX_PINBASED_CTLS,这个寄存器控制了大多数基于引脚的虚拟机执行控制的允许设置,寄存器的位 31:0 指示这些控制的允许的 0 设置。如果 MSR 中的位 X 被清除为 0,VM entry 允许控制 X(基于引脚的虚拟机执行控制的第 X 位)为 0;如果 MSR 中的位 X 设置为 1,如果控制 X 为 0,VM entry 将失败。

  1. CPU Base

用于设置 IA32_VMX_PROCBASED_CTLS,这个寄存器控制大多数基于主处理器的虚拟机执行控件的允许设置,读者如果想具体查看每个位的控制,详见 Intel 白皮书卷三 24 章第 6 节第 2 点

  1. VM Exit Controls

设置 IA32_VMX_EXIT_CTLS 寄存器,控制大部分 VM Exit 允许的控制,详见卷三 24 节第七节第一点

  1. VM Entry Controls

用于设置 IA32_VMX_ENTRY_CTLS,记录大部分 VM Entry 的允许设置,详见卷三 24 节第 8 节第 1 点

  1. Secondar Processor-Based

用于设置 IA32_VMX_PROCBASED_CTLS2,主要用于设置次级处理器,配置 APIC EPT 等,详见卷三 24 节第 6 节第 2 点

2.Guest_State 设置#
  1. 寄存器部分

    需要设置的有段寄存器,段选择子,段限制,AR,段基址,分别为 (ES、CS、DS、FS、GS、FS、TR、GDTR、IDTR、LDTR、RIP,RSP) 等等,文本会给一个文档具体的设置参数

  2. 非寄存器

    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

A VT Demo By yifang

在文章中的代码可能会有一定的问题,请以 github 项目源码参考


文章中需要的文档在这里!!!

Intel 白皮书全卷 & VMCS 设置参考文档::

链接:https://pan.baidu.com/s/1cmTCIKwaT_eGlnmpO178ZQ
提取码:yftx

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。