WeChall - GizCrypt

GizCrypt (Crypto) by gizmore 自制对称加密,GWF_Crypt 算法,key 固定为 11 位 a-zA-Z

Challenge

题面给出一个自制对称加密(算法源码可见),一段 hex 密文,一个 key 的约束:长度 11,只含 a-zA-Z。目标是解密密文得到可读英文文本,从中提取嵌入的 12 位大写 HEX token 作为 answer。

Solution

算法源码(GWF_Crypt):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function decrypt($ciphertext, $key) {
$back = '';
$len = strlen($ciphertext);
$x = 1;
$k = -1;
$e = ord('e'); // 101, 常数
for ($i = 0; $i < $len; $i++) {
$k += $x;
if ($k >= $klen) {
$k = 0;
$x++;
if ($x >= $klen) $x = 1;
}
$back .= chr(ord($key[$k % $klen]) ^ ord($ciphertext[$i]) ^ $e);
}
return $back;
}

注意 encrypt = decrypt,加解密相同。本质是 XOR 流密码,但 key 不是简单循环——有一个递增步长的索引调度(step starts at 1, increments after each full cycle)。

Python 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def gizcrypt_decrypt(ct, key):
klen = len(key)
x, k = 1, -1
e = 101 # ord('e')
plain = bytearray()
for b in ct:
k += x
if k >= klen:
k = 0
x += 1
if x >= klen:
x = 1
plain.append(key[k % klen] ^ b ^ e)
return bytes(plain)

Key 恢复(按 key index 分组 → 评分):

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
import string
from collections import Counter

alphabet = string.ascii_letters # 52 chars

def key_index_seq(length, klen=11):
seq = []
x, k = 1, -1
for _ in range(length):
k += x
if k >= klen:
k = 0; x += 1
if x >= klen: x = 1
seq.append(k % klen)
return seq

seq = key_index_seq(len(ct))
groups = {i: [] for i in range(11)}
for i, kpos in enumerate(seq):
groups[kpos].append(ct[i])

key = []
for pos in range(11):
best = None
best_score = -9999
for k in alphabet:
plain = bytes(k ^ c ^ 101 for c in groups[pos])
score = sum(4 if b in (32,101,116,97,111) else
2 if chr(b).isalpha() or b in (44,46,39) else
1 if 32 <= b < 127 else -10
for b in plain)
if score > best_score:
best_score = score
best = k
key.append(best)

full_key = ''.join(key) # ItsPassword
plain = gizcrypt_decrypt(ct, full_key.encode())
print(plain.decode()) # 包含 answer: 9DD4752982DC

关键发现:

  • Key 是固定的ItsPassword,对所有人所有 session 都相同。只需要对当前 session 的密文解密即可得到唯一的 answer。
  • Answer 是 session-bound:每次页面刷新密文变化,嵌入的 12 位 HEX token 也变化,必须用当前 session 的密文解密。
  • 评分时 key[1] 样本太少(仅有 ~6 个字节),所以 key[1] 置信度最低。可以结合英文上下文人工校正。