USTC Hackergame 2018 小结

最近一边在前后端开发实习一边在复习准备考试,还是挺忙的,或者说,嗯,忙到爆炸。拖这么久才更新博客,顺便也更新了自己的 Journal. 主题。

这大半年始终处于迷茫之中,心情一直不是很好,只能经常找些事情做一做。刚过完十一假期的时候,群里有人发了个链接,看了看是 USTC 发起的 CTF 模式的线上竞赛[1](其实是游戏啦)。最终的参与人数大概有 2500 人?因为自己比较弱,我最终只拿到了 #37 的排名[2]

不过既然是想着找点事情分散注意力,还是重在参与,解题的过程不还是挺有趣的么。

签到题

是个 <Input/> 的小把戏,找个带 Inspector 的现代浏览器改一下即可。你非要 cURL 发 POST 请求也不是不可以。

知识问答

答案略。有没有找回小时候抄答案结果「答案略」的失落感?

借助 Google 即可。

Puzzle 拼个图

上来就是一个压缩包,解压之后就是如下的碎片,这不明摆着要拼图么。

呃,我好久不玩拼图了。要打印出来么?算了,用 Adobe Illustrator。

It works. (强迫症患者请主动忽略 1px 级别的缝隙)

群里有人直接取消桌面图标对齐,在桌面上用缩略图拼,我觉得点子不错,是个人才。

被猫猫玩坏的代码

其实仔细观察就不难发现它只是一个被按照某种方式变换了列的文件。

VIM 是好东西,但是后面别的题我们用 VIM,这道题就让 VSC 出场吧。

几番折腾之后,代码看起来像正经 C++ 了,应该可以用了。

不过我的 Clang 比较旧,C++17 有些无力,于是我随便找了个 Online compiler 跑一下。

能直接跑 C++17 的网站还是不多,虽然磕磕绊绊,但至少还是吐出了我们想要的 Flag。

面对 0 Errors, 9999 Warnings 毫不慌张是一个程序员的基本素养。

我是谁

说实话,我也不知道我是谁,我可能是玛奇朵,也可能是你们熟知的杯装饮料。

他们这个页面原始的 padding-top 看的我特别难受。

上来便是一个简单的表单,出于直觉应该打开 DevTools 碰碰运气。

这页的答案应该是 TEAPOT。关于 HTTP 418 I'M A TEAPOT,有兴趣的同学可以参考 RFC 2324[3],啃英文不适的同学也可以看中文维基百科页面[4]

拿到 Flag,页面给出了通向下一个谜题的链接。然而……

其他 Methods,看来直接 GET 还是太天真了。想到这里,我们还要考虑到这个小家伙可能是个茶壶,我们是不是应该用…… BREW 方法来请求它呢?毕竟上文的 RFC 2324 中也有提到过这个方法。

1
2
3
4
5
6
7
8
2.1.1 The BREW method, and the use of POST
...

A coffee pot server MUST accept both the BREW and POST method
equivalently. However, the use of POST for causing actions to happen
is deprecated.

...

那就来试一下……

似乎少了一些重要的东西,根据提示应该就是 Headers 的锅啦。

1
2
3
4
5
6
7
8
4. The "message/coffeepot" media type

The entity body of a POST or BREW request MUST be of Content-Type
"message/coffeepot". Since most of the information for controlling
the coffee pot is conveyed by the additional headers, the content of
"message/coffeepot" contains only a coffee-message-body:

...

那我们带上 Content-Type: message/coffeepot 的 Header 看看。

人家是茶壶啦,那就改成 Content-Type: message/teapot 好了。

再根据 Response headers 中的提示,顺藤摸瓜。

We have got two flags in a row. Yay!

Office Open XML

下载到的是名为 OfficeOpenXML.docx 的文件,可以在 Office 中打开,文档表面看只是维基百科 Office Open XML 条目[5]中的内容 ,我无趣的用某个 Hex editor 在文件中寻找了一下包含 flag 的字符串,发现了 flag.txt。既然这个形似文件名东西被找到了,就直接解压好了,得到 Flag。

秘籍残篇

这啥啊……

Malbolge,一种编程语言,可以参考它的 Wikipedia 页面[6],我不想说,太烧脑。类似的语言还有 Brainfuck。

文件是可以使用 Malbolge interpreter 运行的,比如这个用 C 语言写的原版[7],但是也可以随便找一个网站在线执行。

执行的结果是如下:

