CTF中常見的四種python逆向

合天网安实验室發表於2024-04-18

說在前面:

什麼是pyc檔案?

pyc是一種二進位制檔案,是由py檔案經過編譯後,生成的檔案,是一種byte code,py檔案變成pyc檔案後,載入的速度有所提高,pyc 檔案是 Python 編譯過的位元組碼檔案。它是 Python 程式在執行過程中由原始碼(通常是 .py 檔案)自動或手動編譯產生的二進位制檔案。

而且pyc是一種跨平臺的位元組碼,是由[Python]的虛擬機器來執行的,這個是類似於[Java]或者.NET的虛擬機器的概念。pyc的內容,是跟python的版本相關的,不同版本編譯後的pyc檔案是不同的,2.5編譯的pyc檔案,2.4版本的python是無法執行的。

為什麼需要pyc檔案?

因為py檔案是可以直接看到原始碼的,如果你是開發商業軟體的話,不可能把原始碼也洩漏出去吧?所以就需要編譯為pyc後,再發布出去。當然,pyc檔案也是可以反編譯的,不同版本編譯後的pyc檔案是不同的,根據python原始碼中提供的opcode,可以根據pyc檔案反編譯出py檔案原始碼,網上可以找到一個反編譯python2.3版本的pyc檔案的工具,不過該工具從python2.4開始就要收費了,如果需要反編譯出新版本的pyc檔案的話,就需要自己動手了,不過你可以自己修改python的原始碼中的opcode檔案,重新編譯python,從而防止不法分子的破解。

pyc檔案

解法:uncompyle6直接反編譯

eg.

def check():
    flag=1+1
    if(flag==2):
        return "right"
    return "error"
print(check())

這是我們所寫的一個簡單的python例子

現在我們來生成pyc檔案 這裡用的是python3

pyhton -m test.py

CTF中常見的四種python逆向

CTF中常見的四種python逆向

pyc檔案也是可以執行的

我們在對應的資料夾的搜尋框下輸入powershell

然後輸入

python .\test.pyc

image

可以看到即使是執行py檔案也是可以執行的

但區別的是我們沒法看到pyc檔案裡面是什麼東西,即使拖進IDA裡面也無濟於事

所以這裡我們需要下載一個工具

uncompyle6.exe

在終端開啟並輸入

pip install uncompyle

安裝後包含uncompyle6 但是版本為3.8.0 會導致一些軟體的反編譯失敗建議使用下面命令回到3.7.4版本

pip install uncompyle6==3.7.4

安裝以後,我們回到我們的tmp目錄並開啟powershell輸入

uncompyle6.exe .\test.py

image test

這裡我們就得到了原始碼

接下來的操作就跟windows逆向別無二致了

【----幫助網安學習,以下所有學習資料免費領!加vx:dctintin,備註 “部落格園” 獲取!】

 ① 網安學習成長路徑思維導圖
 ② 60+網安經典常用工具包
 ③ 100+SRC漏洞分析報告
 ④ 150+網安攻防實戰技術電子書
 ⑤ 最權威CISSP 認證考試指南+題庫
 ⑥ 超1800頁CTF實戰技巧手冊
 ⑦ 最新網安大廠面試題合集(含答案)
 ⑧ APP客戶端安全檢測指南(安卓+IOS)

txt裡面是pyc位元組碼

解法:

  • 讀py位元組碼

  • 根據opcode檔案查詢意思

我們先來得到我們test.py例子的位元組碼

在powershell下先輸入python 然後輸入

import dis,marshal
f=open("test.pyc", "rb").read()
f

dis和marshal庫 一個是裝載庫 一個是反編譯位元組碼的庫

image test

我們可以將我們的test.pyc匯入010Editor中配合著一起看

python2的前八個位元組是python2的魔術字

python3的前十六個位元組是python3的魔術字

所以我們可以不用讀前十六位 我們只需要讀後十六位的東西

code=marshal.loads(f[16:])
code

現在我們讀進來的是二進位制資料,我們可以用dis來進行反編譯 就會得到python的位元組碼 可以理解成python的彙編讀出來了

