Pwn2Own 2020 利用 Oracle VirtualBox 网卡、USB 设备驱动实现虚拟机逃逸的细节 - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

Pwn2Own 2020 利用 Oracle VirtualBox 网卡、USB 设备驱动实现虚拟机逃逸的细节

h1apwn 技术 2020-11-24 09:38:08
653033
收藏

导语:本文将介绍在Pwn2Own 2020中的Oracle VirtualBox逃逸漏洞,这两个漏洞会影响Oracle VirtualBox 6.1.4和更低的版本。

本文将介绍在Pwn2Own 2020中的Oracle VirtualBox逃逸漏洞,这两个漏洞会影响Oracle VirtualBox 6.1.4和更低的版本。

0x01 漏洞分析

漏洞利用链包括2个漏洞:

· 英特尔PRO 1000 MT桌面(E1000)网络适配器-越界读取漏洞

https://www.zerodayinitiative.com/advisories/ZDI-20-581/

· 开放主机控制器接口(OHCI)USB控制器-未初始化变量漏洞

https://www.zerodayinitiative.com/advisories/ZDI-20-582/

1.E1000越界读取漏洞

有关E1000网络适配器内部工作的更多信息,可以在此处阅读有关信息。

https://github.com/hongphipham95/Vulnerabilities/blob/master/VirtualBox/Oracle VirtualBox Intel PRO 1000 MT Desktop - Integer Underflow Vulnerability/Oracle VirtualBox Intel PRO 1000 MT Desktop - Integer Underflow Vulnerability.md

使用E1000网络适配器发送以太网帧时,我们可以通过设置IXSM数据描述符选项字段中的位来控制IP校验和的插入:

