2019 RoarCTF WriteUp
🏛️ 组织架构与规模
主办方:由国内知名信息安全媒体“嘶吼(4hou)”与 W&M 战队联合主办
比赛时间:2019 年 10 月 12 日 10:00 至 10 月 14 日 10:00,共计 48 小时
比赛赛制:线上 Jeopardy-style( jeopardy 解题模式) CTF
参赛规模:吸引了全球超过 2000 名选手参与
| 排名 | 队伍名称 | 解题数 | 得分 |
|---|---|---|---|
| 1 | 0ops | 25 | 14407 |
| 2 | whitzard | 23 | 12931 |
| 3 | 星盟突击队 | 19 | 9151 |
| 4 | Vidar | 17 | 7507 |
| 5 | pwn4fun | 15 | 6598 |
| 6 | Venom | 15 | 6292 |
| 7 | Lanc3t | 13 | 5824 |
| 8 | 楼上请让路 | 14 | 5658 |
| 9 | 如果有人愿意带带我 | 13 | 5186 |
| 10 | Kap0k | 12 | 4566 |
| 11 | C4M31 | 10 | 4366 |
| 12 | 天枢Dubhe | 11 | 4318 |
| 13 | CNSS | 11 | 3917 |
| 14 | Ginkgo | 12 | 3892 |
| 15 | K__K | 11 | 3882 |
| 16 | SUS2019 | 11 | 3768 |
| 17 | 0b1111 | 11 | 3720 |
| 18 | C1sec | 9 | 2994 |
| 19 | sixstars | 8 | 2972 |
| 20 | or4nge | 7 | 2780 |
Misc
黄金 6 年(数据附加隐写 + MP4 截取)

附件是一个 MP4 视频文件,通过 WinHex 打开,发现在末尾隐藏的 Base64

解码是一个 RAR 压缩文件

解码发现需要密码,通过 Free Video to JPG Converter,将视频中的图片提取出来
提取出含有二维码的四张图片,并对四张图片中的二维码单独截图出来,通过 QR Rearch 进行扫描,并获得四个 Key 的密码字符串

拼接得到解压密码 iwantplayctf

davinci_cipher(Unicode + Emoji AES + Wacom 数位板流量分析)

flag.txt 中是 Unicode 编码

解码得到 Emoji 表情包

打开流量包有 USB 协议,直接过滤

在第一次对设备请求的响应中找到关键线索,DEVICE 指定了索要的类型是 “设备描述符”

查看会话统计发现与 1.9.1 接触的最多

Wacom 就是数位板,题目提示达芬奇画画,所以我们需要提取数据出来绘图
从抓包文件 k3y.pcapng 中,提取出所有 USB 数据包中出现过的端点地址
1 | # -Y usb:显示过滤器 |

0x00 和 0x80:这是设备插上电脑时 “打招呼” 的握手包(识别设备型号),接下来只要查看 0x81 端点的数据
| Endpoint | 作用 |
|---|---|
| 0x00 | 控制输出 |
| 0x80 | 控制输入 |
| 0x81 | Interrupt IN |
USBPcap 结构 = Header + HID Report,其中 Header 占 27 字节,需要跳过
pcapng 是由多个 Block 组成的
1 | pcapng file |
官方规定
| Block | Type |
|---|---|
| Section Header Block | 0x0A0D0D0A |
| Interface Description Block | 0x00000001 |
| Enhanced Packet Block | 0x00000006 |
| Simple Packet Block | 0x00000003 |
所以我们可以指定 ENHANCED_PACKET_BLOCK_TYPE = 6 从而直接抓取真正是数据包
再查看 USB 是走的哪个网络接口,在统计 –> 解析文件属性中
Interface 0 和 Interface 2 都是 WLAN,Interface 1 则是 USB
所以可以指定 TARGET_INTERFACE_ID = 1

USB 协议定义 4 种传输方式
| 类型 | 特点 | 典型设备 |
|---|---|---|
| Control 控制传输 | 设备初始化、配置、查询 | 所有USB设备 |
| Interrupt 中断传输 | 小数据、低延迟、人机输入 | 键盘、鼠标、Wacom |
| Bulk 批量传输 | 大量可靠数据 | U盘、硬盘 |
| Isochronous 同步传输 | 实时连续数据,不保证重传 | 摄像头、麦克风 |
在数据包结构中的位置如下
1 | ┌──────────────────────┐ |
所以 REPORT_TRANSFER = 1,开始编写脚本
1 | import struct |
编写脚本绘图
1 | import matplotlib.pyplot as plt |

拼接完整得到 MONA_LISA_IS_A_MAN,Emoji AES 解密得到 flag

Reverse
TankGame(Unity)

附件目录名提示使用 Mono 反编译,直接用 dnSpy 反编译 /Managed/Assembly-CSharp.dll
找到了 WinGame() 函数

1 | public static void WinGame() |
也就是说 flag 取决于地图状态,接下来去看初始地图

但是 WinGame() 检查的是游戏结束时的地图,两者可能不同,比如:坦克开炮,子弹撞墙
在代码中有这样一个条件 if(nDestroyNum == 4 || nDestroyNum == 5)
所以接下来的思路就是找可以被破坏的位置,查看 MapCreation.Initmap()

这里唯一 100% 确定的是 if(num != 8),当 MapState 的值为 8 时,不创建任何游戏物体。即空地
前面代码没有读取 MapState,而是直接 this.item[6] 生成一圈东西,总共四次,应该对应着边界
1 | for (int i = -11; i < 12; i++) |
继续分析 CreateItem 虽然传入了 id,但是没用

继续分析其他的类,在 Bullect 类中找到核心逻辑
1 | // 当子弹击中墙壁时 |
这里 MapManager.nDestroyNum++;,所以玩家需要摧毁 4 个墙或者摧毁 5 个墙
继续往后看
1 | // 当子弹击中非墙壁物体(如敌人)时 |
现在所有变化规则确定,打墙和打敌人都会增加已摧毁物体数量
解题思路就是将 21×17 的二维地图压缩成一维字符串,预先记录所有可破坏 Wall 的位置,通过枚举被摧毁的 4/5 个 Wall(以及 Heart+Wall组合),直接修改对应字符生成候选 MapState,计算 SHA1 与目标值比对,找到正确地图后再计算 MD5 生成 flag
1 | import hashlib |
