GoGoCrypto_writeup

本人参与极客大挑战2023的出题工作,主要负责密码学板块。

对于GoGoCrypto这道反响比较高的题目,这里附上题解。

题目源码

这题用Golang写了一个Flag Folder的交互,流程大概长这样:

image-20231227004357980

加解密采用的算法是AES的CBC mod。

image-20231227004502286

这种加密算法的主要攻击手段是字节翻转(bit flipping),这种攻击的前提是一个明文分组是已知的。

sid是16字节,根据填充算法:

1
2
3
4
5
func Pad(pt []byte) []byte {
padlen := aes.BlockSize - (len(pt) % aes.BlockSize)
padding := bytes.Repeat([]byte{byte(padlen)}, padlen)
return append(pt, padding...)
}

可以知道填充后有两块:

1
2
3
4
block#1 block#2
+-----------------+-----------------+
| sid | Padding ("\x10"*16) |
+-----------------+-----------------+

那么我们就能修改第一个密文分组,来控制解密后的第

二个明文分组。具体的原理是:

image-20231227004700688

这一步显然会破坏第一块的明文,但并不影响我们的利用。

1
2
3
4
func Unpad(pt []byte) []byte {
padlen := int(pt[len(pt)-1])
return pt[:len(pt)-padlen]
}

unpad函数只是将padding的最后一个字节作为padding长度,根本不检查,所以我们可以很容易的让padding长度为len(plaintext) - 1,那么我们就可以得到一个只有一个字节的明文了!

因此攻击流程是:

  • 修改密文并上传

  • 服务端解密,得到只有一个字节的明文

  • 遍历所有明文(254),必然得到flag。

exp:

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
import os 
from urllib.parse import unquote
from base64 import b64decode as dec, b64encode as enc
from pwn import xor
import requests
from hashlib import sha512

def curl_request(url, method='GET', headers=None, data=None):
try:
if method.upper() == 'GET':
response = requests.get(url, headers=headers)
elif method.upper() == 'POST':
response = requests.post(url, headers=headers, data=data)
elif method.upper() == 'PUT':
response = requests.put(url, headers=headers, data=data)
elif method.upper() == 'DELETE':
response = requests.delete(url, headers=headers, data=data)
else:
print("Unsupported HTTP method")
return None

# 检查请求是否成功
response.raise_for_status()

return response.text
except requests.exceptions.RequestException as e:
print(f"Error: {e}")
return None
url = 'http://47.109.106.62:7842/'
response = requests.get(url)
x = str(response.headers)

token = unquote(x.split("token")[1].split(";")[0][1:])
token = dec(token)
nonce = unquote(x.split("nonce")[1].split(";")[0][1:])

c1, c2 = token[:len(token)//2], token[len(token)//2:]
c1 = xor(xor(c1,b'\x10'*16),b'\x1f'*16)

form_data = {
"Rec": enc(c1+c2).decode()
}
res = curl_request('http://47.109.106.62:7842/api/dec', method='POST', headers=None, data=form_data)
print(res)

for i in range(256):
form_data = {
"Password": enc(chr(i).encode()).decode()+enc(sha512(dec(nonce)).digest()).decode()
}
res = curl_request('http://47.109.106.62:7842/api/check', method='POST', headers=None, data=form_data)
if "SYC" in str(res):
print(res)
break
# {"check":"true","msg":"Your flag is: SYC{AL3XEI_FAKE_FLAG}"}