dis.dis(code)

image test

這就是python的彙編

我們這裡是直接將pyc檔案uncompyle6回去的,但是在CTF中一般都是把python的彙編直接複製貼上出來丟給我們...

所以我們只能硬看...或者配合點東西 比如python的opcode一起看

補充一點:python2是3個位元組為1個指令 python3是2個位元組為1個指令

比如test.pyc中LOAD_CONST 指令就佔用了0,1兩個位元組

所以下條指令就從3開始了

1           0 LOAD_CONST               0 (<code object keyinit at 0x0000028C1CC11D20, file "crackPYC.py", line 1>)
2 LOAD_CONST               1 ('keyinit')
4 MAKE_FUNCTION            0
6 STORE_NAME               0 (keyinit)
​
8           8 LOAD_NAME                1 (__name__)
10 LOAD_CONST               2 ('__main__')
12 COMPARE_OP               2 (==)
14 POP_JUMP_IF_FALSE      250
​
9          16 LOAD_NAME                2 (print)
18 LOAD_CONST               3 ('Can you crack pyc?')
20 CALL_FUNCTION            1
22 POP_TOP
​
10          24 LOAD_NAME                3 (input)
26 LOAD_CONST               4 ('Plz give me your flag:')
28 CALL_FUNCTION            1
30 STORE_NAME               4 (str)     #將輸入的字元存入str內
​
11          32 LOAD_CONST               5 (108)
34 LOAD_CONST               6 (17)
36 LOAD_CONST               7 (42)
38 LOAD_CONST               8 (226)
40 LOAD_CONST               9 (158)
42 LOAD_CONST              10 (180)
44 LOAD_CONST              11 (96)
46 LOAD_CONST              12 (115)
48 LOAD_CONST              13 (64)
50 LOAD_CONST              14 (24)
52 LOAD_CONST              15 (38)
54 LOAD_CONST              16 (236)
56 LOAD_CONST              17 (179)
58 LOAD_CONST              18 (173)
60 LOAD_CONST              19 (34)
62 LOAD_CONST              20 (22)
64 LOAD_CONST              21 (81)
66 LOAD_CONST              22 (113)
68 LOAD_CONST              15 (38)
70 LOAD_CONST              23 (215)
72 LOAD_CONST              24 (165)
74 LOAD_CONST              25 (135)
76 LOAD_CONST              26 (68)
78 LOAD_CONST              27 (7)
​
12          80 LOAD_CONST              28 (119)
82 LOAD_CONST              29 (97)
84 LOAD_CONST              30 (45)
86 LOAD_CONST              31 (254)
88 LOAD_CONST              32 (250)
90 LOAD_CONST              33 (172)
92 LOAD_CONST              34 (43)
94 LOAD_CONST              35 (62)
96 BUILD_LIST              32           #建立容量為32的列表
98 STORE_NAME               5 (text)    #以上32個資料為text陣列的數值
​
13         100 LOAD_NAME                6 (len)     
102 LOAD_NAME                4 (str)     
104 CALL_FUNCTION            1
106 LOAD_CONST              36 (32)
108 COMPARE_OP               3 (!=)
110 POP_JUMP_IF_TRUE       140           #判斷str即輸入字串的長度是否為32,不是則跳轉到140
112 LOAD_NAME                4 (str)     
114 LOAD_CONST              37 (0)
116 LOAD_CONST              27 (7)
118 BUILD_SLICE              2
120 BINARY_SUBSCR
122 LOAD_CONST              38 ('DASCTF{')
124 COMPARE_OP               3 (!=)
126 POP_JUMP_IF_TRUE       140           #判斷str字串的前七位是否為'DASCTF{',不是則跳轉到140
128 LOAD_NAME                4 (str)
130 LOAD_CONST              39 (31)
132 BINARY_SUBSCR
134 LOAD_CONST              40 ('}')
136 COMPARE_OP               3 (!=)
138 POP_JUMP_IF_FALSE      154           #判斷str字串的最後一位也就是31位是否為'}',不是則跳轉到154
#因為如果不跳轉繼續執行的話就會執行到輸入字串符合的一段程式碼使程式                                                  #退出
​
14     >>  140 LOAD_NAME                2 (print)    
142 LOAD_CONST              41 ('Bye bye~~')
144 CALL_FUNCTION            1
146 POP_TOP
​
15         148 LOAD_NAME                7 (exit)
150 CALL_FUNCTION            0
152 POP_TOP                              #退出程式
​
16     >>  154 LOAD_NAME                8 (list)    
156 LOAD_NAME                4 (str)
158 CALL_FUNCTION            1
160 STORE_NAME               9 (st)      #建立列表st
​
17         162 BUILD_LIST               0
164 STORE_NAME              10 (key)
​
18         166 LOAD_NAME                0 (keyinit)
168 LOAD_NAME               10 (key)
170 CALL_FUNCTION            1
172 POP_TOP
​
19         174 SETUP_LOOP              48 (to 224)
176 LOAD_NAME               11 (range)
178 LOAD_CONST              36 (32)
180 CALL_FUNCTION            1
182 GET_ITER
>>  184 FOR_ITER                36 (to 222)
186 STORE_NAME              12 (i)         #相當於for i in range(0,32)
​
20         188 LOAD_NAME               13 (ord)
190 LOAD_NAME                4 (str)
192 LOAD_NAME               12 (i)
194 BINARY_SUBSCR
196 CALL_FUNCTION            1
198 LOAD_NAME               10 (key)        
200 LOAD_NAME               12 (i)
202 LOAD_NAME                6 (len)
204 LOAD_NAME               10 (key)
206 CALL_FUNCTION            1            
208 BINARY_MODULO                          #key元素少於str元素,所以要把i和key的長度取餘避免越界
210 BINARY_SUBSCR
212 BINARY_XOR
214 LOAD_NAME                9 (st)
216 LOAD_NAME               12 (i)
218 STORE_SUBSCR                           #此處程式碼將str和key中的元素進行異或處理後存入st
220 JUMP_ABSOLUTE          184             #相當於st[i] = ord(str[i]) ^ key[i % len(key)]
>>  222 POP_BLOCK                            
​
21     >>  224 LOAD_NAME                9 (st)        
226 LOAD_NAME                5 (text)
228 COMPARE_OP               2 (==)
230 POP_JUMP_IF_FALSE      242             #對比st陣列和text陣列,不相等則跳轉到地址242處
​
22         232 LOAD_NAME                2 (print)
234 LOAD_CONST              42 ('Congratulations and you are good at PYC!')
236 CALL_FUNCTION            1
238 POP_TOP
240 JUMP_FORWARD             8 (to 250)
​
24     >>  242 LOAD_NAME                2 (print)
244 LOAD_CONST              43 ('Sorry,plz learn more about pyc.')
246 CALL_FUNCTION            1
248 POP_TOP
>>  250 LOAD_CONST              44 (None)
252 RETURN_VALUE
​
Disassembly of <code object keyinit at 0x0000028C1CC11D20, file "crackPYC.py", line 1>:
2           0 LOAD_CONST               1 (0)
2 STORE_FAST               1 (num)
​
3           4 SETUP_LOOP              42 (to 48)
6 LOAD_GLOBAL              0 (range)
8 LOAD_CONST               2 (8)   
10 CALL_FUNCTION            1
12 GET_ITER
>>   14 FOR_ITER                30 (to 46)
16 STORE_FAST               2 (i)         #相當於for i in range(0,8)
#從這裡我們可以知道key的長度為8
4          18 LOAD_FAST                1 (num)
20 LOAD_CONST               3 (7508399208111569251)
22 BINARY_SUBTRACT
24 LOAD_CONST               4 (4294967295)
26 BINARY_MODULO
28 STORE_FAST               1 (num)
​
5          30 LOAD_FAST                0 (key)
32 LOAD_METHOD              1 (append)
34 LOAD_FAST                1 (num)
36 LOAD_CONST               5 (24)
38 BINARY_RSHIFT                         #不理解這一句的意思
40 CALL_METHOD              1            #但這一段程式碼就是給key賦值
42 POP_TOP
44 JUMP_ABSOLUTE           14
>>   46 POP_BLOCK
>>   48 LOAD_CONST               0 (None)
​
50 RETURN_VALUE

