CVE-2014-0983分析:虚拟机的威胁_VirtualBox逃逸技术

在前一个博客中,我们分享了一个影响Xen hypervisor的客户机-到-主机(guest-to-host)的越狱漏洞的利用技术。在这个新的博客文章中,我们将重点放在另外一个虚拟机逃逸漏洞,VirtualBox。

几个月以前,我们核心安全的朋友发布了一个关于影响VirtualBox的多个内存破坏漏洞的议题,可能允许在客户机操作系统的用户/程序逃逸虚拟机并且在主机操作系统上执行任意代码。

几个星期前,REcon 2014年期间,Francisco Falcon 已经证明可以组合这些漏洞并且利用它们来实现在一个32位windows主机上客户机-到-主机( guest-to-host )的逃逸。

在这篇博客中,我们将分享在64位windows 8主机上只使用一个漏洞(CVE-2014-0983)来实现一个可靠的虚拟机逃逸利用技术,并没有使VirtualBox进程崩溃(也称为进程延续)。

1:该漏洞的技术分析

多个内存破坏漏洞存在于OpenGL图形VirtualBox 3D加速中。在这个分析中,我们将专注于CVE-2014-0983。

从 客户机操作系统方面看,客户机的增加会增加成倍数量的服务如:拖放,共享剪贴板,图形渲染等。其中的一个服务被称为“共享的OpenGL”。当 3D加速在VirtualBox中启用(默认禁用),可以通过客户机/服务器模型提供远程呈现OpenGL图形。客户机操作系统作为一个客户端发送一个渲 染消息给“VBoxGuest.sys”驱动程序,该驱动程序随后通过PMIO/MMIO转发消息到解析它的主机(作为一个服务器)。更多关于 VirtualBox and 3D的细节参考这里

在诸多的渲染消息中有一个名为”CR_MESSAGE_OPCODES”的渲染消息,其结构由操作码(命令标识)构成。在服务器端(主机操作系统)”crUnpack()”函数处理所有的操作码。

 

在 安装VirtualBox时通过位于”src/VBox/HostServices/SharedOpenGL/unpacker /unpack.py”的python脚本自动生成”crUnpack()”函数的内容,该函数可作为一个开关,并根据操作码处理不同的功能。

通过发送包含操作码”CR_VERTEXATTRIB4NUBARB_OPCODE” (0xEA)的消息,”crUnpack()”调用”crUnpackVertexAttrib4NubARB()”,这个函数解析来自客户机操作系统的没有任何验证或检查的渲染消息。

由于缺少数组索引的验证,位于”cr_server.current.c.vertexAttrib.ub4″ 数组之后的内存可以通过 “cr_unpackData”破坏。

如此一来在虚拟机中的客户机操作系统的恶意用户或程序可以在主机操作系统上执行任意代码。

2:Windows 8 (64bit) 主机上的利用

要从虚拟客户机利用此漏洞,我们需要编写一个恶意程序通过可用的客户机提供的驱动程序发送异常消息到主机上。

有漏洞的函数 “crUnpackVertexAttrib4NubARB”位于“VBoxSharedCrOpenGL.dll”中,而且数组位于该dll的.data区段:

因此,继 “cr_server.current.c.vertexAttrib.ub4″数组之后的地址可以被”cr_unpackData”破坏。”cr_unpackData”是一个指向从客户机操作系统发来的渲染消息的指针。

主 机操作系统上的”VBoxSharedCrOpenGL.dll”中相关 的”cr_server.current.c.vertexAttrib.ub4” 数组的内存可以被破坏。有了这个write4 primitive(这里不知道如何翻译),我们可以破坏位于.data区段的函数指针。通过查看该漏洞函数,我们可以看到:

将其翻译成汇编代码如下:

“cr_server.head_spu”在.data区段的位置如下:

这是被破坏的地址。”cr_server.head_spu” 位于数组之后,对于corruption,我们需要一个正向的索引:

索引可被计算,如下:

破坏”cr_server.head_spu”之后,主机操作系统已经完成了渲染消息的解析并 且没有代码重定向。但当包含有操作 码”CR_VERTEXATTRIB4NUBARB_OPCODE”(0xEA)的相同消息再次发送,”cr_server.head_spu”被再次使用,如下:

“cr_server.head_spu” 已经通过”cr_unpackData”(指我们的控制数据)破坏,相关跳转指令rax+0xB0将会重定向执行流。