1
2
3
$ ./mal malbolge.txt
Key: 12345678
Wrong!

随便输入不小于八字符的 Key 便会出现结果,这里没什么思路,就看看第一问好了。

文件空格比较多,文字编辑器字号放到最小,感觉有点 ASCII art 的意思,于是……

中国滑稽大学,有点意思,不过真的很难对齐,这是我对齐最清楚的一次结果。

连猜带看的,拼凑出来 flxg{University_of_Ridiculous}

猫咪银行

你们最好的语言 PHP,好就好在它经常被拿来出 CTF 题。(滑稽)

进入之后是一个简陋的页面,定义了几种货币和转换关系。

当然,下面有我们想要的商品 —— Flag。

完整的买不起,买碎片是没有任何效果的(我试过了)。到这里就要想一下,难道是使用某种兑换和存入的方案可以使得玩家刚好可以赚得足够的虚拟货币来买 Flag 么?似乎不是,毕竟 PHP 是最好的语言。抛开弱类型,最基本的测试大概也就是「溢出」了。

那我就来试试……

呃……

似乎溢出使可以取出收益的时间变得更早了,直接取出即可拿到所有收益。

买爆……

还有冷却时间,不过已经不算什么了。

解决。

黑曜石浏览器

你收到一个女装红包,请使用女装版手机啾啾查看。

HEICORE 还行,出题人都在玩梗。既然没有什么其他线索,那就 Google 一下瞧瞧黑曜石浏览器是啥子。

还有官网,厉害了。但当你准备打开熟悉的 DevTools 时,就会发现网页瞧瞧地清除掉了你的 Console,甚至玩起了 404。

但是网页的源代码还是比较容易弄到的,可以看到有对于 HEICORE 浏览器 UA 的定义。(也是玩梗很开心的样子)

1
2
3
4
5
6
function isLatestHEICORE() {
var ua = navigator.userAgent;
var HEICORE_UA =
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) HEICORE/49.1.2623.213 Safari/537.36";
return ua === HEICORE_UA;
}

那就换下 UA 吧……(嫌弃)

解决。没啥技术含量……

回到过去

是一个上个世纪的大叔用 ed 编辑文件的故事,按照文件提供的内容重放(重新操作)一下就会把正确的 Flag 拼装出来。不过文件中包含 ^C,直接用文件内容代替自己操作或是用了部分不显示 0x1B 的编辑器都有可能把你带到坑中。就像下图一样。

最终 Flag 是 flag{t4a2b8c44039f93345a3d9b2}

生活在今天,我们总是理所当然地认为文本编辑器可以轻松选择光标位置、查找替换、自动保存,这道题倒是为我提供了一个可以去体验历史久远的编辑器是如何工作的机会。这种形似考古的体验也足以让我感到兴奋。

猫咪遥控器

在家里玩猫猫的时候,激光笔是逗猫猫的神器。

题目给出的文件打开后都是类似 RRRRRRRDDDDLLLLLLLLLLLLLLLLDDDDDDRRRRRRRR 的序列,只由 L、U、R 和 D 四个字母组成,总感觉是可以画出来的方向。那就来画画看。

我选择了在 JSFiddle 上[8]使用 JavaScript 的 Canvas,当然 Python 也可以,不过和 VIM 一样,Python 我们后面也会用到,这里就先用 JavaScript 吧。

猫咪电路

Minecraft 的红石电路,先看一遍逻辑电路演示,然后慢慢操作就可以了。最后输出到信标的信号是 1 时,所有开关的状态转写为 0 和 1 组成的字符串就是 Flag 本体啦。

Flag 是 flag{0110101000111100101111111111111111111010}

有 3D 眩晕症的玛奇朵做这道题的时候真的很难受,平时 FPS 不敢玩,MC 也不敢玩太久,玩久就会晕的超难受。

猫咪克星

其实这道题就是制造一个透过 Socket 来接收 Python 表达式、eval 并把结果发回去的程序,人力 eval 就不要想了,会超时的。(笑)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def netcat(hostname, port, content):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((hostname, port))
if content is not None:
s.sendall(content)
data = s.recv(1024)
if data == '':
return
while 1:
line = s.recv(1024).decode("utf-8").replace('\n', '')
if data == '':
break
print(">>> %s" % line)
result = eval(line)
s.sendall(('%s\n' % result).encode('utf-8'))
print("Connection closed.")
s.close()

netcat("202.38.95.46", 12009, None)

