这里的rwctf_station-escape的主要基于长亭师傅在知乎的文章进行,在此基础上进行了比较详细的exp分析和调试
静态分析
这里从头开始,一般出现在ctf的虚拟机逃逸会有俩个vmx,一个是原来正常的,另一个是patch过的
使用010editor比较俩个文件的异同
有俩处不同,拖进ida,找到对应的地址看看改了啥
查看被patch过的地址
在0x1893c9(379)指针置null的操作被nop掉了
另一个在0x1893e6(383)处的函数调用中,replay_type&1变成了v7&0x21
+7对应的成员out_msg_buf
如果该句被nop掉,out_msg_buf指针没有被置NULL,其次在第二处patch中,原先被限制的reply type(&0x1)变成了&0x21,也就是说在finish_recv_func(sub_177700)中可以存在free部分再次释放out_msg_buf
这个patch会导致UAF:如果我们在接收完成之后设置了0x21这个位,那么output buffer就会被释放掉,但由于它没有被清零,所以理论上我们可以无限次的将它free掉。可以通过 多次执行 case 5释放 输出缓冲区,最后制造一个 double free的漏洞。然后利用 info-get和 info-set命令,实现堆块的分配。利用RPC的发送命令和接受返回来实现堆块布局
这里的思路借鉴长亭师傅
利用思路
Leak:
1. 开两个channel:A和B,A的output buffer为buf_A,然后A释放buf_A
2. 这时让B准备给guest发output,B会分配一个buffer,我们利用info-set和info-get来控制我们分配的buffer大小,使得B的output buffer: buf_B=buf_A。
3. A再次释放buf_A,这也导致了buf_B被释放。这个时候我们就可以leak出buf_B的fd了,但是这个指针没有什么用,我们想要的是text base。
4. 因此我们再执行命令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
给出作为一个通信的结构体
这里参考了Alex师傅的符号说明
https://a1ex.online/2021/05/28/2018-RealWorld-Station-Escape/
exp分析
主要看的部分主要是leak()和 expilot()部分,channel_recv_finish2()函数就是把rbx改为0x21最后会执行到free操作
Run_cmd()
发送命令进行执行
Leak()
初始化操作,这里使用 info-set命令发送了0x100的信息,在后续只要使用info-get命令就可以执行malloc(0x100)的操作,这里0x100是因为dnd_version为4时会分配一个0x100的Obj。
第一步:开启通道0,此时通道0分配了buf为0x100
第二步:开启通道1,发送指令分了俩部分,在俩个发送指令之间释放了通道0的内存,此时再发送完指令后,通道1的内存块为buf
第三步,再次通过通道0释放buf(因为指针一直没有置0,就可以一直释放),执行"vmx.capability.dnd_version",在发送“vmx.capability.dnd_version” 命令的时候,对应的处理函数中如果发现当前版本和设置的版本不一致,就会调用函数创建新的 object,把原来的版本的object销毁。再开始时发送指令tools.capability.dnd_version 4,会使得这个分配的大小为0x100。此时的object就会到buf的位置。object里包含vtable的地址,再次通过通道1进行数据的返回(之前一直没有),进而得到数据的基地址
Expilt()
类似于leak,此时的buf安排的大小是0x150
开启四个通道,通道0先malloc出0x150的空间
此时通道0指向buf,第一次0释放,分配1通道,此时1指向buf,buf存在
第二次0释放,buf不存在,此时1通道进行写操作,当内存块不存在时,此时1通道写的位置其实是下一块的地址,pay1是一个在bss段上,且一定存在call的一个地址
Exp中的是FE95C8,可以对应下列的位置
其中rax调试会发现一直为0
分配通道2,buf存在
分配通道3,此时分配的地址应该是buf的下一个的地址,也就是会在bss段上分配出一个buf出来,之后再写入system等参数,相当于此时在bss段写入一个可控的的system函数,因为该地址一定会被call,之后再进行通信,就能触发我们写入的函数。
Gdb调试
先正常启动guest 然后在host机中启动
sudo gdb /usr/lib/vmware/bin/vmware-vmx -q
使用ps -aux | grep vmware-vmx得到进程pid
在启动的gdb 中attach上去
(ps:这里有概率失败,我的处理办法是重启再开一次)
获取此时vmware-vmx的基址
输入codebase
此时将断点设在patch的位置
通过ssh远程连接guest执行exp
此是到达了patch中nop的位置
第一次达到
查看堆信息
因为发了0x100字节的信息,再加上堆块的一些size等8字节信息,此时大小未0x110
该地址就是bufA的位置
最后会将基址写到bufA的位置,但这里很奇怪的是,没有挂gdb泄露出的基址是正确的,也可以弹出计算器,但有时gdb一步步调试,泄露出的基地址是不对的,最后执行也会失败,这种情况可以多调试几次。可以确定该位置就是buf,之后所有的操作都是基于这一块内存进行操作
最后就是类似地劫持fd,把tcache的fd改到bss段,然后改函数指针为system,最后弹calculator
到达expilot部分的内存
断点可以设置在很多地方
比如设在codebase+0x1895AF的地方,这里的rdx寄存器存放的就是此时写入的地址,因为每次只能写4个字节,调试消耗的时间比较久
Ida如下
对应的是发送数据函数
这里挑选的bss地址是在程序中一定会执行到的,最后进行一次正常的通信,就会执行system打开计算器
一些资料参考:
https://www.cnblogs.com/studyskill/p/7279970.html
https://nafod.net/blog/2019/12/21/station-escape-vmware-pwn.html
https://xz.aliyun.com/t/7345
https://a1ex.online/2021/05/28/2018-RealWorld-Station-Escape/
https://matshao.com/2019/12/05/RealWorldCTF2018-Station-Escape/
https://www.anquanke.com/post/id/86483