WriteUp:QCTF asong
题目描述
QCTF 的 asong 题目。
题目本体见这里
这道题解压出来有三个文件。
其中 that_girl 是一首歌的歌词。
稍微有点奇怪的是所有空格换成了下划线。
然后 out 是一个二进制文件。
看起来也没什么特别的。
最后 asong 就是 ELF64 文件了,也就是接下来要逆向的东西。
程序输入
把 asong 拖进 IDA 直接跳到 main 函数 F5 可以看到主体逻辑非常简单。
当然这里的标注(包括后面的各种符号名)是我已经看过每个函数后才有的,这才是 Interactive 嘛。
接下来就先简单看下前三个函数。
initialize
点进去后可以看到只有三条语句。
跟解题毫无关系,一边凉快去。
readinput
点进去后可以看到代码如下。
具体逻辑非常清晰,就是造了一个低配版的 getline 轮子,用于读取一行输入。
cutstring
点进去后可以看到代码如下。
逻辑同样很简单,首先判定前 5 个字符是不是 QCTF{
,然后如果最后一个字符是 }
就用 0 截断,最后取出两个大括号之间的内容。
that_girl 读取
跟踪进入 convert_that_girl 可以看到
这里如果直接看反编译结果的话会一头雾水,因为 e_wtf
这个局部变量没有初始化就被直接使用了,甚至还被用来当指针,这程序难道不崩溃?
看了一个小时百思不得其解后,我决定直接去看汇编,发现这里竟然是 IDA 自己的一个 bug,这段函数对应的汇编如下。
可以看到这里很明显根据 x86_64 的 calling convention,函数的第二个参数 wtf
在 rsi 里传递进来后存入了局部变量 e_wtf
中,也就是说应该有一句 e_wtf = wtf
,但是 IDA 反编译结果并没有。
避开这个坑后我们再看这段函数的逻辑。它打开了 that_girl 文件,然后逐字节读进来映射为一个偏移,最后让 wtf
数组中相应偏移的值加一。
这里有一个映射函数 convert_that_girl_char 如下
并没有什么特别的,也不用刻意去理解逻辑,反正到时候复用就好了。
decode 函数
读取完 that_girl 之后就进入了解码函数。
这里我们看到最后一个函数调用中出现了 out
,也就是题目三个文件中的那个二进制文件,很可能与 flag 有关系,所以我们先看它。
writeout
点进去后可以看到代码如下
乍一看没什么特别的,但是这里有一点非常重要,那就是 out
中写入的字节数目等于 flag 去掉首尾的长度,所以我们通过 writeout 首先确定 flag 的长度为 38 + 6 = 44。
同时我们已经有了 out
的内容,也就是说到这里我们才明白题意是根据 out
来倒推 flag。
roundshift
接下来我们倒着看解码过程,首先是 roudshift。
和之前的 babymips 有点类似,这里是一个循环移位,直观来讲:
原来数组是
1 | 1 0 1 0 1 0 | 1 0 1 0 1 0 1 0 | 1 0 1 0 1 0 1 0 | 1 0 1 0 1 0 1 0 | 1 0 1 0 1 |
那么经过这个函数移位后得到的 out 就是
1 | 1 | 0 1 0 1 0 1 0 1 | 0 1 0 1 0 1 0 1 | 0 1 0 1 0 1 0 1 | 0 1 0 1 0 1 0 1 | 0 1 |
可以理解为整个数组首尾相连然后逆时针左移了 3 位。
所以我们很容易可以从 out 逆推出这个函数的输入,也就是说这个变换是可逆的。
index_round
接着我们再看 index_round 函数。
这里很不幸的是我又踩到了 IDA 的坑,就是上面 v[4] = 0
这句,我们直接看汇编。
显然正确的语句应该是 v[1] = 0
,绕过这个坑后我们再看逻辑。
这里中间一个循环看似很复杂,但是我们先看 index_table
就是个 int 数组嘛。
然后再结合每次循环后的 v[1] = index_table[v[1]]
,也就是说是一个指针按照 index_table
的规则在数组 mixed
上跳跃直到回到数组开头。
同时还有一句是 v[1] = index_table[v[1]]
也就是说在指针跳跃的同时,数组本身随之错位。
所以这个函数的作用就是按照 index_table
的规则让数组自身错位,由于经过后来的验证这个过程中并没有发生元素的覆盖,因此这个变换也是可逆的。
偏移
再往前看就到了最关键的一个循环了。
之所以我一开始的时候把解密数组的名字起为 mixed
就是因为在这里它是由我们的输入 input
和 that_girl
读入后产生的数组 wtf
混合而成,同时也是解密的关键。
这一行中我们很容易正向推出右边的 wtf
,同时根据之前的分析我们可以反向推出左边的 mixed
,那么我们只要根据二者的偏移就可以算出 input
了,也就是 flag。
到这里这道题基本上就没问题了,完全不用爆破,只要逆向走一遍算法就行了。
计算脚本
1 | import struct |
后记
相当好的一道 RE 题目,构思很精巧。一开始总以为是要爆破,结果仔细分析逻辑后才发现都是可逆的,很轻松就可以拿到 flag。
唯一让我感觉很不爽的是被 IDA 坑了两次,远程调试还崩了好几次。