這段程式碼的總體意思就是將輸入的str字串與key陣列進行異或加密後存入st陣列並於text陣列進行對比我們可以從程式碼中得之text陣列的元素值也可以知道str的前七位必為’DASCTF{’,最後一位必為’}’,而key陣列只有8位,所以對str的加密是8位8位的進行的又因為異或具有自反性,所以可以據’DASCTF{‘字串與text前7個元素做異或處理得出前7位,再將’}'與text最後一位進行異或 處理得出第8位,就可以得到key的整個陣列

這段彙編最關鍵的部分如下

Disassembly of <code object keyinit at 0x0000028C1CC11D20, file "crackPYC.py", line 1>:
2           0 LOAD_CONST               1 (0)
2 STORE_FAST               1 (num)
​
3           4 SETUP_LOOP              42 (to 48)
6 LOAD_GLOBAL              0 (range)
8 LOAD_CONST               2 (8)   
10 CALL_FUNCTION            1
12 GET_ITER
>>   14 FOR_ITER                30 (to 46)
16 STORE_FAST               2 (i)         #相當於for i in range(0,8)
#從這裡我們可以知道key的長度為8
4          18 LOAD_FAST                1 (num)
20 LOAD_CONST               3 (7508399208111569251)
22 BINARY_SUBTRACT
24 LOAD_CONST               4 (4294967295)
26 BINARY_MODULO
28 STORE_FAST               1 (num)
​
5          30 LOAD_FAST                0 (key)
32 LOAD_METHOD              1 (append)
34 LOAD_FAST                1 (num)
36 LOAD_CONST               5 (24)
38 BINARY_RSHIFT                         #不理解這一句的意思
40 CALL_METHOD              1            #但這一段程式碼就是給key賦值
42 POP_TOP
44 JUMP_ABSOLUTE           14
>>   46 POP_BLOCK
>>   48 LOAD_CONST               0 (None)
​
50 RETURN_VALUE

