hypervisor-in-rust-0

hypervisors and high-performance fuzzing.

最近学习模糊测试,一些fuzzer借助硬件辅助来提高效率,在找资料时找到了不错的教程,因此学习(CV一下 : Hypervisor 101 in Rust

介绍

Global Cybersecurity Camp 2023 Singapore.

硬件辅助的高性能fuzzer开发,Fuzzing UEFI + 配套练习

课程前提

  1. 熟悉x86_64架构
  1. Rust经历有用,但是不是必要的
  2. 下载相关文档
  1. 需要一台能支持Intel-VT/AMD-V的电脑

课程目标

  • 理解x86_64硬件辅助虚拟化技术
  • 熟悉如何将虚拟化技术应用到高性能fuzzing中

动机

Hypervisor

如何完成

我无法创造的,我就不理解。– 费曼

熟悉相关的特性,引用开源的实现

  • 看文档 + 看代码,知道如何实现或者CV

可以学习到什么

  • 一个可以运行并且fuzz目标的hypervisor
  • 跨平台的hypervisor开发和debug
  • 为自己的测试扩展功能
  • 更好的理解/阅读现存的hypervisor相关实现的代码
  • 开发自己的hypervisor

fuzzer设计

灰盒测试,基于变异,边覆盖率引导
快照功能
输入:快照文件,样本库,patch 文件

hypervisor设计

从快照创建一个虚拟机
开始fuzzing

  • 向内存注入变异的输入
  • VM运行
  • 观察/收集可能的BUG
    一次迭代后恢复快照
    运行尽可能多的VM
    使用Rust书写的UEFI
    在Bochs/VMware测试,选择裸机型号(应该时选择Intel or AMD?)

不是如下的课程类型

  • Rust编程课
  • 模糊测试课程
  • 学习已经存在的软件相关原理
  • 比较详细提供代码的细节,一些注释可能过于简单并且可能是不正确的

Demo

应该是作者现场演示,我们可以clone下来代码仓库运行

为什么是Hypervisor

优点

  • 不局限于用户态的fuzzing
  • 比模拟器块

比较优秀的案例

UEFI 应用

先于操作系统启动,更快的BIOS

Unified Extensible Firmware Interface Forum

UEFI好处

  • 减少系统资源的浪费
  • 与操作系统无关的设计和开发环境
  • 兼容性好
  • 比较容易访问硬件特性
  • 文档比较齐全

Rust

为什么选择了rust

  • 相对与C/C++来说,更方便的UEFI
  • 更多的lib和crate
  • 编译器比较严格,减少BUG
  • 具有安全意识的工程师,现如今应该选择Rust

hypervisor…

Hypervisor

VMM: Virtual Machine Monitor,和 hypervisor 等价。

两类虚拟化技术:

  • 直接运行在硬件上:VMware ESXi, Windows Hyper-V…
  • 运行在操作系统上:VMware Workstation,VirtualBox,QEMU…

虚拟化主要分为几大类:

  • 计算虚拟化,针对CPU和内存资源虚拟化技术。
  • 网络虚拟化,针对网络链路资源虚拟化技术。
  • IO虚拟化,针对IO资源虚拟化技术。
  • 存储虚拟化,针对磁盘存储资源虚拟化技术。

Intel-VT

虽然说本机电脑时AMD CPU,但是网上Intel-VT内容比较多,有问题也好解决,因此首先学习了部分知识。并且知识都是相通的

因此介绍不会很细😋,毕竟有了源码也不能用😭

不错的学习资源,因为是AMD的CPU,主要还是了解概念以及思路

Intel® 64 and IA-32 Architectures Software Developer Manuals搜索Intel 64 and IA-32 Architectures Software Developer’s Manual Volume 3

部分一些专业术语

  • Intel VT-X: Virtualization Technology for x86
  • guest-mode: VMX non-root operation
  • host-mode: VMX root operation
  • VMX: Virtual-Machine eXtensions
  • VMCS: virtual-machine control data structures
  • EPT: Extend Page Table
  • VT-D: Virtualization Technology for Directed I/O
  • GPA: Guest Physical Address
  • HPA: Host Physical Address

VMX

VMM host-guest

  • VMM通过执行VMXON指令进入VMX操作,然后就是类似一个操作系统的启动过程。
  • VM Entry:VMM 可以进入 guest。 VMM使用指令VMLAUNCHVMRESUME进行VM切换(可以有很多guest);使用 VM Exit 返回host。
  • VM Exit: 将控制转移到VMM指定的入口点。 VMM可以针对VM Exit的原因采取适当的操作
  • 最终,VMM 可能决定自行关闭并离开 VMX 操作。 它通过执行 VMXOFF 指令来实现这一点。

**VMCS 组成:**(VMCS 区域是4 KB(位 11:0 必须为零),必须与 4KB 边界对齐)

  • guest state area:在 VM-entry时,处理器的状态信息从guest-state区域中加载。在VM-exit时,处理器的当前状态信息保存在guest-state区域。
  • host state area:在VM-exit时,处理器的状态信息从host-state区域中加载。
  • VM-execution control field:在进入VM后,处理器的行为由VM-execution控制区域中的字段提供控制。
  • VM-EXIT control field:控制处理器在处理VM-exit时的行为,也影响返回VMM后处理器的某些状态。
  • VM-ENTRY control field:控制处理器在处理VM-entry时的行为,也决定进入VM后处理器的某些状态。
  • VM-EXIT infomation field:记录引起VM-exit事件的原因及相关的明细信息。

一个大致的框架:

  1. 检查硬件是否支持VMX: CPUID.1:ECX.VMX[bit 5] = 1
  2. 启用VMX: 令CR4.VMXE[bit 13] = 1,然后通过执行VMXON指令进入VMX操作。
  3. 检查内核中的VMX支持情况:  IA32_FEATURE_CONTROL MSR(MSR 地址 3AH)以查看 锁定位 是否已设置
  4. 分配VMXON区域: 物理地址并使用分配区域的指针执行VMXON指令,并且查询 MSR_IA32_VMX_BASIC。(Intel 手册:在执行 VMXON 之前,软件应将 VMCS 修订标识符写入 VMXON 区域。 具体来说,它应该将 31 位 VMCS 修订标识符写入 VMXON 区域前 4 个字节的位 30:0;位 31 应清除为 0。)
  5. 分配VMCS区域: VMPTRLD,处理器中可能同时存在多个 VMCS,但当前只有其中一个处于活动状态,并且 VMLAUNCH、VMREAD、VMRESUME 和 VMWRITE 指令仅在当前 VMCS 上运行。
  6. 终止VMX并释放我们之前分配的所有内存: VMOFF,并且释放分配的内存(将VMXONVMCS Region转换为虚拟地址

for example

阅读一下 Intel VT-x based hypervisor aiming to provide a thin VM-exit filtering platform on Windows

一个框架,而不是具体应用。因此可能缺少一点逻辑

DriverEntry

DriverEntry() 调用了 VmInitialization()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Checks if a VMM can be installed, and so, installs it
_Use_decl_annotations_ NTSTATUS VmInitialization() {
PAGED_CODE()

if (VmpIsHyperPlatformInstalled()) {
return STATUS_CANCELLED;
}

if (!VmpIsVmxAvailable()) {
return STATUS_HV_FEATURE_UNAVAILABLE;
}

const auto shared_data = VmpInitializeSharedData();
if (!shared_data) {
return STATUS_MEMORY_NOT_ALLOCATED;
}

// Read and store all MTRRs to set a correct memory type for EPT
EptInitializeMtrrEntries();

// Virtualize all processors
auto status = UtilForEachProcessor(VmpStartVm, shared_data);
if (!NT_SUCCESS(status)) {
UtilForEachProcessor(VmpStopVm, nullptr);
return status;
}
return status;
}

首先检查是否之前安装过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void __cpuid( 
int cpuInfo[4], // 包含在 EAX、EBX、ECX 和 EDX 中返回的有关 CPU 支持的功能的信息。
int function_id // 在 EAX 中传递的指定要检索的信息的代码
);

// Tests if HyperPlatform is already installed
_Use_decl_annotations_ static bool VmpIsHyperPlatformInstalled() {
PAGED_CODE()

int cpu_info[4] = {};
__cpuid(cpu_info, 1);
const CpuFeaturesEcx cpu_features = {static_cast<ULONG32>(cpu_info[2])};
if (!cpu_features.fields.not_used) {
return false;
}

__cpuid(cpu_info, kHyperVCpuidInterface);
return cpu_info[0] == 'PpyH';
}

检查CPU和操作系统支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// Checks if the system supports virtualization
_Use_decl_annotations_ static bool VmpIsVmxAvailable() {
PAGED_CODE()

// See: DISCOVERING SUPPORT FOR VMX
// If CPUID.1:ECX.VMX[bit 5]=1, then VMX operation is supported.
int cpu_info[4] = {};
__cpuid(cpu_info, 1);
const CpuFeaturesEcx cpu_features = {static_cast<ULONG32>(cpu_info[2])};
if (!cpu_features.fields.vmx) {
HYPERPLATFORM_LOG_ERROR("VMX features are not supported.");
return false;
}

// See: BASIC VMX INFORMATION
// The first processors to support VMX operation use the write-back type.
const Ia32VmxBasicMsr vmx_basic_msr = {UtilReadMsr64(Msr::kIa32VmxBasic)};
if (static_cast<memory_type>(vmx_basic_msr.fields.memory_type) !=
memory_type::kWriteBack) {
HYPERPLATFORM_LOG_ERROR("Write-back cache type is not supported.");
return false;
}

// See: ENABLING AND ENTERING VMX OPERATION
Ia32FeatureControlMsr vmx_feature_control = {
UtilReadMsr64(Msr::kIa32FeatureControl)};
if (!vmx_feature_control.fields.lock) {
HYPERPLATFORM_LOG_INFO("The lock bit is clear. Attempting to set 1.");
const auto status = UtilForEachProcessor(VmpSetLockBitCallback, nullptr);
if (!NT_SUCCESS(status)) {
return false;
}
}
if (!vmx_feature_control.fields.enable_vmxon) {
HYPERPLATFORM_LOG_ERROR("VMX features are not enabled.");
return false;
}

if (!EptIsEptAvailable()) {
HYPERPLATFORM_LOG_ERROR("EPT features are not fully supported.");
return false;
}
return true;
}
DriverUnload

调用VmTermination关闭VMM

1
2
3
4
5
6
7
8
9
10
11
12
13
// Terminates VM
_Use_decl_annotations_ void VmTermination() {
PAGED_CODE()

HYPERPLATFORM_LOG_INFO("Uninstalling VMM.");
auto status = UtilForEachProcessor(VmpStopVm, nullptr);
if (NT_SUCCESS(status)) {
HYPERPLATFORM_LOG_INFO("The VMM has been uninstalled.");
} else {
HYPERPLATFORM_LOG_WARN("The VMM has not been uninstalled (%08x).", status);
}
NT_ASSERT(!VmpIsHyperPlatformInstalled());
}

回调函数,调用VmpStopVm,使用Cr4 disable VMX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Stops virtualization through a hypercall and frees all related memory
_Use_decl_annotations_ static NTSTATUS VmpStopVm(void *context) {
UNREFERENCED_PARAMETER(context);
PAGED_CODE()

HYPERPLATFORM_LOG_INFO("Terminating VMX for the processor %lu.",
KeGetCurrentProcessorNumberEx(nullptr));

// Stop virtualization and get an address of the management structure
ProcessorData *processor_data = nullptr;
auto status = UtilVmCall(HypercallNumber::kTerminateVmm, &processor_data);
if (!NT_SUCCESS(status)) {
return status;
}

// Clear CR4.VMXE, as there is no reason to leave the bit after vmxoff
Cr4 cr4 = {__readcr4()};
cr4.fields.vmxe = false;
__writecr4(cr4.all);

VmpFreeProcessorData(processor_data);
return STATUS_SUCCESS;
}

UtilVmCall调用AsmVmxCall,最后是如下

1
2
3
4
5
6
7
8
; unsigned char __stdcall AsmVmxCall(_In_ ULONG_PTR hypercall_number,
; _In_opt_ void *context);
AsmVmxCall PROC
vmcall ; vmcall(hypercall_number, context)
jz errorWithCode ; if (ZF) jmp
jc errorWithoutCode ; if (CF) jmp
xor rax, rax ; return VMX_OK
ret
VMM && VM

核心功能

VMM是host主要负责的功能,各种行为的handler,处理vmcall

  • ring -1 掌控着真正的硬件资源

vmcall/hypercall

  • 类似syscall,但是guest OS
  • vmcall 会 vmexit 进入host,因此我们需要相应的Exit Handler来处理

VM Exit: 存在一个reason,我们需要进行分别的处理

1
2
3
4
5
switch (exit_reason.fields.reason) {
case VmxExitReason::kExceptionOrNmi:
VmmpHandleException(guest_context);
break;
...

VM是guest OS,类似一个操作系统的启动流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 初始化VM
VmpInitializeVm()
VmpEnterVmxMode()
__vmx_on()
VmpInitializeVmcs() // 初始化 VMCS
__vmx_clear()
__vmx_vmptrld()
VmpSetupVmcs() // VMCS 成员
_sgdt()
_sidt()
UtilVmWrite()
VmpLaunchVm() // 启动
UtilVmRead()
__vmx_off();

EPT

内存虚拟化

Shadow paging,不需要硬件支持

  • 直接将 gva -> hpa
  • 运行guest时,切换 host CR3 到 shadow paging (hypervisor 拦截 guest 对 CR3的修改

EPT, nested paging, 嵌套页表

  • guest操作系统维护一张页表,用于生成guest的物理地址。CR3/EPTP
  • 另一个页表由 VMM 维护,它将 guest 的物理地址映射到 host 的物理地址。

Windows 四级页表:9-9-9-9-12 (1)

  • PML4T: page map level4 table
  • PDPT: page directory pointer table
  • PD: page directory
  • PT: page table
  • entry + offset

Physical Address Extension: PAE物理地址扩展,处理器功能,使 x86 处理器能够在支持访问超过 4 GB 的物理内存。
Page Size Extension: PSE,是在IA32架构中,实现大于传统的4KB的页面

AMD-V

AMD Developer Documentation中搜索AMD64 Architecture Programmer’s Manual Volume 2: System Programming,第15章

因为笔者的机器是AMD CPU,因此主要学习AMD-V。下一篇文章😋

More

不错的项目文章:Hypervisor Development in Rust Part 1 - memN0ps

Cr8:Irql权限等级。CPU的当前优先级。当中断挂起时,将中断向量号的7:4位与CR8进行比较。如果向量更大,它将被服务,否则它将被挂起,直到CR8被设置为较低的值

Windows hype-V 可以开启嵌套虚拟化。

kAFL: 借助Intel PT 和 VT-x 的硬件反馈fuzzer

  • PT: Processor tracer, 跟踪信息

Ring -1: Intel VT