2017_ZCTF_Reverse_QEtend
自己的目标是,怎么做的,思路是什么,哪儿有细节问题,全部写下来
打开程序,OD和IDA跑起来
我们的输入需要满足四个条件:长度小于0x20,前四个字符需要是ZCTF,然后通过两个检查
先看4026D0的函数,在IDA是找不到的,就很奇怪,那就到OD里下断点来找函数的调用逻辑
会看到这种特别奇怪的代码:在单步调试的时候,到了26E0这儿,需要F7进入下一个函数,F8会报错的
这儿是对输入的每一位进行处理,一些数***算,相当于x%16-1
之后可以看到6个call,是由一个jmp跳转过来的,结合题目的提示:这是个汉诺塔的题,是需要各种移动的,6种移动方式,是3根柱子
随便进去一个call,可以发现这儿的local1,是个常数地址00DE26E5,我们在数据窗口找到它
可以看到对我们有用的东西了,初始状态是第一个柱子上有1,4两个,第二个柱子上有2一个,第三个柱子上有3,5两个
那么,我们需要构造一种方案是可以使得所有的汉诺塔在同一个柱子上
根据计算,我们可以得到输入和移动的关系:
1:1->2
2:1->3
3:2->1
4:2->3
5:3->2
6:3->1
自己手动跑到一组最小移动次数就好的,答案是236521524364124?!
纠结了很久为什么不对,一个一个移动来调试跟踪,发现判断条件在这儿
看到这儿的5个判断语句:je
判断的是第二个柱子上是不是有5个汉诺塔!@
所以,我们要找的最短路径,需要把EE这个最大的移动出来,得到path:
164365215234215635215
这儿还有个地方需要去看:
为什么这个地方会有乱码?!看到DE25E5,就很容易明白:程序刻意是这样弄,把代码段和数据段写在一起,那么OD和IDA的反编译会出现小问题的,只有在动态调试运行的时候才可以发现这些问题的吧
接下来,我们还有最后一个函数判断
看到这个常数字符串,肯定是加密过的咯
同样的道理,还是把数据放在代码段里面,就很难受
继续往下跟踪,看到一个2430的call,进去看了看
看到这4个数字,就知道是MD5加密了咯(很多加密的字符串都是有特征的,MD5就是这4个数字)
那么,就是把我们的输入跟刚才的常数字符串比较的咯
MD5是不可逆的,那么意味着,我们需要写爆破来得到flag
from hashlib import md5
import string
def printline():
print '-' * 120
def crack():
flag = '0f2e7e447593ec9af3463e9c8745b892'
s = string.printable
cset = [[],[],[],[],[],[],[]]
for i in s:
if ord(i) % 16 >= 1 and ord(i) % 16 <= 6:
cset[ord(i)%16].append(i)
#path = '164365215234215635215'
for a in cset[1]:
for b in cset[2]:
for c in cset[3]:
for d in cset[4]:
for e in cset[5]:
for f in cset[6]:
tmp = a+f+d+c+f+e+b+a+e+b+c+d+b+a+e+f+c+e+b+a+e
if md5(tmp).hexdigest() == flag:
print 'ZCTF{%s}' % tmp
return
if __name__ == '__main__':
printline()
crack()
printline()