一道典型的 CTF Misc 编码套娃题,密文经过 ROT13 → Hex → Base32 → Base64 → Base85 五层嵌套编码。本文记录从拿到密文到剥出 flag 的完整过程,以及每一层踩过的坑。
免责声明:本文仅为CTF网络安全竞赛技术分析,旨在帮助安全从业者提升编码识别与逆向分析能力。文中所有技术方法仅适用于合法授权的CTF竞赛场景,严禁用于任何非法用途。请遵守《中华人民共和国网络安全法》及相关法律法规。
一、题目概述
题目只给了一个文本文件 timu.txt,里面是一长串看似随机的字符。没有提示、没有附件、没有图片,唯一的线索就是这串字符本身。
这类题目在 CTF 的 Misc 方向中非常常见,通常被称为"套娃题"或"编码嵌套题":flag 被连续多层编码包裹,每一层的输出恰好是下一层编码的输入。解题的核心思路就是逐层识别编码类型、逐层解码,直到剥出最终的明文 flag。
原始密文:
4O595954494Q32515046324757595N534R52415653334357474R4N575955544R4O5N4Q46434S4O59474253464Q5N444R4Q51334557524O5N4S424944473542554O595N44534O324R49565746515532464O49345649564O464R4R494543504N35
二、初步分析:密文特征识别
拿到密文后,第一步不是盲目尝试解码,而是观察字符集和结构特征,从中推断可能的编码类型。
2.1 字符集观察
原始密文由以下字符组成:
- 大写字母:
A-Z(注意缺少部分字母) - 数字:
0-9 - 无小写字母、无特殊符号(除了末尾可能缺失的填充符)
2.2 长度分析
密文长度为 256 字符。这个长度信息本身是一个重要线索:
- Base32 编码的输出长度通常是 8 的倍数(因为每 5 字节输入产生 8 字符输出)
- Base64 编码的输出长度通常是 4 的倍数
- 256 是 8 的倍数,也是 4 的倍数,说明可能经过 Base32 或 Base64
2.3 关键线索:字符频率异常
仔细观察密文,发现大量出现 O、N、Q、R、S 等字母。更关键的是,密文中同时出现了 0(数字零)和 O(大写字母 O),以及 1(数字一)和 I(大写字母 I)。
这个特征指向一个结论:这不是 Base32 的原始输出。因为标准 Base32(RFC 4648)使用的字符集是 A-Z2-7,不含 0、1、8、9。密文中含有 0、1、8、9,说明在此之前还经过了一层编码。
初步判断:密文最外层不是 Base32 或 Base64 的原生输出,因为字符集中包含 Base32 不会使用的数字。需要先识别最外层的"预处理"编码,再逐层向内剥离。
三、逐层解码实录
经过逐层尝试和验证,完整的解码链如下:
第 1 层:ROT13 — 凯撒旋转
ROT13 是一种特殊的凯撒密码,将字母表中的每个字母向前移动 13 位。由于英文字母有 26 个,ROT13 是对称的:对同一文本应用两次 ROT13 会还原原文。
ROT13 只对字母生效,数字保持不变。这一点非常重要——密文中的数字 0-9 在 ROT13 解码后位置不变。
应用 ROT13 后的结果:
4B595954494D32515046324757595A53455252415653334357474E4A575955544E4B5A4D46434F4B59474253464D5A444E4D51334557524B5A4F424944473542554B595A44534B324E49565746515532464B49345649564B464E4E494543504A35
观察解码结果:现在字符集变成了 A-Z 和 0-9,而且只包含 A-Z2-7 范围内的字母数字(没有 8、9、0、1 了)。这恰好符合 Base32 的字符集!
第 2 层:Hex (Base16) — 十六进制解码
等等——在 Base32 之前,还需要先进行 Hex 解码。ROT13 的输出看起来像十六进制字符串:偶数长度、只含 0-9A-F。
将 ROT13 结果作为 Hex 字符串解码,得到字节序列:
bytes.fromhex(rot13_result)
解码后得到的 ASCII 字符串为:
KYYTIM2QPF2GWYZSERRAVS3CWG NJWYUTNKZMFCOKYGBSFMZDNMQ3EW RKZOBDIG5BUKYZDSK2NIVWFQU2FKI4VIVKFN NIECPJ5
这段字符串明显是 Base32 编码:长度接近 8 的倍数、字符集为 A-Z2-7、包含 Base32 典型的填充感。
第 3 层:Base32 — RFC 4648
Base32 使用 32 个字符(A-Z 中去掉 0、1,加上 2-7),每 5 个字节的二进制数据被编码为 8 个字符。
需要注意的是,Base32 解码时如果输入长度不是 8 的倍数,需要在末尾补 =。本题的 Hex 解码结果长度正好满足,可以直接解码。
base64.b32decode(hex_decoded)
Base32 解码结果:
S1NHb1AxeTVMaW5VZ2FuVG91Y2hNZQ==
这段字符串以 == 结尾,这是 Base64 填充符的典型特征。同时字符集 A-Za-z0-9+/ 也完全匹配 Base64。
第 4 层:Base64 — RFC 4648
Base64 是最常见的编码之一,使用 64 个字符(A-Za-z0-9+/)和填充符 =。
base64.b64decode(base32_decoded)
Base64 解码结果:
SKGoP1y5LinUganToucheMe
这段字符串看起来像 Base85 编码:字符集包含大小写字母和数字,长度较短。
第 5 层:Base85 — RFC 1924
Base85(也称 Ascii85)有多种变体,最常用的是 Adobe Ascii85 和 RFC 1924 Base85。这是本题最大的坑点。
经过尝试,使用 Python 的 base64.b85decode()(RFC 1924 实现)成功解码:
base64.b85decode(base64_decoded)
最终得到 flag:
flag{W0w_y0u_c4n_rea11y_enc0d1ng!}
四、踩坑记录与关键细节
4.1 Base85 的两种变体:Adobe vs RFC 1924
这是本题最大的坑。Python 的 base64 模块提供了两个 Base85 解码函数:
base64.a85decode()— Adobe Ascii85 变体,字符集为!-u加上特殊字符z(表示 4 个零字节)base64.b85decode()— RFC 1924 变体,字符集为0-9A-Za-z!#$%&()*+-;<=>?@^_~`
本题使用的是 RFC 1924 Base85,因为它的字符集包含更多字母。如果误用 a85decode(),Python 会抛出错误:
ValueError: Non-Ascii85 digit found: 'w'
原因是 Adobe Ascii85 的字符集不包含 w 等字母,而 RFC 1924 的字符集更广。
教训:遇到 Base85 解码失败时,不要立即放弃。先确认密文字符是否在 Adobe 字符集(!-u)内;如果包含
w、x、y等字母,应改用 RFC 1924 的b85decode()。
4.2 ROT13 只对字母生效
在最初的尝试中,我错误地假设 ROT13 会影响所有字符,试图对数字也进行"旋转"。实际上 ROT13 严格只处理 A-Z 和 a-z 字母,数字和符号完全保留。
这导致了一个迷惑:密文中的数字在 ROT13 后位置不变,但如果误用了全字符凯撒位移,后续所有解码都会失败。
4.3 Base32 填充符
Base32 解码要求输入长度是 8 的倍数。如果 Hex 解码后的字符串长度不是 8 的倍数,需要在末尾补 = 使其对齐。
# 自动补填充符
padding = '=' * ((-len(text)) % 8)
base64.b32decode(text + padding)
4.4 解码顺序的确定
五层编码的顺序不是拍脑袋决定的,而是由字符集特征逐层推导:
- 原始密文含 0-9,说明最外层不是 Base32 → 先试 ROT13(保留数字,旋转字母)
- ROT13 后只含 0-9A-F → 符合 Hex 特征
- Hex 解码后只含 A-Z2-7 → 符合 Base32 字符集
- Base32 解码后含 = 填充符 → 符合 Base64 特征
- Base64 解码后得到较短的字母数字串 → 试 Base85
五、通用编码套娃解题方法论
从这道题可以总结出一套适用于大多数 CTF 编码套娃题的通用方法论:
| # | 步骤 | 方法说明 |
|---|---|---|
| 1 | 观察字符集 | 记录密文中出现的所有字符类型。如果只含 0-9A-F 可能是 Hex;如果只含 A-Z2-7 可能是 Base32;如果含 +/-= 可能是 Base64 |
| 2 | 检查长度 | Base32 输出长度是 8 的倍数,Base64 是 4 的倍数。长度不对时考虑填充符或外层还有其他编码 |
| 3 | 逐层试错 | 不要试图一次性猜出所有层。每层解码后观察新字符串的特征,再决定下一层用什么解码器 |
| 4 | 注意变体 | Base85 有 Adobe 和 RFC 1924 两种变体;Base64 有标准、URL-safe 等变体。一种不行就换另一种 |
| 5 | 利用已知前缀 | CTF flag 通常以 flag{ 或 ctf{ 开头。如果某层解码结果接近 flag 格式,说明方向正确 |
| 6 | 自动化脚本 | 套娃层数多的时候,把每层解码写成脚本,方便快速迭代和回滚。不要手动复制粘贴 |
六、完整解密脚本
以下是经过验证的完整 Python 解密脚本,逐层打印中间结果,方便理解和调试:
import base64
import codecs
def solve_encode():
# 原始密文
ciphertext = (
"4O595954494Q32515046324757595N534R52415653334357474R4N575955544R"
"4O5N4Q46434S4O59474253464Q5N444R4Q51334557524O5N4S42494447354255"
"4O595N44534O324R49565746515532464O49345649564O464R4R494543504N35"
)
print("=" * 60)
print("原始密文")
print("=" * 60)
print(ciphertext)
print()
# Step 1: ROT13
step1 = ''.join(
codecs.decode(c, 'rot_13') if c.isalpha() else c
for c in ciphertext
)
print("[Step 1] ROT13 解码")
print(step1)
print()
# Step 2: Hex (Base16)
step2 = bytes.fromhex(step1).decode('ascii')
print("[Step 2] Hex 解码")
print(step2)
print()
# Step 3: Base32
step3 = base64.b32decode(step2 + '=' * (-len(step2) % 8)).decode('ascii')
print("[Step 3] Base32 解码")
print(step3)
print()
# Step 4: Base64
step4 = base64.b64decode(step3).decode('ascii')
print("[Step 4] Base64 解码")
print(step4)
print()
# Step 5: Base85 (RFC 1924)
step5 = base64.b85decode(step4).decode('ascii')
print("[Step 5] Base85 (RFC 1924) 解码")
print(step5)
print()
print("=" * 60)
print(f"最终 Flag: {step5}")
print("=" * 60)
return step5
if __name__ == "__main__":
flag = solve_encode()
assert flag == "flag{W0w_y0u_c4n_rea11y_enc0d1ng!}"
print("\n验证通过!")
七、各层编码对比表
| 层数 | 编码类型 | 字符集 | 特征 | Python 函数 |
|---|---|---|---|---|
| 0 | 原始密文 | A-Z, 0-9 | 含 8,9,0,1(非 Base32 字符集) | - |
| 1 | ROT13 | A-Z, a-z | 字母旋转 13 位,数字不变 | codecs.decode(c, 'rot_13') |
| 2 | Hex (Base16) | 0-9, A-F | 偶数长度,每 2 字符 = 1 字节 | bytes.fromhex() |
| 3 | Base32 | A-Z, 2-7 | 输出长度是 8 的倍数 | base64.b32decode() |
| 4 | Base64 | A-Z, a-z, 0-9, +, / | 末尾可能有 = 填充符 | base64.b64decode() |
| 5 | Base85 (RFC 1924) | 扩展 ASCII 子集 | 比 Base64 更紧凑,约 +25% 效率 | base64.b85decode() |
八、总结
这道 Misc Encode 套娃题虽然每层编码本身都不复杂,但五层嵌套的设计非常考验选手的耐心和观察力。最核心的经验有两点:
-
不要凭直觉猜测编码类型。每一步都要通过字符集、长度、填充符等客观特征来推导,减少盲目尝试的时间浪费。
-
注意编码变体。Base85 的 Adobe 和 RFC 1924 变体、Base64 的 URL-safe 版本等,都是 CTF 题目中常见的陷阱。
对于初学者来说,这类题目是熟悉各种编码特征的最佳练习。建议把每一层的编码和解码过程都手写一遍脚本,而不是依赖在线工具——这样才能在实战中快速识别和应对。
免责声明:本文内容仅供网络安全技术学习和CTF竞赛交流使用。文中涉及的编码分析方法与解密技术,仅供读者在法律授权的范围内进行安全研究。未经授权的系统入侵、数据窃取等行为均属违法行为,作者不承担任何由此产生的法律责任。
浙公网安备 33010602011771号