当前位置: 代码迷 >> 综合 >> HYPER-CUBE: High-Dimensional Hypervisor Fuzzing 论文阅读笔记
  详细解决方案

HYPER-CUBE: High-Dimensional Hypervisor Fuzzing 论文阅读笔记

热度:41   发布时间:2023-12-08 00:14:06.0

这是一篇对Hypervisor进行fuzzing的文章,作者来自于德国的波鸿鲁尔大学,是一个安全研究实力很强的课题组,且研究方向很广。

虚拟机监控器VMM也叫Hypervisor是现代软件栈至关重要的一部分,如果让VMM被攻击了可能对整个云基础设施产生问题。作者提出了HyperCube这样一个fuzzer,目的是测试hypervisor,它是基于一个自己开发的操作系统与字节码解释器做的,对比于一维的像AFL这种fuzzerstdinsyscall),hypercube能够和任意数目的interface进行交互。

目前只有很少的fuzzing工作是针对hypervisor做的,主要原因有两个,第一是对hypervisor进行fuzzing需要对不同接口进行fuzz,第二是针对hypervisor进行fuzzing会引入额外的overhead,比如feedback-drivenfuzzing常常会在每轮迭代后重启目标应用,但是重启hypervisor时一件运行时开销很大的事情。

下面介绍两个最新的hypervisor fuzzing工具,第一个是VDF,基于AFL做的,只能和MMIO端口进行交互,第二个是IOFUZZ,只能通过像IO端口写入数据来fuzzing

为了对Hypervisor进行fuzzing,有三个目标:

1.高效的产生测试用例test case;

2.能同时够对多维度的接口interface进行fuzzing

3.能够进行稳定(stable)且确定性(deterministic)的测试。

下面介绍一些背景,因为这篇文章涉及到了一些体系结构的概念,首先说一下操作系统的启动过程,第一步在开机后UEFI或者BIOS会初始化硬件,包括CPU、主存,接着执行Bootloader,比如GRUB,然后bootloader会调用os的内核代码,这个内核代码会配置剩余的硬件,包括中断控制器、PCI接口的设备等。

X86系统上,数据的输入输出主要有三种:

1.Port I/O,这种会在主存之外开辟一个地址空间,用来存放端口地址,这个空间只能用IN/OUT指令来访问;

2.MMIO,这种方式是设备对应的内存区域和主存放在一起,这种方式更灵活,且带宽更高;

3.最后一种是DMA,与MMIO类似,不过设备可以决定向内存写入或者读取的区域与时间,而不是通过CPU来决定;

最后来介绍一下Hypervisorhypervisor是一个优先级很高的软件,它用来给虚拟机提供物理硬件资源,向内存、虚拟CPU之类的,虚拟机是没有权限去控制或者访问物理硬件设备的;hypervisor与虚拟机之间的切换主要通过trap and emulate来时先,当虚拟机要执行优先级比较高的操作时,例如访问一个真实硬件,他会触发一个trap然后引起vm-exit,然后vm-exit会把当前控制权转移给hypervisor然后进行硬件的模拟操作,最后返回给虚拟机。

HypervisorCPU和内存进行虚拟化主要用的技术是二进制翻译,然后会采用一些硬件支持的技术来降低overhead,比如Intel VTxAMD-v,然后本文只考虑Intel VT-x,在硬件仿真这个过程,最关键的步骤是去拦截VM对硬件的交互请求,主要通过在MMIOPort I/O这两部分来处理,对于MMIO这部分的设备,通过把MMIO的页设置为不可访问的,这样当VM访问到的时候会触发中断,Port I/O则扩展IN/OUT指令来引发中断。

下面来看一下作者提出的工具hypercube的架构,这是架构图,主要有三个部分Hypercube OSTesseract和一些外部工具。

想要设计一款能够对Hypervisor进行高效fuzzing的工具,有三个需要完成的目标,首先fuzzer的性能要比较高,接着要能够兼容不同的hypervisor和对不同的接口进行交互,最后是整个fuzzing的过程是要稳定且具备确定性的。

接下来说一下HyperCube是如何完成这三点的,首先第一个点高性能的fuzzing

1.操作系统系统比较小,这样的设计可以在每一次产生crash后快速地重新load系统来提升fuzzing的效率,节约启动时间;

2.解释执行对比编译执行省时间;

解释器TESSERACT会把任何输入的字节流转换成对hypervisor的交互序列,在这个过程中要对输入的任意字节流进行encoding来确保不会有不可用的指令,主要做法就是对产生的数据去模该数据的范围,把字符流转换成解释器对应的opcode与参数,比如这个字节流要选择opcode就指令集个数。要注意的是解释器不会对于内存地址不会用指针来表示,而是用一个二元组<region_id, offset>来表示。作者称通过设置解释器的指令,即便是完全随机的数据产生的指令序列,也可以对多个interface进行交互,进行高维度的fuzzing

Linuxwindows这种系统的内核是脆弱的,通过重写内存的方式对一些必要硬件,如中断控制器这类进行重新配置很容易导致内核崩溃,但是作者认为通过自己实现OS和解释器的方式,可以避免产生这种情况,另外也可以通过控制页表的方式,将每一次测试的地址固定下来,这样如果固定payload几乎可以完全复现结果,只会有时间上的微小差异。

下面讲一下各个模块的具体实现细节,首先说一下OS模块,主要任务:内存管理与设备枚举,主要有两种内存需要被管理,一个是OS与解释器,另一个是当和接口进行操作的时候需要去访问物理内存,硬件枚举是当OS启动完成后,需要去枚举可以用于fuzzing的不同接口。

内存管理这里用了一个简单的堆,在需要的时候来分配内存,因为OS和解释器所占用的内存很少,所以每次分配一个page,大小为4KB。在大多数场景下,使用虚拟地址到物理地址映射的方法访问内存即可,但是有些任务仍然需要直接访问物理地址,比如页表自身的描述需要用到物理地址。OS通过维护一个虚拟地址与物理地址相同的区域来完成这个功能,在0x0-0x100000这个区域内。对于直接分配一个page的做法,作者称不会造成因为分配内存太小而造成需要分配次数太多导致overhead太高的情况,目前观测到平均每次fuzzing需要分配150次左右。

硬件设备枚举主要有三类:

第一类是核心组件,如APICHPET,可以通过去解析ACPI Table来获得它们的相关信息,ACPI会从Bootloader的启动过程传递过来;

第二类是PCIPCIe接口的设备,它们的相关信息会被草存在ECAM中,而ECAM的地址会保存在名为MCFGACPI表中,另外还会扫描PCI总线来检查是否还有别的PCI设备;

第三类是IO Port的设备,比如VGA接口,这一类的做法比较Na?ve,通过主动扫描2^16个这类端口来确认,向相应端口写入数据,如果状态发生改变则认为有设备,但是并不是所有端口都是可写的,但是作者发现只有不到10%的端口会出现不可写的情况。

接下来还剩一些解释器和外部工具的介绍,比较简单,本文不再赘述,可以参考上交Gossip小组的总结。

 

  相关解决方案