首先就是一個num的初始化 因為LOAD_CONST推送到堆疊

然後STORE_FAST將TOS(python的棧)儲存到本地中

這兩條結合起來 其實意思就是

num=0

接下來 SETUP_LOOP(delta)

將一個迴圈的塊推送到塊堆疊。該塊跨越當前指令,大小為delta位元組。

LOAD_GLOBAL 定義一個全域性變數 range LOAD_CONST定義一個常量 8

配合著utools裡面程式設計師手冊裡面的Python庫硬看

for i in range(8):

然後關鍵的這一步

首先LOAD_FAST 將num壓入棧堆,然後又把一個常量(7508399208111569251)推送到堆疊中

然後又執行BINARY_SUBTRACT 也就是減操作 即棧的後一位減去棧頂,對應到程式碼中也就是num減去這個常量

然後又推了個值4294967295 然後進行BINARY_MODULO操作 這是棧頂後一位取餘棧頂的值

最後STORE_FAST 儲存到num這個變數

所以這關鍵的一步python程式碼應該是

num=(num-7508399208111569251)%4294967295

後面乾的操作大體就是 LOAD_FAST num 然後LOAD_CONST 24 然後BINARY_RSHIFT 主要是就是棧頂後一位右移棧頂資料的值 然後儲存到key裡面 大概就是這麼個意思

print(num>>24)

結合起來就是這樣的

num=0
for i in range(8):
    num=(num-7508399208111569251)%4294967295
    print(num>>24)

這樣我們就得到這道題的金鑰

40
80
121
161
202
242
27
67

然後採用每八個位元組都去異或一下這個金鑰,flag就出來了

