AI摘要
文章针对CTF中Flag被多层Base64/Base32随机混合编码的难题,提出利用Base16、Base32、Base64字符集互斥的特性,通过依次尝试解码来还原Flag。核心策略是先试Base16排除,再试Base32,最后试Base64,并提供了相关Python脚本。
概述
CTF Crypto / MISC 中常见一类题目:将 Flag 用 Base64 / Base32 随机混合编码多层,要求选手解码还原。核心挑战在于——编码轮次未知、每轮用的是哪种 Base 也未知,需要"盲破"。
Flag 原文
│
▼ 随机选取 base64 或 base32
┌──────────┐
│ Round 1 │ → 编码后
└────┬─────┘
│ ▼ 再次随机选取
┌──────────┐
│ Round 2 │ → 编码后
└────┬─────┘
│ ▼
... 重复 c 次 (c ∈ [20, 40])
│
▼
最终密文 (base2.txt)编码总轮次 $c \in [20, 40]$,每轮 base64 或 base32 二选一。理论上总组合数为 $2^{20} \sim 2^{40}$,无法穷举——必须用"试探解码"策略。
Base 家族速查
三种编码对比
| 属性 | Base16 (Hex) | Base32 | Base64 |
|---|---|---|---|
| 字符表大小 | 16 | 32 | 64 |
| 字符集 | 0-9 A-F | A-Z 2-7 | A-Z a-z 0-9 + / |
| 每组输入 | 1 字节 | 5 字节 | 3 字节 |
| 每组输出 | 2 字符 | 8 字符 | 4 字符 |
| 膨胀率 | 200% | 160% | 133% |
| = 填充 | 无 | 有 (=) | 有 (=) |
| 输出长度倍数 | 2 的倍数 | 8 的倍数 | 4 的倍数 |
三种编码的互斥识别
最重要的特性:每种编码的合法输入字符集互不包含。
Base16 合法字符: 0123456789ABCDEFabcdef
↑ 不含 G-Z,不含 +/=
Base32 合法字符: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 =
↑ 不含 0 1 8 9 + /
Base64 合法字符: A-Z a-z 0-9 + / =
↑ 几乎包含所有可打印字符利用这个互斥性:尝试 base16 解码 → 若报错 → 不是 base16 → 换 base32 → 若还报错 → 一定是 base64。
┌─────────┐
│ 输入密文 │
└────┬────┘
▼
┌─ try base16_decode ──┐
│ 含 G/K/+ 等非法字符 │
▼ 报错 ▼ 报错
非 base16 ┌─ try base32_decode ──┐
│ 含 0/1/8/9/+ 等 │
▼ 报错 ▼ 报错
非 base32 try base64_decode
│
▼
继续下一轮编码脚本
import base64
import random
a = random.randint(0, 10) # a ∈ [0, 10]
b = random.randint(20, 30) # b ∈ [20, 30]
rounds = a + b # 总轮次 ∈ [20, 40]
print(f'编码轮次: {rounds}')
flag = b'flag{lalala}'
for _ in range(rounds):
k = random.randint(0, 1)
if k == 0:
flag = base64.b64encode(flag)
else:
flag = base64.b32encode(flag)
with open('base2.txt', 'wb') as f:
f.write(flag)
print('finish!')关键参数:
| 变量 | 范围 | 说明 |
|---|---|---|
a | 0~10 | 随机因子一 |
b | 20~30 | 随机因子二 |
rounds = a+b | 20~40 | 总编码轮次 |
k | 0 或 1 | 0 → base64; 1 → base32 |
| 编码路径总数 | $2^{rounds}$ | 约$10^6 \sim 10^{12}$(不可穷举) |
解密脚本
import base64
with open('base2.txt', 'rb') as f:
data = f.read()
decoders = [base64.b16decode, base64.b32decode, base64.b64decode]
while True:
for decoder in decoders:
try:
data = decoder(data)
break
except Exception:
continue
else:
print('[错误] 全部解码尝试失败,密文可能已损坏')
break
try:
text = data.decode('utf-8')
except UnicodeDecodeError:
continue
if text.startswith('flag{') or text.startswith('flag'):
print(text)
break试探顺序和原理:
| 顺序 | 解码方式 | 失败条件 | 为什么会失败 |
|---|---|---|---|
| 1 | base16 | 输入含G-Z、g-z、+、/、= | 编码从未使用 base16,密文中必然存在非 hex 字符 |
| 2 | base32 | 输入含0、1、8、9、+、/ | base32 仅含A-Z 2-7,base64 输出含上述非法字符 |
| 3 | base64 | 几乎不会失败(若失败则密文损坏) | base64 接受几乎所有字符 |
为什么不先试 base64?因为 base64 字符集包含 base32 和 base16 的全部字符——base64 总是"成功"解码,但输出是乱码。所以必须从最严格的(base16)到最宽松的(base64)依次尝试。
深层原理
为什么 Base16 放在第一位
编码脚本仅使用了 b64encode 和 b32encode,从未使用 b16encode。但解密脚本仍把 base16 放在第一位的原因:
base64 的输出一定包含不属于 base16 字符集的字符(如g-z、+/=),因此 base16 解码必然失败。它充当一个方向探测器——快速排除不可能的路径,自动 fallthrough 到 base32。
# Base16 非法字符触发的异常
base64.b16decode(b'abc') # 成功 ('abc' 都是合法 hex)
base64.b16decode(b'abg') # 报错: Non-base16 digit found概率分析
| 项目 | 值 |
|---|---|
| 每轮选择 base64 的概率 | 50% |
| 每轮选择 base32 的概率 | 50% |
| 总轮次期望值$E[rounds]$ | $E[a]+E[b] = 5+25 = 30$ |
| 密文长度膨胀期望 | $1.33^{15} \times 1.6^{15} \approx 3.5 \times 10^4$ 倍 |
| 原始 flag 约 12 字节 → 最终密文约 | ~420 KB |
数据结构追踪
原始: "flag{lalala}" (12 字节)
│
▼ b64encode
"ZmxhZ3tsYWxhbGF9" (16 字符)
│
▼ b32encode
"KNUGK4TDMUQGK3RA..." (更长)
│
▼ ... 重复 20~40 轮
│
▼
最终密文: 数十万字符CTF 应用
常见变体
| 编码组合 | 解密顺序 | 备注 |
|---|---|---|
| b16 + b32 + b64 | b16 → b32 → b64 | 最严格在前 |
| b32 + b64 | b32 → b64 | 跳过 b16 也可 |
| b16 + b64 | b16 → b64 | b32 报错自动跳过 |
| + b85 (Ascii85) | b16→b32→b85→b64 | 在 decoders 列表中间插入base64.b85decode |
| + URL-Safe Base64 | 使用base64.urlsafe_b64decode | 字符集为A-Z a-z 0-9 - _ 不含 +/ |
| + 反转 + b32 | 需额外判断 | 密文反转后再解码 |
与 base64 隐写术的区别
| 混合编码(本文) | Base64 隐写 | |
|---|---|---|
| 目的 | 混淆明文 | 隐藏信息在= 填充位 |
| 操作 | 多层 baseXX 编码 | 修改填充位或等号前的 bit |
| 解密 | 逆序逐层尝试 | 提取特定 bit 并重组 |
| CTF 识别 | 密文极长(多轮膨胀) | 密文长度正常,但可能含异常= |
参考文献
- Python Software Foundation. base64 — Base16, Base32, Base64, Base85 Data Encodings [EB/OL]. https://docs.python.org/3/library/base64.html
- Josefsson S. RFC 4648 — The Base16, Base32, and Base64 Data Encodings [EB/OL]. https://datatracker.ietf.org/doc/html/rfc4648
- Wikipedia. Base64 [EB/OL]. https://en.wikipedia.org/wiki/Base64


