结合CVE-2018-3055与CVE-2018-3085攻击VirtualBox 3D加速

栏目: 服务器 · 发布时间: 6年前

内容简介:通过精妙构造,VirtualBox虚拟机的主机进程可以访问VBoxDrv内核驱动程序。即便它以VM用户权限启动运行,同样也可以用于本地提权。在这篇博文中,我将结合 CVE-2018-3055 和 CVE-2018-3085 来破坏VirtualBox中启用了3D加速的虚拟机。这两个漏洞都已在VirtualBox最新的5.2.16版本中被修复。

结合CVE-2018-3055与CVE-2018-3085攻击VirtualBox 3D加速

通过精妙构造,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_OPCODESCR_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,
};

每个操作码都有一个关联的 解包器 和 调度程序,分别以 crUnpackcrServerDispatch 为前缀。 此特定操作码的解包器如下所示:

/* 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与CVE-2018-3085攻击VirtualBox 3D加速

CVE-2018-3055

Chromium消息解析器中有几个位置,其中 SET_RETURN_PTRSET_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_ptrwriteback_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消息 并指向客户端虚拟机所提供的消息。如果不是这种情况,则将 客户端虚拟机消息 直接放入消息队列中。

结合CVE-2018-3055与CVE-2018-3085攻击VirtualBox 3D加速

触发漏洞

如果 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.ccrVBoxServerInternalClientWriteRead 函数中可见端疑:

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(释放)之后它所包含的一些堆元数据将会是些无效数据。

所以,替代方案如下

1 . 在客户端A中发出 glBegin

2 . 在客户端B中发送一个大的伪造 CR_MESSAGE_REDIR_PTR ,它将触发 路径[[3]] (操作系统提供的 malloc函数)

3 .通过调用具有相同大小和内容的 HGCM 来写入一些缓存,希望它们占用被释放掉的消息缓冲区

4 . 在客户端A中发出 glEnd

最终,来自 步骤3 的消息将在处理之前重用 步骤2 中的消息空间,且最终结果如我们所期待的那样:完全控制传递给 crServerDispatchMessage 的消息,并实现 write-what-where 语句

与之前的信息泄漏配合,这可以变成更灵活和可重复的写语句,并最终实现 任意读/写


以上所述就是小编给大家介绍的《结合CVE-2018-3055与CVE-2018-3085攻击VirtualBox 3D加速》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Windows黑客编程技术详解

Windows黑客编程技术详解

甘迪文 / 人民邮电出版社 / 2018-12 / 108

《Windows黑客编程技术详解》介绍的是黑客编程的基础技术,涉及用户层下的Windows编程和内核层下的Rootkit编程。本书分为用户篇和内核篇两部分,用户篇包括11章,配套49个示例程序源码;内核篇包括7章,配套28个示例程序源码。本书介绍的每个技术都有详细的实现原理,以及对应的示例代码(配套代码均支持32位和64位Windows 7、Windows 8.1及Windows 10系统),旨在......一起来看看 《Windows黑客编程技术详解》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具