夫天地者,万物之逆旅;光阴者,百代之过客。

0%

虎符ctf之game(pyc字节码逆向)

虎符ctf三道逆向中最简单的一道,比赛期间我只做出来这一道。题目对我来说挺新颖的,所以在这里也记录一下。刚写完vm的题解,有点累,所以这篇就简单点写。

只给出一个disasm.txt,部分如下:

1588254571359

查了下,是python的字节码。

pyc可以还原成py源代码,但是这个类似汇编的东西没办法还原出来(如果大佬们知道怎么还原,劳请告诉我一下)

dis模块

同时查到这个文本是怎么来的,python的dis模块。

下面有个小例子:

1
2
3
4
5
6
7
8
9
10
11
12
def f():
s = [90, 100, 87, 109, 86, 108, 86, 105, 90, 104, 88, 102]
b = [90, 100, 87, 109, 86, 108, 86, 105, 90, 104, 88, 102]
#a = s[6:30:3]
#
#s = 'xctf{1231231}'
#a = (((((((ord(s[0])*128+ord(s[1]))*128)+ord(s[2]))*128)+ord(s[3]))*128) + ord(s[4])) * 128 + ord(s[5])
#print(a)
a = map(lambda x:x+1, zip(s, b[2:5]))

import dis
dis.dis(f)

输出:

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
  2           0 LOAD_CONST               1 (90)
2 LOAD_CONST 2 (100)
4 LOAD_CONST 3 (87)
6 LOAD_CONST 4 (109)
8 LOAD_CONST 5 (86)
10 LOAD_CONST 6 (108)
12 LOAD_CONST 5 (86)
14 LOAD_CONST 7 (105)
16 LOAD_CONST 1 (90)
18 LOAD_CONST 8 (104)
20 LOAD_CONST 9 (88)
22 LOAD_CONST 10 (102)
24 BUILD_LIST 12
26 STORE_FAST 0 (s)

3 28 LOAD_CONST 1 (90)
30 LOAD_CONST 2 (100)
32 LOAD_CONST 3 (87)
34 LOAD_CONST 4 (109)
36 LOAD_CONST 5 (86)
38 LOAD_CONST 6 (108)
40 LOAD_CONST 5 (86)
42 LOAD_CONST 7 (105)
44 LOAD_CONST 1 (90)
46 LOAD_CONST 8 (104)
48 LOAD_CONST 9 (88)
50 LOAD_CONST 10 (102)
52 BUILD_LIST 12
54 STORE_FAST 1 (b)

9 56 LOAD_GLOBAL 0 (map)
58 LOAD_CONST 11 (<code object <lambda> at 0x0000019F93CB0270, file "d:\桌面\20200419 虎符ctf\text.py", line 9>)
60 LOAD_CONST 12 ('f.<locals>.<lambda>')
62 MAKE_FUNCTION 0
64 LOAD_GLOBAL 1 (zip)
66 LOAD_FAST 0 (s)
68 LOAD_FAST 1 (b)
70 LOAD_CONST 13 (2)
72 LOAD_CONST 14 (5)
74 BUILD_SLICE 2
76 BINARY_SUBSCR
78 CALL_FUNCTION 2
80 CALL_FUNCTION 2
82 STORE_FAST 2 (a)
84 LOAD_CONST 0 (None)
86 RETURN_VALUE

Disassembly of <code object <lambda> at 0x0000019F93CB0270, file "d:\桌面\20200419 虎符ctf\text.py", line 9>:
9 0 LOAD_FAST 0 (x)
2 LOAD_CONST 1 (1)
4 BINARY_ADD
6 RETURN_VALUE

上面的输出的代码形式风格和题目给出的只有细微的差异(我用的是python3,而题目是python2,可能是这个原因)

然后可以去试着分析下题目给出的代码了,遇到不懂的点,可以自己写个函数,然后disasm,看看和题目中代码的差异。

分析字节码

首先是arr0,arr1,arr2这三个列表的赋值,很容易看出来。

然后有check0, check1,check2,check3四个检测函数。

