hgame ctf week2的逆向wp
题目链接:
链接: https://pan.baidu.com/s/1mmUxYJ-bfh-hf2akSiMRCA
密码: um92
这是一个elf脱壳的题目,我用的方法是先运行程序,然后使用gdb attach到程序,然后dump出来,
首先是先使程序运行起来,
然后查一下这个程序运行时该进程的pid, 使用ps和grep指令,
然后看一下这个进程空间,cat /proc/[pid]/maps
然后使用gdb去:gdb attach [pid]
这里我是要使用root才可以attack成功,有点奇怪?
然后进入以后使用gdb的dump指令,
dump binary memory [path] start_addr end_addr
这里整个dump下来,然后就可以得到这个文件,看到加密还是很简单:
在ida可以看到地址,然后下断,继续运行程序,可以在得到内存中的值:
然后直接可以得到flag:
首先查壳是一个.net文件,用dnSpy做的,打开可以直接诶看到源码,
private void button1_Click(object sender, EventArgs e)
{
if (this.status == 1)
{
MessageBox.Show("你已经激活成功啦,快去提交flag吧~~~");
return;
}
string text = this.textBox1.Text;
if (text.Length != 46 || text.IndexOf("hgame{") != 0 || text.IndexOf("}") != 45)
{
MessageBox.Show("Illegal format");
return;
}
string base64iv = text.Substring(6, 24);
string str = text.Substring(30, 15);
try
{
Aes aes = new Aes("SGc0bTNfMm8yMF9XZWVLMg==", base64iv);
Aes aes2 = new Aes("SGc0bTNfMm8yMF9XZWVLMg==", "MFB1T2g5SWxYMDU0SWN0cw==");
string text2 = aes.DecryptFromBase64String("mjdRqH4d1O8nbUYJk+wVu3AeE7ZtE9rtT/8BA8J897I=");
if (text2.Equals("Same_ciphertext_"))
{
byte[] array = new byte[16];
Array.Copy(aes2.EncryptToByte(text2 + str), 16, array, 0, 16);
if (Convert.ToBase64String(array).Equals("dJntSWSPWbWocAq4yjBP5Q=="))
{
MessageBox.Show("注册成功!");
this.Text = "已激活,欢迎使用!";
this.status = 1;
}
else
{
MessageBox.Show("注册失败!\nhint: " + aes2.DecryptFromBase64String("mjdRqH4d1O8nbUYJk+wVu3AeE7ZtE9rtT/8BA8J897I="));
}
}
else
{
MessageBox.Show("注册失败!\nhint: " + aes2.DecryptFromBase64String("mjdRqH4d1O8nbUYJk+wVu3AeE7ZtE9rtT/8BA8J897I="));
}
}
catch
{
MessageBox.Show("注册失败!");
}
}
中间是调用内部设置的一个aes的加密, 然后中间可以看到是一个CBC的方式,然后有两次判定,
首先第一次:比较text2, 这个位置前面是设置好了的一个keys和iv然后encode字符串解密出来,
注意了解CBC的加密方式,其解密的时候这个iv只有最后一次异或使用了一次,
我们看到输出提示的位置,用的是同样的keys和encode, 也就是说他的解密出来的字符str还未与iv疑惑之前是一致的,异或以后形成了不一样的decode1,2,也就是我们的iv1 = iv2 ^ decode1 ^ decode2:
import base64
iv2 = str(base64.b64decode("MFB1T2g5SWxYMDU0SWN0cw=="), encoding = 'utf-8')
iv1 = ''
decode1 = "Same_ciphertext_"
decode2 = "Learn principles"
for i in range(16):
iv1 += chr(ord(iv2[i]) ^ ord(decode1[i]) ^ ord(decode2[i]))
flag1 = str(base64.b64encode(bytes(iv1,encoding = 'utf-8')),encoding = 'utf-8')
print(flag1)
后面的一个就是加密,长度超出来了,然后就进行分组, 然后再加密,这里要注意,是进行了两次加密,最后加密的到的decode2是第二次加密的,
我们要解决的是第二次加密的位置,分成两次,第一次iv, key,encode都知道,第二次,key和decode知道,我们需要解出来encode是啥,但是注意这个加密方式,我们前一次的密文作为下一次的iv, 所以第二次的iv应该是第一次的decode。
key = str(base64.b64decode("SGc0bTNfMm8yMF9XZWVLMg=="), encoding = 'utf-8')
iv_1 = str(base64.b64decode("MFB1T2g5SWxYMDU0SWN0cw=="), encoding = 'utf-8')
encode_1 = "Same_ciphertext_"
decode_2 = base64.b64decode("dJntSWSPWbWocAq4yjBP5Q==")
encode2 = ''
aes = AES.new(key, AES.MODE_CBC, iv_1)
decode_1 = aes.encrypt(encode_1)
iv_2 = decode_1
aes_2 = AES.new(key, AES.MODE_CBC, iv_2)
encode_2 = aes_2.decrypt(decode_2)
print(encode_2)
flag2 = str(encode_2, encoding = 'utf-8')
print(flag1 + flag2)
最后得到flag, hgame{L1R5WFl6UG5ZOyQpXHdlXw==DiFfer3Nt_w0r1d}
得到一个dis出来的python字节码。然后对着一个官方文档看着,然后自己用ipython写,然后dis出来对照着搞出来的,
大致的算法如下:
def encrypt(flag):
data1 = flag[::-1]
data2 = list(data1)
for i in range(1, len(data2)):
a = data2[i-1] ^ data2[i]
data2[i] = a
b = bytes(data2)
return hex(b)
解密脚本:
def decode():
s = '7d037d045717722d62114e6a5b044f2c184c3f44214c2d4a22'
arr = []
ss = '}'
for i in range(0,50,2):
arr.append(int(s[i:i+2],16))
for i in range(1,25):
ss += chr(arr[i] ^ arr[i-1])
print(ss[::-1])
首先是使用uncompyle6去反pyc文件会产生一个报错,回显出来的是一大片的main函数的python字节码:
和baby py一样对着文档和自己一点点用dis搞:
3 0 JUMP_ABSOLUTE 2 'to 2'
2 LOAD_CONST 0
4 LOAD_CONST None
6 IMPORT_NAME os
8 STORE_NAME os import os as os
10 LOAD_CONST 0
12 LOAD_CONST None
14 IMPORT_NAME sys
4 16 STORE_NAME sys import sys as sys
18 LOAD_CONST 0
20 LOAD_CONST ('b64encode',)
22 IMPORT_NAME base64
24 IMPORT_FROM b64encode
26 STORE_NAME b64encode
6 28 POP_TOP from base64 import b64encode as b64encode
30 LOAD_STR '/KDq6pvN/LLq6tzM/KXq59Oh/MTqxtOTxdrqs8OoR3V1X09J'
8 32 STORE_GLOBAL O0o O0o = '/KDq6pvN/LLq6tzM/KXq59Oh/MTqxtOTxdrqs8OoR3V1X09J'
34 LOAD_CODE
36 LOAD_STR 'getFlag'
38 MAKE_FUNCTION_0 ''
16 40 STORE_NAME getFlag
42 LOAD_NAME getFlag
44 CALL_FUNCTION_0 0 ''
18 46 STORE_NAME flag
48 LOAD_NAME flag
50 LOAD_CONST None
52 LOAD_CONST 6
54 BUILD_SLICE_2 2
56 BINARY_SUBSCR
58 LOAD_STR 'hgame{' flag format: hgame{ }
60 COMPARE_OP !=
62 POP_JUMP_IF_TRUE 76 'to 76'
64 LOAD_NAME flag
66 LOAD_CONST -1
68 BINARY_SUBSCR
70 LOAD_CONST 125
72 COMPARE_OP !=
19 74 POP_JUMP_IF_FALSE 94 'to 94'
76_0 COME_FROM 62 '62'
76 LOAD_NAME print print('Incorrect format!')
78 LOAD_STR 'Incorrect format!'
80 CALL_FUNCTION_1 1 ''
20 82 POP_TOP
84 LOAD_NAME sys sys.exit()
86 LOAD_METHOD exit
88 LOAD_CONST 1
90 CALL_METHOD_1 1 ''
22 92 POP_TOP
94_0 COME_FROM 74 '74'
94 LOAD_NAME flag raw_flag = flag[6:-1]
96 LOAD_CONST 6
98 LOAD_CONST -1
100 BUILD_SLICE_2 2
102 BINARY_SUBSCR
23 104 STORE_NAME raw_flag
106 LOAD_NAME len
108 LOAD_NAME flag
110 CALL_FUNCTION_1 1 '' len(flag) - 7 != 36
112 LOAD_CONST 7
114 BINARY_SUBTRACT
116 LOAD_CONST 36
118 COMPARE_OP !=
24 120 POP_JUMP_IF_FALSE 140 'to 140'
122 LOAD_NAME print print('Worng length!')
124 LOAD_STR 'Wrong length!'
126 CALL_FUNCTION_1 1 ''
25 128 POP_TOP
130 LOAD_NAME sys sys.exit()
132 LOAD_METHOD exit
134 LOAD_CONST 2Python的字节码混淆
136 CALL_METHOD_1 1 ''
27 138 POP_TOP
140_0 COME_FROM 120 '120'
140 LOAD_NAME raw_flag raw_flag = raw_flag[::-1]
142 LOAD_CONST None
144 LOAD_CONST None
146 LOAD_CONST -1
148 BUILD_SLICE_3 3
150 BINARY_SUBSCR
28 152 STORE_NAME raw_flag
154 LOAD_LISTCOMP '>'
156 LOAD_STR ''
158 MAKE_FUNCTION_0 ''
160 LOAD_NAME range结束
162 LOAD_CONST 6
164 CALL_FUNCTION_1 1 ''
166 GET_ITER
168 CALL_FUNCTION_1 1 ''
30 170 STORE_NAME ciphers ciphers <=== raw_flag
172 SETUP_LOOP 260 'to 260'
174 LOAD_NAME range
176 LOAD_CONST 5
178 CALL_FUNCTION_1 1 ''
180 GET_ITER
182 FOR_ITER 258 'to 258' for row in range(5)
31 184 STORE_NAME row
186 SETUP_LOOP 256 'to 256'
188 LOAD_NAME range
190 LOAD_CONST 6
192 CALL_FUNCTION_1 1 ''
194 GET_ITER
196 FOR_ITER 254 'to 254' for col in range(6)
32 198 STORE_NAME col
200 LOAD_NAME ciphers
202 LOAD_NAME row
204 BINARY_SUBSCR
206 LOAD_NAME col
208 DUP_TOP_TWO
210 BINARY_SUBSCR cipher[col + row]
212 LOAD_NAME ciphers
214 LOAD_NAME row
216 LOAD_CONST 1
218 BINARY_ADD
220 BINARY_SUBSCR
222 LOAD_NAME col
224 BINARY_SUBSCR cipher[col + row + 1]
226 INPLACE_ADD
228 ROT_THREE
33 230 STORE_SUBSCR cipher[col + row] += cipher[col + row + 1]
232 LOAD_NAME ciphers
234 LOAD_NAME row
236 BINARY_SUBSCR
238 LOAD_NAME col
240 DUP_TOP_TWO
242 BINARY_SUBSCR
244 LOAD_CONST 256
246 INPLACE_MODULO
248 ROT_THREE
250 STORE_SUBSCR cipher[row + col] %= 256
252 JUMP_BACK 196 'to 196'
254 POP_BLOCK for col in range结束
256_0 COME_FROM_LOOP 186 '186'
256 JUMP_BACK 182 'to 182'
35 258 POP_BLOCK for row in range结束
260_0 COME_FROM_LOOP 172 '172'
260 LOAD_STR ''
36 262 STORE_NAME cipher cipher = ''
264 SETUP_LOOP 336 'to 336'
266 LOAD_NAME range
268 LOAD_CONST 6
270 CALL_FUNCTION_1 1 ''
272 GET_ITER
274 FOR_ITER 334 'to 334' for row in range(6)
37 276 STORE_NAME row
278 LOAD_CONST 0
38 280 STORE_NAME col col = 0
282 SETUP_LOOP 330 'to 330'
284 LOAD_NAME col while col < 6:
286 LOAD_CONST 6
288 COMPARE_OP <
290_292 POP_JUMP_IF_FALSE 328 'to 328'
294 LOAD_NAME cipher
296 LOAD_NAME bytes
298 LOAD_NAME ciphers
300 LOAD_NAME row
302 BINARY_SUBSCR
304 LOAD_NAME col
306 BINARY_SUBSCR
308 BUILD_LIST_1 1
310 CALL_FUNCTION_1 1 ''
312 INPLACE_ADD
40 314 STORE_NAME cipher cipher += bytes(ciphers[row + col])
316 LOAD_NAME col
318 LOAD_CONST 1
320 INPLACE_ADD Python的字节码混淆
322 STORE_NAME col col += 1
324_326 JUMP_BACK 284 'to 284' while循环结束
328_0 COME_FROM 290 '290'
328 POP_BLOCK
330_0 COME_FROM_LOOP 282 '282'
330_332 JUMP_BACK 274 'to 274'
42 334 POP_BLOCK
336_0 COME_FROM_LOOP 264 '264' for row in range(6) 结束
336 LOAD_NAME b64encode
338 LOAD_NAME cipher
340 CALL_FUNCTION_1 1 ''
44 342 STORE_NAME cipher cipher = b64encode(cipher)
344 LOAD_NAME cipher
346 LOAD_GLOBAL O0o
348 COMPARE_OP == if cipher == O0o:
350 352 POP_JUMP_IF_FALSE 364 'to 364'
354 LOAD_NAME print print('Great')
356 LOAD_STR 'Great, this is my flag.'
358 CALL_FUNCTION_1 1 ''
360 POP_TOP
47 362 JUMP_FORWARD 372 'to 372' else:
364_0 COME_FROM 350 '350'
364 LOAD_NAME print print('Wrong')
366 LOAD_STR 'Wrong flag.'
368 CALL_FUNCTION_1 1 ''
370 POP_TOP
372 COME_FROM 362 '362'
大致源码做出来是这样:
def encode ():
import os as os
import sys as sys
from base64 import b64encode as b64encode
O0o = '/KDq6pvN/LLq6tzM/KXq59Oh/MTqxtOTxdrqs8OoR3V1X09J'
getflag = getflag
flag = getflag()
if flag[:6] != 'hgame{' and flag[-1] != '}':
print('format!')
sys.exit()
raw_flag = flag[6:-1]
if len(flag) - 7 != 36 :
print('len!')
sys.exit()
raw_flag = raw_flag[::-1]
#ciphers <<== raw_flag ###
cipher = [[flag[6*i+j]for i in range(6)] for j in range(6)]
for row in range(5):
for col in range(6):
ciphers[row][col] += ciphers[row+1][col]
ciphers[row][col] %= 256
cipher = ''
for row in range(6):
col = 0
while col < 6:
cipher += bytes(ciphers[row][col])
col += 1
cipher = b64encode(cipher)
if cipher == O0o :
print('Great!')
else:
print('Wong!')
主要的是一个base64解出来以后是一个后项加钱项然后又对0xff取余,这里我们直接后项减去前项,然后和0xff按位与可以还原,
但是注意这个加密时先通过一个二位数组,把顺序变换了,我们在解的时候要注意,
def decode():
s = ''
dd1 = '\xfc\xa0\xea\xea\x9b\xcd\xfc\xb2\xea\xea\xdc\xcc\xfc\xa5\xea\xe7\xd3\xa1\xfc\xc4\xea\xc6\xd3\x93\xc5\xda\xea\xb3\xc3\xa8Guu_OI'
dd = 'B\x97\xb9\xb1\x94\x9eZ\xbd\xc2\xd1\x93xq\xe7\xea\xef\xd3\xbc\x9f\xb8\xfa\xda\xdf\xcd\xce\xb7\xae\xa3\xcd\xa9gr0E^P'
b = [[ord(dd[6 * i:6*(i+1)][j]) for j in range(6)] for i in range(6)]
#print(b)
for i in range(4,-1,-1):
for j in range(5,-1,-1):
b[i][j] = b[i][j] - b[i+1][j]
for i in range(6):
for j in range(6):
s += (chr(b[j][i]&0xff))
#print('\n')
print(s[::-1])
这是解密脚本,其中,dd1是我们在字节码中看到的,靠dd1解出来的flag是这样的:
然后提交不对,这个真的是个假flag,然后想起来这个字符串在字节码中显示是一个:STORE_GLOBAL
, 说明我们的调用函数的时候某个函数改变了这个值, 但是我们只看到main函数所以不知,