下一步是处理堆栈平衡。默认情况下,VirtualBox除了 “VBoxREM.dll” 之外所有组件都启用”ASLR/DEP”,”VBoxREM.dll”没有使用”ASLR”技术;因此,可以在利用过程中使用此dll(当然也可以利用其它漏洞实现内存泄露)。

当我们重定向执行流时,所有寄存器状态如下所示:

寄存器RAX,RBX和RBP指向渲染消息:

其中包含有渲染操作码,紧接着的是我们的全部的控制数据。

总之,堆栈平衡不能简单使用指令RET, JMP [register], CALL [register]完成。64位编译器会通过跳转到被调用者来优化最后一次函数调用。这有助于我们找到一个如下的合适的平衡:(我将Gadget翻译为工具代码 :-)

RDX寄存器总是被设置为0×73。 因此,第一条指令将控制数据传送到RAX。

LEAVE 指令将RSP设置为RBP(指向我们的消息),然后跳转到RAX寄存器(我们下一个工具代码)。

现在RSP是可控的,x64 ROP用于调用 “VirtualProtect()”函数和实现代码执行。公开每一个工具代码的详细细节,第一个将会增加堆栈空间(POP RSI,RDI,RBP,R12),工具代码如下:

如此一来,RSP的值可写在堆栈上的任何位置(取决于RDX值)。现在堆栈是可控的,下面的工具代码将用于调用”VirtualProtect()”和绕过DEP:

由于第二个工具代码(堆栈提升),我们得以控制R12和RBP寄存器。

(注:不像x86,在x64系统上前4个函数参数不压入堆栈,fast call函数调用约定下,寄存器 RCX,RDX,R8,R9是作为参数使用的。)

现在堆栈中包含控制数据并且可以将RSP的值写入其中。因此所有的函数参数都可以设置。最后,通过将RBP设置为0x6a70bb20调用“VirtualProtect()”。

不像x86,RBP寄存器被用于访问堆栈上的参数和局部变量,不再是一个帧指针了。

执行”VirtualProtect()”函数并且堆栈权限被设置为RWE。最后的工具代码将会重定向执行流,代码如下:

现在,我们从客户机操作系统发送的数据可以在主机操作系统的上下文中执行了。

Shellcode和进程延续

现在的目标是在不崩溃VirtualBox进程(也成为进程延续)的情况下执行x64 shellcode。

执行的第一条指令是相当敏感的,因为RSP(堆栈指针)几乎相当于RIP(指令指针)。因此RSP必须被移动到渲染消息底部的其他地方:如下图所示:

3D 渲染消息由客户机操作系统分配并且其组件是已知的(opcodes,ROP,pre-shellcode,shellcode,post- shellcode,shellcode stack size)。因此我们能够根据shellcoe和所需的堆栈大小制作此消息。

Pre-shellcode 如下:

现在堆栈指针(RSP)处于安全的位置,我们的shellcode可以执行,post-shellcode 则用于修复。

– 堆栈指针(RSP)

– .data区段被破坏的函数指针(cr_server.head_spu)

为了恢复原来的堆栈指针(RSP),必须使用线程环境块(Thread Environment Block),得益于GS寄存器该结构可以被访问。 TEB开始于一个结构,该结构包含我们所需要的一切,结构如下:

一旦找到stack base,可以使用模式匹配获取原来的堆栈:如下:

堆栈指针必须加上0×170 + 0×100(其中0×170是为了达到代码执行流重定向之前的堆栈调用状态,0×100字节是为了跳过如下蓝色区域的消息解析函数):

使用RET指令,代码可以可以恢复到最初的执行流,但是在那之前,”cr_server.head_spu”必须被修复。

“cr_server.head_spu”已被破坏,该变量的默认值是一个包含虚函数表的堆地址。试图恢复原来的堆地址是不容易的,原因如下:

-每一个不同的windows版本都有一个不同的复杂的堆格式

-无模式匹配;堆的内容是一个函数表

一 个简单的解决方法是重新使用现有的代码。需要注意的是”VBoxSharedCrOpenGL.dll”中 的”crVBoxServerRemoveClient()”函数位于堆栈的顶部,其地址位于.text区段的开始。映射到内存中的每个库都是对齐的,因 此,如果我们只保持函数地址的高部分,就可以得到”VBoxSharedCrOpenGL.dll”的基地址,代码如下:

知 道了 “VBoxSharedCrOpenGL” 基地址,post-shellcode 可以调用其他的函数,比如:”ccrVBoxServerInit()”,这个函数调用修复”cr_server.head_spu”的函 数”crServerSetVBoxConfigurationHGCM()”。

接下来是post-shellcode的最后部分:

结束

[via vupen/ freebuf]