翻译

本文另有 英文版本。

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

Before

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

Day 1

比赛一上来主办方 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 这样的顺序排列,真是够奇葩的设计……

barb-metal 字节码

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

barb-metal 漏洞代码

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

ooows-p92021

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

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

p92021 调试信息

多亏另一道题 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 了。

Day 2

hyper-o

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

hyper-o 地址转换代码

这段代码用来获取 vmxon_virtual[v0] 的物理地址,最后一行实际上可以改写成:

1
vmxon_physical[v0] = (__u64)&vmxon_virtual[v0] + 0x800000 + v2;

实际上的转换关系是:

1
2
3
4
pa = ((va + 0x800000 > 0xFFFFFFFF80000000LL
           ? phys_base
           : 0xFFFFFFFF80000000LL - page_offset_base) -
      (va + 0x800000));

结果 0x800000 被 IDA 识别成前面的结构体里的偏移,让人感到匪夷所思。如果上面这个还好理解的话,那么下面这些 0xCA000、0xFFFFFFFFFFD7C000LL 之类的就让人完全不知所云:

IDA 地址分析结果之一

IDA 地址分析结果之二

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

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

ept_map_memory off-by-one

off-by-one 导致原本 0x200000 大小的内存增加了 4 KB,恰好能改掉 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 被放在了最后,之前是 DEF CON 的运维、主办方、各种工作人员上台致辞,甚至还提到了有两个人在现场没带口罩被轰了出去……我实在撑不住就睡着了,等我再被吵醒的时候会议室里的人已经在欢呼了,等了半天看了个寂寞,之后就匆匆赶回酒店睡觉了。

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