题目给出一个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!
读者看到文章的时候一定要自己上手试试,会发现解题过程还是有点意思的
首先拿到题目的时候是一个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表示如下:
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}