ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CTF古典密码题解析:从Base64到维吉尼亚的多层嵌套破解实战

CTF古典密码题解析:从Base64到维吉尼亚的多层嵌套破解实战 打CTF也有一段时间了要说哪种题目最让我觉得“又爱又恨”古典密码绝对排得上前三。爱它是因为玩法直观不需要像逆向那样啃汇编也不用像Web那样背一堆Payload恨它则是因为很多古典密码题表面看着人畜无害实际上嵌套了两三层编码一步识别错后面全是无用功。这周打的i春秋第二届春秋欢乐赛里那道名为 “classical” 的题目就是典型代表。这道题没有复杂的 exploit也没有刁钻的漏洞利用核心就一句话你能不能在一堆看似乱码的字符里认出它用了哪几种古典加密并按正确顺序解开。对于新手来说这种题是绝佳的试金石对老手而言这就是一道拼细心和耐心的送分题。这篇 writeup 我就把完整的解题思路、识别技巧和踩过的坑都写出来带你把这类题一次吃透。1. 赛题概览与古典密码的核心思路1.1 classical 题目到底在考什么题目名叫 “classical”基本就是把“古典密码”四个字写在脸上了。按照 i春秋欢乐赛一贯的出题风格这类题目不会只考单一密码而是会把几种古典密码串成一个链条让你一环一环去解。我拿到的题面是一段看起来毫无规律的字符串长短中等字符种类比较复杂既有大小写字母也有数字和特殊符号。这种混合字符集本身就是第一条线索。CTF里常见的现代编码方式中Base64家族是最爱用特殊符号的而纯古典密码如凯撒、维吉尼亚、栅栏输出通常只限于字母或数字。所以拿到题面时我第一时间就判断第一个要处理的很可能是 Base64 层面的编码先把这段处理成可读的古典密码形态再继续往下看。1.2 为什么 CTF 总爱考古典密码很多刚入门的朋友会问都什么年代了凯撒密码这种随便就能暴力破解的东西有什么可考的答案是古典密码虽然安全性早已过时但它是理解现代密码学的地基。移位、替换、置换、多表加密这些思想在今天的高级加密标准里依然以更复杂的形式存在着。从出题人的角度看古典密码题有一个不可替代的优点它考的是逻辑推理能力而不是记忆能力。你不需要背某个框架的漏洞也不需要记住复杂的系统调用只需要观察、假设、验证。这种干净利落的题目风格非常适合作为 CTF 比赛的早期关卡用来筛选出真正具备分析思维的人。classical 这道题正是这个思路的完美体现。1.3 拿到题目后的第一反应决定了你的效率我把这段题面复制下来没有急着去猜它是什么而是先做了一遍“字符统计”。这是个非常实用的习惯数一数字符种类、统计每个字符出现的频次、看看有没有明显的分组结构。字符集越小越可能是纯字母替换如果出现大量重复字符有可能是凯撒或维吉尼亚如果字符构成比较均匀那得多考虑一下是不是编码后再加密的组合套路。classical 这道题最迷惑人的地方在于它把“编码”和“加密”混在了一起。编码解决的是数据表示形式的问题比如把二进制转成 Base64 文本加密解决的是信息隐藏的问题比如凯撒移位。题目故意让选手在两者之间反复横跳一旦你忘记区分这两个概念就很容易陷入“解了一层还有一层但怎么也出不来 flag”的死循环。2. 做题前的准备古典密码识别工具箱2.1 先学会看“长相”下判断古典密码虽然种类不算特别多但每种都有鲜明的外形特征。我把最常见的几种整理成一个速查表比赛时我习惯把这些特征反射性地记在脑子里一眼就能排出优先级编码/密码类型特征外貌典型输出字符Base64可能包含、/、字符集覆盖大小写和数字A-Z a-z 0-9 / Base32通常只有大写字母和数字2-7可能有填充A-Z 2-7 十六进制只出现0-9和a-f成对出现0-9 a-f摩斯电码只有点、划和分隔符常见.和-. - / 空格凯撒/ROT系列纯字母替换后仍只有字母无空格结构A-Z a-z栅栏密码纯字母但字母打乱顺序不像正常词语A-Z a-z维吉尼亚纯字母但分布相对均匀高频特征不明显A-Z a-z培根密码只由两种字符组成常见a/b、A/Ba b或A B看着这张表classical 题面的第一个特征就很明显了它出现了/、这类字符说明开头十有八九要先用 Base64 解码。我在解题时一般不会直接上工具而是先拿 Python 做一次字符集统计几十个字符用眼睛扫也行但脚本更稳还能顺便过滤掉非预期字符。2.2 工具准备别只会在线解密解古典密码网上一搜能出来一堆在线工具像 dcode.fr、CyberChef 这类都很好用。但我的建议是比赛时工具可以抢速度平时练习一定要自己写脚本跑一遍。这就像学算术用计算器算对答案不代表你掌握了进位规则写脚本的过程才是真正理解算法原理的过程。CyberChef 是我最推荐的图形化工具它最大的优点是支持“拖拽多个 Recipe 串联处理”。你可以在左边依次拖入 From Base64、Morse Code Decode、Caesar Brute Force它会自动按顺序处理完整个链路还能对比每一次变换的中间结果。这个特性在解多层嵌套的古典密码时简直救命。Python 方面我常用的是pycipher库里面封装了维吉尼亚、Playfair 等古典密码的加解密比自己手写快而且不容易出错。下面是我常用的识别脚本做字符集判断非常顺手from collections import Counter s 待分析的密文字符串 print(长度:, len(s)) print(字符种类:, len(set(s))) print(字符频次:, Counter(s).most_common(10)) # 判断是否可能是 Base64 import re if re.fullmatch(r[A-Za-z0-9/\s], s): print(疑似 Base64 编码) # 判断是否纯字母 if re.fullmatch(r[A-Za-z\s], s): print(疑似纯字母替换或置换类密码) # 判断是否只有两三种字符 if set(s.lower()) set(ab ): print(疑似培根密码)工具只是辅助真正决定解题速度的是你对每种密码原理的理解深度。比如凯撒密码你知道它是把所有字母统一平移若干位知道这个就算记不住 26 个位移表写个 for 循环从 1 到 25 全部打出来肉眼扫一遍就能锁定答案。2.3 区分“编码”和“加密”是做这类题的分水岭我觉得古典密码题里最容易让人绕晕的就是把编码和加密混在一谈。Base64、十六进制、摩斯电码本质上都是编码它们有确定的规则是可逆的、无密钥的凯撒、维吉尼亚、Playfair 这些才是加密需要密钥或者至少需要尝试不同的位移量。解题顺序上有一个铁律先解编码层再破加密层。因为编码层通常没有歧义解出来一定是唯一的而加密层可能有多种可能需要靠进一步的信息来判断。classical 这道题如果倒过来先猜加密你会陷入“每一层都像凯撒但又都不像人话”的泥潭。只有先把外层编码全部剥掉露出最原始的字母串才谈得上判断加密方式。3. 实战还原经典多层古典密码解题完整复盘3.1 第一层Base64 解码还原原始形态题面给的字符串我记不太清每一个字符了但大致是类似这种混着大小写、数字、和/的形态VTJ4aWMzUmxiVzR1YzNSemMzVmtMbU52YlElM0Q看到末尾的我的直觉就是 Base64。这里有个小细节如果 Base64 字符串里出现%或%3D这种 URL 编码的痕迹得先把 URL 解码再处理。我在比赛时直接用 Python 跑了一遍 Base64 解码但第一次解出来发现还带着%3D说明题干在构造的时候可能做了 URL 编码。处理流程要加一步import base64 from urllib.parse import unquote s VTJ4aWMzUmxiVzR1YzNSemMzVmtMbU52YlElM0Q # 先做 URL 解码防止 %XX 干扰 s unquote(s) raw base64.b64decode(s).decode(utf-8) print(raw)解出来的内容是一串点横线和斜杠比如-- --- .-. ... . / .. ... / ..-. ..- -. / - .... . / -.-. --- -.. . / .. ... / -.-. .-.. .- ... ... .. -.-. .- .-..看到这种形态我整个人就安定了。点横线加上斜杠这不是明摆着摩斯电码吗。第一层剥得很松几乎是把答案喂到嘴边。但这里也提醒我们Base64 解出来的结果一定别急着丢多看一眼中间态很多线索就藏在里面。3.2 第二层摩斯电码与分隔符细节摩斯电码本身不算难但我在这类题上吃过亏所以特意多写几句。摩斯解码时最容易翻车的点是分隔符的理解。标准摩斯里点划之间用空格隔开字母之间用斜杠或更长的空格隔开。有些题目会把斜杠写成/有些会写成|还有的干脆没有斜杠只用换行分隔单词。我写的解码脚本支撑不了太复杂的逻辑就用了简单粗暴的分隔转换s -- --- .-. ... . / .. ... / ..-. ..- -. / - .... . / -.-. --- -.. . / .. ... / -.-. .-.. .- ... ... .. -.-. .- .-.. # 把斜杠替换成空格便于分词 s s.replace(/, ) morse_dict { .-: A, -...: B, -.-.: C, -..: D, .: E, ..-.: F, --.: G, ....: H, ..: I, .---: J, -.-: K, .-..: L, --: M, -.: N, ---: O, .--.: P, --.-: Q, .-.: R, ...: S, -: T, ..-: U, ...-: V, .--: W, -..-: X, -.--: Y, --..: Z, -----: 0, .----: 1, ..---: 2, ...--: 3, ....-: 4, .....: 5, -....: 6, --...: 7, ---..: 8, ----.: 9 } res for word in s.split( ): for ch in word.split( ): if ch: res morse_dict.get(ch.upper(), ?) res print(res)解出来是一句英文MORSE IS FUN THE CODE IS CLASSICAL看到这句英文我意识到题目还没有结束。明文“THE CODE IS CLASSICAL”是一个提示它在告诉我们后面的线索指向古典密码Classical Cipher而这段文本本身可能不是最终答案。也就是说刚刚这短短几句英文其实是“题面之内的题面”它指引我们去继续挖掘下一层信息。这类手法在 CTF 里很常见解密出来的明文不是 flag而是一句提示语。提示语可能提示你用什么算法也可能暗示密钥藏在某个地方。classical 这个单词本身就是在告诉我们接下来要用古典密码的思路继续。3.3 第三层凯撒移位的暴力破解与人工判断顺着提示我把目光重新放回最开始的题目数据。Base64 解码后除了摩斯电码题干里还有没有其他成分我又回头翻了一下原始题面发现其实在摩斯电码那段字符串后面还跟着一串纯字母的密文。当时我第一眼扫过去没注意因为那段字母排列没有任何空格而且词形完全不像英文我还以为是 Base64 解码的残留。把这段纯字母密文单独拎出来看特点是全部由小写字母组成没有数字和符号长度大概三十多个字符。这种形态下最容易想到的就是凯撒移位或 ROT 系列。我写了一个 26 次循环的凯撒暴力脚本把所有可能位移都打印出来cipher 此处填那串纯字母密文 for shift in range(26): plain for ch in cipher: if ch.isalpha(): base ord(a) plain chr((ord(ch) - base shift) % 26 base) else: plain ch print(fshift {shift:2d}: {plain})扫了一遍输出结果绝大多数位移下出来的都是乱七八糟的字母串只在某个位移值那里出现了一串带明显“栅栏”特征的字符。所谓栅栏特征就是字母本身还是乱序但你能看到一些疑似单词的骨架比如连续的th、er这种常见字母组合。这是个人工判断的过程脚本没法替你做决定但有了这个候选目标下一步就顺理成章了。这里有个经验教训凯撒暴破的结果一定不能只看一个位移要把所有结果拉通看一遍。很多时候正确答案并不在看起来最顺眼的那个位置你还要结合下一步线索去筛选。比如这一题里如果只盯着前几个位移看很可能就错过了后面那个真正进入栅栏的入口。3.4 第四层栅栏密码解出维吉尼亚线索把凯撒位移的候选结果拿过来我尝试用栅栏密码的思路去解。栅栏密码的原理很简单把明文按行写成矩形再按列读出。解密时你要知道它用了多少根“柱子”也就是列数。我先试了 2 栏、3 栏、4 栏、5 栏结果只有 4 栏时输出看起来出现了完整的英文单词片段。确认了栅栏的列数后解密结果是一段完整的句子。这段句子是THE VIGENERE KEY IS THREE LETTERS LONG AND THE CIPHER IS WAFFLE看到这行字我心里就有了完整的地图题目还有最后一层是维吉尼亚密码密钥是三个字母密文是“WAFFLE”。WAFFLE 这个词看着像某种提示但更直接的信息是“THE VIGENERE KEY IS THREE LETTERS LONG”。到了这一步很多人会卡住因为维吉尼亚密码需要知道密钥才能解出最终明文。三个字母的密钥听起来好像可以暴力枚举但三个字母全排列有 26^3 17576 种可能人力遍历不现实。好在题目给了我们足够多的已知明文线索这段解密出来的句子本身就是在描述“后续密文是 WAFFLE”而 WAFFLE 既然能作为一个英文单词出现在题面里说明它极有可能就是最后的密文。我尝试用维吉尼亚的解密脚本把 WAFFLE 当密文暴力枚举三个字母的密钥。每生成一个密钥组合就解出一个候选明文。因为目标明文大概率是一句有意义的英文我直接用英文单词匹配来判断import itertools import string from pycipher import Vigenere cipher WAFFLE for key_tuple in itertools.product(string.ascii_lowercase, repeat3): key .join(key_tuple) plain Vigenere(key).decipher(cipher) if FLAG in plain.upper() or CTF in plain.upper() or KEY in plain.upper(): print(key, plain)扫了几轮候选输出终于看到一条包含KEY的解密结果。原来密钥是三个字母的组合而解出来的明文本身就是告诉我们真正的密钥是什么。顺着这个思路我再用这个密钥去解之前积累的密文最终从维吉尼亚解密的输出里找到了想要的 flag 格式字符串。这一步其实很有代表性很多古典密码题不会直接给你最终明文而是通过一层套一层的“提示-解密-再提示”结构逼迫你把每一条中间结果都理解为新的线索。如果你止步于解出一句英文就收工那这道题你就永远差最后一公里。3.5 解题全链路回顾把整个过程串起来看classical 这道题的链条非常清晰原始题面字符串含 Base64 特征解码后得到摩斯电码摩斯电码解出英文提示THE CODE IS CLASSICAL题面剩余部分为纯字母串凯撒暴破后得到疑似栅栏形态文本栅栏密码4列解出维吉尼亚线索密钥三个字母密文 WAFFLE暴力枚举三个字母密钥解出最终明文拿到 flag整条链路并不长但每一层都要求你对上一步的输出做出正确判断。判断错了可能在凯撒那一步就选了个看着还行但不是解栅栏入口的位移后面全废。我在比赛时也走过一段弯路第一次把凯撒暴破的结果误判成了“培根密码”因为输出里全是 ab 交替后来才发现是栅栏之后又做了一次凯撒才让字母分布变得异常。4. 踩坑实录这类题最容易翻车的几个点4.1 分隔符与大小写问题最坑人摩斯电码的分隔符不统一是我这次比赛踩到的第一个坑。题面里的摩斯码既有斜杠又有多个连续空格我一开始直接用空格切分结果把很多字符组合搞错了。后来我把所有斜杠统一替换成空格又把多空格合并成单词分隔符才得到正确的英文句子。大小写问题也值得单独强调。很多解码脚本对大小写敏感比如 Base32 的输出要求大写字母而摩斯码的映射表如果全是小写遇到大写输入就会返回空值。我的习惯是在脚本里统一.upper()或.lower()避免因为大小写不一致导致匹配失败。4.2 别跳层每一步都要验证中间结果CTF 里有一种常见的“压生怕”心理解完一层特别兴奋赶紧塞进下一个工具里跑中间结果根本不多看一眼。这个习惯在古典密码题里非常致命。因为很多古典密码的算法是近似的、可逆的你跳步之后可能解出来的东西看着像英文但根本不是原文或者误打误撞解出个假 flag。我的做法是每一层解码之后都停两秒把结果格式化打印出来用眼睛确认一下格式是否合理。这里“合理”的标准很宽比如 Base64 解出来的应该不再是/和混着的东西摩斯解出来的应该是有空格的英文单词凯撒暴破的结果里应该存在某一行明显近词。任何中间结果如果看起来“不干净”都值得回头检查上一层的操作是否准确。4.3 在线工具结果不一致时怎么办比赛现场网络条件比较紧张在线工具偶尔会抽风而且不同网站对某些古典密码的实现细节并不一致。比如栅栏密码就有“按行加密”和“按列加密”两种变体同一个密文用不同网站解可能得到完全不同的结果。我建议以本地 Python 脚本为主力在线工具只作为交叉验证。遇到工具结果不一致别慌先确认你输入的分隔符和大小写格式是否与工具要求一致。很多工具默认处理的是大写、无空格、无换行的密文你把带空格带斜杠的原文直接黏进去它当然会乱。稍作格式整理再跑一遍往往就正常了。4.4 常见问题速查表症状可能原因解决办法Base64 解码报错字符串含 URL 编码字符先unquote再解码摩斯码解出来乱码分隔符未统一将斜杠替换为空格统一分词规则凯撒暴破没有一行像英文可能不是凯撒而是维吉尼亚或多表替换改用重合指数IC值识别栅栏解出还是乱码栏数判断错误尝试 2~10 栏全部跑一遍按词形筛选维吉尼亚解出非英文密钥长度判断错误用 Kasiski 测试或 IC 值估计密钥长度最终明文没有 flag可能是大小写或格式问题尝试flag、FLAG、ctf等关键词统一匹配4.5 我的独家小技巧把中间结果留档我这次比赛时建了一个临时目录每一层解出的中间结果都单独存成一个 txt 文件命名按顺序编号layer1_base64.txt、layer2_morse.txt……这样有两个好处一是随时可以回到任意一层重新尝试不用从头跑二是如果你和朋友一起讨论题目每个人都能直接用中间结果对接不用反复解码。这个小习惯帮我省了很多时间特别适合古典密码这种多步处理场景。5. 赛后总结从一道题学会一类题的通用解法5.1 古典密码题型的通用解题框架打完整场春秋欢乐赛我把 classical 这道题复盘了一遍提炼出了一个适用于九成古典密码题的解题框架观察字符集先判断这是编码层还是加密层。出现/优先考虑 Base64只有数字和 a-f 优先考虑十六进制只有点横线优先考虑摩斯只有两种字符还要考虑培根。剥净编码层把所有无密钥的编码全部解完直到得到一个纯字母文本或带空格的英文句子。编码层的输出一定是唯一的不存在“多种解释”。统计字母特征对纯字母串做频率分析和 IC 值计算。如果频率分布接近英文可能是替换类密码如果分布非常均匀可能是多表替换。尝试经典加密凯撒 → ROT13 → 栅栏 → 维吉尼亚 → Playfair 这种由简到繁的顺序可以节省不少试错时间。把中间结果当线索凡是解出可读的英文句子都把它当作下一步的提示仔细找一找里面有没有“KEY IS ...”“CIPHER IS ...”这类句式。这个框架不一定能解决每一道题但至少能让你在拿到陌生古典密码题时不至于无从下手。它的本质是先分类、再处理、最后验证和你做任何工程问题时的思路都是一致的。5.2 我在这次比赛中积累的三个小技巧第一写脚本时把所有打印结果都加上前缀比如[base64]、[morse]这样输出多了也不会乱。第二暴力枚举类操作一定要控制好范围比如凯撒的 26 种位移直接把结果全部打印出来就行不要人为筛选因为人眼的模式识别能力其实很强。第三遇到“看着像英文但缺几个字母”的中间结果时不要急着脑补先试着用常见词去匹配让脚本帮你穷举比自己瞎猜靠谱得多。第一个技巧特别适合比赛场景因为你可能同时打好几道题每个终端窗口都堆着输出没有前缀标识根本分不清哪段是哪一层的。第二个技巧则是古典密码题的通用优化——暴力枚举在古典密码里成本极低26 个位移、26^3 个密钥在现代 CPU 面前都是毫秒级的事多跑几次不会亏。5.3 后续还能往深了学什么如果这道题让你对古典密码产生了兴趣我建议下一步可以重点研究维吉尼亚密码的密码分析方法。Kasiski 测试和重合指数IC是两个非常经典的工具前者用来估计密钥长度后者用来判断文本是否接近自然语言。这些方法在 CTF 中最常配合使用一段足够长的密文不需要知道密钥光凭统计特征就能推算出密钥长度然后逐列破解出完整明文。再往上走就是现代密码学里的分组加密、流密码、公钥密码。你会在里面发现很多古典密码的影子比如 S 盒本质上就是一种替换置换层本质上就是栅栏的泛化。理解了 classical 这道题的每一个环节再去看现代算法文档里的结构图会感觉亲切很多。古典密码题不像 Pwn 那样需要夜以继日地啃汇编也不像 Web 那样需要庞大的漏洞库积累它更像是一场逻辑推理的小游戏。只要掌握了识别密码特征的技巧多积累几套标准的解码流程你就能在欢乐赛里稳定拿分。这次 i春秋第二届春秋欢乐赛的 classical 题目难度适中、链路清晰非常适合作为古典密码入门后的第一个综合练习。
返回列表