PIZZA-CTF 赛后分析报告,涵盖了多个关卡和附加题。每个关卡都有其独特的出题原理和解题思路,涉及信号处理、隐写术、编码与密码学等领域。难度梯度从易到难,需要参赛者综合运用多种技能。最后,附加题中还包括了LSB隐写和星点图解码等挑战。
PIZZA-CTF 赛后分析&复盘
原帖子链接:https://www.nodeseek.com/post-625709-1
运用到的关卡(在对应level运用到的)
不排序
一、正式关卡(levels/)
Level 1:SSTV 慢扫描电视 ★☆☆☆☆
分发文件: level1_sstv.wav
出题原理:
SSTV(Slow Scan Television)是业余无线电爱好者用来通过音频信道传输静态图像的技术。本关使用 Robot36 格式——一种将彩色图像编码为音频信号的协议。图像的每一行被转换为一系列频率在 1500–2300Hz 之间变化的音调,其中频率的高低对应像素的亮度。
生成过程:
创建一张 320×256 的图片,上面写有密钥文字
将图片转为 YCrCb 色彩空间
按 Robot36 规范逐行扫描:先发送 VIS 码标识格式,然后每行由同步脉冲(1200Hz)+ 亮度扫描(88ms)+ 色度扫描(44ms)组成
使用连续相位正弦波生成,避免频率切换时的爆音
解题思路:
直接用手机 APP "Robot36" 对着扬声器播放即可解码出图片
也可使用 PC 端的 RX-SSTV 或 MMSSTV
图片上直接显示密钥
Level 2:摩尔斯电码 ★★☆☆☆
分发文件: level2_morse.wav
出题原理:
最经典的电子通信编码。每个字母/数字对应一组"点"(短音)和"划"(长音)的组合,不同符号之间有固定的时间间隔规则:
点(dit):1 单位时长
划(dah):3 单位时长
符号内间隔:1 单位
字符间间隔:3 单位
单词间间隔:7 单位
生成过程:
以 12 WPM(每分钟 12 个单词)的速度,将密钥每个字符查表转为点划序列,再用 800Hz 正弦波生成音调,加上平滑包络避免爆音。
解题思路:
耳听:短音为点(·),长音为划(—),对照摩尔斯码表逐字解码
使用在线摩尔斯解码器直接上传音频
Audacity 打开也能从波形直观看出点划模式
Level 3:音频频谱隐写 ★★★☆☆
分发文件: level3_spectrogram.wav
出题原理:
将一张黑白文字图片"画"进音频的频谱图中。图片的每一列对应一个时间片段,每一行对应一个频率。像素越亮,该频率的振幅就越大。直接听只能听到噪音,但用频谱分析工具查看就能看到隐藏的文字。
生成过程:
创建一张 600×128 的黑白图片,白色文字写着密钥
遍历图片每个像素,对于亮像素,在对应的时间位置叠加对应频率(800–8000Hz)的正弦波
像素亮度控制振幅大小
最终归一化并保存为 WAV
解题思路:
用 Audacity 打开 → 点击音轨名称 → 选择"频谱图"视图 → 密钥直接显示在频谱中
也可使用 Spek 或 Sonic Visualiser
Level 4:LSB 图片隐写 ★★★☆☆
分发文件: level4_image.png
出题原理:
LSB(Least Significant Bit,最低有效位)隐写是最经典的图片隐写术。由于人眼无法分辨像素值相差 1 的颜色(例如 RGB(100,200,50) 与 RGB(101,200,51)),可以利用每个颜色通道的最低位来存储秘密信息。
生成过程:
创建一张 640×480 的渐变装饰图作为封面
将密钥转为二进制串,末尾加 NULL 终止符(8 个 0)
依次修改每个像素的 R、G、B 通道最低位为消息比特
必须保存为 PNG(无损格式),JPEG 压缩会破坏最低位
解题思路:
使用
zsteg工具一键提取:zsteg level4_image.png或用 stegsolve 逐通道查看
或写几行 Python:提取所有像素 RGB 最低位,每 8 位组合成一个 ASCII 字符
Level 5:DTMF 电话拨号音 ★★★☆☆
分发文件: level5_dtmf.wav
出题原理:
DTMF(Dual-Tone Multi-Frequency,双音多频)是电话拨号使用的信号编码。每按一个按键,电话会同时发出两个特定频率的音调——一个来自低频组(697/770/852/941 Hz),一个来自高频组(1209/1336/1477/1633 Hz)。两个频率的组合唯一确定一个按键。
生成过程:
将密钥的每个数字查 DTMF 频率表,生成 0.25 秒的双频叠加音,按键之间留 0.15 秒间隔,加包络平滑处理。
解题思路:
使用在线 DTMF decoder 上传音频
或用 Audacity 频谱分析,手动识别每段音频的两个峰值频率,查表得到对应数字
验证脚本使用 Goertzel 算法(比完整 FFT 更高效地检测特定频率)
Level 6:多重编码 ★★★★☆
分发文件: level6_cipher.png、level6_ciphertext.txt
出题原理:
三层套娃编码,玩家需要像剥洋葱一样逐层解开:
玩家看到的是一串十六进制字符串,需要反向操作:
生成过程:
对密钥先做 ROT13(字母替换,A↔N、B↔O...)
结果做 Base64 编码
再将 Base64 字符串转为十六进制
同时生成一张"终端风格"的密文图片和纯文本文件
解题思路:
观察密文全是十六进制字符 → 尝试 Hex 解码 → 得到看起来像 Base64 的字符串(有
=填充)→ Base64 解码 → 结果仍然是乱码字母 → 尝试 ROT13 → 得到明文CyberChef 一站式解决:拖入 From Hex + From Base64 + ROT13 三个操作
Level 7:二维码碎片 + 频谱隐写(终极关)★★★★★
分发文件: level7_piece1~4.png、level7_mystery.wav、level7_puzzle_map.png
出题原理:
综合前面所学的技能。一张完整的二维码被切成 5 块:4 块直接给玩家作为图片碎片,第 5 块(中心部分)被编码到音频的频谱中——需要用第 3 关学到的频谱分析技能提取。
生成过程:
用高容错(Error Correct H)生成 300×300 的二维码
切成 5 块(四角 + 中心),四角碎片带少量重叠确保定位点完整
将中心碎片作为灰度图编码进音频频谱(复用 Level 3 的逻辑)
生成拼图指引图标注各碎片位置
解题思路:
用 Audacity 打开
mystery.wav,切换频谱视图,截图得到第 5 块碎片参考
puzzle_map.png的布局,用图片编辑器将 5 块碎片拼成完整二维码扫描二维码得到最终密钥
二维码使用了高容错级别,即使拼接不够精确也能容忍约 30% 的误差
二、附加题(new/)
Bonus 1:零宽字符隐写 ★★★★☆
分发文件: bonus_message.txt(可选:bonus_message_clean.txt 作对比)
出题原理:
Unicode 中存在一类"零宽字符"——它们在屏幕上完全不可见,但确实存在于文本中。本题利用三种零宽字符构建一套二进制编码:
字符 | Unicode | 含义 |
|---|---|---|
零宽空格 | U+200B | 二进制 0 |
零宽非连接符 | U+200C | 二进制 1 |
零宽连接符 | U+200D | 字符分隔符 |
每个 ASCII 字符被编码为 8 个零宽字符(8 位二进制),字符之间用 U+200D 分隔。这些不可见字符被均匀嵌入到一段正常的英文文本中。
关键线索:
文件"看起来"只有一段普通的英文短文(约 200 字节),但实际文件大小超过 1000 字节——多出来的全是零宽字符(每个零宽字符占 3 字节 UTF-8)。
解题思路:
对比文件大小与可见文本长度,发现异常
用十六进制编辑器打开,发现大量
E2 80 8B/E2 80 8C/E2 80 8D字节序列识别为零宽 Unicode 字符
提取所有零宽字符,按 U+200D 分割,将 U+200B 视为 0、U+200C 视为 1,每 8 位转一个 ASCII 字符
Bonus 2:敲击密码 ★★★☆☆
分发文件: bonus2_prison_log.txt
出题原理:
Tap Code(敲击密码)是战俘和囚犯之间通过墙壁敲击进行秘密通信的经典方式。它基于 5×5 的 Polybius 方阵:
每个字母用两组敲击表示:第一组 = 行号,第二组 = 列号。例如 "H" 在第 2 行第 3 列,编码为"敲 2 下,停顿,敲 3 下"。注意字母 K 与 C 共用位置(5×5 只能放 25 个字母)。
伪装形式:
谜题文件被包装成一份"监狱监控日志",记录了 Cell B-7 和 B-8 之间的墙壁敲击模式,带有时间戳和环境噪音记录(管道声、脚步声等)增加真实感。
解题思路:
阅读日志,注意到结构化的敲击模式:
●●● ●●●●或(... ....)搜索 "Tap Code" 或 "Prison Knock Code"
构建 5×5 方阵,将每组敲击的第一个数字作为行、第二个作为列
注意结果中的 C 可能实际是 K(需结合语义判断)
Bonus 3:DNA 碱基编码 ★★★★☆
分发文件: bonus3_lab_report.txt、bonus3_sequence.fasta
出题原理:
DNA 有四种碱基(A、T、C、G),恰好可以用 2 位二进制表示:
碱基 | 二进制 |
|---|---|
A (Adenine) | 00 |
T (Thymine) | 01 |
C (Cytosine) | 10 |
G (Guanine) | 11 |
因此每 4 个碱基 = 8 位 = 1 个 ASCII 字符。例如字母 'H'(0x48 = 01001000)编码为 TACA。
伪装形式:
谜题被包装成一份 XenoTech BioLabs 的"机密基因序列分析报告",包含三段 FASTA 格式的 DNA 片段:Fragment Alpha 和 Gamma 是随机生成的干扰序列,Fragment Beta 是编码了密钥的目标序列,并被标注为"异常/非生物来源"。
关键线索:
报告明确指出 Fragment Beta "不匹配任何已知生物"
分析备注提到"序列长度恰好能被 4 整除"和"4-base periodicity"
Beta 的碱基分布偏离自然序列(自然 DNA 四种碱基大致均匀,而编码后 ASCII 可打印字符集中在 0x41-0x7A 区间,导致 T 偏多)
解题思路:
锁定 Fragment Beta 为目标
意识到 4 种碱基 = 2 位二进制
每 4 个碱基一组,转为 8 位二进制,再转 ASCII
Bonus 4:盲文 Unicode 编码 ★★★★☆
分发文件: bonus4_accessibility_report.txt、bonus4_braille_raw.txt
出题原理:
Unicode 盲文模式块(U+2800 ~ U+28FF)恰好包含 256 个字符,与一个字节的取值范围完全对应。每个盲文字符的码点偏移量(相对于 U+2800)就是 8 个点位的位图。
这意味着:任意一个字节都可以直接映射为一个盲文字符:
例如 'B'(0x42 = 66)→ chr(0x2842) = ⡂
伪装形式:
谜题被包装成一份"无障碍合规测试报告"。文档中包含标准英文盲文的 Grade 1 测试(干扰项,解码后是正常英语)和一段"异常序列"(目标,解码后不是标准盲文)。
关键线索:
报告直接列出了异常序列每个字符的 Unicode 码点
分析师备注:"256 = 2^8 = one byte. Coincidence?"
异常序列用标准盲文解码后是乱码,而正常测试段可以正确解码
解题思路:
尝试用标准盲文解码异常序列 → 失败(不是真正的盲文)
查看码点:全部在 U+2800~U+28FF 范围
意识到 256 个字符 = 一个字节
每个码点减去 0x2800 即得 ASCII 值
三、知识体系总结
整个谜题集覆盖了 CTF(Capture The Flag)竞赛中 Misc 方向的核心知识领域:
信号处理类:
Level 1 (SSTV):无线电图像传输协议
Level 2 (Morse):经典电报编码
Level 5 (DTMF):电话信令系统
隐写术(Steganography):
Level 3 (Spectrogram):音频频谱隐写
Level 4 (LSB):图片最低有效位隐写
Bonus 1 (Zero-Width):Unicode 零宽字符隐写
编码与密码学:
Level 6 (Multi-Cipher):多层编码(Hex/Base64/ROT13)
Bonus 2 (Tap Code):Polybius 方阵替换密码
Bonus 3 (DNA):自定义二进制编码方案
Bonus 4 (Braille):Unicode 码点映射
综合应用:
Level 7 (QR Fragments):融合频谱隐写 + 图像拼接 + 二维码扫描
难度梯度:
关卡按从易到难排列。Level 1–2 是入门级,只需找到正确的工具;Level 3–5 需要理解编码原理;Level 6 需要逐层分析;Level 7 和附加题需要综合运用多种技能。
后续还添加了sidequest线 那首乌托邦里面藏的是lsb隐写得到密文 图片的星点图是以红点X坐标排序 Y为ASCII码 解出后得到密钥 最后就是答案
最后恭喜解开谜题的参赛者
我们明年有缘再见

再玩一次?https://101121.notion.site/trythispuzzlepizza
我们将在本服务器停止后公开谜题制作脚本&校验脚本