上周末打了一下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为:39>155>39>
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}
|