XDCTF2015逆向部分Write up

西电2015CTF比赛时间为10.1-10.3,比赛已经过去有一段时间了,由于笔者只做了逆向部分和PWN部分,这里将逆向部分的解题思路贴出来,仅供参考。这里提供题目下载,提取密码:7pvv。有兴趣可以自己动手尝试下。

1.RE100

这题真的有点坑,题目提示为” donn’t belive your eyes”,注定要麻烦一点。

UE打开程序,发现是64位ELF文件,直接拖进kali,运行发现是要输12字节正确序列号。 IDA反汇编查看一下,发现函数并不多,直接点进去一个一个看。发现sub_4008E1处出现可疑函数,F5看一下。

QQ截图20151012112716

OK,调试之,gdb打开之后,直接退出,输出‘Aha ,Bye’,应该是碰到反调试了。

QQ截图20151012113649

函数ptrace百度一下发现是反调试函数,这里比较简单,直接暴力nop掉这段代码就可以正常调试了。正如反编译看到的代码一样,先将input与字符串’|Gq\@?BelTtK5L\|Dd42;’异或再异或7。但是这里最终的异或key不是 ‘|Gq\@?BelTtK5L\|Dd42;’,而是将该key经过base64之后的字符串,这是调试过程发现的,如图所示。

QQ截图20151012114536

QQ截图20151012114616

可以看到key变为”ZzAwZF9DcjRrM3JfZzBfb24=”。接着在函数sub_4006D5进行变换,将字符串第1-6位与第13-18位对称变换。这步是可逆的。

QQ截图20151012153023

得到的结果再进行最后一次变换,如果小于31则加32,这步也是可逆的。最将字符串与

'%#848N!0Z?7',27h,'%23]/5#1"YX'

'%#848N!0Z?7',27h,'%23]/5#1"YX'

 

对比正确则Iput为flag。,接着开始写解密过程,最后解密得到的flag总是不对,命令行下提示也是过了的。真是坑啊,继续查看其他函数,最后发现函数sub_400787与刚刚的疑似解密函数非常相似,只是少了个异或7

QQ截图20151012153724

重新修改刚刚的解密算法,最后计算出的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下软件信息。

QQ截图20151012195314

可以看出是个PE文件,直接win下运行,发现要求输入flag,随便输一个,提示‘You shall not pass’。OD之,依旧是那个调试,猜测有反调试,IDA查找这个字符串,在交叉引用处逐一查看,发现了简单的反调试伎俩。

QQ截图20151012195846

QQ截图20151012195905

简单修改下跳转条件即可,winhex直接将跳转条件机器码反一下。或者直接用strongOD直接过了。这样可以正常调试了,发现存在一段代码解密过程,对0x401166处开始解密。QQ截图20151012200414

查看解密后的代码,发现是验证flag的关键代码。只能强撸汇编代码了,主要分为5个部分。

1.获取input,判断字符串大小是否在0-25之间,否则不能通过

QQ截图20151012201550

2.要求输入开头以XDCTF{,指示输入中_和$在字符串中的位置,并且指示}位于字符串偏移值24处。

QQ截图20151012202236

3.按照要求构造XDCTF{123456_89QWE$TYUI},找后续检查代码

QQ截图20151012203421

4. XDCTF_循环相减输入是否等于固定值,推导出前6个字符为Congra。

5.接着第二部分检查。此部分直接同tUlat,比对,因此第二部分字符串为tUltra,这部分比较简单,

6.接下来是第三部分,竟然是对运行时间的检测,当大于一个固定时间时则退出程序,不过这部分可以直接爆破掉,得到第四部分检查。将剩下的字符与key异或后判断是否为字符dst。

QQ截图20151012203943

最终得到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简单很多,以后不能以分数高的就很难。还是一个破解注册码的程序,在错误提示处断下。

QQ截图20151012215002

习惯性上溯,找到按钮函数入口处

QQ截图20151012215634

发现只有两个Edit框,ID是10086和10010,这俩移动联通热线的ID类型根本就不存在,问题来了。只好IDA中一个个查看,看到408550处有了启发。

QQ截图20151012220314

这里它自己Hook了自己的GetDlgItemText。发现这两个一个是验证注册码长度,一个验证注册码是否包含非法字符。后面就是验证了,首先DES(!NOT SURE! Result of KANAL)解密传入的注册码,然后将注册码最后两位移到最前面,接着和程序中的一组字节挨个异或,最后和机器码比对,若相同则正确。但是死活没有找到DES 的解密密钥,所以各种尝试均无功而返。

然后就打算看了一下解密的函数,结果手抽点错了,发现两个函数基本相同,引起了我的怀疑,又发现两个函数分别有“加密之前”、“解密之前”字符串,所以断定他们俩是一对互逆的函数。所以直接在原来调用解密函数的地方把函数换成了加密函数,并设置好相关的缓冲区(函数的第二个参数,不要忘了末尾用0x08 填充到24 字节,不要被IDA 错误的变量类型迷惑住了)为已经逆向变换过的MachineCode,即可得到注册码,提交得到flag。

Flag:XDCTF{1azy_Cm_15_2_s1mp0!}

【via@91ri团队-Leon】