s=[108,17,42,226,158,180,96,115,64,24,38,236,179,173,34,22,81,113,38,215,165,135,68,7,119,97,
45,254,250,172,43,62]
key=[]
flag=''
num=0
for i in range(8):
    num=(num-7508399208111569251)%4294967295
    key.append(num>>24)
for i in range(32):
    flag += chr(key[i%len(key)] ^ s[i])                 
print(flag)

打包成exe的pyc檔案

解法:

  • 透過指令碼變成結構體和一個檔案

  • 重點:再把時間屬性和版本的魔術字放回去儲存

  • uncompyle6即可

image test

下載完題目發現這是個exe檔案 但是圖示又是很明顯的pyc檔案

所以這是個打包成exe的py檔案

這裡我們需要用到一個工具pyinstxtractor.py

把這個py檔案複製到我們的題目資料夾裡面

在搜尋框中輸入powershell 在開啟的終端中輸入

python .\pyinstxtractor.py .\attachment.exe

image test

執行後生成attachment.exe_extracted資料夾,進入之後看到一些原始檔,由於我電腦上的python是3.8版本,解包要3.6版本,所以生成了不正常的入口檔案login而不是login.pyc,想要變成正常的可反編譯的pyc檔案就要對生成檔案進行修改。(如果不嫌麻煩可以換一下python3.6的環境)

現在開始修改login入口檔案,這裡用的是winhex。

修改之前需要了解一點,在將python檔案打包成exe檔案的過程中,會抹去pyc檔案前面的部分資訊,所以在反編譯之前需要檢查並新增上這部分資訊,這部分資訊可以透過struct檔案獲取。

windex中開啟struct檔案後,把struct檔案前幾個位元組插入login開頭。(具體要插入幾個位元組還是要看解包後的檔案,我的檔案是E3位元組碼前面的丟失,那麼就只需要看struct中E3之前的位元組碼有哪些,ctrl + c複製,然後在login開頭ctrl + v 貼上即可。)

image test

image test

修改後如下

image test

儲存後 將login字尾名修改為.pyc即可

將login.pyc複製貼上到題目的資料夾後開啟powershell終端

並且呼叫uncompyle6.exe

uncompyle6.exe .\login.pyc

就可以看到原始碼了

image test

import sys
input1 = input('input something:')
if len(input1) != 14:
    print('Wrong length!')
    sys.exit()
else:
    code = []
    for i in range(13):
        code.append(ord(input1[i]) ^ ord(input1[(i + 1)]))
