自己的目标是,怎么做的,思路是什么,哪儿有细节问题,全部写下来


打开程序,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()