但是别高兴得太早,这道题坏就坏在它下发的表达式可能长这样:

1
(((int(5!=**import**('time').sleep(100))|1)*(int(**import**('os').system('find ~')==57)+14))|((int(83!=exit())-4)&(91**82)))

总之就是会掺着耗时操作 and/or 退出,会导致超时,得不到最终的 Flag。

但是 Python 下 hook 一些 built-in 函数实在是太简单了吧,直接 def 函数即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Import:
def system(self, **args):
pass

def sleep(self, **args):
pass


def __import__(x):
return Import


def exit():
pass

最后的表达式是:

1
>>> flag{'Life_1s_sh0rt_use_PYTH0N'*1000}

也就是 Flag 了。

她的诗

拜我对编码产生怀疑之心所赐,这道题我卡关蛮久。

题目给出了一个 Python 文件和一个文本文件,Python 文件的内容主要是按行解码 Uuencode 编码的文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python3
# This script helps you decode "her poem"

from codecs import decode

fin = open("poem.txt", "r")
fout = open("poem.out", "w")

for i in fin:
data = "begin 666 <data>\n" + i + " \nend\n"
decode_data = decode(data.encode("ascii"), "uu")
print(decode_data)
fout.write(decode_data.decode("ascii") + "\n")

fin.close()
fout.close()

文本内容如下:

