作者:林以
公众号:支付宝安全实验室

Oracle在北京时间4月15日发布了本年度第二次的Critical Patch Update Advisory,并在最新版VirtualBox(6.1.6)中修复了支付宝光年安全实验室@鹜望与@林以提交的USB XHCI模块的堆越界读写漏洞。这篇文章将对此漏洞进行分析。

VirtualBox USB XHCI漏洞模块介绍

VirtualBox是一款虚拟化软件,由于其开源属性,在个人用户中盛行。VirtualBox大部分模块是开源的,但是部分模块是以二进制插件包的形式发布,包括了,USB 2.0与 3.0虚拟设备,VirtualBox RDP协议模块,硬盘加密,NVMe以及用于 Intel的 PXE引导部分。CVE-2020-2905 漏洞存在于 VirtualBox二进制插件包中的 USB 3.0 XHCI模块。USB协议目前有三代接口标准,OHCI、UHCI都是 USB1.1的接口标准,而 EHCI是对应 USB2.0的接口标准,最新的 xHCI是 USB3.0的接口标准。在这里我们只需关注 XHCI协议的数据结构。

漏洞分析

漏洞简介

该漏洞存在于二进制插件包 VBoxEhciR3.so中,VBoxEhciR3.so包括了 Ehci以及 Xhci的实现。VirtualBox在 6.1版本之后将插件包中的二进制的符号都给 strip了,这里我们以 6.0.14带符号版本进行分析。复现环境中宿主机与虚拟机均为 Ubuntu,宿主机(Host)版本:

虚拟机(Guest)

漏洞成因

该漏洞是个数组越界读写,存在于 xhciR3WriteEvent函数, iIntr 参数可以被攻击者控制。从IDA Pro的 Structures段可以得知 aInterrupters 数组只有8个元素(下图所示),但是iIntr的值却可以是0~1023。iIntr用来对 aInterrupters数组进行索引,但代码未对iIntr值进行校验,因此造成了堆溢出。

逆向分析回溯调用流,可发现,xhciR3WriteEvent的调用路径是:

xhciR3WorkerLoop->xhciR3PostXferEvent->xhciR3WriteEvent

具体而言,当xhci模块正常启动时,它会调用xhciR3WorkerLoop函数。在该函数中,宿主机的xhci驱动会与虚拟机通过xhci命令进行通信。宿主机会等待xhci相应的物理寄存器的读写来进行响应。后面的文章我们将通过理清XHCI协议的相应数据结构来进行分析。

XHCI协议数据结构分析

虚拟机通过读写宿主机的物理地址来与宿主机响应驱动进行通信。通过在虚拟机中执行cat/proc/iomem命令,我们可以得知宿主机相应的驱动与其映射出来的物理地址,如下我们可知,xhci驱动对应的物理地址范围是0xf1810000-0xf181ffff,0x10000个字节大小。

而xhci驱动会将物理地址转换成xhci协议中的寄存器,当我们在虚拟机对物理地址进行读写时,其实就是对xhci协议的相应寄存器进行读写,读者可查阅XHC Ispecification[1]来对xhci协议进行深入了解。

该漏洞的触发需要CRCR以及DCBAAP两个寄存器的配合:

CRCR寄存器

CRCR(Command Ring Control)寄存器存储的内容用于对xhchi驱动进行控制,存储着dequeue pointer,指针指向trb ring,trb ring是个数组存储着trb结构体。对应到CRCR寄存器,存储的是Command TRBs。Command TRBs包含了17个commands用来对宿主机xhci驱动发起命令,而xhciR3WorkerLoop函数就是在轮询处理Command TRBs的command,包括了Enable Slot Command,Address Device Command,Stop Endpoint Command等commands,在specification 6.4.3节中定义。

