0CTF_2017_python逆向

上周末打了一下0CTF,实力被虐了一波,好歹也是拿下了萌新组第一。。。

Python逆向 in 0CTF—2017

题目是一个.pyc文件,和一段密文,解题思路类似于python的pyc逆向。

查了一下pyc文件的格式,发现文件的前四位是magic number,用来识别python版本,接着的四位为时间戳。

1
2
3
4
5
6
7
8
9
>>> import dis , marshal

>>> f = open("crypt.pyc")

>>> f.read(4)
'\x03\xf3\r\n'

>>> f.read(4)
'f4oX'

接下来是读取opcode,发现opcode逻辑不对,所以修改opcode来还原函数算法。

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
>>> code = marshal.load(f)

>>> code.co_consts
(-1, None, <code object encrypt at 0x7f434f8a09b0, file "/Users/hen/Lab/0CTF/py/crypt.py", line 2>, <code object decrypt at 0x7f434fed9430, file "/Users/hen/Lab/0CTF/py/crypt.py", line 10>)

>>> enc = code.co_consts[2]

>>> dec = code .co_consts[3]

>>> dis.dis(dec.co_code)
0 <153> 1
3 BUILD_SET 1 //右边数字代表了调用了第几个量,从0开始计数
6 <153> 2
9 BUILD_SET 2
12 <153> 3
15 BUILD_SET 3
18 STORE_GLOBAL 1 (1)
21 <153> 4
24 PRINT_EXPR
25 <153> 5
28 <39>
29 STORE_GLOBAL 2 (2)
32 STORE_GLOBAL 1 (1)
35 <39>
36 STORE_GLOBAL 3 (3)
39 <39>
40 <153> 6
43 PRINT_EXPR
44 <39>
45 <153> 5
48 <39>
49 STORE_GLOBAL 2 (2)
52 <153> 6
55 PRINT_EXPR
56 <39>
57 <153> 7
60 <39>
61 BUILD_SET 4
64 <155> 0
67 DELETE_ATTR 1 (1)
70 STORE_GLOBAL 4 (4)
73 CALL_FUNCTION 1
76 BUILD_SET 5
79 STORE_GLOBAL 5 (5)
82 DELETE_ATTR 2 (2)
85 STORE_GLOBAL 0 (0)
88 CALL_FUNCTION 1
91 RETURN_VALUE

然后查看有关函数的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> dec.co_names
('rotor', 'newrotor', 'decrypt')

>>> enc.co_names
('rotor', 'newrotor', 'encrypt')

>>> enc.co_varnames
('data', 'key_a', 'key_b', 'key_c', 'secret', 'rot')

>>> dec.co_varnames
('data', 'key_a', 'key_b', 'key_c', 'secret', 'rot')

>>> dec.co_consts
(None, '!@#$%^&*', 'abcdefgh', '<>{}:"', 4, '|', 2, 'EOF')

>>> enc.co_consts
(None, '!@#$%^&*', 'abcdefgh', '<>{}:"', 4, '|', 2, 'EOF')

发现两个函数的变量和常量一致,所以我们只需要复现出解密函数就可以解密密文。函数有三个比较瞩目的参数’key_a’, ‘key_b’, ‘key_c’,三个参数在’varnames’中正好对应了’1’,’2’,’3’三个位置,所以我们可以推断出’co_code’的前六行是利用’!@#$%^&*’, ‘abcdefgh’, ‘<>{}:”‘对’key_a’, ‘key_b’, ‘key_c’赋值。

https://github.com/python/cpython/blob/master/Include/opcode.h
https://docs.python.org/2/library/dis.html#opcode-BUILD_SET

可查opcode表,读取常量操作应为“100”,而我们得到的却是“153”,并且赋值的指令应为“125”,所以,我们的第一次修改:

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
>>> dis.dis(dec.co_code.replace("\x99","\x64").replace("\x68","\x7d"))
0 LOAD_CONST 1 (1) ('!@#$%^&*')
3 STORE_FAST 1 (1) (key_a)
6 LOAD_CONST 2 (2) ('abcdefgh')
9 STORE_FAST 2 (2) (key_b)
12 LOAD_CONST 3 (3) ('<>{}:"')
15 STORE_FAST 3 (3) (key_c)
18 STORE_GLOBAL 1 (1)
21 LOAD_CONST 4 (4) (4)
24 PRINT_EXPR
25 LOAD_CONST 5 (5) ('|')
28 <39>
29 STORE_GLOBAL 2 (2)
32 STORE_GLOBAL 1 (1)
35 <39>
36 STORE_GLOBAL 3 (3)
39 <39>
40 LOAD_CONST 6 (6) (2)
43 PRINT_EXPR
44 <39>
45 LOAD_CONST 5 (5) ('|')
48 <39>
49 STORE_GLOBAL 2 (2)
52 LOAD_CONST 6 (6) (2)
55 PRINT_EXPR
56 <39>
57 LOAD_CONST 7 (7) ('EOF')
60 <39>
61 STORE_FAST 4 (4) (secret)
64 <155> 0
67 DELETE_ATTR 1 (1)
70 STORE_GLOBAL 4 (4)
73 CALL_FUNCTION 1
76 STORE_FAST 5 (5) (rot)
79 STORE_GLOBAL 5 (5)
82 DELETE_ATTR 2 (2)
85 STORE_GLOBAL 0 (0)
88 CALL_FUNCTION 1
91 RETURN_VALUE