​
    code.append(ord(input1[13]))
    a1 = code[2]
    a2 = code[1]
    a3 = code[0]
    a4 = code[3]
    a5 = code[4]
    a6 = code[5]
    a7 = code[6]
    a8 = code[7]
    a9 = code[9]
    a10 = code[8]
    a11 = code[10]
    a12 = code[11]
    a13 = code[12]
    a14 = code[13]
    if (a1 * 88 + a2 * 67 + a3 * 65 - a4 * 5 + a5 * 43 + a6 * 89 + a7 * 25 + a8 * 13 - a9 * 36 + a10 * 15 + a11 * 11 + a12 * 47 - a13 * 60 + a14 * 29 == 22748) & (a1 * 89 + a2 * 7 + a3 * 12 - a4 * 25 + a5 * 41 + a6 * 23 + a7 * 20 - a8 * 66 + a9 * 31 + a10 * 8 + a11 * 2 - a12 * 41 - a13 * 39 + a14 * 17 == 7258) & (a1 * 28 + a2 * 35 + a3 * 16 - a4 * 65 + a5 * 53 + a6 * 39 + a7 * 27 + a8 * 15 - a9 * 33 + a10 * 13 + a11 * 101 + a12 * 90 - a13 * 34 + a14 * 23 == 26190) & (a1 * 23 + a2 * 34 + a3 * 35 - a4 * 59 + a5 * 49 + a6 * 81 + a7 * 25 + (a8 << 7) - a9 * 32 + a10 * 75 + a11 * 81 + a12 * 47 - a13 * 60 + a14 * 29 == 37136) & (a1 * 38 + a2 * 97 + a3 * 35 - a4 * 52 + a5 * 42 + a6 * 79 + a7 * 90 + a8 * 23 - a9 * 36 + a10 * 57 + a11 * 81 + a12 * 42 - a13 * 62 - a14 * 11 == 27915) & (a1 * 22 + a2 * 27 + a3 * 35 - a4 * 45 + a5 * 47 + a6 * 49 + a7 * 29 + a8 * 18 - a9 * 26 + a10 * 35 + a11 * 41 + a12 * 40 - a13 * 61 + a14 * 28 == 17298) & (a1 * 12 + a2 * 45 + a3 * 35 - a4 * 9 - a5 * 42 + a6 * 86 + a7 * 23 + a8 * 85 - a9 * 47 + a10 * 34 + a11 * 76 + a12 * 43 - a13 * 44 + a14 * 65 == 19875) & (a1 * 79 + a2 * 62 + a3 * 35 - a4 * 85 + a5 * 33 + a6 * 79 + a7 * 86 + a8 * 14 - a9 * 30 + a10 * 25 + a11 * 11 + a12 * 57 - a13 * 50 - a14 * 9 == 22784) & (a1 * 8 + a2 * 6 + a3 * 64 - a4 * 85 + a5 * 73 + a6 * 29 + a7 * 2 + a8 * 23 - a9 * 36 + a10 * 5 + a11 * 2 + a12 * 47 - a13 * 64 + a14 * 27 == 9710) & (a1 * 67 - a2 * 68 + a3 * 68 - a4 * 51 - a5 * 43 + a6 * 81 + a7 * 22 - a8 * 12 - a9 * 38 + a10 * 75 + a11 * 41 + a12 * 27 - a13 * 52 + a14 * 31 == 13376) & (a1 * 85 + a2 * 63 + a3 * 5 - a4 * 51 + a5 * 44 + a6 * 36 + a7 * 28 + a8 * 15 - a9 * 6 + a10 * 45 + a11 * 31 + a12 * 7 - a13 * 67 + a14 * 78 == 24065) & (a1 * 47 + a2 * 64 + a3 * 66 - a4 * 5 + a5 * 43 + a6 * 112 + a7 * 25 + a8 * 13 - a9 * 35 + a10 * 95 + a11 * 21 + a12 * 43 - a13 * 61 + a14 * 20 == 27687) & (a1 * 89 + a2 * 67 + a3 * 85 - a4 * 25 + a5 * 49 + a6 * 89 + a7 * 23 + a8 * 56 - a9 * 92 + a10 * 14 + a11 * 89 + a12 * 47 - a13 * 61 - a14 * 29 == 29250) & (a1 * 95 + a2 * 34 + a3 * 62 - a4 * 9 - a5 * 43 + a6 * 83 + a7 * 25 + a8 * 12 - a9 * 36 + a10 * 16 + a11 * 51 + a12 * 47 - a13 * 60 - a14 * 24 == 15317):
        print('flag is GWHT{md5(your_input)}')
        print('Congratulations and have fun!')
    else:
        print('Sorry,plz try again...')

看到一堆數字 就明白這是要解方程 用z3庫來寫方便點