check0

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
# <genexpr> line 6 of game.py

6 0 LOAD_FAST 0 '.0'
3 FOR_ITER 32 'to 38'
6 STORE_FAST 1 'x'
9 LOAD_GLOBAL 0 'ord'
12 LOAD_FAST 1 'x'
15 CALL_FUNCTION_1 1 None
18 LOAD_GLOBAL 1 'range'
21 LOAD_CONST 32
24 LOAD_CONST 128
27 CALL_FUNCTION_2 2 None
30 COMPARE_OP 6 in
33 YIELD_VALUE
34 POP_TOP
35 JUMP_BACK 3 'to 3'
38 LOAD_CONST None
41 RETURN_VALUE
//推测是判断字符ord(x) in (32, 128)

# check0 line 5 of game.py

6 0 LOAD_GLOBAL 0 'all'
3 LOAD_GENEXPR '<code_object <genexpr>>'
6 MAKE_FUNCTION_0 0 None
9 LOAD_FAST 0 's'
12 GET_ITER
13 CALL_FUNCTION_1 1 None
16 CALL_FUNCTION_1 1 None
19 RETURN_VALUE

//check0()判断字符介于(32,128)

配合着我代码中的注释就应该能看懂了吧,我就不多解释了。

check1

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
# check1 line 8 of game.py

9 0 LOAD_GLOBAL 0 'len'
3 LOAD_FAST 0 's'
6 CALL_FUNCTION_1 1 None
9 LOAD_CONST 100
12 COMPARE_OP 0 <
15 POP_JUMP_IF_FALSE 58 'to 58'
//if len(s)<100
18 LOAD_GLOBAL 0 'len'
21 LOAD_FAST 0 's'
24 CALL_FUNCTION_1 1 None
27 LOAD_GLOBAL 0 'len'
30 LOAD_FAST 0 's'
33 CALL_FUNCTION_1 1 None
36 BINARY_MULTIPLY //乘
//len(s) * len(s)
37 LOAD_CONST 777
40 BINARY_MODULO //取模
//(len(s) * len(s))%777
41 LOAD_CONST 233
44 BINARY_XOR //异或
//((len(s) * len(s))%777) ^ 233
45 LOAD_CONST 513
48 COMPARE_OP 2 ==
//if ((len(s) * len(s))%777) ^ 233 == 513
51_0 COME_FROM 15 '15'
51 POP_JUMP_IF_FALSE 58 'to 58'

10 54 LOAD_GLOBAL 1 'True'
57 RETURN_END_IF
58_0 COME_FROM 51 '51'

12 58 LOAD_GLOBAL 2 'False'
61 RETURN_VALUE
62 LOAD_CONST None
65 RETURN_VALUE

//check1()判断flag长度,经爆破得知len(flag) = 39

