今天杭州又是阴雨连绵的天气,实在叫人打不起精神。很久以前我就发誓要改掉拖延症的毛病,但是几年过去依旧没有什么改变,想起自己去年的DEF CON CTF 2020 Finals和2021 Quals之后都信心满满地打算发点东西,但到最后都不了了之,这次不能再鸽了,写一写自己在上海的2天。

Before

和Quals一样,比赛是在上海的腾云大厦会议室,科恩实验室的后勤工作十分完美,零食饮料不间断供应,可惜这次没有万丽可以住了,于是AAA订了附近的和颐酒店,本来还打算中场休息的时候回去睡个觉划划水,但最后也没有回去。

Day1

比赛一上来主办方OOO就犯了个错误,把所有题目都泄露了出来。题目里有一个ooows系列,都是和虚拟设备相关。我想着先挑个软柿子捏一捏,就先玩了玩zero is you,这个游戏是根据Baba is You改的,大致逻辑是拼好CPU is run之后就执行shellcode,还要求移动的步数最少,显然我对算法一点也不在行,这题没啥可贡献的……

barb-metal

题目给了一个mruby字节码和一个mrubyc解释器,mrubyc里注册了Thermometer、Alarm等几个设备类,模拟了一个裸机传感器平台。mruby字节码里主要对参数进行校验,然后传给mrubyc里的对象。

这种mruby字节码最大的特点在于所有的指令都是对相邻的寄存器进行操作,比如OP_SEND用作函数调用,如果R1被用作this指针的话,参数就必须按照R2,R3,R4这样的顺序排列,真是够奇葩的设计…

最开始对题目的字节码逆得不够仔细,没看出来下标越界的漏洞,直到后来在解释器里找不到洞,椒哥又问了一遍mruby的检查有没有问题,这才看出来time和date都有溢出问题。赛后看了一眼主办方给的源码,这里漏洞简直是白给,感觉有些可惜。

另一个漏洞在Speaker里面,但是Speaker里面用了一个数组实现的堆,结构过于复杂,没有逆出来,造成最后一直挨打,看来自己的逆向水平还是有待提高。

ooows-p92021

barb-metal下线之后我就来看p92021,好在之前ooows-flag-baby已经被大佬们解决了,已经搞明白这个ooows系列是研究虚拟设备里的漏洞,我们需要上传一个BIOS固件上去进行攻击。

p92021是实现了一个9p协议的文件服务器,9p的文档十分凌乱,而且各个版本的结构还有些许的不同,只能照着binary里的代码一点点猜。龟爷找到了里面的UAF,我们立刻patch好了自己的程序,但是却不知道怎么写代码和9p交互。

多亏另一道题ogx里有调试信息,把里面的结构体复制到p92021里面,我们搞清楚了VMM里的MMIO的实现。这个题目是借助virtio驱动实现的9p协议交互,然而virtio需要一些列的vring结构体才能工作,用汇编实在太难写,我就找来了U-Boot的virtio驱动的代码,改装了一下,拿来当固件用。

我和Kira整个下午和晚上都在调试改装的virtio驱动,我有些撑不住就去了隔壁的气垫床上睡觉,我印象中睡了不到2个小时就被TTX叫醒了。TTX跑到我身边悄悄和我说起来做题了,我在梦里差点以为见到了鬼,醒了之后站在原地愣了半分钟才回过神来,看来CTF真是一项不健康的运动。

Kira又调试了一段时间,终于搞定了编译和链接的问题。把之前准备的9p协议的payload装到virtio的scatter-gather结构体里发送给0号ring,从1号ring读出flag写到串口就ok了。

Day2

hyper-o

主办方之前说hyper-o不放了,但到后面又忽然说要放一个改过的题,新题会用到旧题里的hyper-o.ko。这题目是用VT-x指令集实现了一个虚拟机,用来跑shellcode,我和Kira看了EPT的实现,没看出来里面有什么问题。看页表的过程中踩了不少的坑,一旦结构体里涉及到了虚拟地址和物理地址的转化,IDA对结构体成员的分析就显得十分无力,程序里用来获取物理地址的pattern如下:

这段代码用来获取vmxon_virtual[v0]的物理地址,最后一行实际上可以改写成vmxon_physical[v0] = (__u64)&vmxon_virtual[v0] + 0x800000 + v2;,实际上的转换关系是pa = (( va+0x800000>0xFFFFFFFF80000000LL ? phys_base : 0xFFFFFFFF80000000LL - page_offset_base ) - (va+0x800000),结果0x800000被IDA识别成前面的结构体里的偏移,让人感到匪夷所思。如果上面这个还好理解的话,那么下面这些0xCA000,0xFFFFFFFFFFD7C000LL之类的就让人完全不知所云:

一个0x800000给逆向带来不少的麻烦,耽误不少时间,关键时候还是要直接看汇编才比较靠谱。我一个白嫖用户就不吐槽IDA的拉跨了吧。

我们实在没找到洞,便开始猜测是不是多CPU的race问题,或者vmlaunch的过程中某个寄存器没设置正确,我还和Xshj看了看.altinstr_replacement段有没有猫腻,到了最后甚至还翻了翻KVM、VirtualBox和VMware的实现,结果发现VirtualBox在切换到虚拟机的时候会pushf保存RFLAGS寄存器,而KVM不会,然而调试之后发现RFLAGS并不会受虚拟机影响。直到后来有人才发现ept_map_memory里面有一个off-by-one,又是一个无比寻常的漏洞,没有及时看出来。

off-by-one导致原本0x200000大小的内存增加了4KB,恰好能改掉EPTP的内容。找出漏洞之后大佬们就开始研究利用,但这时候StarBugs已经拿到了一血,我们搜了搜流量找到了作业。但主办方的运维实在是不给力,先是patch被revert掉,之后我们直接连网络都访问不了了,只能坐着挨打……好在最后只有4个半小时,没过多久比赛就结束了。

看到wzh大佬调试shellcode过程时,往shellcode最后加了一个vmcall指令,如果shellcode正常跑完,没遇到段错误之类的话vmcall就会造成VM-Exit,而且有一个独特的退出代码VMX_REASON_VMCALL(18),这样只要看log就知道shellcode有没有跑完,虽然之前研究vmtools的时候碰到过vmcall这条指令,但没想到居然还能这么用,这招实在是厉害,或许这就是我和大佬之间的差距吧(

other

其他的题目我都没有仔细看过,听dydxh说ogx不是Intel SGX指令集,而是Intel MPX,想要用它模拟一个enclave,可惜自己电脑CPU太老,根本没有这些指令集,没得可玩。broadcooom貌似是一种别的架构的固件,研究这题的人挺多,再加上我很想睡觉,就没有看。

After

比赛结束的时候是凌晨5点半,我还想看一看closing ceremony,就留在了腾讯,没想到CTF被放在了最后,之前是DEFCON的运维,主办方,各种工作人员上台致辞,甚至还提到了有两个人在现场没带口罩被轰了出去…我实在撑不住就睡着了,等我再被吵醒的时候会议室里的人已经在欢呼了,等了半天看了个寂寞,之后就匆匆赶回酒店睡觉了。

这次DEFCON和去年相比,观赏性有些下降,没有去年的打飞机、扑克牌之类的游戏,但是题目变得硬核了许多,和去年单纯地参加逆向工作不同,今年的参与感更强一些,我patch了mruby字节码,也试着写了virtio驱动,有不少的收获,希望明年还能抱到大腿,继续参加DEF CON CTF。