原题:玄机靶场 0x401-Broken Voice 题型:MISC(音频取证 + 文件还原) 隐藏链:zip → 按字节拆碎 → 每字节一个文件 → 文件名=偏移 → 拼接还原 → MP3 → 语音念 flag 最终 flag:
flag{12398093888892380234800981239082}
0. 整体思路
题目名字 “Broken Voice”(坏掉的声音)+ 一个 zip,第一反应:zip 里藏着音频,且大概率被拆碎了(“broken” 暗示损坏/拆分)。
1. 识别文件类型(不用解压就能看出来)
hexdump 开头:
50 4b 03 04 14 00 ...50 4b 03 04 = ASCII PK\x03\x04 = zip 的 Local File Header 签名。
常见文件头魔数速查
| 魔数 | 类型 |
|---|---|
50 4b 03 04 | zip |
ff d8 | jpg |
89 50 4e 47 | png |
RIFF....WAVE | wav |
ff fb / ff f3 / ff e0~ff ff | mp3 帧同步 |
ID3 | mp3(带标签) |
OggS | ogg |
2. hexdump 文本还原成二进制
hexdump -C 是文本,不能直接用。用正则抠出每行的十六进制字节:
import redata = []for line in open("Broken+Voice.zip.hexdump"): m = re.match(r"^[0-9a-f]{8}\s+(.*?)\s*\|", line) if m: data += [int(b, 16) for b in re.findall(r"[0-9a-f]{2}", m.group(1))]open("broken.zip", "wb").write(bytes(data))💡 也可以用
xxd -r直接还原,但 Python 更可控(能顺便统计行数、校验大小)。
3. 分析 zip 内容——发现”拆碎”模式
import zipfilezf = zipfile.ZipFile("broken.zip")infos = zf.infolist()# → 35712 个文件,文件名全是纯数字:0, 1, 10, 100, 1000, 10000...# 每个文件 comp_size=1, uncomp_size=1, method=0 (stored 不压缩)关键观察
- 35712 个文件 × 每文件 1 字节
- 文件名 0~35711 连续无缺失(用
set(range(0, 35712)) - set(names)验证为空) - 文件名 = 字节在原始文件中的偏移量
这就是典型的”字节切片”混淆:把一个大文件切成单字节,用文件名记录位置。遇到”大量规律命名的小文件”,直接想到这招。
4. 按文件名排序拼接还原
infos.sort(key=lambda i: int(i.filename)) # 关键:按数值排序,不是字典序!buf = b"".join(zf.read(i) for i in infos)open("broken.bin", "wb").write(buf) # → 35712 字节⚠️ 坑:文件名是数字时必须按 int 排序。按字符串排序会变成 0, 1, 10, 100, 1000… 顺序全乱!
5. 判断还原出来的文件类型
open("broken.bin","rb").read(4).hex() # → fff348c4开头 ff f3 → MP3 帧同步字(0xFF + 高 3 位掩码 0xE0 匹配)。再用 ffprobe 确认参数:
ffprobe broken.bin# → MPEG2 Layer III, 32000 bps, 16000 Hz, mono, duration 8.93s自洽性验证
32kbps / 16kHz 的 MPEG2 L3:每帧 144 字节、0.036 秒 → 248 帧 × 144 = 35712 字节 = 文件大小分毫不差
证明还原正确、无多余数据。顺带发现尾部藏 LAME3.100 编码器标记,属于正常 MP3 结构。
6. 音频常规检查(排除隐写)
# 转 WAVffmpeg -i broken.bin broken.wav
# 频谱图——看有没有藏文字/图案/摩斯码ffmpeg -i broken.wav -lavfi showspectrumpic=s=1200x600 spec.png
# 波形图——看能量分布ffmpeg -i broken.wav -lavfi showwavespic=s=1200x300 wave.png频谱图显示正常语音特征,无隐写图案 → flag 在语音内容本身。
💡 音频隐写常见检查点:频谱图(图案/文字)、倒放、变速、摩斯、DTMF、SSTV、LSB、DeepSound/MP3Stego。
7. 语音识别(关键一步)
本机没装 ASR,安装 faster-whisper:
pip install faster-whisper
# 国内下载模型必须挂镜像,否则卡死/报 401 错误:export HF_ENDPOINT=https://hf-mirror.comexport HF_HUB_DISABLE_XET=1 # 新版 HF 的 xet 存储在国内会 401from faster_whisper import WhisperModelm = WhisperModel("small", device="cpu", compute_type="int8")segs, info = m.transcribe("broken.wav", language="zh")for s in segs: print(s.text)# → "Flag 是 Flag 123980 9388889 2380 234800 981239082"方向验证
同时识别倒放版本:
ffmpeg -i broken.wav -af areverse broken_rev.wav倒放版本被识别成俄语乱码 → 正放才是正确方向。
数字验证
用 base + small 两个模型交叉识别,数字串完全一致 → 可信。
最终 flag:flag{12398093888892380234800981239082}
8. 工具清单
| 工具 | 用途 |
|---|---|
| Python + regex | hexdump → 二进制 |
| Python zipfile | zip 结构分析、拼接 |
file / 魔数 / ffprobe | 文件类型识别 |
| ffmpeg | 转 wav、频谱图、波形图、倒放 |
| faster-whisper | 语音识别 |
9. 这类题的通用套路
| 特征 | 处理 |
|---|---|
| zip 里大量规律命名小文件 | 文件名=偏移,排序拼接 |
| 文件名是数字 | 按 int 排序,别用字典序 |
| 拼出来的文件不知道类型 | 看魔数 / file / 帧同步字 |
| 音频是”语音” | 先频谱图查隐写,再 ASR 听内容 |
| 语音听不清/方向可疑 | 试倒放、变速(areverse / atempo) |
| 数字串易识别错 | 换模型交叉验证 |