from z3 import *
#初始化變數
a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14 = Ints("a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13 a14")
x = Solver()
#根據題目來新增限制
x.add(a1 * 88 + a2 * 67 + a3 * 65 - a4 * 5 + a5 * 43 + a6 * 89 + a7 * 25 + a8 * 13 - a9 * 36 + a10 * 15 + a11 * 11 + a12 * 47 - a13 * 60 + a14 * 29 == 22748)
x.add(a1 * 89 + a2 * 7 + a3 * 12 - a4 * 25 + a5 * 41 + a6 * 23 + a7 * 20 - a8 * 66 + a9 * 31 + a10 * 8 + a11 * 2 - a12 * 41 - a13 * 39 + a14 * 17 == 7258)
x.add(a1 * 28 + a2 * 35 + a3 * 16 - a4 * 65 + a5 * 53 + a6 * 39 + a7 * 27 + a8 * 15 - a9 * 33 + a10 * 13 + a11 * 101 + a12 * 90 - a13 * 34 + a14 * 23 == 26190)
x.add(a1 * 23 + a2 * 34 + a3 * 35 - a4 * 59 + a5 * 49 + a6 * 81 + a7 * 25 + a8 * 128 - a9 * 32 + a10 * 75 + a11 * 81 + a12 * 47 - a13 * 60 + a14 * 29 == 37136)
x.add(a1 * 38 + a2 * 97 + a3 * 35 - a4 * 52 + a5 * 42 + a6 * 79 + a7 * 90 + a8 * 23 - a9 * 36 + a10 * 57 + a11 * 81 + a12 * 42 - a13 * 62 - a14 * 11 == 27915)
x.add(a1 * 22 + a2 * 27 + a3 * 35 - a4 * 45 + a5 * 47 + a6 * 49 + a7 * 29 + a8 * 18 - a9 * 26 + a10 * 35 + a11 * 41 + a12 * 40 - a13 * 61 + a14 * 28 == 17298)
x.add(a1 * 12 + a2 * 45 + a3 * 35 - a4 * 9 - a5 * 42 + a6 * 86 + a7 * 23 + a8 * 85 - a9 * 47 + a10 * 34 + a11 * 76 + a12 * 43 - a13 * 44 + a14 * 65 == 19875)
x.add(a1 * 79 + a2 * 62 + a3 * 35 - a4 * 85 + a5 * 33 + a6 * 79 + a7 * 86 + a8 * 14 - a9 * 30 + a10 * 25 + a11 * 11 + a12 * 57 - a13 * 50 - a14 * 9 == 22784)
x.add(a1 * 8 + a2 * 6 + a3 * 64 - a4 * 85 + a5 * 73 + a6 * 29 + a7 * 2 + a8 * 23 - a9 * 36 + a10 * 5 + a11 * 2 + a12 * 47 - a13 * 64 + a14 * 27 == 9710)
x.add(a1 * 67 - a2 * 68 + a3 * 68 - a4 * 51 - a5 * 43 + a6 * 81 + a7 * 22 - a8 * 12 - a9 * 38 + a10 * 75 + a11 * 41 + a12 * 27 - a13 * 52 + a14 * 31 == 13376)
x.add(a1 * 85 + a2 * 63 + a3 * 5 - a4 * 51 + a5 * 44 + a6 * 36 + a7 * 28 + a8 * 15 - a9 * 6 + a10 * 45 + a11 * 31 + a12 * 7 - a13 * 67 + a14 * 78 == 24065)
x.add(a1 * 47 + a2 * 64 + a3 * 66 - a4 * 5 + a5 * 43 + a6 * 112 + a7 * 25 + a8 * 13 - a9 * 35 + a10 * 95 + a11 * 21 + a12 * 43 - a13 * 61 + a14 * 20 == 27687)
x.add(a1 * 89 + a2 * 67 + a3 * 85 - a4 * 25 + a5 * 49 + a6 * 89 + a7 * 23 + a8 * 56 - a9 * 92 + a10 * 14 + a11 * 89 + a12 * 47 - a13 * 61 - a14 * 29 == 29250)
x.add(a1 * 95 + a2 * 34 + a3 * 62 - a4 * 9 - a5 * 43 + a6 * 83 + a7 * 25 + a8 * 12 - a9 * 36 + a10 * 16 + a11 * 51 + a12 * 47 - a13 * 60 - a14 * 24 == 15317)
​
print(x.check())
print(x.model())

得到下面這些玩意

[a13 = 88,
 a3 = 10,
 a4 = 7,
 a10 = 108,
 a12 = 74,
 a1 = 119,
 a7 = 28,
 a6 = 43,
 a9 = 52,
 a14 = 33,
 a5 = 104,
 a8 = 91,
 a2 = 24,
 a11 = 88]

也就是[119, 24, 10, 7, 104, 43, 28, 91, 52, 108, 88, 74, 88, 33]

