长亭百川云 - 文章详情

浅谈CTF中Pycode字节码逆向 - admin-神风

博客园 - admin-神风

57

2024-07-19

一、题目原题

题目给出一个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}
相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2