CCB2026 wp

by cha

感谢带飞的两位师傅

主要完成的工作就是取证1的前半部分,虽然题出的不咋地但是还是能学到点知识的。就附这个题的wp了

GhostPatch

观察流量可以发现主要存在 8888 和 9999 两个服务端口。

过滤:

1
tcp.port == 8888

追踪 TCP 流,可以看到 FGT/0.9 明文协议,其中泄露了新版 FGT/1.0 的协议说明。关键信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FGT/1.0 (port 9999)

shared = B^a = A^b mod p
K = SHA-256(shared)[:16]

RC4 with K, one independent stream per direction

frame:
[u16be length][rc4(frame)]

01 GET
02 META
03 DATA
04 END
06 LIST
07 SHELL
09 CIN
0A COUT

因此后续重点分析 9999。

过滤:

1
tcp.port == 9999

追踪 TCP 流,握手部分为明文:

1
2
3
FGT/1.0 READY p=8348d41a7225 g=5
FGT/1.0 HELLO 3709a1d52d1d
FGT/1.0 OK d90673c26b

得到:

1
2
3
4
p = 0x8348d41a7225
g = 5
A = 0x3709a1d52d1d
B = 0xd90673c26b

破解 DH

p 很小,且:

1
2
p - 1
= 2^2 × 3 × 41 × 71 × 73 × 79 × 83 × 89 × 97

p-1 为光滑数,可以直接求离散对数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from sympy import discrete_log
import hashlib

p = 0x8348d41a7225
g = 5
A = 0x3709a1d52d1d
B = 0xd90673c26b

a = discrete_log(p, A, g)
shared = pow(B, a, p)

shared_bytes = shared.to_bytes(
(shared.bit_length() + 7) // 8, "big"
)
key = hashlib.sha256(shared_bytes).digest()[:16]

print("a =", a)
print("shared =", hex(shared))
print("key =", key.hex())

得到:

1
2
3
a = 8357903120266
shared = 0x75901cbea117
key = b75ad015fa6436b6dd6479dcb9c660b4

RC4 解密

协议注明:

1
RC4 with K, one independent stream per direction

因此 Client → Server 和 Server → Client 必须分别维护 RC4 状态。

在 Wireshark 的 Follow TCP Stream 中分别导出两个方向的 Raw 数据,例如:

1
2
c2s.bin
s2c.bin

握手明文结束后的帧格式为:

1
[u16be length][RC4 ciphertext]

标准 RC4:

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
class RC4:
def __init__(self, key):
self.S = list(range(256))
j = 0

for i in range(256):
j = (j + self.S[i] + key[i % len(key)]) & 0xff
self.S[i], self.S[j] = self.S[j], self.S[i]

self.i = 0
self.j = 0

def crypt(self, data):
out = bytearray()

for x in data:
self.i = (self.i + 1) & 0xff
self.j = (self.j + self.S[self.i]) & 0xff
self.S[self.i], self.S[self.j] = \
self.S[self.j], self.S[self.i]

k = self.S[
(self.S[self.i] + self.S[self.j]) & 0xff
]
out.append(x ^ k)

return bytes(out)


def decrypt_frames(data, offset, key):
rc4 = RC4(key)
frames = []

while offset + 2 <= len(data):
length = int.from_bytes(data[offset:offset + 2], "big")
offset += 2

ciphertext = data[offset:offset + length]
offset += length

frames.append(rc4.crypt(ciphertext))

return frames

注意:同一方向的所有 frame 共用一个 RC4 状态,不能逐帧重新初始化。

解密客户端请求后,可以看到:

1
2
3
4
5
6
7
8
LIST
GET notes.txt
GET checksum.txt
GET fw_v2.bin
GET libc.so.6
GET ld-linux-x86-64.so.2
SHELL
...

由此确认需要恢复:

1
2
3
4
5
notes.txt
checksum.txt
fw_v2.bin
libc.so.6
ld-linux-x86-64.so.2

文件重组

服务端文件传输使用:

1
2
3
02 META
03 DATA
04 END

结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
META:
01 byte type
08 bytes file size
32 bytes SHA-256
02 bytes filename length
N bytes filename

DATA:
01 byte type
04 bytes sequence number
04 bytes data length
N bytes data

END:
01 byte type
32 bytes SHA-256

DATA 帧并不保证按顺序出现,因此必须根据 sequence number 重排。

恢复逻辑:

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
import hashlib

current = None

for frame in frames:
t = frame[0]

if t == 0x02:
size = int.from_bytes(frame[1:9], "big")
sha256 = frame[9:41]

name_len = int.from_bytes(frame[41:43], "big")
name = frame[43:43 + name_len].decode()

current = {
"name": name,
"size": size,
"sha256": sha256,
"chunks": {}
}

elif t == 0x03 and current:
seq = int.from_bytes(frame[1:5], "big")
length = int.from_bytes(frame[5:9], "big")
current["chunks"][seq] = frame[9:9 + length]

elif t == 0x04 and current:
data = b"".join(
current["chunks"][i]
for i in sorted(current["chunks"])
)

data = data[:current["size"]]

assert hashlib.sha256(data).digest() == current["sha256"]

with open(current["name"], "wb") as f:
f.write(data)

print("[+] recovered:", current["name"])
current = None

最终成功恢复:

1
2
3
4
5
notes.txt
checksum.txt
fw_v2.bin
libc.so.6
ld-linux-x86-64.so.2

恢复出的主要文件 SHA-256 为:

1
2
3
4
5
6
7
8
9
10
11
fw_v2.bin
0f9b3ce7363a988f72dc46812d21d061123045f3521e12289c12fc7405c9629c

libc.so.6
8db37cf3f2169f59a0f07ef1fea308c35656668c64c8ff294e1860f4121eb161

ld-linux-x86-64.so.2
cd4df4f3c7b83673d61189bf2eaebd33ca4f2853ab9772b8a25e025ef99b1e81

notes.txt
d2e5441746107d0fd94a3638e1a016fac62be82874f781069548033eda5dfa36

至此完成加密流量解密及文件恢复。