其他部分链接:
在这部分,我们合并了这上述两个漏洞,用来实现虚拟机逃逸并获得QEMU的特权实现在宿主机上运行代码。
首先,我们利用 CVE-2015-5165来重建QEMU的结构。更确切的说,利用该漏洞获得以下地址来实现ASLR的绕道:
- 用户物理内存基地址。在我们的漏洞利用中,需要定位用户并获得其在QEMU虚拟地址空间中的准确地址。
- .text字段的基地址。这是为了获得qemu_set_irq()函数的地址。
- .plt字段的基地址。这是为了确定一些函数的地址,比如生成我们恶意代码的fork()和execv()函数。还需要mprotect()函数来修改用户物理地址。记得用户的物理地址是不可执行的。
5.1 RIP控制
如第4部分所示,我们已经控制了%rip寄存器。为了让QEMU在特定地址处泄露,我们需要用一个指向假IRQState结构体的地址指针来泄露PCNET缓冲区,该结构调用了一个我们选择的函数。
首先,可以尝试构建一个假IRQState结构体调用system()。然而,有时调用会失败,因为一些QEMU内存映射不通过fork()调用。更确切的说即映射的物理内存有 MADV_DONTFORK参数。
调用execv()也没用,因为我们失去了对用户机器的控制。
还要注意可以连接一些泄露的IRQState来构造恶意代码调用许多函数,因为qemu_set_irq()被PCNET设备仿真器多次调用。然而,我们发现更方便实用的方法是,设置恶意代码所在页内存的PROT_EXEC参数之后执行恶意代码。
我们的方法是构造两个假IRQState结构体。第一个调用mprotect()。第二个调用恶意代码,首先取消MADV_DONTFORK参数,然后在用户和主机之间互动。
如之前所述,当调用qemu_set_irq()时,输入两个参数:irq(IRQstate结构体的指针)和level(IRQ level),然后调用如下处理程序:
如之前所述,我们只能控制前两个参数。所以怎样调用mprotect()这样有三个参数的函数呢?
为了解决这个问题,我们让qemu_set_irq()先调用自己的两个参数:
- irq:指向假IRQState结构体的指针,设置处理程序指针指向mprotect()函数。
- level:设置mprotect参数为PROT_READ | PROT_WRITE | PROT_EXEC
这通过构造两个假IRQState结构体来实现,代码段如下:
当溢出发生后,qemu_set_irq()被一个假的处理程序调用,该处理程序在调整level参数为7(要求mprotect参数)后调用mprotect。
现在内存可以执行了,我们可以通过将第一个IRQState的处理程序重写为我们的恶意代码地址,把控制权转交给交互式shell。
5.2 交互式Shell
好了,我们可以简单的写一个基本的shellcode,构建一个远程控制端口的shell并从一个单独的机器上连接这个shell。这是个好的方案,但是我们可以做的更好来逃过防火墙限制。我们利用在用户和主机之间的共享内存来构建一个bindshell。
利用QEMU的漏洞是很巧妙的,因为我们在用户内存上写的代码已经在QEMU的进程内存中可用。所以不需要再注入shellcode。更好的,我们可以共享代码并让它运行在用户机和连接的主机上。
我们构造了两个循环缓冲区(输入和输出)并给这些共享内存空间提供有自旋锁的读/写方法。在主机上,我们运行一个shellcode,在一个单独的进程中复制了其stdin和stdout文件后执行/bin/sh的shell。我们还创建爱你了两个线程。第一个线程从共享内存中读命令并通过一个管道传递给shel。第二个线程读取shell的输出(通过另一个管道)然后写入共享内存。
这两个线程还分别实例化了用户机,一个写入用户输入的命令到专用的共享内存,一个输出从第二个循环缓冲读取的结果到stdout。
注意在我们的漏洞利用中,有第三个线程(和一个专用共享区域)来处理stderr输出。
5.3 虚拟机逃逸的利用
这部分我们列出了虚拟机逃逸(vm-escape.c)用到的主要结构体和函数。
注入的payload结构体定义如下:
fake_irq是与假IRQState结构体一对的,负责调用mprotect()并改变配置所在页的页保护。
shared_data结构体用来传递数据到主shellcode。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!