作者: Hcamael@知道創宇404實驗室
發布時間:2017-03-20
上周末的0CTF出現了一個pyc的題目,但是Pyopcode損壞,于是手擼了一波
題目: https://github.com/Hcamael/CTF_repo/tree/master/0CTF%202017/Re3%28py%29
通過pyc還原出py網上的資料挺多了,py也有專門的庫可以還原,但是0CTF這題卻無法還原,目測是opcode損壞,同時根據題目描述,也知道是要修復pyc文件。
這里用到兩個庫,一個dis,可以把二進制反編譯CPython bytecode。一個是marshal,可以把字符串轉換成pyopcode對象
>>> import dis, marshal
>>> f = open("crypt.pyc")
>>> f.read(4)
'\x03\xf3\r\n' # magic number
>>> f.read(4) # time
'f4oX'
>>> code = marshal.load(f)
# 對我們有用的屬性有:
>>> code.co_argcount # 參數的個數
0
>>> code.co_varnames # 局部變量
()
>>> code.co_consts # 常量
(-1, None, <code object encrypt at 0x7f1987df65b0, file "/Users/hen/Lab/0CTF/py/crypt.py", line 2>, <code object decrypt at 0x7f1987e10430, file "/Users/hen/Lab/0CTF/py/crypt.py", line 10>)
# 從這個常量中我們可以看出,該py文件中定義了兩個函數,encrypt和decrypt
>>> code.co_code
'\x99\x00\x00\x99\x01\x00\x86\x00\x00\x91\x00\x00\x99\x02\x00\x88\x00\x00\x91\x01\x00\x99\x03\x00\x88\x00\x00\x91\x02\x00\x99\x01\x00S'
# CPython bytecode的二進制, 可以通過dis反編譯
>>> dis.disassemble_string(code.co_code)
0 <153> 0
3 <153> 1
6 MAKE_CLOSURE 0
9 EXTENDED_ARG 0
12 <153> 2
15 LOAD_DEREF 0
18 EXTENDED_ARG 1
21 <153> 3
24 LOAD_DEREF 0
27 EXTENDED_ARG 2
30 <153> 1
33 RETURN_VALUE
# 發現bytecode損壞,根本無法閱讀
二進制對應的bytecode可以參考: https://github.com/Python/cpython/blob/2.7/Include/opcode.h
從上面的參考連接可以得知153沒有對應的bytecode,所以猜測bytecode損壞
每個bytecode所代表的意義: https://docs.Python.org/2/library/dis.html
>>> code.co_name # 當前對象名
'<module>'
>>> code.co_names # 當前對象中使用的對象名
('rotor', 'encrypt', 'decrypt')
# 從上可以看出,encrypt和decrypt是我們定義的兩個函數,那么rotor我們可以猜測是通過import rotor得來的
rotor的使用可以參考: https://docs.Python.org/2.0/lib/module-rotor.html
# 我們可以通過以下方式查看兩個函數中的信息
>>> enc = code.co_consts[2]
>>> dec = code.co_consts[3]
>>> enc.co_argcount
1
>>> dec.co_argcount
1
# 兩個函數中都有一個傳入的參數
>>> enc.co_varnames
('data', 'key_a', 'key_b', 'key_c', 'secret', 'rot')
>>> dec.co_varnames
('data', 'key_a', 'key_b', 'key_c', 'secret', 'rot')
# 兩個函數中的局部變量, 我們可以猜測,data是傳入的參數,需要加解密的數據
>>> enc.co_consts
(None, '!@#$%^&*', 'abcdefgh', '<>{}:"', 4, '|', 2, 'EOF')
>>> dec.co_consts
(None, '!@#$%^&*', 'abcdefgh', '<>{}:"', 4, '|', 2, 'EOF')
# 兩個函數中的常量,我們可以猜測key_a, key_b, key_c三個變量對應的值
>>> enc.co_code
"\x99\x01\x00h\x01\x00\x99\x02\x00h\x02\x00\x99\x03\x00h\x03\x00a\x01\x00\x99\x04\x00F\x99\x05\x00'a\x02\x00a\x01\x00'a\x03\x00'\x99\x06\x00F'\x99\x05\x00'a\x02\x00\x99\x06\x00F'\x99\x07\x00'h\x04\x00\x9b\x00\x00`\x01\x00a\x04\x00\x83\x01\x00h\x05\x00a\x05\x00`\x02\x00a\x00\x00\x83\x01\x00S"
>>> dec.co_code
"\x99\x01\x00h\x01\x00\x99\x02\x00h\x02\x00\x99\x03\x00h\x03\x00a\x01\x00\x99\x04\x00F\x99\x05\x00'a\x02\x00a\x01\x00'a\x03\x00'\x99\x06\x00F'\x99\x05\x00'a\x02\x00\x99\x06\x00F'\x99\x07\x00'h\x04\x00\x9b\x00\x00`\x01\x00a\x04\x00\x83\x01\x00h\x05\x00a\x05\x00`\x02\x00a\x00\x00\x83\x01\x00S"
# 發現兩個函數bytecode的二進制是一樣的,操作是一樣的?
>>> enc.co_name
'encrypt'
>>> enc.co_names
('rotor', 'newrotor', 'encrypt')
>>> dec.co_name
'decrypt'
>>> dec.co_names
('rotor', 'newrotor', 'decrypt')
# 通過研究rotor的用法,猜測兩個函數的區別可能是在于rotor.newrotor(key).encrypt(data)和rotor.newrotor(key).decrypt(data)
所以現在的問題就在于,key是怎么來的,然后就開始了手擼CPython bytecode
>>> dis.disassemble_string(dec.co_code)
0 <153> 1
3 BUILD_SET 1
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
右邊的數字為操作數,括號里的是注釋
因為題目啥信息也沒給我們。。。所以修bytecode只能靠猜
我們先假設這里所有的153為同一個操作符,同理所有的39也為同一個
先看第一部分
0 <153> 1
3 BUILD_SET 1
6 <153> 2
9 BUILD_SET 2
12 <153> 3
15 BUILD_SET 3
這是最容易猜的地方,右邊的操作數為123,在看常量和局部變量的tuple,可以猜測是:
key_a = '!@#$%^&*'
key_b = 'abcdefgh'
key_c = '<>{}:"'
然后去上面給的參考文檔里,查出對應的bytecode
0 LOAD_CONST 1
3 STORE_FAST 1
6 LOAD_CONST 2
9 STORE_FAST 2
12 LOAD_CONST 3
15 STORE_FAST 3
再去opcode.h中查其對應的值進行替換
>>> dis.disassemble_string(dec.co_code.replace("\x99","\x64").replace("\x68","\x7d"))
0 LOAD_CONST 1 (1)
3 STORE_FAST 1 (1)
6 LOAD_CONST 2 (2)
9 STORE_FAST 2 (2)
12 LOAD_CONST 3 (3)
15 STORE_FAST 3 (3)
18 STORE_GLOBAL 1 (1)
21 LOAD_CONST 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)
43 PRINT_EXPR
44 <39>
45 LOAD_CONST 5 (5)
48 <39>
49 STORE_GLOBAL 2 (2)
52 LOAD_CONST 6 (6)
55 PRINT_EXPR
56 <39>
57 LOAD_CONST 7 (7)
60 <39>
61 STORE_FAST 4 (4)
64 <155> 0
67 DELETE_ATTR 1 (1)
70 STORE_GLOBAL 4 (4)
73 CALL_FUNCTION 1
76 STORE_FAST 5 (5)
79 STORE_GLOBAL 5 (5)
82 DELETE_ATTR 2 (2)
85 STORE_GLOBAL 0 (0)
88 CALL_FUNCTION 1
91 RETURN_VALUE
繼續,發現肛不動了。。。。然后從底下開始肛
64 <155> 0
67 DELETE_ATTR 1 (1)
70 STORE_GLOBAL 4 (4)
73 CALL_FUNCTION 1
76 STORE_FAST 5 (5)
79 STORE_GLOBAL 5 (5)
82 DELETE_ATTR 2 (2)
85 STORE_GLOBAL 0 (0)
88 CALL_FUNCTION 1
91 RETURN_VALUE
根據之前得到的結論,可以猜測這里的代碼是:
xxx = rotor.newrotor(secret)
return xxx.decrypt(data)
所以猜測上面的操作數,0指的是局部變量data,1指的是全局變量newrotor, 2猜測可能是全局變量decrypt, 4指的是局部變量secret, 5指的是局部變量rot,<155>的0可能指的是全局變量rotor
然后查找bytecode,改成:
64 LOAD_GLOBAL 0
67 LOAD_ATTR 1 (1)
70 LOAD_FAST 4 (4)
73 CALL_FUNCTION 1
76 STORE_FAST 5 (5)
79 LOAD_FAST 5 (5)
82 LOAD_ATTR 2 (2)
85 LOAD_FAST 0 (0)
88 CALL_FUNCTION 1
91 RETURN_VALUE
發現,合情合理,使人姓胡.....
然后和上面一樣,整體替換bytecode:
>>> dis.disassemble_string(dec.co_code.replace("\x99","\x64").replace("\x68","\x7d").replace("\x61","\x7c").replace("\x60","\x6a").replace("\x9b","\x74"))
0 LOAD_CONST 1 (1)
3 STORE_FAST 1 (1)
6 LOAD_CONST 2 (2)
9 STORE_FAST 2 (2)
12 LOAD_CONST 3 (3)
15 STORE_FAST 3 (3)
18 LOAD_FAST 1 (1)
21 LOAD_CONST 4 (4)
24 PRINT_EXPR
25 LOAD_CONST 5 (5)
28 <39>
29 LOAD_FAST 2 (2)
32 LOAD_FAST 1 (1)
35 <39>
36 LOAD_FAST 3 (3)
39 <39>
40 LOAD_CONST 6 (6)
43 PRINT_EXPR
44 <39>
45 LOAD_CONST 5 (5)
48 <39>
49 LOAD_FAST 2 (2)
52 LOAD_CONST 6 (6)
55 PRINT_EXPR
56 <39>
57 LOAD_CONST 7 (7)
60 <39>
61 STORE_FAST 4 (4)
64 LOAD_GLOBAL 0 (0)
67 LOAD_ATTR 1 (1)
70 LOAD_FAST 4 (4)
73 CALL_FUNCTION 1
76 STORE_FAST 5 (5)
79 LOAD_FAST 5 (5)
82 LOAD_ATTR 2 (2)
85 LOAD_FAST 0 (0)
88 CALL_FUNCTION 1
91 RETURN_VALUE
18 LOAD_FAST 1 (1)
21 LOAD_CONST 4 (4)
24 PRINT_EXPR
25 LOAD_CONST 5 (5)
28 <39>
29 LOAD_FAST 2 (2)
32 LOAD_FAST 1 (1)
35 <39>
36 LOAD_FAST 3 (3)
39 <39>
40 LOAD_CONST 6 (6)
43 PRINT_EXPR
44 <39>
45 LOAD_CONST 5 (5)
48 <39>
49 LOAD_FAST 2 (2)
52 LOAD_CONST 6 (6)
55 PRINT_EXPR
56 <39>
57 LOAD_CONST 7 (7)
60 <39>
61 STORE_FAST 4 (4)
猜測這部分就是局部變量secret計算的方法,最后一句STORE_FAST 4,猜測就是把上面計算后的值儲存到secret中
整體看看,發現主要就剩<39>和PRINT_EXPR不通順了。。。然后他們都是沒操作數的,所以排除了調用函數,調用屬性之類的
之后聯想到最開頭對key_a, key_b, key_c的賦值,然后目前的bytecode中沒有任何運算操作
再來看這部分
18 LOAD_FAST 1 ("!@#$%^&*")
21 LOAD_CONST 4 (4)
24 PRINT_EXPR
所以猜測PRINT_EXPR, 是字符串和整型之間的操作運算
29 LOAD_FAST 2 ("abcdefgh")
32 LOAD_FAST 1 ("!@#$%^&*")
35 <39>
這里猜測<39>是字符串和字符串之間的操作運算
到這里,我們來想想,整型和字符串之間的操作有啥?
>>> "a"*3
>>> "aaa"[1]
>>> "aaa"[:1]
>>> "aaa"[1:]
字符串和字符串之間呢?
>>> "aaa" + "bbb" # 我只想到了這一個
從上面可以猜測出,<39>可能是字符串拼接的操作,然后PRINT_EXPR需要一個一個去試,現在我們可以還原出decrypt函數了:
# 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"
# secret=key_a[4] + "|" + (key_b+key_a+key_c)[2] + "|" + key_b[2] + "EOF"
# secret=key_a[4:] + "|" + (key_b+key_a+key_c)[2:] + "|" + key_b[2:] + "EOF"
# secret=key_a[:4] + "|" + (key_b+key_a+key_c)[:2] + "|" + key_b[:2] + "EOF"
rot = rotor.newrotor(secret)
return rot.decrypt(data)
簡直難受.......
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/443/
暫無評論