Fork me on GitHub

TokyoWesterns_CTF_2018-re_dec_dec_dec-wp

最近在搞小学期的项目,没空写wp,这题是上周搞出来的,这里简单记录一下,可能有些细节忘了。。。

==>题目地址<==

0x00 简单分析

程序是64位ELF,先拖进IDA看下逻辑。

main函数代码如下

可以看到a2[1]实际上就是argv[1],在程序中也就是我们输入的flag。将a2[1]复制给dest后,经过三轮加密,与s2`进行明文比较。那么接下来分析三轮加密具体做了什么。

0x01 函数sub_860

看代码发现在开头有一串字符ABCDEFG......,并且是每三个字符变换成四个字符,猜测是base64

用IDA动态调试验证一下(关于如何用IDA动态调试ELF)。

如下图,在函数sub_860执行之后,v6即第一轮加密的结果,并且其存在寄存器rax里。

查看rax的值如下

我输入的flag为123456789,其base64加密结果如下

1
2
3
>>> import base64
>>> base64.b64encode("123456789")
'MTIzNDU2Nzg5'

可以看到与程序第一轮加密结果相同,于是函数sub_860即base64加密。

0x02 函数sub_F59

方法一:

同样,继续调试,可以看到第二轮加密结果赋给了v7,而v7的内容存在了寄存器rax里,查看rax值如下。

经验丰富的赛棍(比如说我m4x师傅)会直接看出这是rot13加密。

方法二:

但我没看出来233,我的解决办法是看这个函数的伪代码,发现只是一个简单的替换加密,那么直接逐位爆破即可。

0x03 函数sub_BE7

这个函数是题目里最复杂的加密,接下来逐行分析。

首先分析如下代码,v1表示上一轮加密结果的长度,其实从v34 = malloc(4 * v1 / 3 + 1)就可以猜出这轮加密也是明文每三个字符变换得到密文的四个加密字符,但还多了一个字节,这个字节非常关键

1
2
38 :v1 = strlen(a1);
39:v34 = malloc(4 * v1 / 3 + 1);

接着往下会看到for ( i = v1; i > 45; i -= 45 ),如果v1小于45不执行这个循环,由于我们不知道v1应该是多少,于是跳过这个循环往下看。

如下图,如果没执行上面的循环,v26v34即密文的第一位,iv1 ,下面代码的含义就是将v1经过一个算式得到一个新的值放在密文的第一位,也就是说密文的第一位表示了v1(第二轮加密的密文)的长度。

从最后strcmp可以跟踪到密文的内容为@25-Q44E233=,>E-M34=,,$LS5VEQ45)M2S-),7-$/3T,第一位为@,值为64,逆推可以得到第二轮的密文长度为32。

知道了长度之后用z3即可爆破出第二轮的密文,然后就爆破第二轮的算法再解base64即可。

0x04 脚本

由于第二轮密文的长度为32,不是3的倍数,在最后一次三个字符加密成四个字符时密文的第四个字符由于找不到明文的第三个字符,默认为32,即空格,,这也是为什么密文的最后一位是空格,并且爆破时最后一次无结果。根据flag格式就直接猜出是’}‘,不用过于纠结。

第三轮的爆破我分别用z3和直接三重循环跑了一下,发现用z3只需0.3s,暴力循环要1.36s。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__Auther__ = 'L1B0'
import base64
from string import printable
from z3 import *

cipher = "25-Q44E233=,>E-M34=,,$LS5VEQ45)M2S-),7-$/3T \x00"
print len(cipher)

def decode_level1(s):
if len(s)%4 != 0:
return base64.b64decode(s[:len(s)-len(s)%4])

def decode_level2(s):

s1 = ''
for i in s:
j = ord(i)
for k in range(256):
l = 0
if (k <= 0x40 or k > 0x5a):
if (k <= 0x60 or k > 0x7a):
l = k
else:
l = (k-0x54)%26 + 0x61
else:
l = (k-0x34)%26 + 0x41
if l == j:
s1 += chr(k)
break

#print len(s),len(s1)
return s1

def decode_level3(cipher):

ss = ""

for i in range(11):

s = Solver()
a1,a2,a3 = [ BitVec('a'+str(j),16) for j in range(1,4) ]
for j in range(1,4):
s.add( And( eval('a'+str(j)) > 0, eval('a'+str(j)) < 256 ) )

s.add( (a1>>2) + 32 == ord(cipher[4*i]) )
s.add( (( ((16*a1)&0x30) + 32 ) | (a2 >> 4)) == ord(cipher[4*i+1]) )
s.add( ((((4*a2)&0x3c) +32) | (a3 >> 6) ) == ord(cipher[4*i+2]) )
s.add( (a3&0x3f) +32 == ord(cipher[4*i+3]) )

#print s.check()
if s.check() == sat:

#print s.model()

ss += chr(s.model()[a1].as_long())
ss += chr(s.model()[a2].as_long())
ss += chr(s.model()[a3].as_long())
#print ss

#print ss
return ss

def decode_level30(cipher):

ss = ""
for i in range(11):
flag = 0
for a1 in printable:
for b1 in printable:
for c1 in printable:
a,b,c = ord(a1),ord(b1),ord(c1)
if (a>>2) + 32 == ord(cipher[4*i]) and (( ((16*a)&0x30) + 32 ) | (b >> 4)) == ord(cipher[4*i+1]) and ((((4*b)&0x3c) +32) | (c >> 6) ) == ord(cipher[4*i+2]) and (c&0x3f) +32 == ord(cipher[4*i+3]):
ss += chr(a)+chr(b)+chr(c)
flag = 1
break
if flag:
break
if flag:
break

print ss
return ss
if __name__ == "__main__":

flag2 = decode_level30(cipher)
#flag2 = decode_level30(cipher)
flag1 = decode_level2(flag2)
print flag1
flag = decode_level1(flag1)

print flag