淺談CTF中Pycode字節碼逆向


一、題目原題

題目給出一個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

 

首先看到定義了一個長度為26的enc數組,然后輸入flag到本地變量,並判斷了改flag是不是等於26的。

往下看

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表示如下:

  1. j + flag_arr[j]

  2.  (26 - j) ^ flag_arr[j]

  3. 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}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM