内容简介:通过精妙构造,VirtualBox虚拟机的主机进程可以访问VBoxDrv内核驱动程序。即便它以VM用户权限启动运行,同样也可以用于本地提权。在这篇博文中,我将结合 CVE-2018-3055 和 CVE-2018-3085 来破坏VirtualBox中启用了3D加速的虚拟机。这两个漏洞都已在VirtualBox最新的5.2.16版本中被修复。
通过精妙构造,VirtualBox虚拟机的主机进程可以访问VBoxDrv内核驱动程序。即便它以VM用户权限启动运行,同样也可以用于本地提权。
在这篇博文中,我将结合 CVE-2018-3055 和 CVE-2018-3085 来破坏VirtualBox中启用了3D加速的虚拟机。这两个漏洞都已在VirtualBox最新的5.2.16版本中被修复。
概述
3D加速功能作为 共享OpenGL
存在于代码库中,基于用来作分布式OpenGL渲染的Chromium库,不要与和它同名(但7年后才诞生)的Web浏览器相混淆
Chromium定义了一个描述OpenGL操作的网络协议,它可以传递给一个已存在的OpenGL实例
VirtualBox正在维护Chromium的一个分支,并通过 HGCM
(主机-客户端 通信管理器)隧道协议传输消息。HGCM本质上是一个非常简单的 客户端-主机 RPC协议,一旦连接到HGCM服务,客户端虚拟机就可以使用整数和缓冲区参数进行简单的远程调用,并在主机端处理它们。之后会返回一个状态代码,并且被调用者可能会更改参数,以便将数据传递回客户端虚拟机
值得注意的是,HGCM接口由 客户端虚拟机添加程序 暴露给非特权进程。如果没有安装 客户端虚拟机添加程序,则需要root权限才能安装 客户端虚拟机驱动程序 并且需要公开设备,以便攻击共享的OpenGL。
Chromium消息基础
存在不同类型的Chromium消息,它们表示为 CRMessage联合类型
typedef struct { CRMessageType type; unsigned int conn_id; } CRMessageHeader; typedef struct CRMessageOpcodes { CRMessageHeader header; unsigned int numOpcodes; } CRMessageOpcodes; typedef struct CRMessageRedirPtr { CRMessageHeader header; CRMessageHeader* pMessage; #ifdef VBOX_WITH_CRHGSMI CRVBOXHGSMI_CMDDATA CmdData; #endif } CRMessageRedirPtr; typedef union { CRMessageHeader header; CRMessageOpcodes opcodes; CRMessageRedirPtr redirptr; ... } CRMessage;
该类型存储在 header.type
字段中,我们重点关注 CR_MESSAGE_OPCODES
和 CR_MESSAGE_REDIR_PTR 消息
。 CR_MESSAGE_OPCODES消息
包含作为前缀的操作码数,紧接着的是描述实际Chromium操作码的字节数组,这些操作码以特殊方式编码。 例如,一条简单的消息可能如下所示:
uint32_t message[] = { CR_MESSAGE_OPCODES, // msg.header.type 0x41414141, // msg.header.conn_id 1, // msg.numOpcodes CR_EXTEND_OPCODE << 24 // 8-bit opcode indicating an extended opcode follows 0x42424242, // <padding, for whatever reason> CR_WRITEBACK_EXTEND_OPCODE // 32-bit extended opcode 0x43434343, // some extra payload data for this opcode 0x44444444, };
每个操作码都有一个关联的 解包器 和 调度程序,分别以 crUnpack
和 crServerDispatch
为前缀。 此特定操作码的解包器如下所示:
/* in cr_unpack.h */ extern CRNetworkPointer * writeback_ptr; // ... #define SET_WRITEBACK_PTR( offset ) do { CRDBGPTR_CHECKZ(writeback_ptr); crMemcpy( writeback_ptr, cr_unpackData + (offset), sizeof( *writeback_ptr ) ); } while (0); /* in unpack_writeback.c */ void crUnpackExtendWriteback(void) { /* This copies the unpack buffer's CRNetworkPointer to writeback_ptr */ SET_WRITEBACK_PTR( 8 ); cr_unpackDispatch.Writeback( NULL ); }
这告诉Chromium将偏移量为8处的Payload写回到响应缓冲区,上面例子中的字符串是“ ccccdddd
”。我不确定这对此功能的正常使用有什么必要性,但它为我们提供了一个“ echo
”语句来写回我们控制的数据,这对于利用漏洞肯定是有很大帮助的。
CVE-2018-3055
Chromium消息解析器中有几个位置,其中 SET_RETURN_PTR
和 SET_WRITEBACK_PTR
使用用户控制的偏移调用。比如 src/VBox/HostServices/SharedOpenGL/unpacker/unpack_program.c
里的 crUnpackExtendAreProgramsResidentNV
:
void crUnpackExtendAreProgramsResidentNV(void) { GLsizei n = READ_DATA(8, GLsizei); const GLuint *programs = DATA_POINTER(12, const GLuint); SET_RETURN_PTR(12 + n * sizeof(GLuint)); SET_WRITEBACK_PTR(20 + n * sizeof(GLuint)); (void) cr_unpackDispatch.AreProgramsResidentNV(n, programs, NULL); }
我们在Chromium消息响应中的 return_ptr
和 writeback_ptr
处接收数据,并且能完全控制 n
。这意味着泄漏的数据可以位于消息缓冲区中的任意偏移量处,而无需进行边界检查。唯一的限制是 n
必须为非负数,否则我们将遇到其他整数溢出问题,在调用程序时崩溃。由于我们能够通过值 n
控制消息的分配大小和泄漏的偏移量,因此这是一个完美的用来公开 存储在堆上的指针 和 数据 的语句。
CVE-2018-3085
Chromium消息最终由 src/VBox/HostServices/SharedOpenGL/crserverlib/server_stream.c
里的 crServerDispatchMessage
函数处理
static void crServerDispatchMessage(CRConnection *conn, CRMessage *msg, int cbMsg) { // ... if (msg->header.type == CR_MESSAGE_REDIR_PTR) { #ifdef VBOX_WITH_CRHGSMI // this is defined in prod builds pCmdData = &msg->redirptr.CmdData; #endif msg = (CRMessage *) msg->redirptr.pMessage; } CRASSERT(msg->header.type == CR_MESSAGE_OPCODES); msg_opcodes = (const CRMessageOpcodes *) msg; opcodeBytes = (msg_opcodes->numOpcodes + 3) & ~0x03; // handle opcodes here... #ifdef VBOX_WITH_CRHGSMI if (pCmdData) { int rc = VINF_SUCCESS; CRVBOXHGSMI_CMDDATA_ASSERT_CONSISTENT(pCmdData); if (CRVBOXHGSMI_CMDDATA_IS_SETWB(pCmdData)) { uint32_t cbWriteback = pCmdData->cbWriteback; rc = crVBoxServerInternalClientRead(conn->pClient, (uint8_t*)pCmdData->pWriteback, &cbWriteback); Assert(rc == VINF_SUCCESS || rc == VERR_BUFFER_OVERFLOW); *pCmdData->pcbWriteback = cbWriteback; } VBOXCRHGSMI_CMD_CHECK_COMPLETE(pCmdData, rc); } #endif }
很明显,如果 msg 完全由 客户端 控制,将能够以各种方式中断。尤其,客户端虚拟机可以将消息类型设置为 CR_MESSAGE_REDIR_PTR
,并设置 msg-> redirpt
用来将其指向伪造的 CR_MESSAGE_OPCODES
消息。如果伪造的消息产生响应,它将被写入 pCmdData-> pWriteback
,这也是攻击者能够控制的,因为它是从 msg-> redirptr
获取过来的。现在我们已经知道可以使用 CR_WRITEBACK_EXTEND_OPCODE消息
来控制8个字节的响应,如果我们可以注入 CR_MESSAGE_REDIR_PTR消息
,问题将仍然存在。
如果通过HGCM访问Chromium子系统 src/VBox/GuestHost/OpenGL/util/vboxhgcm.c
里的 _crVBoxHGCMReceiveMessage
函数,将负责从缓冲区读取消息并将其放入Chromium处理队列中:
static void _crVBoxHGCMReceiveMessage(CRConnection *conn) { // ... if (conn->allow_redir_ptr) { // ... // [[ 1 ]] hgcm_buffer = (CRVBOXHGCMBUFFER *) _crVBoxHGCMAlloc( conn ) - 1; hgcm_buffer->len = sizeof(CRMessageRedirPtr); msg = (CRMessage *) (hgcm_buffer + 1); msg->header.type = CR_MESSAGE_REDIR_PTR; msg->redirptr.pMessage = (CRMessageHeader*) (conn->pBuffer); msg->header.conn_id = msg->redirptr.pMessage->conn_id; // ... cached_type = msg->redirptr.pMessage->type; // ... } else { /* we should NEVER have redir_ptr disabled with HGSMI command now */ CRASSERT(!conn->CmdData.pvCmd); if ( len <= conn->buffer_size ) { // [[ 2 ]] /* put in pre-allocated buffer */ hgcm_buffer = (CRVBOXHGCMBUFFER *) _crVBoxHGCMAlloc( conn ) - 1; } else { // [[ 3 ]] /* allocate new buffer, * not using pool here as it's most likely one time transfer of huge texture */ hgcm_buffer = (CRVBOXHGCMBUFFER *) crAlloc( sizeof(CRVBOXHGCMBUFFER) + len ); hgcm_buffer->magic = CR_VBOXHGCM_BUFFER_MAGIC; hgcm_buffer->kind = CR_VBOXHGCM_MEMORY_BIG; hgcm_buffer->allocated = sizeof(CRVBOXHGCMBUFFER) + len; } hgcm_buffer->len = len; _crVBoxHGCMReadBytes(conn, hgcm_buffer + 1, len); msg = (CRMessage *) (hgcm_buffer + 1); cached_type = msg->header.type; } // ... // [[ 4 ]] crNetDispatchMessage( g_crvboxhgcm.recv_list, conn, msg, len ); // [[ 5 ]] /* CR_MESSAGE_OPCODES is freed in crserverlib/server_stream.c with crNetFree. * OOB messages are the programmer's problem. -- Humper 12/17/01 */ if (cached_type != CR_MESSAGE_OPCODES && cached_type != CR_MESSAGE_OOB && cached_type != CR_MESSAGE_GATHER) { _crVBoxHGCMFree(conn, msg); } }
我们将看到两种不同的情况:
如果 conn-> allow_redir_ptr
为 true,则分配 CR_MESSAGE_REDIR_PTR消息
并指向客户端虚拟机所提供的消息。如果不是这种情况,则将 客户端虚拟机消息
直接放入消息队列中。
触发漏洞
如果 allow_redir_ptr
永远为 true,那么由于 _crVBoxHGCMAlloc
的工作方式, use-after-free
将无法被利用。 那这个标志是什么意思呢? cr_net.h
中的注释给出了一个线索:
/* Used on host side to indicate that we are not allowed to store above pointers for later use * in crVBoxHGCMReceiveMessage. As those messages are going to be processed after the corresponding * HGCM call is finished and memory is freed. So we have to store a copy. * This happens when message processing for client associated with this connection * is blocked by another client, which has send us glBegin call and we're waiting to receive glEnd. */ uint8_t allow_redir_ptr;
由于Chromium必须能够同时处理多个连接,即VirtualBox的多个HGCM连接,它需要复用来自不同客户端所有传入的OpenGL命令。如果一个客户端发送了 glBegin
,它将无法处理来自其他客户端的命令,直到接受了相应的 glEnd
。
虽然情况确实如此,但对于其他客户端, allow_redir_ptr
仍然为 false。在 src/VBox/HostServices/SharedOpenGL/crserverlib/server_main.c
的 crVBoxServerInternalClientWriteRead
函数中可见端疑:
if ( #ifdef VBOX_WITH_CRHGSMI !CRVBOXHGSMI_CMDDATA_IS_SET(&pClient->conn->CmdData) && #endif cr_server.run_queue->client != pClient && crServerClientInBeginEnd(cr_server.run_queue->client)) { crDebug("crServer: client %d blocked, allow_redir_ptr = 0", pClient->conn->u32ClientID); pClient->conn->allow_redir_ptr = 0; } else { pClient->conn->allow_redir_ptr = 1; }
因此,要触发 allow_redir_ptr == 0
分支,我们只需要在一个客户端发出 glBegin
,然后在另一个客户端发送伪造的消息,它将被放入消息队列中而不进行检查。在发送了 glEnd
后,才会被处理。所以一下是第一个攻击计划:
glBegin CR_MESSAGE_REDIR_PTR glEnd
然而这并没有什么卵用
我们必须继续处理,因为以下事件并不会自动完成:由于 路径[[5]]
中不合适的空闲时机,我们在步骤2中发送的消息在被处理之前释放掉了。如果我们选择通过 路径[[2]]
在步骤2中分配消息,那么来自步骤3的消息又将覆盖它并将被处理两次。如果我们通过 路径[[3]]
分配它,那么(至少在 Linux 和Windows上)在 free(释放)之后它所包含的一些堆元数据将会是些无效数据。
所以,替代方案如下
glBegin
2 . 在客户端B中发送一个大的伪造 CR_MESSAGE_REDIR_PTR
,它将触发 路径[[3]]
(操作系统提供的 malloc函数)
3 .通过调用具有相同大小和内容的 HGCM
来写入一些缓存,希望它们占用被释放掉的消息缓冲区
glEnd
最终,来自 步骤3 的消息将在处理之前重用 步骤2 中的消息空间,且最终结果如我们所期待的那样:完全控制传递给 crServerDispatchMessage
的消息,并实现 write-what-where
语句
与之前的信息泄漏配合,这可以变成更灵活和可重复的写语句,并最终实现 任意读/写
。
以上所述就是小编给大家介绍的《结合CVE-2018-3055与CVE-2018-3085攻击VirtualBox 3D加速》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 简单的shell脚本结合awk实现防止对web服务的dos攻击
- 代理模式——结合SpringAOP讲解
- 如何结合 Scrum 和 Kanban
- NServiceBus 结合 RabbitMQ 使用教程
- ActiveMQ结合Spring收发消息
- quicklink学习以及结合React
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Windows黑客编程技术详解
甘迪文 / 人民邮电出版社 / 2018-12 / 108
《Windows黑客编程技术详解》介绍的是黑客编程的基础技术,涉及用户层下的Windows编程和内核层下的Rootkit编程。本书分为用户篇和内核篇两部分,用户篇包括11章,配套49个示例程序源码;内核篇包括7章,配套28个示例程序源码。本书介绍的每个技术都有详细的实现原理,以及对应的示例代码(配套代码均支持32位和64位Windows 7、Windows 8.1及Windows 10系统),旨在......一起来看看 《Windows黑客编程技术详解》 这本书的介绍吧!