20210529-DozerCTF-CryptoSecPartWrtieUp

 

手滑的袁学长

题目描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import os
from Crypto.Util import *
from Crypto.Util.strxor import *
from Crypto.Cipher import AES


answer = open('FLAG1/flag', 'rb').read()
print(len(answer))


def Function_enc(msg, key, p_2, c_2):
msg = Padding.pad(msg, 16)
struct = [msg[i:i+16] for i in range(0, len(msg), 16)]

out = b''
for p in struct:
c = strxor(p, c_2)
c = AES.new(key, AES.MODE_ECB).encrypt(c)

out += strxor(p_2, c)
c_2 = c
p_2 = p

return out


KEY = os.urandom(16)
msg = 'I do not care the result' + KEY.hex()
text = Function_enc(msg.encode(), KEY, answer[:16], answer[16:])

print('key = ' + KEY.hex())
print('cipher =' + text.hex())

while True:
print(0)

解题思路

加密算法的大致思路如下,比较简单易懂

20210529 DozerCTF2021-bb5bf248.png

首先的思路是从后往前做,求出out3之后看看包不包含已知部分,核心思想还是Brute-force

一个小插曲

一开始以为思路跑偏了,因为袁学长删除的部分是hex之后的,就不是原理四部分组成的out了,但还是先记录下脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import os
from Crypto.Util import *
from Crypto.Util.strxor import *
from Crypto.Cipher import AES
import sys
# flag_length =

hex_num = '0123456789abcdef'

# ==========================枚举key的============================ #
key_hex = list('2$9cf037f8b3a$2b19b5bda978c294$5')

possible_key = []
for i in hex_num:
key_hex[1] = i
for j in hex_num:
key_hex[13] = j
for k in hex_num:
key_hex[-2] = k
possible_key.append("".join(key_hex))
# print(possible_key)

# ==========================枚举out的============================ #
cipher_hex = '91e5fb43f053b21ce12e41df0b0ae0bb6a20c55719151$$fccecb4$$$$$2a27c8c582c6704f$$$$153bd3313b84235ace16a7b3b190$e487abfa9$cf379d1a3c'
cipher_hex1 = '91e5fb43f053b21ce12e41df0b0ae0bb'
cipher_hex2 = list('6a20c55719151$$fccecb4$$$$$2a27c')
cipher_hex3 = list('8c582c6704f$$$$153bd3313b84235ac')
cipher_hex4 = list('e16a7b3b190$e487abfa9$cf379d1a3c')

possible_cipher_hex4 = []
for i in hex_num:
cipher_hex4[11] = i
for j in hex_num:
cipher_hex4[21] = j
possible_cipher_hex4.append("".join(cipher_hex4))
# print(possible_cipher_hex4)

flag = 'DozerCTF{'

for tmp_key in possible_key:
# key知道,msg就知道
msg = ('I do not care the result' + tmp_key.encode().hex()).encode()
msg = Padding.pad(msg, 16)
struct = [msg[i:i + 16] for i in range(0, len(msg), 16)]
tmp_m2, tmp_m3, tmp_m4 = struct[1], struct[2], struct[3]

for tmp_out4 in possible_cipher_hex4:
# 第四次aes的结果解密
tmp_aes_c4 = strxor(number.long_to_bytes(int(tmp_out4, 16)), tmp_m3)
tmp_aes_m4 = AES.new(tmp_key.encode(), AES.MODE_ECB).decrypt(tmp_aes_c4)

tmp_aes_c3 = strxor(tmp_m4, tmp_aes_m4)
tmp_out3 = strxor(tmp_aes_c3, tmp_m2)
tmp_out3 = tmp_out3.hex()
if tmp_out3.startswith('8c582c6704f'):
print(tmp_out3)
sys.exit(0)

但后来发现应该没有错,我们自己写了一个flag,然后进行加密,看out输出和hex之后的结果,截取其中的某一段进行hex的反操作

1
2
3
4
# answer = b'DozerCTF{yuan-xz-weism-yaoshhne}'

out = b'\xafF/\xcaM\x1a\xcf\x9b%\x88T\x01\x8a\x1fKb\x04\x85\xd0\xd7\x8d\xb5\x16\xb6\xc5\xd2\xe1\x92\xff\xf3\xe6e\xe0A\x8f\xc5{(GG>\xbd\xff\xbc\xef\xef\xf0X@ .\xd6L\x08,\xa2H\xc3\x1a\xf4{DQB'
cipher = af462fca4d1acf9b258854018a1f4b620485d0d78db516b6c5d2e192fff3e665e0418fc57b2847473ebdffbcefeff05840202ed64c082ca248c31af47b445142
1
2
3
4
5
from Crypto.Util.number import *

c = '0485d0d78db516b6c5d2e192fff3e665'

print(long_to_bytes(int(c, 16)))

出来的结果是

1
b'\x04\x85\xd0\xd7\x8d\xb5\x16\xb6\xc5\xd2\xe1\x92\xff\xf3\xe6e'

显然结果是完全一样的,至于原理,应该是字节转数字的问题,这里不再深究
20210529 DozerCTF2021-98f6b60a.png

所以思路的基础没有毛病


然后发现aes的key有问题,之前代码里的key和题目中用来充当密钥的key不是一样的

我随机生成了一个key,并输出hex之后的结果

1
2
3
b'\xdb\x8dL+*\xf0?\x824oy\xf2cY\x95y'

key_hex = db8d4c2b2af03f82346f79f263599579

用之前的简单转回字节的方法

