抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

《打 THUCTF 打的》

终于大四了,有一点闲心搞一点神奇的东西。

上一周你清的 CTF 队举办了 THUCTF(从网站上来看,貌似是个临时网址,因此没啥必要放还是放一下)。这场比赛是和北大联办的,对外的名字貌似叫 PKU GeekGame 3rd

最终解出 30/53 题,居然排名还挺靠前。。。

这里写 blog 不会聊太多技术方面的东西,毕竟我仅仅学完了计算机本科的相关专业课,并没有接受过系统的 CTF 竞赛培训(不过最后还是附上我的 Writeup)。这里首先还是会聊一些自己参与比赛的感受。

首先是一些初印象:题目真的是量大管饱。周六中午加题的时候属实是吃了一惊(当然也趁机抢了个 FB),这也让赛程实打实的 span 了一周。比较遗憾的是,上周恰好手头还有好几件另外的事情,导致上周整个人略微有点疯癫。。。

作为一个资深的老 OIer,其实这场比赛很多地方有令人感到亲切的地方。这可能和参与 CTF 的成员很多也有 OI 经历有关。最明显的就是多项式三件套(其实我觉得这三道题对非 OIer 有点不大公平,毕竟 Subtask2 反编译完我看到那几个 for 就可以直接【一眼丁真,鉴定为 FFT】了,虽然出题人的写法和我的有些差别,最后凹输出的时候稍微费了点劲)。另外,正则那道题也让我想到了某段时间在高等级竞赛中流行的【自定义编程语言题/造机题】。所以打完这场比赛后,我奉劝某些想出奇葩提答题的出题人,可以尝试投一下 CTF。。。

另外,这场比赛展现出来的特质与其说是【安全】领域的竞赛,不如说是【互联网综合能力】的考验。比赛自然会有一些比较传统的安全攻击题目(比如说像 Pwn 系列和 Web 系列的部分题目),更多的题目展现出来的则是需要综合自己各种经验才能解出。比如说 service 那题,看代码稍微思考之后感觉是可以做一些类似 SQL 注入的事情,然后就去查 service 的用法,看到多篇博文强调了 service 的字符串拼接特性(其实在这里看源码会有点绕路,因为大多数的篇幅是在处理环境变量),于是可以轻松解决。这个过程在我看来是自然的,不过这道题过的人感觉不如一些看起来似乎是经典题(指在 ctf-wiki 中有专题)的玩意儿。另外,像【汉化绿色版】这个题组的题,它解包几个数据文件,在我眼中,也是经典的了解一个完全不熟悉领域的过程(在这里是某种游戏引擎的 hack 工具)。

我之所以如此看这点,也许也和在各种地方答疑时的高血压有关。毕竟学会从各种渠道上获取信息应该说是在互联网上一项重要的技能。《提问的智慧》的开头部分,“善用搜索引擎”,也包括《都市天际线》的名梗The Ability to read都在说这件事情。或许 CTF 这种特质可以扩展到非技术领域(你想搜索的是不是:NazoGame)。

当然也会有翻车的时候,参考 rk1 ouuan 的 writeup,比如说 Darker Room,我也套出来了代码,也感觉可以时间侧信道,但是我的判断是:上下文不足以让我充分理解这段代码。而 Web 组的 Vivo50 题,我也想到了第二个 bug,但是由于某种神秘原因,并没有把它触发出来(也许是操作失误)。至于说方块人三题,如果之前没碰过 MC(比如像我,在小学之后就没玩过),一周之内掌握这些还是有一定难度的(?)

就说那么多吧,反正这一周过得过分的充实了。。。


Writeup

此处略去意义不大的签到题。

P.S. 写完 writeup 之后偶然看到了往年大佬的,发现他们的 writeup 内容会更丰富(包括关键步骤的代码和截图。)为了偷懒,我就没有复制赛事组要求的 flag 获得界面截图。同时,出于我一贯的习惯,我很多情况也懒得贴代码,而会用文字尝试叙述相关过程。这也就导致了可能会比较抽象,请见谅。

猫咪状态监视器

观察代码,我们知道 STATUS 指令只是将输入拼接到 /usr/sbin/service中,而查阅 /usr/sbin/service 相关信息得知它也只是完成将该指令拼接到 /etc/init.d 后。因此我们只需要使用 .. 即可运行其它目录下的指令,例如 ../../bin/cat

