西电2015CTF比赛时间为10.1-10.3,比赛已经过去有一段时间了,由于笔者只做了逆向部分和PWN部分,这里将逆向部分的解题思路贴出来,仅供参考。这里提供题目下载,提取密码:7pvv。有兴趣可以自己动手尝试下。
1.RE100
这题真的有点坑,题目提示为” donn’t belive your eyes”,注定要麻烦一点。
UE打开程序,发现是64位ELF文件,直接拖进kali,运行发现是要输12字节正确序列号。 IDA反汇编查看一下,发现函数并不多,直接点进去一个一个看。发现sub_4008E1处出现可疑函数,F5看一下。
OK,调试之,gdb打开之后,直接退出,输出‘Aha ,Bye’,应该是碰到反调试了。
函数ptrace百度一下发现是反调试函数,这里比较简单,直接暴力nop掉这段代码就可以正常调试了。正如反编译看到的代码一样,先将input与字符串’|Gq\@?BelTtK5L\|D
d42;’异或再异或7。但是这里最终的异或key不是 ‘|Gq\@?BelTtK5L\|D
d42;’,而是将该key经过base64之后的字符串,这是调试过程发现的,如图所示。
可以看到key变为”ZzAwZF9DcjRrM3JfZzBfb24=”。接着在函数sub_4006D5进行变换,将字符串第1-6位与第13-18位对称变换。这步是可逆的。
得到的结果再进行最后一次变换,如果小于31则加32,这步也是可逆的。最将字符串与
'%#848N!0Z?7',27h,'%23]/5#1"YX'
'%#848N!0Z?7',27h,'%23]/5#1"YX' |
对比正确则Iput为flag。,接着开始写解密过程,最后解密得到的flag总是不对,命令行下提示也是过了的。真是坑啊,继续查看其他函数,最后发现函数sub_400787与刚刚的疑似解密函数非常相似,只是少了个异或7
重新修改刚刚的解密算法,最后计算出的flag在提示下终于过了。所以这个函数才是真正的解密过程。解密过程python之。flag:U’Re_aWes0me
#!usr/bin/python # -*- coding: utf-8 -*- key = ";%#848N!0Z?7'%23]/5#1\"YX" payload = "ZzAwZF9DcjRrM3JfZzBfb24=" tmp=[] for i in key: if (ord(i) - 0x20 < 0x1f): tmp.append(chr(ord(i) - 0x20)) else: tmp.append(i) for i in range(0,12): v3=tmp[i] tmp[i]=tmp[17-i] tmp[17-i]=v3 for i in range(0,24): tmp[i]=chr(ord(tmp[i])^ord(payload[i])) print ''.join(tmp)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #!usr/bin/python
# -*- coding: utf-8 -*- key = ";%#848N!0Z?7'%23]/5#1\"YX" payload = "ZzAwZF9DcjRrM3JfZzBfb24="
tmp=[] for i in key: if (ord(i) - 0x20 < 0x1f): tmp.append(chr(ord(i) - 0x20)) else: tmp.append(i) for i in range(0,12): v3=tmp[i] tmp[i]=tmp[17-i] tmp[17-i]=v3 for i in range(0,24): tmp[i]=chr(ord(tmp[i])^ord(payload[i])) print ''.join(tmp) |
2.RE200
这道题比较简单,就是用OD跟踪,作者为了不让我们使用IDA的反编译功能,将主要代码加密起来了。第一步还是info下软件信息。
可以看出是个PE文件,直接win下运行,发现要求输入flag,随便输一个,提示‘You shall not pass’。OD之,依旧是那个调试,猜测有反调试,IDA查找这个字符串,在交叉引用处逐一查看,发现了简单的反调试伎俩。
简单修改下跳转条件即可,winhex直接将跳转条件机器码反一下。或者直接用strongOD直接过了。这样可以正常调试了,发现存在一段代码解密过程,对0x401166处开始解密。
查看解密后的代码,发现是验证flag的关键代码。只能强撸汇编代码了,主要分为5个部分。
1.获取input,判断字符串大小是否在0-25之间,否则不能通过
2.要求输入开头以XDCTF{,指示输入中_和$在字符串中的位置,并且指示}位于字符串偏移值24处。
3.按照要求构造XDCTF{123456_89QWE$TYUI},找后续检查代码
4. XDCTF_循环相减输入是否等于固定值,推导出前6个字符为Congra。
5.接着第二部分检查。此部分直接同tUlat,比对,因此第二部分字符串为tUltra,这部分比较简单,
6.接下来是第三部分,竟然是对运行时间的检测,当大于一个固定时间时则退出程序,不过这部分可以直接爆破掉,得到第四部分检查。将剩下的字符与key异或后判断是否为字符dst。
最终得到flagXDCTF{Congra_tUlat$eyOu}。
3.RE300
这个是python的一行代码,手工拆分一下,拆到这样就差不多了。
__g = globals() __y = (lambda f: (lambda x: x(x))(lambda y: f(lambda: y(y)()))) string = (__import__('string', __g, __g)) table = (string.printable.strip()) setbit = (lambda p, pos, value: (lambda __l: [[[(lambda __target, __slice, __value: [(lambda __target, __slice, __value: [__l['p'] for __target[__slice] in [((lambda __old: (lambda __ret: __old | __value if __ret is NotImplemented else __ret)(getattr(__old, '__ior__', lambda other: NotImplemented)(__value)))(__target[__slice]))]][0])(__l['p'], __l['cpos'], (__l['value'] << __l['bpos'])) for __target[__slice] in [((lambda __old: (lambda __ret: __old & __value if __ret is NotImplemented else __ret)(getattr(__old, '__iand__', lambda other: NotImplemented)(__value)))(__target[__slice]))]][0])(__l['p'], __l['cpos'], (~(1 << __l['bpos']))) for __l['bpos'] in [((__l['pos'] % 8))]][0] for __l['cpos'] in [((__l['pos'] / 8))]][0] for __l['p'], __l['pos'], __l['value'] in [(p, pos, value)]][0])({})) setbit.__name__ = 'setbit' getbit = (lambda p, pos: (lambda __l: [[[((__l['p'][__l['cpos']] >> __l['bpos']) & 1) for __l['bpos'] in [((__l['pos'] % 8))]][0] for __l['cpos'] in [((__l['pos'] / 8))]][0] for __l['p'], __l['pos'] in [(p, pos)]][0])({})) getbit.__name__ = 'getbit' encode = (lambda data, buf: (lambda __l: [[(lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [[__this() for __l['data'][__l['i']] in [((table.index(__l['data'][__l['i']]) + 1))]][0] for __l['i'] in [(__i)]][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(xrange(__l['_len'])), lambda: (lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [[[__this() for __l['buf'] in [(setbit(__l['buf'], __l['i'], getbit(__l['data'], __l['j'])))]][0] for __l['j'] in [((((__l['i'] / 6) * 8) + (__l['i'] % 6)))]][0] for __l['i'] in [(__i)]][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(xrange((__l['_len'] * 6))), lambda: __l['buf'], []), []) for __l['_len'] in [(len(__l['data']))]][0] for __l['data'], __l['buf'] in [(data, buf)]][0])({})) encode.__name__ = 'encode' fin = open('flag.txt', 'r') s = fin.read().strip() fin.close() ss = ([]) sss = ([]) __items0 = iter(s) __after0 = lambda: [[(lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [(lambda __value: [__this() for __g['sssss'] in [((lambda __ret: __g['sssss'] + __value if __ret is NotImplemented else __ret)(getattr(__g['sssss'], '__iadd__', lambda other: NotImplemented)(__value)))]][0])(chr(c)) for __g['c'] in [(__i)]][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(ssss), lambda: [(fout.write(sssss), (fout.close(), None)[1])[1] for __g['fout'] in [(open('flag.enc', 'wb+'))]][0], []) for __g['sssss'] in [('')]][0] for __g['ssss'] in [(encode(ss, sss))]][0] __sentinel0 = [] __y(lambda __this: lambda: (lambda __i: [(ss.append(c), (sss.append(0), __this())[1])[1] for __g['c'] in [(__i)]][0] if __i is not __sentinel0 else __after0())(next(__items0, __sentinel0)))()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | __g = globals() __y = (lambda f: (lambda x: x(x))(lambda y: f(lambda: y(y)()))) string = (__import__('string', __g, __g)) table = (string.printable.strip()) setbit = (lambda p, pos, value: (lambda __l: [[[(lambda __target, __slice, __value: [(lambda __target, __slice, __value: [__l['p'] for __target[__slice] in [((lambda __old: (lambda __ret: __old | __value if __ret is NotImplemented else __ret)(getattr(__old, '__ior__', lambda other: NotImplemented)(__value)))(__target[__slice]))]][0])(__l['p'], __l['cpos'], (__l['value'] << __l['bpos'])) for __target[__slice] in [((lambda __old: (lambda __ret: __old & __value if __ret is NotImplemented else __ret)(getattr(__old, '__iand__', lambda other: NotImplemented)(__value)))(__target[__slice]))]][0])(__l['p'], __l['cpos'], (~(1 << __l['bpos']))) for __l['bpos'] in [((__l['pos'] % 8))]][0] for __l['cpos'] in [((__l['pos'] / 8))]][0] for __l['p'], __l['pos'], __l['value'] in [(p, pos, value)]][0])({})) setbit.__name__ = 'setbit' getbit = (lambda p, pos: (lambda __l: [[[((__l['p'][__l['cpos']] >> __l['bpos']) & 1) for __l['bpos'] in [((__l['pos'] % 8))]][0] for __l['cpos'] in [((__l['pos'] / 8))]][0] for __l['p'], __l['pos'] in [(p, pos)]][0])({})) getbit.__name__ = 'getbit' encode = (lambda data, buf: (lambda __l: [[(lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [[__this() for __l['data'][__l['i']] in [((table.index(__l['data'][__l['i']]) + 1))]][0] for __l['i'] in [(__i)]][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(xrange(__l['_len'])), lambda: (lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [[[__this() for __l['buf'] in [(setbit(__l['buf'], __l['i'], getbit(__l['data'], __l['j'])))]][0] for __l['j'] in [((((__l['i'] / 6) * 8) + (__l['i'] % 6)))]][0] for __l['i'] in [(__i)]][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(xrange((__l['_len'] * 6))), lambda: __l['buf'], []), []) for __l['_len'] in [(len(__l['data']))]][0] for __l['data'], __l['buf'] in [(data, buf)]][0])({})) encode.__name__ = 'encode' fin = open('flag.txt', 'r') s = fin.read().strip() fin.close() ss = ([]) sss = ([]) __items0 = iter(s) __after0 = lambda: [[(lambda __items, __after, __sentinel: __y(lambda __this: lambda: (lambda __i: [(lambda __value: [__this() for __g['sssss'] in [((lambda __ret: __g['sssss'] + __value if __ret is NotImplemented else __ret)(getattr(__g['sssss'], '__iadd__', lambda other: NotImplemented)(__value)))]][0])(chr(c)) for __g['c'] in [(__i)]][0] if __i is not __sentinel else __after())(next(__items, __sentinel)))())(iter(ssss), lambda: [(fout.write(sssss), (fout.close(), None)[1])[1] for __g['fout'] in [(open('flag.enc', 'wb+'))]][0], []) for __g['sssss'] in [('')]][0] for __g['ssss'] in [(encode(ss, sss))]][0] __sentinel0 = [] __y(lambda __this: lambda: (lambda __i: [(ss.append(c), (sss.append(0), __this())[1])[1] for __g['c'] in [(__i)]][0] if __i is not __sentinel0 else __after0())(next(__items0, __sentinel0)))() |
这个代码与原代码是等效的,不过更清晰一点。
一开始的setbit和getbit两个函数看函数名就能猜出个大概,然后在用下就能知道其作用。不过其第一个参数是个数组,操作中的bit位数是在整个数组中的位数。
关键加密方式依然很难看懂,不过也能看个大概,其中最关键的地方在这里:
[[[__this() for __l['buf'] in [(setbit(__l['buf'], __l['i'], getbit(__l['data'], __l['j'])))]][0] for __l['j'] in [((((__l['i'] / 6) * 8) + (__l['i'] % 6)))]][0] for __l['i'] in [(__i)]]
[[[__this() for __l['buf'] in [(setbit(__l['buf'], __l['i'], getbit(__l['data'], __l['j'])))]][0] for __l['j'] in [((((__l['i'] / 6) * 8) + (__l['i'] % 6)))]][0] for __l['i'] in [(__i)]] |
在配合着通过修改flag.txt看变量ss和sss的变化就能猜出加密方法:
将输入转化为在数组string.printable.strip()中的位置,然后取后6个字节拼在一起。
因为只取了后6个字节,而第7个字节有可能为0也有可能为1,所以反解的时候两个都要考虑到。
然后拼接一下得到flag:xdctf{0ne-l1n3d_Py7h0n_1s_@wes0me233}
4.RE400
这道题刚开始没做出来,一直无法调试,猜测应该是编译器版本过高导致的,作者还未整理。
5.RE500
此题较RE400简单很多,以后不能以分数高的就很难。还是一个破解注册码的程序,在错误提示处断下。
习惯性上溯,找到按钮函数入口处
发现只有两个Edit框,ID是10086和10010,这俩移动联通热线的ID类型根本就不存在,问题来了。只好IDA中一个个查看,看到408550处有了启发。
这里它自己Hook了自己的GetDlgItemText。发现这两个一个是验证注册码长度,一个验证注册码是否包含非法字符。后面就是验证了,首先DES(!NOT SURE! Result of KANAL)解密传入的注册码,然后将注册码最后两位移到最前面,接着和程序中的一组字节挨个异或,最后和机器码比对,若相同则正确。但是死活没有找到DES 的解密密钥,所以各种尝试均无功而返。
然后就打算看了一下解密的函数,结果手抽点错了,发现两个函数基本相同,引起了我的怀疑,又发现两个函数分别有“加密之前”、“解密之前”字符串,所以断定他们俩是一对互逆的函数。所以直接在原来调用解密函数的地方把函数换成了加密函数,并设置好相关的缓冲区(函数的第二个参数,不要忘了末尾用0x08 填充到24 字节,不要被IDA 错误的变量类型迷惑住了)为已经逆向变换过的MachineCode,即可得到注册码,提交得到flag。
Flag:XDCTF{1azy_Cm_15_2_s1mp0!}
【via@91ri团队-Leon】