2018年 RealWorld CTF的一道VMware逃逸题目,针对的攻击面是 VMware-vmx中的RPC机制,利用难度不是很高。
RPC机制
前置知识
在VMware
中,有一个奇特的攻击面,就是 vmtools
。vmtools
帮助宿主机和客户机完成包括文件传输在内的一系列的通信和交互,其中使用了一种被称为 backdoor
的接口。backdoor
接口是如何和宿主机进行通信的呢。观察 backdoor
函数的实现,可以发现如下代码:
1 | MOV EAX, 564D5868h /* magic number */ |
首先需要明确的是,该接口在用户态就可以使用。在通常环境下,IN
指令是一条特权指令,在普通用户态程序下是无法使用的。因此,运行这条指令会让用户态程序出错并陷出到 hypervisor
层,从而 hypervisor
层可以对客户机进行相关的操作和处理,因此利用此机制完成了通信。利用 backdoor
的通信机制,客户机便可以使用 RPC
进行一系列的操作,例如拖放、赋值、获取信息、发送信息等。
backdoor
机制所有的命令和调用方法,基本都是首先设置寄存器、然后调用 IN
或 OUT
特权指令的模式。那么使用 backdoor
传输 RPC
指令需要经过哪些步骤呢。这里以 Real World CTF 2018 Finals Station-Escape Writeup
该题目涉及到的 backdoor
操作进行说明,主要参考这篇文档。
1 | +------------------+ |
Open RPC channel
RPC subcommand: 00h
调用 IN(OUT)
前,需要设置的寄存器内容:
1 | EAX = 564D5868h - magic number |
返回值:
1 | ECX = 00010000h: success / 00000000h: failure |
该功能用于打开 RPC
的 channel
,其中 ECX
会返回是否成功,EDX
返回值会返回一个 channel
的编号,在后续的 RPC
通信中,将使用该编号。这里需要注意的是,在单个虚拟机中只能同时使用 8 个 channel(#0 - #7)
,当尝试打开第9个 channel
的时候,会检查其他 channel
的打开时间,如果时间过了某一个值,会将超时的 channel
关闭,再把这个 channel
的编号返回。如果都没有超时,create channel
会失败。
可以使用如下函数实现 Open RPC channel
的过程:
1 | void channel_open(int *cookie1,int *cookie2,int *channel_num,int *res){ |
Send RPC command length
RPC subcommand: 01h
调用:
1 | EAX = 564D5868h - magic number |
返回值:
1 | ECX = 00810000h: success / 00000000h: failure |
在发送 RPC command
前,需要先发送 RPC command
的长度,需要注意的是,此时我们输入的 channel number
所指向的 channel
必须出于已经 open
的状态。ECX
会返回是否成功发送,具体实现如下:
1 | void channel_set_len(int cookie1,int cookie2,int channel_num,int len,int *res){ |
Send RPC command data
RPC subcommand:02h
调用:
1 | EAX = 564D5868h - magic number |
返回值:
1 | ECX = 000010000h: success / 00000000h: failure |
该功能必须在 Send RPC command length
后使用,每次只能发送 4 个字节,例如:如果要发送命令 machine.id.get
,那么必须要调用 4 次,分别为:
1 | EBX set to 6863616Dh ("mach") |
ECX
会返回是否成功,具体实现如下:
1 | void channel_send_data(int cookie1,int cookie2,int channel_num,int len,char *data,int *res){ |
Recieve RPC reply length
RPC subcommand:03h
调用:
1 | EAX = 564D5868h - magic number |
返回值:
1 | EBX = reply length (not including the terminating NULL) |
接收 RPC reply
的长度。需要注意的是所有的 RPC command
都会返回至少2个字节的 reply
的数据,其中 1
表示 success
,0
表示 failure
,即使 VMware
无法识别 RPC command
,也会返回 0 Unknown command
作为 reply
。也就说,reply
数据的前两个字节始终表示 RPC command
命令的状态。
1 | void channel_recv_reply_len(int cookie1,int cookie2,int channel_num,int *len,int *res){ |
Receive RPC reply data
RPC subcommand:04h
调用:
1 | EAX = 564D5868h - magic number |
返回:
1 | EBX = 4 bytes from the reply data (the first byte in LSB) |
在实际的逆向分析中,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
。
1 | EAX = 564D5868h - magic number |
Finish receiving RPC reply
RPC subcommand:05h
调用:
1 | EAX = 564D5868h - magic number |
返回:
1 | ECX = 00010000h: success / 00000000h: failure |
和前文所述一样,在 EBX
中存储的是 reply type
。在接受完 reply
的数据后,调用此命令。如果没有通过 Receive RPC reply data
接受完整个reply
数据的话,就会返回 failure
。
1 | EAX = 564D5868h - magic number |
Close RPC channel
RPC subcommand:06h
调用:
1 | EAX = 564D5868h - magic number |
返回:
1 | ECX = 00010000h: success / 00000000h: failure |
关闭 channel
:
1 | void channel_close(int cookie1,int cookie2,int channel_num,int *res){ |
VMware-vmx分析
结构体分析
1 | 00000000 channel struc ; (sizeof=0x59, mappedto_361) |
程序分析
1 | __int64 __fastcall sub_189310(__int64 a1, __int64 a2) |
漏洞点分析
1 | case 5: // case 5:结束接受返回流程 |
增加了执行路径,导致可以在 case 5
中执行 free(out_msg_buf)
流程:
1 | void __fastcall finish_recv(__int64 a1, unsigned __int16 a2, char reply_type1) |
漏洞利用
可以通过 多次执行 case 5
释放 输出缓冲区,最后制造一个 double free
的漏洞。然后利用 info-get
和 info-set
命令,实现堆块的分配。利用RPC
的发送命令和接受返回来实现堆块布局:
1 |
|
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2021/05/28/2018-RealWorld-Station-Escape/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!