1
2
3
4
5
6
)+2TM+2TM+2TM
@5&AE<F4@:7,@<V]M971H:6YG(&EN('1H:7,@=V]R;&1F
A=&AA="!N;R!O;F4@:&%S(&5V97(@<V5E;B!B969O<F4N
7270@:7,@9V5N=&QE(&%N9"!S=V5E="YL
:36%Y8F4@:68@:70@8V]U;&0@8F4@<V5E;BQA
...

直接运行 Python 程序会得到 poem.out 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---

There is something in this world
that no one has ever seen before.
It is gentle and sweet.
Maybe if it could be seen,
everyone would fight over it.
That is why the world hid it,
so that no one could get their hands
on it so easily.
However, someday, someone will find it.
The person who deserves it the most
will definitely find it.
...

这首诗解码后的几个小节都出自《龙与虎》、《Clannad》和《氷菓》,但是题目中有文字提醒大家不要在字面意思上纠结,这样看来还并不是和它的内容或是背后的作品有关。

于是参考 Wikipedia 对于 Uuencoding 的介绍[9],其中有一个对于每行编码格式的定义:

1
<length character><formatted characters><newline>

由此可见,每一行首个字符的 ASCII 值便是这一行包含字节的数量加上 32 得到的。

疑点

好像哪里不对劲?

以原文第二行为例:

1
7270@:7,@9V5N=&QE(&%N9"!S=V5E="YL

7 的 ASCII 值为 0x37 即 55,也就是说它标记这一行除了长度字元应包含 55 - 32 = 23 bytes = 184 bits,而这一行除去长度字元还有 32 个字符。又由 Uuencoding 是每四个由 6 bits 表示的字符转换为三个由 8 bits 表示的字符,所以 32 个字符对应 24 个原文字符。

1
It is gentle and sweet.

现在来看一下这一行解码后的结果,只有 23 个字符,你可能会说 \n 也算一个字符,但仔细看一下上面的解码代码就会发现每行的换行符并不是解码出来的,而是在 write 的时候加上的。

看来还是藏了一些不得了的东西。

想个办法

编码后的文本通过改小每行首部长度标记的方法来夹带「私货」的事情败露了,现在我们就要想个办法来把它的「私货」揪出来。

其实做法也不难,因为每行我们只需要改动第一个字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python3
# This script helps you decode "her poem"

from codecs import decode

fin = open("poem.txt", "r")
fout = open("poem.out", "w")

for i in fin:
i = chr(ord(i[0]) + 1) + i[1:] # add this line
data = "begin 666 <data>\n" + i + " \nend\n"
decode_data = decode(data.encode("ascii"), "uu")
print(decode_data)
fout.write(decode_data.decode("ascii") + "\n")

fin.close()
fout.close()

对原有的 Python 进行小改动即可,之后查看输出的文件可能包含一些无法显示的字符,但我们可以确认的是,它的私货暴露了出来,也就是我们想要的 Flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---

There is something in this worldf
that no one has ever seen before.
It is gentle and sweet.l
Maybe if it could be seen,a
everyone would fight over it.g
That is why the world hid it,{
so that no one could get their hands
on it so easily.ST
However, someday, someone will find it.
The person who deserves it the moste
will definitely find it
...

把每行尾部多出来的字符都串起来,得到 flag{STegAn0grAPhy_w1tH_uUeNc0DE_I5_50_fun}

It is painful! Not fun at all!!

不过不得不承认出题人还是挺机智的。

FLXG 和六十四卦

故事讲一大堆,主要线索只有一个文件,来看看内容。

我随便截的,都差不多,截哪里不重要了。根据题目线索这应该是六十四卦,一提到卦,就想到不少把卦和二次元二进制联系到一起的故事。

解密码要码表,现在我们需要一个卦表,于是我在万能的 GitHub 上找到了一个开源项目[10],其中有一个记录卦名与其对应二进制值的 JSON 文件。

我也写了一个粗糙的 Python 程序[11]来转译这个文件中的卦名到二进制数据,在输出时反转了文件的顺序,这样在头部附近就可以找到 Flxg(Flag)。

See what we have found!

不反转的话,你可以把文件拉到底,就是正序的 Flag 啦。

Pwn Me: Calc

我连 checksec 都懒得看,这应该也算是个有点不争气的 Pwn 题吧?

这种用 nc 来玩的东西,要么是像 Python 那道题交互操作最后给 Flag,要么是放在服务器上让你自己想办法看文件。既然题目都疯狂暗示是后者了,那我们就开始挖洞吧。

题目给了一个 calc 程序,但是我当时只有 macOS,无法运行,只能 IDA Pro 走起。

现在建起来了在 macOS 下很方便用的 Kali 虚拟机,在近期解决 CCUT 的一道 Pwn 题时很方便,后续可能会单独写一篇 CCUT 的小结。(虽然我不是报名选手)

进来先搜 Strings,要是直接能搜出来 Flag 字符串,这题也就没啥好做的了。

其实流程也没什么好看的,我想找找可以构造格式化字符串溢出的地方,但是并没有找到。

再看看有没有 systemsh 相关的东西,结果找到了 execlp 的调用。

这不明摆着是扯着脖子喊「Pwn me pls!」么?

还可以看到它注册了几个 Signal handler:

感觉能用上的也就是 SIGFPESIGSEGV。但之前看格式化字符串溢出的机会几乎没有,于是我只好把希望寄托于 SIGFPE 之上,最经典的方法是把一个数除以零,但是我们能看到代码中已经加了(一部分)对于这个情况的检查。

但是还存在一个可能会引发异常的做法,那就是 INT_MIN / -1

本地和服务器上测试一下:

It works again!

值得一提的是,因为我当时不能在本地直接执行 calc 这个程序,于是我便照着 IDA 的结果实现了一个供测试用的版本,并且忘记了把原来程序中的 alarm 加进去,直到在服务器上测试时我才意识到原来的程序只有五秒的操作时间,在本地测试只能 LD_PRELOAD hook 掉 alarm 这个东西。

既然可以执行命令了,那我们是不是可以拿到 Flag 了呢?

试试 ls

1
2
3
4
5
6
7
8
9
Program crashed! You can run a program to examine:
ls -
bin
calc
dev
flag
lib
lib32
lib64

看来还是有 flag 文件存在的,那我们再试试 cat flag

1
2
3
Program crashed! You can run a program to examine:
cat flag
flag

好像哪里不对劲。再去看下 IDA 中的结果,就会发现输入的命令会截断为 10 字符。并且能存在空格,在 alarm 设置为 5 秒的情况下,暴力测试还是比较难受的。

还记得我们之前说过,VIM 会在后面使用,那我们就试试 VIM。

啊哈。VIM 真是太棒了,还帮我们解除掉了 alarm 的问题。那我们就直接用它打开 flag 好了。

VIM: Not here!

Got it!!

后记

草草地总结了一下自己做过的题,和其他做出来计算和密码类题目的大佬相比,是在惭愧。当时比赛时实习那边还搞出了点部署的失误,心乱乱的。不过回想这次经历也是蛮有趣的,赛后看到厉害的 Coxxs 大佬写的 Write-up 感觉很多地方都可以学习。说到这里,我在大概两年前结识了另一位好友,他让我感受到了信息安全这一领域的乐趣,从那之后我也打开了通往新世界的大门,还去 SRC 提交过一次 XSS 漏洞,现在虽然有些迷茫,但也愿他一切安好。深夜编辑,如有错误欢迎指正。

Keep thinking & hack for fun.