check1检测flag长度是否符合条件( if ((len(s) * len(s))%777) ^ 233 == 513

爆破可以求得长度为39

check2

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
94
95
# check2 line 14 of game.py

15 0 LOAD_GLOBAL 0 'ord'
3 LOAD_FAST 0 's'
6 LOAD_CONST 0
9 BINARY_SUBSCR //TOS = TOS1[TOS]
10 CALL_FUNCTION_1 1 None
13 LOAD_CONST 128
16 BINARY_MULTIPLY

//ord(s[0]) * 128

17 LOAD_GLOBAL 0 'ord'
20 LOAD_FAST 0 's'
23 LOAD_CONST 1
26 BINARY_SUBSCR
27 CALL_FUNCTION_1 1 None
30 BINARY_ADD
31 LOAD_CONST 128
34 BINARY_MULTIPLY

//(ord(s[0])*128+ord(s[1])) * 128

35 LOAD_GLOBAL 0 'ord'
38 LOAD_FAST 0 's'
41 LOAD_CONST 2
44 BINARY_SUBSCR
45 CALL_FUNCTION_1 1 None
48 BINARY_ADD
49 LOAD_CONST 128
52 BINARY_MULTIPLY

//(((ord(s[0])*128+ord(s[1]))*128) + ord(s[2])) * 128

53 LOAD_GLOBAL 0 'ord'
56 LOAD_FAST 0 's'
59 LOAD_CONST 3
62 BINARY_SUBSCR
63 CALL_FUNCTION_1 1 None
66 BINARY_ADD
67 LOAD_CONST 128
70 BINARY_MULTIPLY

//(((((ord(s[0])*128+ord(s[1]))*128)+ord(s[2])) * 128) + ord(s[3])) * 128

71 LOAD_GLOBAL 0 'ord'
74 LOAD_FAST 0 's'
77 LOAD_CONST 4
80 BINARY_SUBSCR
81 CALL_FUNCTION_1 1 None
84 BINARY_ADD
85 LOAD_CONST 128
88 BINARY_MULTIPLY

//(((((((ord(s[0])*128+ord(s[1]))*128)+ord(s[2]))*128)+ord(s[3]))*128) + ord(s[4])) * 128

89 LOAD_GLOBAL 0 'ord'
92 LOAD_FAST 0 's'
95 LOAD_CONST 5
98 BINARY_SUBSCR
99 CALL_FUNCTION_1 1 None
102 BINARY_ADD

//(((((((ord(s[0])*128+ord(s[1]))*128)+ord(s[2]))*128)+ord(s[3]))*128) + ord(s[4])) * 128 + ord(s[5])

103 LOAD_CONST 3533889469877L
106 COMPARE_OP 2 ==
109 POP_JUMP_IF_FALSE 138 'to 138'
112 LOAD_GLOBAL 0 'ord'
115 LOAD_FAST 0 's'
118 LOAD_CONST -1
121 BINARY_SUBSCR
122 CALL_FUNCTION_1 1 None
125 LOAD_CONST 125
128 COMPARE_OP 2 ==
131_0 COME_FROM 109 '109'
131 POP_JUMP_IF_FALSE 138 'to 138'

16 134 LOAD_GLOBAL 1 'True'
137 RETURN_END_IF
138_0 COME_FROM 131 '131'

18 138 LOAD_GLOBAL 2 'False'
141 RETURN_VALUE
142 LOAD_CONST None
145 RETURN_VALUE

/*
if 表达式 == 3533889469877L:
if ord(s[-1]) == 125:
return True
else return false
else return false
*/
//check2()判断flag{}这个框架,以及flag{}主体的第一个字符flag[5]

check2用于判断前6个字符是否满足(((((((ord(s[0])*128+ord(s[1]))*128)+ord(s[2]))*128)+ord(s[3]))*128) + ord(s[4])) * 128 + ord(s[5]) == 3533889469877,同时判断最后一个字符是否是}

