Reproduction Report: CVE-2018-2844

0x00 漏洞原理

漏洞所在的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
static int vboxVDMACmdExec(PVBOXVDMAHOST pVdma, const uint8_t *pvBuffer, uint32_t cbBuffer)
{
    do
    {
        Assert(pvBuffer);
        Assert(cbBuffer >= VBOXVDMACMD_HEADER_SIZE());

        if (!pvBuffer)
            return VERR_INVALID_PARAMETER;
        if (cbBuffer < VBOXVDMACMD_HEADER_SIZE())
            return VERR_INVALID_PARAMETER;

        PVBOXVDMACMD pCmd = (PVBOXVDMACMD)pvBuffer;
        switch (pCmd->enmType)
        {
            case VBOXVDMACMD_TYPE_CHROMIUM_CMD:
            {
# ifdef VBOXWDDM_TEST_UHGSMI
                static int count = 0;
                static uint64_t start, end;
                if (count==0)
                {
                    start = RTTimeNanoTS();
                }
                ++count;
                if (count==100000)
                {
                    end = RTTimeNanoTS();
                    float ems = (end-start)/1000000.f;
                    LogRel(("100000 calls took %i ms, %i cps\n", (int)ems, (int)(100000.f*1000.f/ems) ));
                }
# endif
                /** @todo post the buffer to chromium */
                return VINF_SUCCESS;
            }
            case VBOXVDMACMD_TYPE_DMA_PRESENT_BLT:
            {
                const PVBOXVDMACMD_DMA_PRESENT_BLT pBlt = VBOXVDMACMD_BODY(pCmd, VBOXVDMACMD_DMA_PRESENT_BLT);
                int cbBlt = vboxVDMACmdExecBlt(pVdma, pBlt, cbBuffer);
                Assert(cbBlt >= 0);
                Assert((uint32_t)cbBlt <= cbBuffer);
                if (cbBlt >= 0)
                {
                    if ((uint32_t)cbBlt == cbBuffer)
                        return VINF_SUCCESS;
                    else
                    {
                        cbBuffer -= (uint32_t)cbBlt;
                        pvBuffer -= cbBlt;
                    }
                }
                else
                    return cbBlt; /* error */
                break;
            }
            case VBOXVDMACMD_TYPE_DMA_BPB_TRANSFER:
            {
                const PVBOXVDMACMD_DMA_BPB_TRANSFER pTransfer = VBOXVDMACMD_BODY(pCmd, VBOXVDMACMD_DMA_BPB_TRANSFER);
                int cbTransfer = vboxVDMACmdExecBpbTransfer(pVdma, pTransfer, cbBuffer);
                Assert(cbTransfer >= 0);
                Assert((uint32_t)cbTransfer <= cbBuffer);
                if (cbTransfer >= 0)
                {
                    if ((uint32_t)cbTransfer == cbBuffer)
                        return VINF_SUCCESS;
                    else
                    {
                        cbBuffer -= (uint32_t)cbTransfer;
                        pvBuffer -= cbTransfer;
                    }
                }
                else
                    return cbTransfer; /* error */
                break;
            }
            case VBOXVDMACMD_TYPE_DMA_NOP:
                return VINF_SUCCESS;
            case VBOXVDMACMD_TYPE_CHILD_STATUS_IRQ:
                return VINF_SUCCESS;
            default:
                AssertBreakpoint();
                return VERR_INVALID_FUNCTION;
        }
    } while (1);

在这个函数中,使用switch case来根据VDMA的命令类型来调用相应的函数。 而在linux中,编译时候,编译器将会优化这一操作,将switch修改为跳转表来进行跳转。

这边的switch优化的跳转表是一个二级跳转表,这就为TCOTOU攻击做了基础。

first:
.text:00000000000B957A                 cmp     dword ptr [r12], 0Ah ; switch 11 cases
.text:00000000000B957F                 ja      VBOXVDMACMD_TYPE_DEFAULT ; jumptable
00000000000B9597 default case
second:
.text:00000000000B9585                 mov     eax, [r12]
.text:00000000000B9589                 lea     rbx, vboxVDMACmdExec_JMPS
.text:00000000000B9590                 movsxd  rax, dword ptr [rbx+rax*4]
.text:00000000000B9594                 add     rax, rbx
.text:00000000000B9597                 jmp     rax             ; switch jump

.rodata:0000000000185538 vboxVDMACmdExec_JMPS dd offset VBOXVDMACMD_TYPE_DEFAULT - 185538h
.rodata:0000000000185538                                         ; DATA XREF: vboxVDMACommand+1D9o
.rodata:0000000000185538                 dd offset VBOXVDMACMD_TYPE_DMA_PRESENT_BLT - 185538h ; jump table for switch statement
.rodata:0000000000185538                 dd offset VBOXVDMACMD_TYPE_DMA_BPB_TRANSFER - 185538h
.rodata:0000000000185538                 dd offset VBOXVDMACMD_TYPE_DEFAULT - 185538h
.rodata:0000000000185538                 dd offset VBOXVDMACMD_TYPE_DEFAULT - 185538h
.rodata:0000000000185538                 dd offset VBOXVDMACMD_TYPE_DEFAULT - 185538h
.rodata:0000000000185538                 dd offset VBOXVDMACMD_TYPE_DEFAULT - 185538h
.rodata:0000000000185538                 dd offset VBOXVDMACMD_TYPE_DMA_NOP - 185538h
.rodata:0000000000185538                 dd offset VBOXVDMACMD_TYPE_DMA_NOP - 185538h
.rodata:0000000000185538                 dd offset VBOXVDMACMD_TYPE_DEFAULT - 185538h
.rodata:0000000000185538                 dd offset VBOXVDMACMD_TYPE_DMA_NOP - 185538h
.rodata:0000000000185564                 align 20h

关于TCOTOU,以下面的程序举个例子:

1
2
3
4
5
file = "/tmp/X";
fileExist = check_file_existence(file);
if (fileExist == FALSE)
{// The file does not exist, create it.
f = open(file, O_CREAT);}

在file的fileExist=FALSE时候才能调用open读取,但是如果我们将程序视为一步一步的执行函数的时候,在if这个check过了后,我们假设有一分钟的时间程序才会执行open,那么这时候有另外一个程序把file这个指针修改为我们想要的open的文件,这时候就相当于我们可以任意读取文件了。

TCOTOU这个攻击技术就是在程序的这两个checkuse阶段之间的时间差中,用另外一个进程去修改指针,以此达到我们攻击的目的。

这样,这个漏洞的利用也就很明显了,因为是二级跳转表,同时程序的变量没有加上volatile来标记,导致程序不会每次调用都检查变量的类型,只需要变量通过了check1时候,也就是检查是否是那11个case时候,成功过了这个check后,能够用另外一个进程修改掉这个变量的数值时候,并且计算可控地址跟第二级跳转表的offset,控制程序的switch流程跳转到可控区域后,就可以来执行我们事先布置好的shellcode了。

另外,这个漏洞可以实现逃逸的原因是VBVA是他在HGSMI的基础上的,HGSMI是通过视频的ram缓存区实现的共享内存,vram缓存区物理地址为0xE0000000,所以可以通过这个缓存区去获取物理机权限。

而这个漏洞函数的地址就是在处理客户机传递给主机的视频DMA命令的代码中。

0x01 漏洞利用

这个条件竞争的时间窗口比较小,在有着多个vcpu的客户机时候,可以通过调整cpu的亲和性来保证攻击程序占用的计算内存比系统本身计算内存多,以此达到能在这个条件竞争的时间窗口中稳定利用的目的。 而在寄存器中rax由客户机控制,R8R12R15存放的是指向崩溃期间HGSMI缓冲区内的相关偏移量的指针。能寻找到一个jmp rax的汇编,因此只要能控制rax的值落到我们shellcode的范围时候,就可以劫持程序来达到逃逸的目的。

 RAX  0xdeadbeef
 RBX  0x7fff8abf2538 ◂— rol    byte ptr [rdx - 0xd], 1
 RCX  0x7fff9c508ac0 —▸ 0x7ffff7e30000 ◂— 0x5
 RDX  0xe7b
 RDI  0xeeb
 RSI  0x7fffdc022000 ◂— xor    byte ptr [rax], al /* 0xffe40030; '0' */
 R8   0x7fff89d20000 ◂— jmp    0x7fff89d20010 /* 0xb020000000eeb */
 R9   0x7fff8ab06040 ◂— push   rbp
 R10  0x7fff9c50ad48 ◂— 0x1
 R11  0x7fff9c508d48 ◂— 0x0
 R12  0x7fff89d20078 ◂— 0xa /* '\n' */
 R13  0xf3b
 R14  0x7fff9c50d0e0 —▸ 0x7fff9c508ac0 —▸ 0x7ffff7e30000 ◂— 0x5
 R15  0x7fff89d20030 ◂— 0xffffffdc0f3b0eeb
 RBP  0x7fffba44dc40 —▸ 0x7fffba44dca0 —▸ 0x7fffba44dce0 —▸ 0x7fffba44dd00 —▸ 0x7fffba44dd50 ◂— ...
 RSP  0x7fffba44db80 —▸ 0x7fffba44dbb0 —▸ 0x7fff9c508ac0 —▸ 0x7ffff7e30000 ◂— 0x5
 RIP  0x7fff8ab26590 ◂— movsxd rax, dword ptr [rbx + rax*4]
 ► 0x7fff8ab26590    movsxd rax, dword ptr [rbx + rax*4]
   0x7fff8ab26594    add    rax, rbx
   0x7fff8ab26597    jmp    rax

之后,在条件竞争成功后,我们可以控制落到的伪跳转表中,寻找到能跳转到具有rwx权限的vram区域。(之前解释过,vram区域也是跟客户机的共享内存区域,因此可以在这儿实现逃逸代码)

作者这边是通过VboxDD.so区域中寻找到了一个能跳转到VRAM区域的地方,并且把VRAM区域其他地方设置为nop,以保证能滑到shellcode

0x02 环境搭建

测试环境

  • 主机系统:Ubuntu 16.04 Desktop x86_64
  • 客户系统:Ubuntu 16.04 Desktop x86_64
  • Vbox版本:5.2.6 r121009

安装相应版本virtualbox

根据https://www.voidsecurity.in/2018/08/from-compiler-optimization-to-code.html 原文作者使用的5.2.6版本virtualbox,从Virtualbox官网 下载版本号为5.2.6的软件包,安装:

wget 'https://download.virtualbox.org/virtualbox/5.2.6/virtualbox-5.2_5.2.6-120293~Ubuntu~xenial_amd64.deb'
sudo dpkg -i virtualbox-5.2_5.2.6-120293_Ubuntu_xenial_amd64.deb
sudo apt-get -f install

安装完成:

安装虚拟机

Ubuntu官网 下载Ubuntu 16.04 Desktop镜像,并在virtualbox中安装。

配置虚拟机

根据作者给出的exp,首先需要给虚拟机分配至少2个CPU来使得条件竞争可能发生:

其次,需要将网络配置成

  • 连接方式:桥接网卡
  • 界面名称:(根据本机配置选择)
  • 控制芯片:PCnet-FAST III(Am79C973)
  • 混杂模式:拒绝
  • MAC地址:(默认)

开启虚拟机,至少要保证安装好以下软件:

  • gcc
  • make
  • git
  • openssh-server

开启ssh服务:

获取主机系统和客户系统在本地网络中的ip地址:

并保证两者可以ping通:

在虚拟机上,从github上下载exp

git clone https://github.com/renorobert/virtualbox-cve-2018-2844

0x03 溢出过程

虚拟机开启监听6969端口(任意一个未封锁的端口)

主机系统通过ssh连接虚拟机shell

通过虚拟机shell编译exp,并运行(exploit 参数填虚拟机ip地址和虚拟机开启监听的端口)

运行exp时,可以看到虚拟机会有一瞬间的灰屏

灰屏后,可以在虚拟机中看到有来自主机系统的连接。此时,虚拟机已逃逸,并利用shellcode将主机的shell传给了自己,使得虚拟机可以运行主机系统的shell

通过whoami命令验证shell是来自主机系统的

尝试在主机系统的桌面上添加文件

成功!

0x04 Exploit

https://github.com/renorobert/virtualbox-cve-2018-2844

0x05 Reference

[1] https://xz.aliyun.com/t/2658

[2] https://nvd.nist.gov/vuln/detail/CVE-2018-2844

[3] https://www.voidsecurity.in/2018/08/from-compiler-optimization-to-code.html