未来磁盘·小

将压缩包解压至最后一层。观察其二进制文件内容,可以发现全 0 内容进行过一次压缩后内容仍然具有规律性。因此我们只需要找到循环节删去多余的循环节内容。这里我采用人工搜索的方式找到循环节并编写代码删除。

注:我怀疑第二问也是类似的做法多嵌套几次。不过由于人工找循环节有点困难,所以没有验证这个想法。(或许可以用 KMP 类似算法查找?)

Dark Room

在网上找到原版游戏,根据其地图和代码功能容易确定通关方式。

而对于 san 值的要求,注意到原代码存在 bug,在开门增加 san 值时并没有检查门是否已经开启,因此可以通过这个指令刷 san 值。

简单的基本功

根据另一个文件 chromedriver_linux64.zip 和其大小在网络上找到对应的包(89.0.4389.23 版本)。使用 ZIP 明文攻击工具进行破解。注意在压缩明文时需选择和目标压缩包相同的 Store 方式。

easycrypto-{1,2}

Python 文件提示第一问仅将大小写分别 Shuffle。使用字母频率分析,最多的 c 对应原文的 e。

但是剩余几个高频字母区分度不大。此时我注意到这个单词 e_e_e___ 很有特征,通过在词典中搜索,得到很有可能是 elements。此时另外逐渐又多个词变得容易辨认,如 (re-)releases, and,
although 等。之后结合上下文即可解码出小写字母部分和 flag1.

通过阅读符号表以及简单的替换尝试,二进制可执行文件的作用为输入 table 和 flag,输出 table(base64(flag))。

剩余的大写字母关系到人名,需要通过网络检索获得。但仍然有小部分在密文中字母无法确定,因此采用枚举法,观察输出,筛选符合 flag 格式的。最后依然有数个答案,逐个提交尝试。

第一问要求根据提供了 Random 伪随机数发生器前 2500 字节的输出,要求我们推测出后面若干位并解出 flag。

randcrack 提供了这一功能,只需要将前 624×4624 \times 4 字节的内容输入给 randcrack 即可猜测出随机序列。

在实现攻击时,不同位宽、值域的随机数生成方式需要和 random 库完全匹配,因此我参阅了 random 库的源码。

第二问在第一问的基础上,给出了一个随机数种子,并要求输入另一个随机数种子。输出结果会再异或上这两个随机数字节流偏移相同随机值后的内容。

根据 cpython 的实现,负数随机数种子会取绝对值后输入。可以利用这一点使得两个随机数发生器内容完全相同而相互抵消。后面的过程和第一问一样。

第三问完全不需要任何随机数生成相关知识。本问没有检查输入是否与原来的种子相同,因此完全可以原样输入。又注意到 zip 遍历的范围是较短迭代器的长度,因此可以直接只输入第一项的值。

Another V ME 50

阅读 python 文件得知希望我们找到两个串(以同一前缀开头),sha256 后 14 位相同。

注意到根据生日悖论,我们期望只需要 16142×108\sqrt{16^{14}} \approx 2 \times 10^8
次尝试,在脱机计算可接受范围内。因此只需随机生成字串并放入哈希表中即可。

babystack

代码虽然虽然有输入长度检查,但是由于长度变量(记为 len)为 unsigned 类型,且比较的是 len - 1。因此当输入为 0 时,len - 1 溢出,导致长度检查形同虚设。之后使用常规方法将 PC 引导到 system 处。

关于调用 system 的段错误,我在网上搜索到的说法是不要从 return 到开头,直接跳转到填写参数的地方。但显然是提示中的解释更加本质。

禁止执行,启动

查看 lldbhelp 发现可以使用 memory 指令写入内存。因此加载 busybox 程序后在开始位置后打断点,随后使用 memory write 指令向内存写入攻击指令,并使用 jump 跳转到写入处。

初学 C 语言

本题要求使用 printf 的 format string 攻击。显然可以通过构造过量的占位符来尝试输出。

本人对 va_list 的原理不大清楚,但可以推测过量的参数会溢出到栈上。因此通过增加输出量并观察,发现在某一位置出现了 flag string。(需要使用 64-bit 占位符,且 %s 我尝试会崩溃)。随后将输出内容转化为对应的字符串即可。

