做CTF的Misc题,最怕的不是题目难,而是你盯着图看半天,什么都看不出来,然后气得想砸电脑。这道来自BUUCTF平台的“[SWPU2019]伟大的侦探”就是这样一道折腾了我一晚上的题。题目名叫“伟大的侦探”,附件是一张和福尔摩斯相关的图片,整个题不涉及什么高深算法,也不需要爆破,纯粹考你能不能像侦探一样发现那些藏在眼皮底下的细节。
这道题适合刚接触Misc杂项、想系统学习图片隐写常见套路的选手。做完之后你的收获不会只是“拿到一个flag”,而是会记住一类非常关键的隐写手法——通过改变图像的显示比例来暴露隐藏信息,顺带把摩斯电码的识别和解密也练熟了。下面我直接把完整的破题过程写出来,包括中间踩过的坑。
1. 附件到手先别急:文件识别与信息收集的先后顺序
拿到题目的第一步不是急着上Stegsolve翻通道,而是先把附件本身摸清楚。很多新手容易犯的毛病是看到是图片就一头扎进隐写工具里翻半天,结果线索其实在文件结构里早就明晃晃地摆着了。我习惯的流程是固定的,这套流程对绝大多数Misc图像题都适用。
先看文件类型。题目附件下载下来,先用file命令认一下:
bash复制file 伟大的侦探.png
输出是PNG image data, 874 x 1181, 8-bit/color RGB, non-interlaced,看着是一张正常的PNG图,尺寸还不小。注意这个尺寸信息,后面会用到。然后看一下文件大小,如果一张874×1181的PNG只有几百KB,属于正常范围;如果特别小或者特别大,都要留个心眼。
接着跑字符串提取:
bash复制strings 伟大的侦探.png | head -50
大部分PNG的strings输出都是些IDAT、IHDR之类的块标识,偶尔会混入一些额外信息。我习惯把strings的输出完整看一遍,尤其是找有没有flag、zip、rar、http、base64特征字符串。这道题第一轮strings下来什么都没有,干净的有点反常,但我没多想,直接进入下一步。
然后是二进制层面扫描,这里用到的是binwalk:
bash复制binwalk 伟大的侦探.png
第一次扫描的结果让我有点意外,文件尾部疑似存在一个ZIP压缩包结构,但binwalk扫出来的特征不算强烈,显示的是一个比较小的偏移量。当时我的第一反应是“是不是误报”,因为很多图片的IDAT数据块里也可能出现类似ZIP头的字节序列。我把这个疑似点记在笔记里,没急着分离,想着先看看图片本身有没有更明显的线索。
信息收集阶段还有一个容易被忽略的动作——看十六进制头尾。用010 Editor打开图片,或者直接xxd看一眼:
bash复制xxd 伟大的侦探.png | head -5
xxd 伟大的侦探.png | tail -5
PNG的文件头89 50 4E 47和文件尾IEND都正常。如果图片尾部直接跟了其他文件的内容,这里一眼就能看出来。当时看到尾部没有明显异物,我就把主要精力放回了图片本身上。
信息收集阶段需要记住的结论:常规手段没有直接暴露flag,文件结构还算正常,binwalk可能存在一个疑似压缩包特征。这几条结论会成为后面破题的暗线。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 图片隐写初查:Stegsolve、zsteg、LSB都试过却没收获
信息收集做完,进入图片隐写的常规检查环节。很多人喜欢一上来就用Stegsolve点来点去,其实我建议用zsteg先做一发全自动扫描,效率高得多:
bash复制zsteg 伟大的侦探.png
zsteg是一款专门检测PNG和BMP图片隐写内容的工具,可以自动测试LSB替换、LSB匹配、Extractable数据、不同通道组合下的隐藏信息。跑完一圈,输出没有任何有意义的字符串,说明常规的LSB隐写这条路基本可以排除掉。
接着打开Stegsolve,把图片加载进去,逐个切换通道。Stegsolve的界面右下角可以浏览RGB的各个通道,也可以看灰度图、反色图、不同颜色通道的组合。我习惯从Red plane 0开始一路看到Blue plane 7,然后看灰度、反色、甚至把所有通道异或一遍。点了几十个视图之后,肉眼没有发现任何异常图案、文字轮廓或者明显的噪声块。
其实做到这里,大部分人可能就卡住了,因为常规三板斧——strings、binwalk、LSB检测——全都没出东西。我当时也一度怀疑是不是题目解法在别的方向,比如音频隐写或者别的格式,但重新确认了附件确实只有一张图。于是静下心来重新审视这张图本身。
这里分享一个经验:当所有自动化工具都失效时,重新回到“看这张图到底画了什么”这个最朴素的思路上来。题目叫“伟大的侦探”,图里是一位戴着猎鹿帽、叼着烟斗的经典侦探形象。这种命名方式往往暗示着题目需要你用侦探的观察力去发现细节,而不是靠工具一把梭。我开始尝试改变图片的显示方式,也就是从“看内容”切换到“看结构”。
3. 换了视角才看见:压缩图片尺寸后浮现的摩斯电码
触发点是一次偶然的尝试。我想看看这张图如果被强行拉伸或者压缩,会不会暴露出横向的条纹信息。这类手法的原理说穿了很简单:有些隐写信息会被藏在图片的某一组像素行或像素列中,当图像以原始尺寸显示时,这些信息因为混在正常画面里,肉眼几乎察觉不到;但如果把图片的高度压缩或宽度拉长,这些行与行之间的颜色差异会被放大成肉眼可辨的条带状图案。
用Python的Pillow库可以很方便地做这个处理。我写了一个小脚本,先把图片宽度固定,高度压缩到原来的约五分之一,然后保存成新图看效果:
python复制from PIL import Image
img = Image.open("伟大的侦探.png")
print("原始尺寸:", img.size)
# 第一种尝试:固定宽度,压缩高度
new_width = img.size[0]
new_height = int(img.size[1] / 5)
img_resized = img.resize((new_width, new_height), Image.LANCZOS)
img_resized.save("compressed_height.png")
生成的新图打开一看,图的内容缩成一团,但在图片的中下方,隐约出现了一组黑白相间的横向条纹。条纹宽度不均匀,有的细、有的粗,排列方式很有规律,像极了摩斯电码里的“点”和“划”。我当时心里咯噔一下,就觉得这题要突破了。
为了确认不是压缩算法带来的伪影,我又把图片的显示比例换了几种方式验证。用系统自带的画图工具把图片高度拉伸到原来的三倍再看,条纹依然存在,而且宽度比例关系保持不变。这就排除了resize插值算法导致的幻觉,确认是图片里真实存在的信息。
顺带说一句,这种“压缩高度看横条、压缩宽度看竖条”的方法在Misc里很常用,不只是这道题,很多题目都会把一串黑白条纹藏在图片的特定行或列里。遇到画面纹理复杂的图,可以试着把图片缩小到很小的尺寸,隐藏的规律性图案往往就会显形。
4. 摩斯码识别与解码踩坑纪实:长度比例、分隔符和方向问题
看到条纹之后,下一个问题就是怎么把条纹准确翻译成摩斯电码。这里有一个关键细节:条纹本身没有告诉你哪边是起点、哪边是一组、宽条纹到底算“划”还是“点”。我当时先用眼睛硬数了一遍,记录下来的结果是类似“长长短 长短 长短长短短”这种描述,然后在在线摩斯解码网站上一顿试,试出来的全是乱码,一度怀疑是不是记录错了方向。
后来我放弃肉眼数数,改用脚本来自动解析。思路是这样的:把已经压缩好的图片按像素行灰度值扫描,遇到一条黑色条纹就记一段宽度,遇到白色间隔也记一段宽度,然后根据宽度比例自动映射成短音和长音。脚本如下:
python复制from PIL import Image
img = Image.open("compressed_height.png").convert("L")
width, height = img.size
# 取图片中间一行作为采样线,避免内容干扰
row = height // 2
pixels = [img.getpixel((x, row)) for x in range(width)]
# 二值化,并切分出连续的黑/白区间
threshold = 128
segments = []
current_val = None
current_len = 0
for p in pixels:
v = 1 if p < threshold else 0 # 黑色为1
if current_val is None:
current_val = v
current_len = 1
elif v == current_val:
current_len += 1
else:
segments.append((current_val, current_len))
current_val = v
current_len = 1
if current_val is not None:
segments.append((current_val, current_len))
# 打印所有连续区间的长度
for idx, (val, length) in enumerate(segments):
print(idx, "黑" if val == 1 else "白", length)
跑出来的结果是一串数字列表,黑色条纹和白色间隔的宽度交替出现。我统计了一下,最短的黑色区间大约5个像素,最长的黑色区间20个像素左右,白色间隔也有明显长短差异。这样就能把黑色区间统一映射为“点”或者“划”:接近最短的黑区记为.,接近最长的黑区记为-,宽度居中的则结合上下文判断。
为什么不能用白色间隔来分隔?因为白色间隔同样有长短差异,长间隔通常是单词与单词之间的分隔,短间隔是同一个字母内点和划之间的分隔。当时我在这一步栽了跟头:把所有白色间隔都当成同一种分隔符,导致解码结果完全错乱。正确做法是先看黑色条纹的宽度差,把短黑条记为点、长黑条记为划,然后看白色间隔的宽度差异,短间隔当作同一字母内的分隔,长间隔当作字母之间的分隔。
把映射关系套进去之后,我得到了这样一段摩斯码:
code复制- . . --. .-. .- .--. ....
在线工具解码出来是“telegraph”。看到这个单词的那一刻,基本上就可以确定它是一把钥匙——要么是压缩包密码,要么是后续某个步骤的线索。结合最开始binwalk疑似扫到ZIP结构,我立刻想到了用这个密码去解那个潜在的压缩包内容。
这里记录一下摩斯码解码的几个坑,免得你们也踩:
- 方向和起点不确定时,先按默认规则解一遍,如果是乱码,再尝试反着读、从最后一个区间往前读,或者把点和划的含义互换。这几种情况在题目中都很常见。
- 分隔符不只是空格,有些题会使用
/,甚至直接给一串无空格的连续点和划。遇到无空格的输出,优先检查是不是某种替换密码,而不是硬按固定长度分组。 - 在线摩斯解码工具大多数都支持标准的点和划,但如果复制粘贴时混入了全角字符,解码会直接失败。我后来直接把脚本输出的点划字符串粘贴到一个简洁的在线工具里,才得到了干净的结果。
5. 压缩包解压与flag提取:回到最初的疑似点重新挖
现在手里有一张图和一个密码“telegraph”,下一步自然是回头去处理那个binwalk扫出来的疑似ZIP特征。第一次扫描时我把这块当成误报忽略掉了,但现在有了密码,这个疑似点的价值就完全不同了。
重新用binwalk做一次更深入的分离操作:
bash复制binwalk -e 伟大的侦探.png
这次binwalk成功分离出了一个ZIP压缩包。分离出来的文件位于_伟大的侦探.png.extracted目录下,里面是一个加密的压缩文件。第一次扫描时为什么没处理?其实binwalk已经给出了提示,只是我当时扫了一眼偏移量没有仔细跟进,这是Misc做题初期很典型的失误——工具给了线索,人没当回事。
用unzip试着解压,提示需要密码:
bash复制unzip secret.zip
输入刚才解出来的telegraph,压缩包顺利解开,里面躺着一个flag.txt文件。查看内容:
bash复制cat flag.txt
拿到flag之后提交到平台,题目完成。
这道题的完整链路其实是:观察图片→压缩高度→发现摩斯电码→解码得到密码→用密码解开隐藏的压缩包→获得flag。每一步单独拎出来都不难,难的是在没有任何提示的情况下,把“压缩高度”这个动作和“摩斯电码”这个解码方向联系起来。这也是为什么题目要叫“伟大的侦探”的原因——破局点不是技术,而是观察角度。
6. 复盘方法论:这一类“侦探题”的通用破题思路
做完这道题,我最大的感触是Misc题目考得比较多的不是工具量,而是“你愿不愿意换一个视角看同一个文件”。把这类题的通用思路整理成一套可复用的检查顺序,以后再遇到类似题目能少走很多弯路。
拿到一个Misc图像附件,我建议按这个顺序排查:
- 文件识别:
file确认格式,strings看字符串,binwalk看结构,xxd看头尾。这一步能解决大约三成题目。 - 显示比例变换:把图片缩小、拉伸、压缩宽度或高度,用画图软件或者Pillow都行。这一步专门针对隐藏在特定像素行/列中的条纹型信息。
- LSB和通道隐写:
zsteg全自动扫一遍,再用Stegsolve手工逐通道翻,重点看有没有规律性噪声或字符轮廓。 - 附加文件分离:如果binwalk扫到ZIP、RAR或者其它文件结构,用
binwalk -e或者foremost分离出来,尤其是当你已经拿到了可能的密码线索时。 - 编码与密码识别:分离出的内容如果是密文,优先考虑BASE64、摩斯电码、培根密码、栅栏密码、维吉尼亚密码等常见编码,识别特征比盲目试错更重要。摩斯电码的特征就是只由点和划组成,且存在明显长度差异。
工具整理成表的话大概是这样:
| 阶段 | 常用工具 | 核心目的 |
|---|---|---|
| 文件识别 | file, strings, binwalk, xxd | 确认类型、发现附加结构 |
| 图像视觉变换 | Pillow, 画图, ACDSee | 暴露隐藏的条纹或规律图案 |
| 通道隐写检测 | zsteg, Stegsolve | 发现LSB等像素级隐写 |
| 附加文件分离 | binwalk -e, foremost | 提取图片中附加的压缩包 |
| 编码解码 | 在线摩斯工具, CyberChef, Python脚本 | 将密文转成明文或密码 |
这道题的另一个启示是:不要轻视题目名称给的暗示。“伟大的侦探”这个名字本身就在引导你往“观察细节”的方向想。做CTF题目时,题目名字、描述、图片内容里的道具细节,往往都是出题人故意留下的路标。我见过不少选手做题时完全不看题目名和图片内容,上来就工具砸,结果把简单题做成难题。
最后再说一个解题习惯的问题。这个题如果一开始binwalk扫描的结果被我认真对待,理论上可以在更早的阶段锁定压缩包的存在,但那样的话摩斯电码的线索链条还是得走一遍,密码仍然必不可少。所以整个流程中最重要的不是某一个工具,而是“发现线索→保留线索→串联线索”这条思维线。我后来做Misc题都会开一个记事本,随时把可疑的点记下来,哪怕当时觉得没用,也先留着。这道题里binwalk的疑似ZIP就是个典型例子,前期觉得是误报,后期成了关键突破口。
这种“先记录、后验证”的习惯,比任何工具都值得培养。