这17个commands中,我们需要关注Stop Endpoint Command,xhciR3WorkerLoop就是在处理Stop Endpoint Commahnd中触发了漏洞。我们就以Stop Endpoint Command TRB为例子来介绍TRB数据结构。一个TRB数据长128bits,在Stop Endpoint Command TRB中,前12个字节都是保留使用的,最后4个字节才有意义。其中TRB Type字段就是表明该TRB的类型,如果我们要发送Stop Endpoint Command,我们就要将该字段设为对应的值,Stop Endpoint Command对应值15。Slot ID最大值为32,即XHCI中最多拥有32个slot,slot的定义为:“Device Slot refers to the xHC interface associated with an individual USB device,e.g. the associated Device Context Base Address Array entry,a Doorbell Array register,and its Device Context.”

DCBAAP寄存器

DCBAAP(Device Context Base Address Array Pointer Register)存储着指向Device Context Base Address Array的指针,该指针通过slot id来进行索引。而Device Context Base Address Array数组存储着Device Context Data Structure数据结构,Device Context Data Structure共有 32个入口,第一个入口是Slot Context Data Structure,其余的入口是Endpoint Context Data Structure。

漏洞具体触发方式

回到xhciR3WorkerLoop函数:

攻击者需要首先调用CR_ENABLE_SLOT命令来开启对应的slot,不然后续处理的时候遇到对应的slot没开启,程序会直接返回,在这里我们可以将所有32个 slot都开启。

在CR_STOP_ENDPOINT命令处理代码中,v134即slot id,通过v37 = xhciR3FetchDevCtxAddr(v2, v134);将对应slot id的Device Context Base Address Array的地址取出存到v37中,随后在pfnPCIPhysRead调用中读取 32字节到 icc_0变量中,由上文的XHCI协议数据结构分析中的Device Context Base Address Array结构分析可知,前32个字节应该是slot context数据结构。

随后,驱动会调用xhciR3PostXferEvent函数,该函数的第二个参数为(unsigned __int16)(HIWORD(icc_0.resvd[0]) >> 6),即为被控制的icc_0中的数据。

参数(unsigned __int16)(HIWORD(icc_0.resvd[0]) >> 6)会传至uIntTgt变量,最后会传到xhciR3WriteEvent函数的iIntr参数,实现控制aInterrupters中的index的值。

由上述分析可知icc_0中存储着slot context结构,我们需要分析出 (unsigned __int16)(HIWORD(icc_0.resvd[0]) >> 6)对应的 slot context中的字段,以此来控制iIntr。查看 IDA Pro中 icc_0 结构体的定义,我们可以得知 (unsigned __int16)(HIWORD(icc_0.resvd[0]) >> 6)是Slot Context Data Structure中的Interrupter Target字段。该字段有10bits长,值范围是0~1023,意味着我们可以越界写1023个元素长。

总结而言,分析清楚这些数据结构对应的关系之后,我们就可以构造poc来触发该漏洞了,还有一些校验字段由于篇幅原因略去分析了,读者可以根据 specification以及 VirtualBox代码的实现来bypass对应的校验。

补丁分析

VirtualBox在6.1.6版本修复了该漏洞,查看补丁,漏洞修复很简单,将iIntr值限定在0~7中。

可利用性分析

攻击者可以进行越界写,比如在函数xhciR3WriteEvent中变量v4是被攻击者控制的,下面代码会将v4 的值写入v7->errp中,v7即是从aInterrupters数组中取出的越界的地址。

攻击者通过内存布局,将代码中的关键数据结构布局到越界写的数据内容之后,可覆盖关键数据结构的值,从而造成虚拟机逃逸。

引用

关于作者

支付宝光年安全实验室: 隶属于支付宝安全实验室。通过对基础软件及设备的安全研究,达到全球顶尖破解能力,致力于保障蚂蚁金服及行业金融级基础设施安全。因发现并报告行业系统漏洞,数十次获得Google、Apple等国际厂商致谢。

扫码关注支付宝安全实验室微信公众号,干货不断!


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1188/