我们可以确定如上数据。程序有两个’CALL_FUNCTION’,所以一共调用了两次函数,第二次可以确定为为return。查看rotor库的说明,发现其有’rotor.newrotor()’操作和’rot.decrypt()’操作。可以推断return返回值为解密得到的值,并且解密和return是同一操作,可以确定’return rot.decrypt(data)’。https://docs.python.org/2.0/lib/module-rotor.html

1
2
3
79 STORE_GLOBAL        5 (5)    (rot)
82 DELETE_ATTR 2 (2) (decrypt)
85 STORE_GLOBAL 0 (0) (data)

借此可以修改82和85行的opcode:

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
>>> dis.dis(dec.co_code.replace("\x99","\x64").replace("\x68","\x7d").replace("\x60","\x6a").replace("\x61","\x7c"))
0 LOAD_CONST 1 (1) ('!@#$%^&*')
3 STORE_FAST 1 (1) (key_a)
6 LOAD_CONST 2 (2) ('abcdefgh')
9 STORE_FAST 2 (2) (key_b)
12 LOAD_CONST 3 (3) ('<>{}:"')
15 STORE_FAST 3 (3) (key_c)
18 LOAD_FAST 1 (1) (key_a)
21 LOAD_CONST 4 (4) (4)
24 PRINT_EXPR
25 LOAD_CONST 5 (5) ('|')
28 <39>
29 LOAD_FAST 2 (2) (key_b)
32 LOAD_FAST 1 (1) (key_a)
35 <39>
36 LOAD_FAST 3 (3) (key_c)
39 <39>
40 LOAD_CONST 6 (6) (2)
43 PRINT_EXPR
44 <39>
45 LOAD_CONST 5 (5) ('|')
48 <39>
49 LOAD_FAST 2 (2) (key_b)
52 LOAD_CONST 6 (6) (2)
55 PRINT_EXPR
56 <39>
57 LOAD_CONST 7 (7) ('EOF')
60 <39>
61 STORE_FAST 4 (4) (secret)
64 <155> 0
67 LOAD_ATTR 1 (1) (newrotor)
70 LOAD_FAST 4 (4) (secret)
73 CALL_FUNCTION 1
76 STORE_FAST 5 (5) (rot)
79 LOAD_FAST 5 (5) (rot)
82 LOAD_ATTR 2 (2) (decrypt)
85 LOAD_FAST 0 (0) (data)
88 CALL_FUNCTION 1
91 RETURN_VALUE

由于程序对’key_a’, ‘key_b’, ‘key_c’的操作都没有经过函数调用,所以只是基本的字符串操作。在第18和21行中看出,’key_a’有个对’4’的操作,所以有三种情况:

1
2
3
key_a[4:]
key_a[:4]
key_a * 4

所以所有的’<39>‘有三种可能,需要一种一种的去测试。对于第64行的’<155>‘,结合上下代码可以推断出该处应为’rot = rotor.newrotor(secret)’。至此可以复现出源python代码。
经过测试’<39>‘的操作为’key_a * 4’。
自后得到的opcode为:

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
 0 LOAD_CONST               1 ('!@#$%^&*')
3 STORE_FAST 1 (key_a)
6 LOAD_CONST 2 ('abcdefgh')
9 STORE_FAST 2 (key_b)
12 LOAD_CONST 3 ('<>{}:"')
15 STORE_FAST 3 (key_c)
18 LOAD_FAST 1 (key_a)
21 LOAD_CONST 4 (4)
24 INPLACE_MULTIPLY
25 LOAD_CONST 5 ('|')
28 BINARY_ADD
29 LOAD_FAST 2 (key_b)
32 LOAD_FAST 1 (key_a)
35 BINARY_ADD
36 LOAD_FAST 3 (key_c)
39 BINARY_ADD
40 LOAD_CONST 6 (2)
43 INPLACE_MULTIPLY
44 BINARY_ADD
45 LOAD_CONST 5 ('|')
48 BINARY_ADD
49 LOAD_FAST 2 (key_b)
52 LOAD_CONST 6 (2)
55 INPLACE_MULTIPLY
56 BINARY_ADD
57 STORE_FAST 4 (secret)
60 LOAD_GLOBAL 0 (rotor)
63 LOAD_ATTR 1 (newrotor)
66 LOAD_FAST 4 (secret)
69 CALL_FUNCTION 1
72 STORE_FAST 5 (rot)
75 LOAD_FAST 5 (rot)
78 LOAD_ATTR 2 (decrypt)
81 LOAD_FAST 0 (buf)
84 CALL_FUNCTION 1
87 RETURN_VALUE

还原程序的加密部分:

1
2
3
4
5
6
7
8
import rotor
def decrypt(data):
key_a = "!@#$%^&*"
key_b = "abcdefgh"
key_c = '<>{}:"'
secret=key_a*4 + "|" + (key_b+key_a+key_c)*2 + "|" + key_b*2 + 'EOF'
rot = rotor.newrotor(secret)
return rot.decrypt(data)

payload.py

1
2
3
4
5
6
7
8
9
10
11
12
13
import rotor
def decrypt(data):
key_a = "!@#$%^&*"
key_b = "abcdefgh"
key_c = '<>{}:"'
secret=key_a*4 + "|" + (key_b+key_a+key_c)*2 + "|" + key_b*2 + 'EOF'
rot = rotor.newrotor(secret)
return rot.decrypt(data)

f = open("/home/processor/encrypted_flag")
data = f.read()
flag = decrypt(data)
print flag

解密过程:

1
2
 ~ python payload.py
flag{Gue55_opcode_G@@@me}
文章目录
  1. 1. Python逆向 in 0CTF—2017
,