来自强网杯线下赛的一题VbEscape(VE),真的想不到事隔一年多再为这个系列续上一篇。在强网杯的RealWorld题目遇到之前调过的漏洞,心里却是没底。为了做这道题目,眼睁睁看着三大桌面虚拟化软件在我的ThinkPad上大打出手。当然不是因为主力的Hyper-v虚拟机和主办方提供的vmware虚拟机镜像会打架,临时需要把windows系统升级到新的2004版本才能跑;也不是因为用现场龟速外网花了半天升级完成后发现Hyper-v还是会跟vmware嵌套虚拟化打架,而vmware里面的64位virtualbox一定要嵌套虚拟化才能运行;说到底就是因为太菜了,之前调试复现的时候都在debug版本上面,只是跟着exp照虎画猫过了一遍,也没有彻底弄懂。学艺不精因为偷懒欠下的债,始终都是要还的。
Vulnerabilities
题目环境链接:https://share.weiyun.com/FX5WwUT0 密码a2uamv
附件提供一组ovf格式的vmware虚拟机,一个VBoxSharedCrOpenGL_patch.so
文件,以及virtualbox 6.0.14版本的deb包。在漏洞挖掘方面几乎没有设置难度,只要把deb解压,然后通过和path过后的库文件对比一下就能发现问题,这里使用了diaphora工具,比对发现patch只集中在两个函数,分别是crUnpackExtendShaderSource
, 对应函数地址0xf1230
, 以及crUnpackExtendGetUniformLocation
, 对应函数地址0xf2c10
。其实看到这两个函数就基本确定题目是改编自上一篇blog的C3的题目,现在来看看具体做了哪些修改。
0xf2c10 - patched
1 | void __fastcall sub_F2C10(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6) |
0xf2c10 - origin
1 | void __fastcall sub_F2C10(__int64 a1) |
即使是单从字符串的差异也能看出,这个函数去掉了大段的越界检查,因此可以利用该函数来做越界读的操作。
0xf1230 - patched
1 | void __fastcall sub_F1230(unpacker_state *a1, __int64 a2, unsigned __int64 pos, __int64 a4, __int64 a5, int a6) |
0xf1230 - origin
1 | void __fastcall sub_F1230(__int64 a1, __int64 a2, unsigned __int64 a3, __int64 a4, __int64 a5, int a6) |
这个函数的修改就更加微妙一点,我在patched版本的反编译代码加入了少量符号信息。主要是在patch版本在第58行判断越界的时候把其中(int)*pos <= 0 || 0x7FFFFFFF - (int)a3 <= v14
的条件偷换成了(int)pos <= 0 && (v16->field_0 & 0x233) != 0x233
。结合源码的话我们可以更清楚地看到patch所做的修改,相关代码位于VirtualBox-6.0.13/src/VBox/HostServices/SharedOpenGL/unpacker/unpack_shaders.cpp
。
对比原题,结合Tea Delivers的writeup我们可以得出以下结论:
crUnpackExtendGetUniformLocation
部分越界读的漏洞和原题完全一样,然而wp给出的exp中并没有使用越界读,而是通过未初始化漏洞读取堆上残留的指针,因此这部分需要调试堆布局泄露目标指针。crUnpackExtendShaderSource
修改了检查size的条件,目测可以通过传入一个0x233结尾的负数绕过检查,达成越界写的效果。- 调试发现关键结构体
CRConnection
的大小发生变化,原题是0x290,这里是0x238,里面成员变量的偏移也相应需要重新计算。这部分需要结合库文件的代码确定,没办法像上篇一样通过pahole工具很方便地确定。
Debugging
前文提到了Hyper-V和Vmware冲突的情况,为了开启Vmware的嵌套虚拟化,只能暂时禁用掉了日常使用Hyper-V虚拟机。这个问题对比赛影响挺大的,因为常用的脚本工具,AD需要的所有东西都在Hyper-V虚拟机当中,而冲突造成Hyper-V和Vmware两个虚拟机就只能二选一,要打AD就不能同时调试这道RW题目。比赛后段还是切回Hyper-V去AD抢分数了,比赛结束后才把这题做出来。后来的调试方案是通过windows terminal ssh连接到vmware里面,然后结合调试脚本启动gdb attach VirtualBox的虚拟机。
感觉指望微软和Vmware解决这种小众的冲突问题怕是得等上一段时间了,也算是个教训吧,选虚拟机平台的时候还是尽量用Vmware这种业界选手普遍使用的。
为了尽可能虚拟机启动时间,提高调试效率,这里写了一个简单的调试脚本:
1 | #!/bin/sh |
1.gdb 主要是一些gdb的配置
1 | # 1.gdb |
还写了一个自动加载目标库文件基地址的python script,可以自动把库文件基地址保存到$B
,感觉这个方法有点挫,如果有直接用原生gdb script就能实现的方法请告诉我。
1 | # load_source.py |
poc
在原exp的基础上改了一份poc,执行poc1或者poc2都可以造成Virtualbox虚拟机崩溃的效果。poc1对应的是crUnpackExtendGetUniformLocation
越界读的漏洞,poc2对应的是crUnpackExtendShaderSource
越界写换行符的漏洞。
1 | #!/usr/bin/env python2 |
第一个漏洞就和之前一样,这里还是解释一下第二个漏洞,贴一下patch之前的源码:
1 | void crUnpackExtendShaderSource(PCrUnpackerState pState) |
可以看到这版本的源码在第54行前后都加了越界检查,因此不能使用之前的方法来进行越界。但是patch过后允许我们输入一个负数的pLocalLength
比如0xfffff233
来绕过检查,这里可以传入两个pLocalLength
,第一个为负数0xfffff233
,解析负数pLocalLength
之后会把pos指针指向buf的前面,通过第二个pLocalLength
可以来控制越界写换行符的范围。
exp
剩下就是调试堆布局和指针偏移的问题了,发现难点主要是在第一步泄露CRConnection
地址的时候如何确定越界读的偏移,这里给点小tips。
我们可以下断点在0xf2c36的位置,然后查看rbx的值,这时候对应的就是越界读的基地址。
用gef的命令tele $rbx
一直往下翻到0x820的位置可以看到类似的结构
1 | 0x00007fece46780e8│+0x0828: 0x00000000000009e5 |
0x9e5
是CRClient结构体的size,第一个指针指向0x0000000100000000
,正是CRConnection
指针,再下面一个qword 0x15是该client的id。
观察CRConnection
指针就能看到CRConnection
的字段了。
1 | gef➤ tele 0x00007fece4678ad0 |
能够正确泄露CRConnection
和CRClient
的地址以及对应id过后,再确定关键成员变量的偏移,接下来的步骤就和原exp完全一样了。这里给出自己其中一个版本的exp,需要注意是调试发现直接重新启动虚拟机和从快照启动会导致不一样的堆布局,总共调好了两个不同的exp,下面给出的exp是用来打直接启动的。用两台真机测试过都稳定弹计算器,要是现场演示应该也没有问题吧。
1 | #!/usr/bin/env python2 |
Demo
)
Lesson Learned
题目调试下来难度其实不算高,尤其是已经有前人exp的情况下,修改主要是集中在调试越界读时候的堆布局和通过库文件binary确定结构体的成员变量偏移。比赛当中没有解出来,列出几点思考:
- 环境没有准备好,比赛就应该了解到Hyper-V和vmware的冲突以及解决方法,提前升级windows到2004版本,安装Vmware Workstation 16
- 考虑将主力虚拟机从hyper-v迁移到vmware,在RealWorld题目中会省去很多麻烦
- 运用gdb调试还不是很熟练,比如如何实现自动加载目标库基地址的功能,以及如何下断点更能提高调试效率
- windows terminal ssh连接目标机器,配合tmux作为调试环境已经非常好用了
- 心理素质还是有点不够硬,这类题目如果在线上赛,身处熟悉的环境应该能按时解出的。现场气氛比较紧张,加上第一天上来就被windows pwn打蒙了,心态还是有点影响。
大概是学生时期最后一次参加强网杯了, 感谢主办方设计的精彩题目,希望以后还有机会在现场解开RealWorld题目上台演示。