简单的打字稿

使用【类型体操】技术将 flag 的第一位移除,然后报错信息就能正常显示。

Chrone-1

在线上容器中的 pow 使用下发文件中的穷举法破解即可。如果嫌时间不够可以修改 Cookie 的过期时间

根据代码,我们需要在 chrome 中实现某种方式的跳转,使得 hostname 不再是 localhost。内部环境禁止了 javascript,而在 <head> 内跳转也无法在 domcontentloaded 前跳转。

根据提示,可以输入一个很长的 URL (我使用 20 KiB 左右的输入),在本地 debug 时发现它跳转到了 chrome 内部的报错页面。

Emodle - Level *

根据提示,我们可以观察网页的 session 信息,session 前两部分均为 base64 编码,第一部分为算法,第三部分(疑似)为签名。第二部分则保存了所有的游戏状态。

子任务中直接保存了答案,直接复制提交即可。

子任务一中保存了 Remaining times,因此我们只需要每次都提交相同的 Remaining times 就可以破解。

猜谜的方式我采用暴力的方式:每次提交 placeholder 中的内容,并将返回的结果中黄块和绿块对应的 emoji 加入集合。当集合大小收敛时进入第二阶段。第二阶段枚举集合中的 emoji,提交该 emoji 重复 64 遍,返回的绿块位置就是它所在的位置。

子任务三额外保存了随机数种子和开始时间,并增加了 1 分钟的解题限制。由于签名的存在,我们无法修改开始时间,但是我们只需要使上述脚本在一分钟内运行完毕即可。

{绝妙, 美妙, 巧妙}的多项式

使用反编译工具(binary ninja)将程序转化为人能看的内容。然后就 OIer 狂喜

阅读代码得知输入均为 Flag 的 ASCII 数组。子任务 1 为将其看为系数多项式从 1 到 nn 进行求值,与给定结果比较。子任务 2 为 FFT(的魔改版,不包括位反转),子任务 3 为多项式乘法(对 x64x^{64} 取模)。

因此只需完成其逆操作即可:子任务 1 为插值,子任务 2 为 IFFT,子任务 3 直接 O(n2)O(n^2) 逆推。

绿色汉化版免费{普通、高速}下载

游玩游戏,发现 flag1 的内容被隐藏,怀疑采用了和背景色相同的颜色。

因此尝试解包 data.xp3。我使用的是 xp3-rs 包,解包后在游戏脚本中就能看到 flag。

第二个任务需要解包存档文件,使用 kirikiriTools 可以解密。解密后在 data0.kdt 中可以看到第一遍输入的 hash 值,这样信息不足。但是我们可以在 datasu.ksd 中看到每个字母按键的点击次数,这样信息就足以唯一确定 flag 了,暴力搜索即可。

Z 公司的{服务器、流量包}

服务器使用的是 telnet 协议,文件传输使用 Z-MODEM 协议。

结合传输 flag1 时的抓包结果、 ZMODEM 协议说明和jpg 文件格式容易定位数据包的位置。在中间还需要处理转义符 ZDLE 和子数据包的尾部信息。(一开始这里没有找到好的参考文献因此卡了很久,最后参考的是这里

关键词过滤喵,(.*)谢谢喵

字数统计的算法思路如下:

首先将所有字符转化成特定的一个 emoji。然后尝试将其转化成十进制数。设一个 emoji[i] 代表 10i10^i,那么我们可以依次将 10 个最开头的 emoji[i-1] 变成 emoji[i]。然后将 10 个以下的 emoji 转化为对应的数字。

需要特别处理的是某一位为 0 的情况(其中长度为 0 的字符串直接特判),我们可以处理完一位之后在末尾添加一个分隔符,这样可以通过分隔符的位置来确定 0 添加在哪里。

排序算法如下:

首先删除空行 \n\n \rightarrow \n

然后在每行开头添加字符串长度个 emoji(后面记作 E): (.) \rightarrow E\1, repeat (.)E \rightarrow E\1

随后每次循环删除最开头的一个 emoji (E(E*) \rightarrow \1),并将没有 emoji 的行类似于冒泡排序的方式和有 emoji 的行交换,使其移动到开头((E+)([^\nE]+)\n([^\nE]+) \rightarrow \3\n\1\2),直至所有 emoji 被删除。