按照ord(input1[i]) ^ ord(input1[i + 1])進行異或, 反推回input

然後反推一個異或:注意這裡原始碼中code和a不是一一對應的....就是原始碼中a1=code[2]這些的

aim = [119, 24, 10, 7, 104, 43, 28, 91, 52, 108, 88, 74, 88, 33] #a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14
aim = [10, 24, 119, 7, 104, 43, 28, 91, 108, 52, 88, 74, 88, 33] #code[0]=a3,code[1]=a2.....原始碼中的....
print(len(aim))
flag = [0 for i in range(14)]
flag[13] = aim[13]
​
for i in range(12, -1, -1):#從12開始 到-1終止,每一步是-1
    flag[i] = aim[i] ^ flag[i+1]
    
print(flag)

[85, 95, 71, 48, 55, 95, 116, 104, 51, 95, 107, 51, 121, 33], 轉換為字串

c = [85, 95, 71, 48, 55, 95, 116, 104, 51, 95, 107, 51, 121, 33]
for i in c:
    print(chr(i), end="")
print("\n")

最後得到U_G07_th3_k3y! 交上去是不對的,還需要將其轉換為md5才可以

加花指令的pyc

解法:

  • 根據uncompyle6和位元組碼判斷花

  • 讀取co_code的長度

  • 去掉花 並修改co_code長度

  • 儲存uncompyle6即可

不加花指令的程式碼

def check():
    flag=5+5
    if(flag==10):
        return "right"
    return "Wrong"
#input=raw_input("Input something")
print(check())

我們先分別執行一下加了花指令和沒有加花指令的pyc檔案,看看加了花指令的pyc檔案是否能輸出right

image test

我們可以看到 加了花指令的pyc檔案 即Pz_error.pyc也是可以輸出right的

因為會把加了花指令的指令直接跳過去,就導致執行是沒什麼問題的

但是如果呼叫uncompyle6來反編譯出原始碼的話,加了花指令的pyc檔案就會報錯

image test

最下面報錯的資訊給我們的是 tuple index out of range 陣列下標越界

我們先來得到Pz_error.pyc的位元組碼,然後用code.marshal,loads(f[8:])讀取魔術字8位後的東西,並將其反編譯,看看花指令是加在哪條指令上了

image test

我們可以看到 JUMP_ABSOLUTE將位元組碼計數器設定為目標 強制跳轉到18

18也就是正常指令之後

LOAD_CONST 255 我們沒有加花指令的py檔案 就那麼點程式碼 哪來的255,所以明顯花指令就是在這裡了。

所以我們現在定位到了花指令的地方,我們要做的就是把花指令去掉 並且修改co_code長度 也就是整個pyc檔案的長度

那麼我們來進行第二步 讀取co_code的長度

len(code.co_code)

image test

我們可以看到是27個位元組

我們把Pz_error.pyc丟進Winhex裡面 那麼我們該怎麼在Winhex裡面定位到我們所要找的花指令呢?

我們要藉助python2程式裡面的opcode.h工具

已知花指令是JUMP_ABSOLUTE 在opcode.h裡面搜尋JUMP_ABSOLUTE

image test

113轉換為16進位制就是71

在Winhex裡面找的71那條 因為python2是3個位元組碼為1個指令

image test

接下來 我們找LOAD_CONST這條指令在opcode.h對應的數字

image test

100對應的16進位制數字就是64 255對應的16進位制數就是FF

所以基本可以確定 71 12 00 64 FF 00 這六個就是我們要找的花指令

image test

然後delete鍵刪掉

現在我們到了第三步 修改co_code的長度

我們剛讀出來Pz_error.pyc位元組碼長度是27 27-6=21

27對應的16進位制數是1B

21對應的16進位制數是15

我們在winhex裡面將找的1B修改為15,然後儲存,這樣我們的Pz_error.pyc就修好了

現在我們來試試看uncompyle6能否反編譯出來

image test

大功告成!

更多網安技能的線上實操練習,請點選這裡>>

相關文章