某CTF一道逆向题分析
某个周末,公司群里发了个某CTF的逆向题,刚好没啥事就尝试做一下。
详情分析
首先,先运行看看情况,打开程序之后提示找不到VCRUNTIME140_1.dll,如下:
在此链接下载vcruntime140_1.dll,并将其放到C:\Windows\System32\目录下,再次打开程序,可以看到是个控制台程序,并且需要输入,如下:
vcruntime140_1.dll,Win10下不受影响接着查壳,看看是否加壳,使用LoadPE查看区段,发现区段正常,并无异常,应该是没有壳的,如下:
接下来用x64dbg打开程序,并且搜索字符串,如下:
可以看到如下字符串:
猜测应该是输入特定的字符串,然后对输入一顿加密,最后比较加密后的结果是否与程序内设定的一致,一致则提示找到flag,否则就退出啥的。
接下来再用IDA打开程序,找到main函数,看下流程图,如下:
看到这种的时候,就是 IDA关闭 ==> 虚拟机关闭 ==> 爱奇艺打开。追了会剧,吃了个饭,想了想,还是分析分析吧,毕竟还是想装装逼学习学习的。
整个程序分成以下几块:
- 获取输入
- 判断输入是否合法
- 判断第二段字符串是否正确
- 判断第一段字符串是否正确
- 判断第三段字符串是否正确
- 完
获取输入:
IDAf5 大法之后,从104 ~ 155行,是获取输入,如下(请忽略小学鸡的注释😂):
上图中,标注了两处地方,这两处地方是这个获取输入的地方最重要的地方,下面详细分析。
首先看第二处,第二处比较简单,也就是这个输入怎么结束。这里的逻辑是判断输入的字符是否为#号,当输入了3个#号之后,循环结束,完成输入。
再来看第一处,这里比较复杂。
if判断的是两个全局变量,这两个变量一开始都为0,然后开始执行sub_140001F60函数,该函数会将输入的字符存放在某个数组中,并且给三个变量赋值。
变量1为存放字符的数组首地址。变量2为该数组+1的地址,其实也就是输入字符串的长度,变量2 - 变量1 = 输入字符串长度。变量3不知道存放的是什么地址,它要么与变量2相等,要么比变量2大一些。
而if判断的就是变量2和变量3,当相等时就去执行sub_140001F60函数,不相等时则将变量2+1。
这里最主要就是变量3的变化,经测试,变量3的变化主要与输入的字符长度有关,具体如下:
| 输入字符长度 | 变量3的值 |
|---|---|
| 1 ~ 4 | 变量2+0,也就是字符串实际长度 |
| 5 ~ 6 | 变量2+1,也就是字符串实际长度+1 |
| 7 ~ 9 | 变量2+2,也就是字符串实际长度+2 |
| 10 ~ 13 | 变量2+3,也就是字符串实际长度+3 |
| 14 ~ 19 | 变量2+5,也就是字符串实际长度+5 |
| 20 ~ 28 | 变量2+8,也就是字符串实际长度+8 |
| … | … |
可以看出点端倪了,这应该是斐波那契数列。当字符串长度大于等于 5 时,就将变量2按照斐波那契数列依次累加,并且赋值给变量3。
从这个获取输入的位置,可以得出以下结论:
- 输入的字符串,包含3个
#号 - 输入的字符串长度应该有所限制
判断输入是否合法:
当接受完输入的字符串之后,接下来就是判断输入是否合法了,从这个标题应该就能看出,这个输入字符串是有格式的,我们接着来分析。
从156 ~ 162行,是在进行输入合法性校验,代码如下:
这里也有两处比较重要的点。
第一处判断几个全局变量相加相减之后的值是否等于37,不等于直接退出。这几个变量就是上面分析的变量1与变量3,只不过有三组变量1与变量3。为什么有三组呢,因为前面输入的位置每遇到一个#号,就进行一组的变量操作,三个#号刚好三组。用每一组的变量3 - 变量1,也就是将斐波那契数列处理过的数 - 字符串首地址,得到的差值再相加,必须等于37。下面是输入字符串长度与变量3的关系,也就是上面的完善版:
| 输入字符长度 | 变量3与变量2的关系 | 最终的差值 |
|---|---|---|
| 1 ~ 4 | 变量2+0,也就是字符串实际长度 | 字符串实际长度 |
| 5 ~ 6 | 变量2+1,也就是字符串实际长度+1 | 6 |
| 7 ~ 9 | 变量2+2,也就是字符串实际长度+2 | 9 |
| 10 ~ 13 | 变量2+3,也就是字符串实际长度+3 | 13 |
| 14 ~ 19 | 变量2+5,也就是字符串实际长度+5 | 19 |
| 20 ~ 28 | 变量2+8,也就是字符串实际长度+8 | 28 |
| … | … |
从这个表格中可以看到,要想满足37的标准,最简单得是28+9,如果这样得话那么一段字符串为20 ~ 28位,一段字符串为7 ~ 9位。
第二处,是三个变量相比较,也就是三段字符串的长度,比较结果是第二段字符串长度最长,其次是第一段,最后是第三段。
从这个地方可得出以下结论:
- 输入为一段包含3个
#号的字符串,刚好分成3段字符串,第二段字符串最长,其次是第一段,最后是第三段。 - 按照上面这个长度划分,再结合相加得为37来看,第二段字符串应该在20 ~ 28位之间,第一段和第三段加起来为9,且第一段大于第三段。
判断第二段字符串是否正确:
接着是判断第二段字符串是否正确,代码为163 ~ 189、410 ~ 414行。
先是执行410 ~ 414行,这里这个while循环,是遍历输入的第二段字符串,判断每个字符是否等于1或者等于2,不等于这两个直接退出,代码如下:
接着就是163 ~ 189行,将第二段字符串进行MD5哈希,并且判断是否与39c1ca4b6d64c40558425432c11624a8相等,不相等则退出,代码如下:
从这里得到结论:
- 第二段字符串必须由
1、2组成 MD5后必须等于39c1ca4b6d64c40558425432c11624a8
在这里将39c1ca4b6d64c40558425432c11624a8放入到网上的彩虹表查询,都没有查询到结果。由于只由两个字符组成,这里直接采取暴力破解的方式,来找到对应的字符串,代码如下:
| |
最后找到1221222221212121211122112111,如下:
最终结论:
- 第二段字符串为
1221222221212121211122112111 - 最终字符串为:
?#1221222221212121211122112111#?
判断第一段字符串是否正确:
然后判断第一段字符串是否正确,这里的代码就比较长了,主要点在下图:
主要逻辑为先定义了一个加密字符数组,然后取数组内容相加,并与输入的字符一个一个异或,得到的结果再与当前字符所在的下标进行异或,最后将异或后的结果与定义的字符相比较,其中一个字符不相等则退出。
这里我用的笨办法,因为本来剩余的字符就不多了,所以我直接动态调试找到异或的加密字符,然后与正确字符异或就得到了输入的字符串,代码如下:
| |
得到的结果为Rc44cR:
可以看到,输入的字符有乱码,说明第一段字符串肯定不是9,那就可以用3+6的组合来实现,也就是第一段字符长度为6,第二段字符长度为3,这样既能满足37(6+28+3)的要求,又能满足第一段字符长度 > 第三段字符长度 && 第一段字符长度 < 第二段字符长度的要求,所以最后第一段字符串为Rc44cR。
这里如果用3+6的方法的话,根据上面表格总结出的关系,第一段字符串也可以是5个字符,所以这里可能有两种答案。
总结:
- 第一段字符串长度为6,正确答案是
Rc44cR - 完整字符串为:
Rc44cR#1221222221212121211122112111#?
判断第三段字符串是否正确:
第三段字符串判断逻辑,与第一段字符串判断逻辑类似,只是字符串长度变短了,并且最后异或比较的时候,没有与字符所在下标进行异或了,所以代码如下:
| |
得到结果为TCF:
最终结果
最终的输入字符串为:Rc44cR#1221222221212121211122112111#TCF#,由于上面的表格总结的关系,以及CTF已经关闭的缘故,没办法验证Flag是否正确,字符串也可能是Rc44c#1221222221212121211122112111#TCF#。执行结果如下:
END
第一次写 CTF 逆向题的WriteUp,写得比较啰嗦,而且太久没搞逆向了,可能有些地方分析得也不对,如发现有任何错误,欢迎与我联系。














