一道典型的 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 解码顺序的确定

五层编码的顺序不是拍脑袋决定的,而是由字符集特征逐层推导:

  1. 原始密文含 0-9,说明最外层不是 Base32 → 先试 ROT13(保留数字,旋转字母)
  2. ROT13 后只含 0-9A-F → 符合 Hex 特征
  3. Hex 解码后只含 A-Z2-7 → 符合 Base32 字符集
  4. Base32 解码后含 = 填充符 → 符合 Base64 特征
  5. 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 套娃题虽然每层编码本身都不复杂,但五层嵌套的设计非常考验选手的耐心和观察力。最核心的经验有两点:

  1. 不要凭直觉猜测编码类型。每一步都要通过字符集、长度、填充符等客观特征来推导,减少盲目尝试的时间浪费。

  2. 注意编码变体。Base85 的 Adobe 和 RFC 1924 变体、Base64 的 URL-safe 版本等,都是 CTF 题目中常见的陷阱。

对于初学者来说,这类题目是熟悉各种编码特征的最佳练习。建议把每一层的编码和解码过程都手写一遍脚本,而不是依赖在线工具——这样才能在实战中快速识别和应对。


免责声明:本文内容仅供网络安全技术学习和CTF竞赛交流使用。文中涉及的编码分析方法与解密技术,仅供读者在法律授权的范围内进行安全研究。未经授权的系统入侵、数据窃取等行为均属违法行为,作者不承担任何由此产生的法律责任。