只要学过数学的小学生都知道6元一次等式根本不可能求出六个未知数,所以题目可能是假设了前五个字符是flag{,由此我们可以求出flag的第6个字符,也就是flag主体部分的第一个字符。

注意,我和炜昊都被这里坑到了,看到了flag[5],以为只有5个字符,恰好是flag{,忽略了第六个字符。

这个低级的下标错误平常不会错,但是每次到这个综合性题目中总会出错,还是要注意啊。

check3

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# check3 line 20 of game.py

21 0 LOAD_GLOBAL 0 'map'
3 LOAD_GLOBAL 1 'ord'
6 LOAD_FAST 0 's'
9 CALL_FUNCTION_2 2 None
12 STORE_FAST 1 'arr'
//e.g: map(function, iterable, ...)
//arr = map(ord, s)

22 15 LOAD_FAST 1 'arr'
18 LOAD_CONST 6
21 LOAD_CONST 30
24 LOAD_CONST 3
27 BUILD_SLICE_3 3 //切片,3个一组
30 BINARY_SUBSCR
31 STORE_FAST 2 'a'

//a = arr[6:30:3]

23 34 SETUP_LOOP 62 'to 99'
37 LOAD_GLOBAL 2 'range'
40 LOAD_GLOBAL 3 'len'
43 LOAD_FAST 2 'a'
46 CALL_FUNCTION_1 1 None
49 CALL_FUNCTION_1 1 None
52 GET_ITER
53 FOR_ITER 42 'to 98'
56 STORE_FAST 3 'i'

//类似for i in range(len(a)) ,不过这里用的是迭代器

24 59 LOAD_FAST 2 'a'
62 LOAD_FAST 3 'i'
65 BINARY_SUBSCR
66 LOAD_CONST 17684
69 BINARY_MULTIPLY
70 LOAD_CONST 372511
73 BINARY_ADD
74 LOAD_CONST 257
77 BINARY_MODULO
78 LOAD_GLOBAL 4 'arr0'
81 LOAD_FAST 3 'i'
84 BINARY_SUBSCR
85 COMPARE_OP 3 !=
88 POP_JUMP_IF_FALSE 53 'to 53'

/*
if (a[i] * 17684 + 372511) % 257 == arr0[i]:(是==,我没搞错(因为jump if false))
goto 53(继续循环)
else return flase
*/

25 91 LOAD_GLOBAL 5 'False'
94 RETURN_END_IF
95_0 COME_FROM 88 '88'
95 JUMP_BACK 53 'to 53'
98 POP_BLOCK
99_0 COME_FROM 34 '34'

26 99 LOAD_FAST 1 'arr'
102 LOAD_CONST -2
105 LOAD_CONST 33
108 LOAD_CONST -1
111 BUILD_SLICE_3 3
114 BINARY_SUBSCR
115 LOAD_CONST 5
118 BINARY_MULTIPLY
119 STORE_FAST 4 'b'

//b = arr[-2:33:-1] * 5

27 122 LOAD_GLOBAL 0 'map'
125 LOAD_LAMBDA '<code_object <lambda>>'
128 MAKE_FUNCTION_0 0 None
131 LOAD_GLOBAL 6 'zip'
134 LOAD_FAST 4 'b'
137 LOAD_FAST 1 'arr'
140 LOAD_CONST 7
143 LOAD_CONST 27
146 SLICE+3
147 CALL_FUNCTION_2 2 None
150 CALL_FUNCTION_2 2 None
153 STORE_FAST 5 'c'
//c = map(lambda x:函数体, zip(b, arr[7,27]))
//即c[i] = b[i] ^ arr[7,27][i]

28 156 LOAD_FAST 5 'c'
159 LOAD_GLOBAL 7 'arr1'
162 COMPARE_OP 3 !=
165 POP_JUMP_IF_FALSE 172 'to 172'
29 168 LOAD_GLOBAL 5 'False'
171 RETURN_END_IF
172_0 COME_FROM 165 '165'

/*
if c == arr1:
goto 172
else return false
*/

30 172 LOAD_CONST 0
175 STORE_FAST 6 'p'

31 178 SETUP_LOOP 105 'to 286'
181 LOAD_GLOBAL 2 'range'
184 LOAD_CONST 28
187 LOAD_CONST 34
190 CALL_FUNCTION_2 2 None
193 GET_ITER
194 FOR_ITER 88 'to 285'
197 STORE_FAST 3 'i'

/*
for i in range(28, 34)
*/

32 200 LOAD_FAST 1 'arr'
203 LOAD_FAST 3 'i'
206 BINARY_SUBSCR
207 LOAD_CONST 107
210 BINARY_ADD
211 LOAD_CONST 16
214 BINARY_DIVIDE
215 LOAD_CONST 77
218 BINARY_ADD

// ((arr[i] + 107)//16)+77 (除法//还是/存疑)

219 LOAD_GLOBAL 8 'arr2'
222 LOAD_FAST 6 'p'
225 BINARY_SUBSCR
226 COMPARE_OP 3 !=
229 POP_JUMP_IF_TRUE 268 'to 268'
/*
if arr2[p] != ((arr[i] + 107)//16)+77 :
return false
*/
232 LOAD_FAST 1 'arr'
235 LOAD_FAST 3 'i'
238 BINARY_SUBSCR
239 LOAD_CONST 117
242 BINARY_ADD
243 LOAD_CONST 16
246 BINARY_MODULO
247 LOAD_CONST 99
250 BINARY_ADD
251 LOAD_GLOBAL 8 'arr2'
254 LOAD_FAST 6 'p'
257 LOAD_CONST 1
260 BINARY_ADD
261 BINARY_SUBSCR
262 COMPARE_OP 3 !=
265_0 COME_FROM 229 '229'
265 POP_JUMP_IF_FALSE 272 'to 272'
/*
if (arr[i]+117)%16+99 == arr2[p+1]:
goto 272
else return false
*/

33 268 LOAD_GLOBAL 9 'false'
271 RETURN_END_IF
272_0 COME_FROM 265 '265'

34 272 LOAD_FAST 6 'p'
275 LOAD_CONST 2
278 INPLACE_ADD //TOS = TOS1 + TOS
279 STORE_FAST 6 'p'
282 JUMP_BACK 194 'to 194'
285 POP_BLOCK
286_0 COME_FROM 178 '178'

//p += 2,然后继续循环

35 286 LOAD_GLOBAL 10 'True'
289 RETURN_VALUE

整理一下,check3的逻辑是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def check3():
arr = map(ord, flag)
a = arr[6:30:3]
for i in range(len(a)):
if (a[i] * 17684 + 372511) % 257 != arr0[i]:
return False
b = arr[-2:33:-1] * 5
c = map(lambda x: x[0]^x[1], zip(b, arr[7,27]))
if c != arr1:
return False
p = 0
for i in range(28, 34):
if arr2[p] != (((arr[i]+107)//16)+77):
return False
if arr2[p+1] != ((arr[i]+117)%16+99):
return False
p += 2
return True

逆向

倒着逆,脚本如下:

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
arr0 = [249, 91, 149, 113, 16, 91, 53 ,41]
arr1 = [43, 1, 6, 69, 20, 62, 6, 44, 24, 113, 6, 35, 0 , 3, 6, 44, 20, 22, 127, 60]
arr2 = [90, 100, 87, 109, 86, 108, 86, 105, 90, 104, 88, 102]


def check3():
arr = map(ord, flag)
a = arr[6:30:3]
for i in range(len(a)):
if (a[i] * 17684 + 372511) % 257 != arr0[i]:
return False
b = arr[-2:33:-1] * 5
c = map(lambda x: x[0]^x[1], zip(b, arr[7,27]))
if c != arr1:
return False
p = 0
for i in range(28, 34):
if arr2[p] != (((arr[i]+107)//16)+77):
return False
if arr2[p+1] != ((arr[i]+117)%16+99):
return False
p += 2
return True


def solve_check1():
for i in range(100):
if ((i *i )%777) ^ 233 == 513:
print(i)

def solve_5():
for i,n in enumerate('flag{'):
flag[i] = ord(n)
flag[38] = ord('}')
sum = 0
for i in range(5):
sum = (sum + flag[i]) * 128
#print(sum)
for i in range(32,128):
if sum+i==3533889469877:
flag[5] = i

def solve_6_30_3():
for i in range(6, 30, 3):
for j in range(32, 128):
if (j * 17684 + 372511) % 257 == arr0[(i-6)//3]:
flag[i] = j
break

def solve_28_34():
p = 0
for i in range(28,34):
for j in range(32,128):
if (((j+107)//16)+77) == arr2[p] and ((j+117)%16+99) == arr2[p+1]:
flag[i] = j
break
p += 2

def solve_34_38():
flag[34] = flag[18] ^ arr1[11]
flag[35] = flag[9] ^ arr1[2]
flag[36] = flag[12] ^ arr1[5]
flag[37] = flag[15] ^ arr1[8]

def solve_7_27():
b = flag[37:33:-1]*5
for i in range(20):
flag[i+7] = b[i] ^ arr1[i]


def main():
solve_check1()
global flag
flag = [0] * 39
solve_5()
solve_6_30_3()
solve_28_34()
solve_34_38()
solve_7_27()
for i in flag:
print(chr(i),end='')

main()

注意flag[7, 27]的值需要依靠flag[34, 38]求出。