20210529 DozerCTF2021-ada5b99a.png
1
2
3
from Crypto.Util.number import long_to_bytes
print('db8d4c2b2af03f82346f79f263599579'.encode())
print(long_to_bytes(int('db8d4c2b2af03f82346f79f263599579', 16)))

出来是

1
b'db8d4c2b2af03f82346f79f263599579'

破案啦,显然两个字节流并不一样,问题出在aes的key上

然后用hex的反操作就可以逆回去

最终的操作如下

1
2
3
4
5
6
7
8
9
10
11
from Crypto.Util.number import long_to_bytes
import os

KEY = os.urandom(16)
print("key = ", KEY)
print("key_hex = ", KEY.hex())
key = KEY.hex()
print("tmp_key = ", bytes.fromhex(key))
print(long_to_bytes(int(key, 16)))
assert KEY == bytes.fromhex(key)
assert KEY == long_to_bytes(int(key, 16))

运行结果

1
2
3
4
key =  b':\xa7\xb6G\x02m\x8f\xcc,n\xa6\xfcH\xea\xc44'
key_hex = 3aa7b647026d8fcc2c6ea6fc48eac434
tmp_key = b':\xa7\xb6G\x02m\x8f\xcc,n\xa6\xfcH\xea\xc44'
b':\xa7\xb6G\x02m\x8f\xcc,n\xa6\xfcH\xea\xc44'

当然,有用到key的地方全部要修改

20210529 DozerCTF2021-dd7c5f37.png

回归正轨

修改之后的脚本,在一百万次以内就跑出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import os
from Crypto.Util import *
from Crypto.Util.strxor import *
from Crypto.Cipher import AES
import sys

# flag_length =

hex_num = '0123456789abcdef'

# ==========================枚举key的============================ #
key_hex = list('2$9cf037f8b3a$2b19b5bda978c294$5')

possible_key = []
for i in hex_num:
key_hex[1] = i
for j in hex_num:
key_hex[13] = j
for k in hex_num:
key_hex[-2] = k
possible_key.append("".join(key_hex))
# print(possible_key)

# ==========================枚举out的============================ #
cipher_hex = '91e5fb43f053b21ce12e41df0b0ae0bb6a20c55719151$$fccecb4$$$$$2a27c8c582c6704f$$$$153bd3313b84235ace16a7b3b190$e487abfa9$cf379d1a3c'
cipher_hex1 = '91e5fb43f053b21ce12e41df0b0ae0bb'
cipher_hex2 = list('6a20c55719151$$fccecb4$$$$$2a27c')
cipher_hex3 = list('8c582c6704f$$$$153bd3313b84235ac')
cipher_hex4 = list('e16a7b3b190$e487abfa9$cf379d1a3c')

possible_cipher_hex4 = []
for i in hex_num:
cipher_hex4[11] = i
for j in hex_num:
cipher_hex4[21] = j
possible_cipher_hex4.append("".join(cipher_hex4))
# print(possible_cipher_hex4)

flag = 'DozerCTF{'
count = 0
for tmp_key in possible_key:
# key知道,msg就知道
msg = ('I do not care the result' + tmp_key).encode()
msg = Padding.pad(msg, 16)
struct = [msg[i:i + 16] for i in range(0, len(msg), 16)]
tmp_m2, tmp_m3, tmp_m4 = struct[1], struct[2], struct[3]

for tmp_out4 in possible_cipher_hex4:
# 第四次aes的结果解密
tmp_aes_c4 = strxor(bytes.fromhex(tmp_out4), tmp_m3)
tmp_aes_m4 = AES.new(bytes.fromhex(tmp_key), AES.MODE_ECB).decrypt(tmp_aes_c4)

tmp_aes_c3 = strxor(tmp_m4, tmp_aes_m4)
tmp_out3 = strxor(tmp_aes_c3, tmp_m2)
tmp_out3 = tmp_out3.hex()
count += 1
print(count)
if tmp_out3.startswith('8c582c6704f'):
print("key_hex =", tmp_key)
print("tmp_aes_m4 =", tmp_aes_m4)
sys.exit(0)

跑出来是

1
2
key_hex = 279cf037f8b3a92b19b5bda978c294f5
tmp_aes_m4 = b'\xde@=0N\xbf\x0c\xc0i\x82\x02x\xd6z\x0e\x93'

什么都已经知道了,剩下的就是还原answer了,四步奏aes,只要一步一步走回去就好了

解题脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import os
from Crypto.Util import *
from Crypto.Util.strxor import *
from Crypto.Cipher import AES


def function_dec(msg_p, key, aes_p):
msg_p = Padding.pad(msg_p, 16)
struct = [msg_p[i:i+16] for i in range(0, len(msg_p), 16)]

tmp_c = b''
for p in struct[3:0:-1]:
c = strxor(aes_p, p)
tmp_c = c
c = AES.new(key, AES.MODE_ECB).decrypt(c)
aes_p = c
return tmp_c


key_hex = '279cf037f8b3a92b19b5bda978c294f5'
aes_m4 = b'\xde@=0N\xbf\x0c\xc0i\x82\x02x\xd6z\x0e\x93'
KEY = bytes.fromhex(key_hex)
msg = 'I do not care the result' + KEY.hex()
aes_c1 = function_dec(msg.encode(), KEY, aes_m4)

out1 = '91e5fb43f053b21ce12e41df0b0ae0bb'
answer1 = strxor(bytes.fromhex(out1), aes_c1)

msg1 = b'I do not care th'
aes_m1 = AES.new(KEY, AES.MODE_ECB).decrypt(aes_c1)
answer2 = strxor(msg1, aes_m1)

print(answer1 + answer2)