一、題目原題
題目給出一個python匯編文件和一個輸出文件,要求逆向出程序中的flag值
3 0 LOAD_CONST 1 ('XXXXXX') //This is flag,try to figure it out ! Don't forget to fill it in flag{} ! 2 STORE_FAST 0 (flag) 4 4 LOAD_CONST 2 (0) 6 BUILD_LIST 1 8 LOAD_CONST 3 (18) 10 BINARY_MULTIPLY 12 STORE_FAST 1 (num) 5 14 LOAD_CONST 2 (0) 16 STORE_FAST 2 (k) 6 18 LOAD_GLOBAL 0 (range) 20 LOAD_GLOBAL 1 (len) 22 LOAD_FAST 0 (flag) 24 CALL_FUNCTION 1 26 CALL_FUNCTION 1 28 GET_ITER >> 30 FOR_ITER 112 (to 144) 32 STORE_FAST 3 (i) 7 34 LOAD_GLOBAL 2 (ord) 36 LOAD_FAST 0 (flag) 38 LOAD_FAST 3 (i) 40 BINARY_SUBSCR 42 CALL_FUNCTION 1 44 LOAD_FAST 3 (i) 46 BINARY_ADD 48 LOAD_FAST 2 (k) 50 LOAD_CONST 4 (3) 52 BINARY_MODULO 54 LOAD_CONST 5 (1) 56 BINARY_ADD 58 BINARY_XOR 60 LOAD_FAST 1 (num) 62 LOAD_FAST 3 (i) 64 STORE_SUBSCR 8 66 LOAD_GLOBAL 2 (ord) 68 LOAD_FAST 0 (flag) 70 LOAD_GLOBAL 1 (len) 72 LOAD_FAST 0 (flag) 74 CALL_FUNCTION 1 76 LOAD_FAST 3 (i) 78 BINARY_SUBTRACT 80 LOAD_CONST 5 (1) 82 BINARY_SUBTRACT 84 BINARY_SUBSCR 86 CALL_FUNCTION 1 88 LOAD_GLOBAL 1 (len) 90 LOAD_FAST 0 (flag) 92 CALL_FUNCTION 1 94 BINARY_ADD 96 LOAD_FAST 3 (i) 98 BINARY_SUBTRACT 100 LOAD_CONST 5 (1) 102 BINARY_SUBTRACT 104 LOAD_FAST 2 (k) 106 LOAD_CONST 4 (3) 108 BINARY_MODULO 110 LOAD_CONST 5 (1) 112 BINARY_ADD 114 BINARY_XOR 116 LOAD_FAST 1 (num) 118 LOAD_GLOBAL 1 (len) 120 LOAD_FAST 0 (flag) 122 CALL_FUNCTION 1 124 LOAD_FAST 3 (i) 126 BINARY_SUBTRACT 128 LOAD_CONST 5 (1) 130 BINARY_SUBTRACT 132 STORE_SUBSCR 9 134 LOAD_FAST 2 (k) 136 LOAD_CONST 5 (1) 138 INPLACE_ADD 140 STORE_FAST 2 (k) 142 JUMP_ABSOLUTE 30 10 >> 144 LOAD_GLOBAL 3 (print) 146 LOAD_FAST 1 (num) 148 CALL_FUNCTION 1 150 POP_TOP 152 LOAD_CONST 0 (None) 154 RETURN_VALUE
output文件:
[115, 120, 96, 84, 116, 103, 105, 56, 102, 59, 127, 105, 115, 128, 95, 124, 139, 49]
二、解題思路
首先看到匯編可以發現程序創建了一個長度18的num數組,而程序結尾的print函數調用也是輸出的num值,所以整體我們只需要關注num的值變化即可
4 4 LOAD_CONST 2 (0) 6 BUILD_LIST 1 8 LOAD_CONST 3 (18) 10 BINARY_MULTIPLY 12 STORE_FAST 1 (num)
之后程序創建了k變量,循環flag的長度,也就是18次,for循環具體可以分為兩個步驟來
7 34 LOAD_GLOBAL 2 (ord) 36 LOAD_FAST 0 (flag) 38 LOAD_FAST 3 (i) 40 BINARY_SUBSCR 42 CALL_FUNCTION 1 44 LOAD_FAST 3 (i) 46 BINARY_ADD 48 LOAD_FAST 2 (k) 50 LOAD_CONST 4 (3) 52 BINARY_MODULO 54 LOAD_CONST 5 (1) 56 BINARY_ADD 58 BINARY_XOR 60 LOAD_FAST 1 (num) 62 LOAD_FAST 3 (i) 64 STORE_SUBSCR
按函數調用棧來看獲取flag[i]的十進制數值,再加上i的值
然后就是BINARY_MODULO調用取模,也就是 k % 3,但是別忘了后面還有一個BINARY_ADD
最后就是將上述兩個值進行XOR異或
再來看看后一部分
8 66 LOAD_GLOBAL 2 (ord) 68 LOAD_FAST 0 (flag) 70 LOAD_GLOBAL 1 (len) 72 LOAD_FAST 0 (flag) 74 CALL_FUNCTION 1 76 LOAD_FAST 3 (i) 78 BINARY_SUBTRACT 80 LOAD_CONST 5 (1) 82 BINARY_SUBTRACT 84 BINARY_SUBSCR 86 CALL_FUNCTION 1 88 LOAD_GLOBAL 1 (len) 90 LOAD_FAST 0 (flag) 92 CALL_FUNCTION 1 94 BINARY_ADD 96 LOAD_FAST 3 (i) 98 BINARY_SUBTRACT 100 LOAD_CONST 5 (1) 102 BINARY_SUBTRACT 104 LOAD_FAST 2 (k) 106 LOAD_CONST 4 (3) 108 BINARY_MODULO 110 LOAD_CONST 5 (1) 112 BINARY_ADD 114 BINARY_XOR 116 LOAD_FAST 1 (num) 118 LOAD_GLOBAL 1 (len) 120 LOAD_FAST 0 (flag) 122 CALL_FUNCTION 1 124 LOAD_FAST 3 (i) 126 BINARY_SUBTRACT 128 LOAD_CONST 5 (1) 130 BINARY_SUBTRACT 132 STORE_SUBSCR 9 134 LOAD_FAST 2 (k) 136 LOAD_CONST 5 (1) 138 INPLACE_ADD 140 STORE_FAST 2 (k) 142 JUMP_ABSOLUTE 30
前一段手撕的時候還能接受,但是到這一段確實有點繞,還是用工具習慣了,突然手撕匯編確實有點吃力
其實根據調用棧的平衡就可以很好的逆出來對應的代碼
這里我直接放出我手撕的源代碼:
num = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] flag = 'UNCTF{qwertyuiopa}' k=0 for i in range(len(flag)): num[i] = (ord(flag[i]) + i) ^ (k % 3 + 1) num[len(flag)-i-1] = ((ord(flag[len(flag)-i-1]) + len(flag)) - i - 1) ^ (k % 3 +1) k+=1 print(num)
現在就是編寫對應的poc來逆序生成flag,但是看到這個程序發現,其實這是一個雙指針的加密方式,而且會覆蓋到之前指針已經填充的num下標中
因此我將解密程序分為兩個部分,前9個字符用第二部分的加密方式來解密,后9個字符則用第一部分的
最終poc如下:
enc = [115, 120, 96, 84, 116, 103, 105, 56, 102, 59, 127, 105, 115, 128, 95, 124, 139, 49] str1 = '' for i in range(9): str1 += chr(((enc[i] ^ ((17-i) % 3 + 1)) + (17-i) + 1) - 18) str2 = '' for i in range(9,18): str2 += chr((enc[i] ^ (i % 3 + 1)) - i) print(str1+str2) #py_Trad3_1s_fuNny!
讀者看到文章的時候一定要自己上手試試,會發現解題過程還是有點意思的
三、SCUCTF的一道逆向題
首先拿到題目的時候是一個pyc文件,所以我第一思路就是想反編譯成py文件
但是問題來了,uncompyle6這些解密都不能成功,因為目前不支持解密python 3.9編寫的
所以只能另尋出路,后來編譯了pydcd來解密pyc發現是缺省的,關鍵代碼沒有翻譯完整,因此在這里卡了很久。
之后給了hint是用了pydisasm工具來直接轉換成匯編代碼
這里截取部分關鍵代碼處
1: BUILD_LIST 0 LOAD_CONST 0 ((0, 250, 444, 678, 880, 1260, 1788, 952, 2352, 1944, 1960, 1144, 2784, 2522, 2576, 3450, 3712, 4182, 5040, 5282, 4680, 3906, 5676, 5520, 3504, 7550)) LIST_EXTEND 1 STORE_NAME 0 (enc) 3: LOAD_NAME 1 (input) LOAD_CONST 1 ('Input:') CALL_FUNCTION 1 STORE_NAME 2 (flag) 7: LOAD_NAME 3 (len) LOAD_NAME 2 (flag) CALL_FUNCTION 1 LOAD_CONST 2 (26) COMPARE_OP 3 (!=) POP_JUMP_IF_FALSE L44 (to 44) 8: LOAD_NAME 4 (print) LOAD_CONST 3 ('Wrong!') CALL_FUNCTION 1 POP_TOP 9: LOAD_NAME 5 (exit) LOAD_CONST 4 (1) CALL_FUNCTION 1 POP_TOP
往下看
L44: 13: LOAD_CONST 5 (<Code38 code object listcomp_0x2e83dc0 at 0x2ea2080, file Re7_PyCode.py>, line 13) LOAD_CONST 6 ('<listcomp>') MAKE_FUNCTION 0 (Neither defaults, keyword-only args, annotations, nor closures) LOAD_NAME 2 (flag) GET_ITER CALL_FUNCTION 1 STORE_NAME 6 (flag_arr)
這里調用了13地址上的節選代碼,並將13地址上的調用結果存放到本地的flag_arr變量上,再來看看13上的匯編代碼
13: BUILD_LIST 0 LOAD_FAST 0 (.0) L4: FOR_ITER L18 (to 18) STORE_FAST 1 (i) LOAD_GLOBAL 0 (ord) LOAD_FAST 1 (i) CALL_FUNCTION 1 LIST_APPEND 2 JUMP_ABSOLUTE L4 (to 4) L18: RETURN_VALUE
大概的意思就是將傳進來的flag獲取對應的十進制數值以數組的形式返回
繼續往下走
L66: FOR_ITER L88 (to 88) STORE_NAME 8 (j) 17: LOAD_NAME 6 (flag_arr) LOAD_NAME 8 (j) DUP_TOP_TWO BINARY_SUBSCR LOAD_NAME 8 (j) INPLACE_ADD ROT_THREE STORE_SUBSCR JUMP_ABSOLUTE L66 (to 66) L88: 20: LOAD_NAME 7 (range) LOAD_CONST 2 (26) CALL_FUNCTION 1 GET_ITER
看到這里就可以發現,此處的代碼就是for循環,並以j為下標,與arr[j]做加法運算,最后將結果存放到flag_arr[j]
再繼續往下走
L96: FOR_ITER L122 (to 122) STORE_NAME 8 (j) 21: LOAD_NAME 6 (flag_arr) LOAD_NAME 8 (j) DUP_TOP_TWO BINARY_SUBSCR LOAD_CONST 2 (26) LOAD_NAME 8 (j) BINARY_SUBTRACT INPLACE_XOR ROT_THREE STORE_SUBSCR JUMP_ABSOLUTE L96 (to 96)
老樣子,還是循環,將(26 - j)的值與flag_arr[j]異或,再將結果存放到flag_arr
23: BUILD_LIST 0 LOAD_FAST 0 (.0) L4: FOR_ITER L26 (to 26) STORE_FAST 1 (i) LOAD_GLOBAL 0 (flag_arr) LOAD_FAST 1 (i) BINARY_SUBSCR LOAD_CONST 0 (2) BINARY_MULTIPLY LOAD_FAST 1 (i) BINARY_MULTIPLY LIST_APPEND 2 JUMP_ABSOLUTE L4 (to 4) L26: RETURN_VALUE L122: 23: LOAD_CONST 7 (<Code38 code object listcomp_0x2ea7330 at 0x2e83da0, file Re7_PyCode.py>, line 23) LOAD_CONST 6 ('<listcomp>') MAKE_FUNCTION 0 (Neither defaults, keyword-only args, annotations, nor closures) LOAD_NAME 7 (range) LOAD_CONST 2 (26) CALL_FUNCTION 1 GET_ITER CALL_FUNCTION 1 STORE_NAME 6 (flag_arr)
再來看看關鍵代碼的最后一處,for循環i下標,將flag_arr[i]乘上CONST數值2,再乘上下標i
L148: FOR_ITER L186 (to 186) STORE_NAME 9 (i) 26: LOAD_NAME 6 (flag_arr) LOAD_NAME 9 (i) BINARY_SUBSCR LOAD_NAME 0 (enc) LOAD_NAME 9 (i) BINARY_SUBSCR COMPARE_OP 3 (!=) POP_JUMP_IF_FALSE L148 (to 148) 27: LOAD_NAME 4 (print) LOAD_CONST 8 ('Wrong') CALL_FUNCTION 1 POP_TOP 28: LOAD_NAME 5 (exit) LOAD_CONST 4 (1) CALL_FUNCTION 1 POP_TOP JUMP_ABSOLUTE L148 (to 148) L186: 30: LOAD_NAME 4 (print) LOAD_CONST 9 ('Success!') CALL_FUNCTION 1 POP_TOP
最后就是判斷flag_arr和enc的數組值是否相等。
經過上述分析,發現flag_arr數組經過三次關鍵代碼運算,分別用py表示如下:
-
j + flag_arr[j]
-
(26 - j) ^ flag_arr[j]
- flag_arr[i] * 2 * i
所以就可以編寫獲取flag的腳本:
enc = [0, 250, 444, 678, 880, 1260, 1788, 952, 2352, 1944, 1960, 1144, 2784, 2522, 2576, 3450, 3712, 4182, 5040, 5282, 4680, 3906, 5676, 5520, 3504, 7550] str = '' for i in range(1,26): str += chr(((enc[i] / 2 / i) ^ (26 - i)) - i) print(str) #scuctf{Pyth0n_Binary_Cod3}