// VirtualBox-6.1.4\src\VBox\Devices\Network\DevE1000.cpp:5191
static bool e1kLocateTxPacket(PE1KSTATE pThis)
{
    ...    
    E1KTXDESC *pDesc = &pThis->aTxDescriptors[i];
    switch (e1kGetDescType(pDesc))
    {
    ...                
        case E1K_DTYP_DATA:
     ...                
            if (cbPacket == 0)
            {
                /*
                 * The first fragment: save IXSM and TXSM options
                 * as these are only valid in the first fragment.
                 */
                pThis->fIPcsum  = pDesc->data.dw3.fIXSM;
                pThis->fTCPcsum = pDesc->data.dw3.fTXSM;
                        fTSE     = pDesc->data.cmd.fTSE;
     ...                    
}

随着pThis->fIPcsum有效标记,IP校验将被插入到以太网帧:

// VirtualBox-6.1.4\src\VBox\Devices\Network\DevE1000.cpp:4997
static int e1kXmitDesc(PPDMDEVINS pDevIns, PE1KSTATE pThis, PE1KSTATECC pThisCC, E1KTXDESC *pDesc,
                       RTGCPHYS addr, bool fOnWorkerThread)
{
    ...
    switch (e1kGetDescType(pDesc))
    {
        ...            
        case E1K_DTYP_DATA:
        {
            STAM_COUNTER_INC(pDesc->data.cmd.fTSE?
                             &pThis->StatTxDescTSEData:
                             &pThis->StatTxDescData);
            E1K_INC_ISTAT_CNT(pThis->uStatDescDat);
            STAM_PROFILE_ADV_START(&pThis->CTX_SUFF_Z(StatTransmit), a);
            if (pDesc->data.cmd.u20DTALEN == 0 || pDesc->data.u64BufAddr == 0)
            {
         ...                
            }
            else
            {
         ...                
                else if (!pDesc->data.cmd.fTSE)
                {
                    ...
                    if (pThis->fIPcsum)
                        e1kInsertChecksum(pThis, (uint8_t *)pThisCC->CTX_SUFF(pTxSg)->aSegs[0].pvSeg, pThis->u16TxPktLen,
                                          pThis->contextNormal.ip.u8CSO,
                                          pThis->contextNormal.ip.u8CSS,
                                          pThis->contextNormal.ip.u16CSE);

函数e1kInsertChecksum()将计算校验和并将其放入框架中,u8CSO,u8CSS以及u16CSE中pThis->contextNormal可以通过上下文描述符指定:

// VirtualBox-6.1.4\src\VBox\Devices\Network\DevE1000.cpp:5158
DECLINLINE(void) e1kUpdateTxContext(PE1KSTATE pThis, E1KTXDESC *pDesc)
{
    if (pDesc->context.dw2.fTSE)
    {
        ...        
    }
    else
    {
        pThis->contextNormal = pDesc->context;
        STAM_COUNTER_INC(&pThis->StatTxDescCtxNormal);
    }
...    
}

执行函数e1kInsertChecksum():

// VirtualBox-6.1.4\src\VBox\Devices\Network\DevE1000.cpp:4155
static void e1kInsertChecksum(PE1KSTATE pThis, uint8_t *pPkt, uint16_t u16PktLen, uint8_t cso, uint8_t css, uint16_t cse)
{
    RT_NOREF1(pThis);

    if (css >= u16PktLen)       // [1]
    {
        E1kLog2(("%s css(%X) is greater than packet length-1(%X), checksum is not inserted\n",
                 pThis->szPrf, cso, u16PktLen));
        return;
    }

    if (cso >= u16PktLen - 1)      // [2]
    {
        E1kLog2(("%s cso(%X) is greater than packet length-2(%X), checksum is not inserted\n",
                 pThis->szPrf, cso, u16PktLen));
        return;
    }

    if (cse == 0)         // [3]
        cse = u16PktLen - 1;
    else if (cse < css)        // [4]
    {
        E1kLog2(("%s css(%X) is greater than cse(%X), checksum is not inserted\n",
                 pThis->szPrf, css, cse));
        return;
    }

    uint16_t u16ChkSum = e1kCSum16(pPkt + css, cse - css + 1);
    E1kLog2(("%s Inserting csum: %04X at %02X, old value: %04X\n", pThis->szPrf,
             u16ChkSum, cso, *(uint16_t*)(pPkt + cso)));
    *(uint16_t*)(pPkt + cso) = u16ChkSum;
}

· css是要开始计算校验和的数据包中的偏移量,它必须小于u16PktLen当前数据包的总大小(check [1])。

· cse是数据包中的偏移量,用于停止计算校验和。

     · 将cse字段设置为0表示校验和将覆盖css到数据包的末尾(校验[3])。

     · cse需要大于css(检查[4])。

· cso是要写入校验和的数据包中的偏移量,它必须小于u16PktLen - 1(check [2])。

由于不检查cse的最大值,我们可以将此字段设置为大于当前数据包的总大小,从而导致越界访问,并导致e1kCSum16()在数据包之后立即计算数据的校验和pPkt。

“ overread”校验和将被插入以太网帧中,稍后可以由接收器读取。

信息泄漏利用

因此,如果我们想从过校验和中泄漏一些信息,我们需要一种可靠的方法来知道哪些数据与过缓冲器相邻。在仿真的E1000设备中,发送缓冲区由以下e1kXmitAllocBuf()函数分配:

// VirtualBox-6.1.4\src\VBox\Devices\Network\DevE1000.cpp:3833
DECLINLINE(int) e1kXmitAllocBuf(PE1KSTATE pThis, PE1KSTATECC pThisCC, bool fGso)
{
    ...    
    PPDMSCATTERGATHER pSg;
    if (RT_LIKELY(GET_BITS(RCTL, LBM) != RCTL_LBM_TCVR))   // [1]
    {
        ...        
        int rc = pDrv->pfnAllocBuf(pDrv, pThis->cbTxAlloc, fGso ? &pThis->GsoCtx : NULL, &pSg);
        ...        
    }
    else
    {
        /* Create a loopback using the fallback buffer and preallocated SG. */
        AssertCompileMemberSize(E1KSTATE, uTxFallback.Sg, 8 * sizeof(size_t));
        pSg = &pThis->uTxFallback.Sg;
        pSg->fFlags      = PDMSCATTERGATHER_FLAGS_MAGIC | PDMSCATTERGATHER_FLAGS_OWNER_3;
        pSg->cbUsed      = 0;
        pSg->cbAvailable = sizeof(pThis->aTxPacketFallback);
        pSg->pvAllocator = pThis;
        pSg->pvUser      = NULL; /* No GSO here. */
        pSg->cSegs       = 1;
        pSg->aSegs[0].pvSeg = pThis->aTxPacketFallback;    // [2]    
        pSg->aSegs[0].cbSeg = sizeof(pThis->aTxPacketFallback);
    }
    pThis->cbTxAlloc = 0;

    pThisCC->CTX_SUFF(pTxSg) = pSg;
    return VINF_SUCCESS;
}

寄存器中的LBM(环回模式)字段RCTL控制以太网控制器的环回模式,它会影响数据包缓冲区的分配方式(请参阅参考资料[1]):

· 没有回送模式:e1kXmitAllocBuf()使用pDrv->pfnAllocBuf()回调分配数据包缓冲区,此回调将使用OS分配器或VirtualBox的自定义分配器。

· 使用回送模式:数据包缓冲区是aTxPacketFallback数组(请参阅参考资料[2])。

aTxPacketFallback是一个PE1KSTATE pThis属性对象:

// VirtualBox-6.1.4\src\VBox\Devices\Network\DevE1000.cpp:1024
typedef struct E1KSTATE
{
    ...
    /** TX: Transmit packet buffer use for TSE fallback and loopback. */
    uint8_t     aTxPacketFallback[E1K_MAX_TX_PKT_SIZE];
    /** TX: Number of bytes assembled in TX packet buffer. */
    uint16_t    u16TxPktLen;
    ...    
} E1KSTATE;

/* Pointer to the E1000 device state. */
typedef E1KSTATE *PE1KSTATE;

因此,通过启用环回模式:

· 数据包的接收者是我们,我们不需要其他主机来读取校验和

· 数据包缓冲区驻留在pThis结构中,因此,数据是pThis对象的其他字段

现在我们知道哪些数据与数据包缓冲区相邻,我们可以通过以下步骤逐字泄漏:

· 发送包含E1K_MAX_TX_PKT_SIZE字节的CRC-16校验和的帧,将其称为crc0。

· 发送包含E1K_MAX_TX_PKT_SIZE + 2字节校验和的第二帧,将其称为crc1。

· 由于校验算法是CRC-16,通过计算之间的区别crc0和crc1,我们会知道的右后两个值的字节aTxPacketFallback数组。

每次都将过读大小增加2个字节,直到获得一些有趣的数据为止。幸运的是,在pThis对象之后,我们可以在offset处找到指向全局变量VBoxDD.dll的指针E1K_MAX_TX_PKT_SIZE + 0x1f7。

在pThis对象之后,aTxPacketFallback数组之后,每发送一帧,其他设备的计数器寄存器就会不断增加,因此,如果我们发送两个具有相同大小的帧,也会导致两个不同的校验和,但是每次计数器的增量都是相似的,因此这种差异是可以预测的,可以通过加到0x5a第二个校验和上来使之相等。

2.OHCI控制器未初始化的变量漏洞

可以在此处阅读有关VirtualBox OHCI设备的更多信息。

https://github.com/hongphipham95/Vulnerabilities/blob/master/VirtualBox/Oracle VirtualBox OHCI Use-After-Free Vulnerability/Oracle VirtualBox OHCI Use-After-Free.md

在向USB设备发送控制消息URB时,我们可以包含一个设置包来更新URB消息:

// VirtualBox-6.1.4\src\VBox\Devices\USB\VUSBUrb.cpp:834
static int vusbUrbSubmitCtrl(PVUSBURB pUrb)
{
    ...    
    if (pUrb->enmDir == VUSBDIRECTION_SETUP)
    {
        LogFlow(("%s: vusbUrbSubmitCtrl: pPipe=%p state %s->SETUP\n",
                 pUrb->pszDesc, pPipe, g_apszCtlStates[pExtra->enmStage]));
        pExtra->enmStage = CTLSTAGE_SETUP;
    }

    ...    

    switch (pExtra->enmStage)
    {
        case CTLSTAGE_SETUP:
            ...            
            if (!vusbMsgSetup(pPipe, pUrb->abData, pUrb->cbData))
            {
                pUrb->enmState = VUSBURBSTATE_REAPED;
                pUrb->enmStatus = VUSBSTATUS_DNR;
                vusbUrbCompletionRh(pUrb);
                break;
// VirtualBox-6.1.4\src\VBox\Devices\USB\VUSBUrb.cpp:664
static bool vusbMsgSetup(PVUSBPIPE pPipe, const void *pvBuf, uint32_t cbBuf)
{
    PVUSBCTRLEXTRA  pExtra = pPipe->pCtrl;
    const VUSBSETUP *pSetupIn = (PVUSBSETUP)pvBuf;

        ...

    if (pExtra->cbMax < cbBuf + pSetupIn->wLength + sizeof(VUSBURBVUSBINT))  // [1]
    {
        uint32_t cbReq = RT_ALIGN_32(cbBuf + pSetupIn->wLength + sizeof(VUSBURBVUSBINT), 1024);
        PVUSBCTRLEXTRA pNew = (PVUSBCTRLEXTRA)RTMemRealloc(pExtra, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq]));       // [2]
        if (!pNew)
        {
            Log(("vusbMsgSetup: out of memory!!! cbReq=%u %zu\n",
                 cbReq, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq])));
            return false;
        }
        if (pExtra != pNew)
        {
            pNew->pMsg = (PVUSBSETUP)pNew->Urb.abData;
            pExtra = pNew;
            pPipe->pCtrl = pExtra;
        }
        pExtra->Urb.pVUsb = (PVUSBURBVUSB)&pExtra->Urb.abData[cbBuf + pSetupIn->wLength]; // [3]
        pExtra->Urb.pVUsb->pUrb = &pExtra->Urb;            // [4]
        pExtra->cbMax = cbReq;
    }
    Assert(pExtra->Urb.enmState == VUSBURBSTATE_ALLOCATED);

    /*
     * Copy the setup data and prepare for data.
     */
    PVUSBSETUP pSetup = pExtra->pMsg;
    pExtra->fSubmitted      = false;
    pExtra->Urb.enmState    = VUSBURBSTATE_IN_FLIGHT;
    pExtra->pbCur           = (uint8_t *)(pSetup + 1);
    pSetup->bmRequestType   = pSetupIn->bmRequestType;
    pSetup->bRequest        = pSetupIn->bRequest;
    pSetup->wValue          = RT_LE2H_U16(pSetupIn->wValue);
    pSetup->wIndex          = RT_LE2H_U16(pSetupIn->wIndex);
    pSetup->wLength         = RT_LE2H_U16(pSetupIn->wLength);

      ...
    
    return true;
}

pSetupIn是我们的URB数据包,pExtra是控制管道的当前额外数据,如果设置请求的大小大于当前控制管道的额外数据(检查[1])的大小,则会在pExtra处以更大的大小重新分配[2]。

原始文件pExtra已在vusbMsgAllocExtraData()以下位置分配和初始化:

// VirtualBox-6.1.4\src\VBox\Devices\USB\VUSBUrb.cpp:609
static PVUSBCTRLEXTRA vusbMsgAllocExtraData(PVUSBURB pUrb)
{
/** @todo reuse these? */
    PVUSBCTRLEXTRA pExtra;
    const size_t cbMax = sizeof(VUSBURBVUSBINT) + sizeof(pExtra->Urb.abData) + sizeof(VUSBSETUP);
    pExtra = (PVUSBCTRLEXTRA)RTMemAllocZ(RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbMax]));
    if (pExtra)
    {
        ...        
        pExtra->Urb.pVUsb = (PVUSBURBVUSB)&pExtra->Urb.abData[sizeof(pExtra->Urb.abData) + sizeof(VUSBSETUP)];
        //pExtra->Urb.pVUsb->pCtrlUrb = NULL;
        //pExtra->Urb.pVUsb->pNext = NULL;
        //pExtra->Urb.pVUsb->ppPrev = NULL;
        pExtra->Urb.pVUsb->pUrb = &pExtra->Urb;
        pExtra->Urb.pVUsb->pDev = pUrb->pVUsb->pDev;  // [5]
        pExtra->Urb.pVUsb->pfnFree = vusbMsgFreeUrb;
        pExtra->Urb.pVUsb->pvFreeCtx = &pExtra->Urb;
        ...        
    }
    return pExtra;
}

函数RTMemRealloc()不执行任何初始化,因此生成的缓冲区将包含两部分:

· A部分:pExtra。

· B部分:新分配的未初始化数据。

重新分配后:

· pExtra->Urb.pVUsb对象将用新的更新pVUsb,它驻留在B部分(在[3])

· 但是新内容pVUsb驻留在未初始化的数据中,并且仅pVUsb->pUrb在更新[4],

因此,pExtra->Urb.pVUsb对象的其他属性保持未初始化,包括pExtra->Urb.pVUsb->pDev对象(请参阅参考资料[5])。

pExtra->Urb对象将在以后的vusbMsgDoTransfer()函数中使用:

// VirtualBox-6.1.4\src\VBox\Devices\USB\VUSBUrb.cpp:752
static void vusbMsgDoTransfer(PVUSBURB pUrb, PVUSBSETUP pSetup, PVUSBCTRLEXTRA pExtra, PVUSBPIPE pPipe)
{
    ...    
    int rc = vusbUrbQueueAsyncRh(&pExtra->Urb);
    ...    
}
// VirtualBox-6.1.4\src\VBox\Devices\USB\VUSBUrb.cpp:439
int vusbUrbQueueAsyncRh(PVUSBURB pUrb)
{
    ...    
    PVUSBDEV pDev = pUrb->pVUsb->pDev;
    ...    
    int rc = pDev->pUsbIns->pReg->pfnUrbQueue(pDev->pUsbIns, pUrb);
    ...    
}

当VM主机进程取消引用未初始化的时,将发生pDev访问冲突。

为了利用未初始化的对象,我们可以在重新分配之前执行堆喷,然后希望该pDev对象已驻留在我们的数据中。

由于存在虚表调用,而VirtualBox尚未通过CFG缓解,因此我们可以将漏洞和伪造pDev对象的堆喷射结合起来,以控制主机进程的指令指针(RIP)。

代码执行利用

我们之前的文章描述了如何执行堆喷涂以在主机进程中获取VRAM缓冲区的地址范围,我们将在此范围内选择一个地址作为伪造的pDEv指针。

https://starlabs.sg/blog/2020/04/adventures-in-hypervisor-oracle-virtualbox-research/

然后,完整的利用流程将如下所示:

· VBoxDD.dll使用E1000漏洞泄漏模块基地址,然后收集一些ROP gadget

· 我们伪造的pDEv指针指向VRAM中的某个地方,因此我们在VRAM中喷射了多个块,每个块包含:

     · PVUSBDEV使用包含堆栈gadget的假vtable对齐对象,以将堆栈指针指向主机的VRAM缓冲区

     · 包含WinExecROP链的假堆栈

· 堆喷,用我们选择的VRAM地址填充未初始化的内存,这将使pExtra->Urb.pVUsb->pDev对象指向伪造的PVUSBDEV对象之一。

· 触发OHCI漏洞,进而执行ROP链

0x02 漏洞补丁

· https://www.virtualbox.org/changeset/83613/vbox/trunk/src/VBox/Devices/Network/DevE1000.cpp

trunk/src/VBox/Devices/Network/DevE1000.cpp
r82968 r83613  
4171 4171     } 
4172 4172  
4173       if (cse == 0) 
  4173     if (cse == 0 || cse >= u16PktLen) 
4174 4174         cse = u16PktLen - 1; 
4175 4175     else if (cse < css)

· https://www.virtualbox.org/changeset/83617/vbox/trunk/src/VBox/Devices/USB/VUSBUrb.cpp

trunk/src/VBox/Devices/USB/VUSBUrb.cpp
r83592 r83617  
703 703     if (pExtra->cbMax < cbBuf + pSetupIn->wLength + sizeof(VUSBURBVUSBINT)) 
704 704     { 
  705 #if 1 
  706         LogRelMax(10, ("VUSB: Control URB too large (wLength=%u)!\n", pSetupIn->wLength)); 
  707         return false; 
  708 #else 
705 709         uint32_t cbReq = RT_ALIGN_32(cbBuf + pSetupIn->wLength + sizeof(VUSBURBVUSBINT), 1024); 
706 710         PVUSBCTRLEXTRA pNew = (PVUSBCTRLEXTRA)RTMemRealloc(pExtra, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq])); 
… …  
717 721             pPipe->pCtrl = pExtra; 
718 722         } 
  723  
  724         PVUSBURBVUSB pOldVUsb = (PVUSBURBVUSB)&pExtra->Urb.abData[pExtra->cbMax - sizeof(VUSBURBVUSBINT)]; 
719 725         pExtra->Urb.pVUsb = (PVUSBURBVUSB)&pExtra->Urb.abData[cbBuf + pSetupIn->wLength]; 
  726         memmove(pExtra->Urb.pVUsb, pOldVUsb, sizeof(VUSBURBVUSBINT)); 
  727         memset(pOldVUsb, 0, (uint8_t *)pExtra->Urb.pVUsb - (uint8_t *)pOldVUsb); 
720 728         pExtra->Urb.pVUsb->pUrb = &pExtra->Urb; 
  729         pExtra->Urb.pVUsb->pvFreeCtx = &pExtra->Urb; 
721 730         pExtra->cbMax = cbReq; 
  731  
  732 #endif 
722 733     } 
723 734     Assert(pExtra->Urb.enmState == VUSBURBSTATE_ALLOCATED);
本文翻译自:https://starlabs.sg/blog/2020/09/pwn2own-2020-oracle-virtualbox-escape/如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务