CTF入门的时候,十个人里有八个都是从Misc开始上手的,因为Misc杂项不需要太深的底层基础,却需要你像侦探一样把藏在图片里、音频里、甚至流量包里的Flag翻出来。这个系列写到第三篇,我决定把最经典、也最容易出成绩的两类隐写放在一起讲:图片隐写和音频隐写。看完这一篇,你至少能解决BUUCTF和各大CTF题库里六七成的Misc签到题,也知道拿到一个未知文件后该按什么顺序做检查,而不是对着图片瞎点。
1. 隐写题到底在考什么:图片与音频的出题套路
1.1 Misc为什么总让新手又爱又恨
Misc中文叫杂项,但比赛里它一点也不“杂”,反而是最能反映一个人信息检索能力和细心程度的方向。图片隐写和音频隐写就是其中最典型的两座山。很多新手第一次接触CTF,拿到一张看起来完全正常的PNG图片,用记事本打开发现一堆乱码,于是开始怀疑人生。其实隐写题的核心思路非常简单:出题人把Flag藏在某个载体里,但表面上看不出来,你需要借助工具、代码、甚至像素级别的差异把它找出来。
为什么说又爱又恨?爱是因为Misc通常不需要写复杂的算法,很多题目的解法就是一条命令或者一个点击。恨的是有些题目会连续套好几层,比如先隐藏压缩包,压缩包里再藏一个音频,音频里又藏了一段摩斯码,最后摩斯码转出来才是Flag。这种套娃式设计是最常见的坑。你如果只会一个工具,遇到套娃就会卡住。所以这一篇不只是罗列工具,而是会带你梳理一套完整的隐写排查思路,顺手解决“拿到文件不知道干嘛”的问题。
1.2 从出题人视角拆解选题逻辑
站在出题人的角度,隐写题选图片和音频是非常合理的,因为它们有两个天然优势:一是文件结构复杂,数据量大,随便藏点东西很难被肉眼看出来;二是人类对视觉和听觉的感知有盲区,你看一张800x600的图片,根本不会注意到某个颜色值从128变成了127,听一段30秒的背景音乐,也很难察觉中间某个频率被塞进了一段波形。
理解了这一点,你就明白为什么解题要重构线索链。比如图片隐写里最常见的LSB算法,它的载体通常是无损格式的PNG或BMP,因为JPEG有损压缩会把低位数据弄丢,所以出题人不会用JPEG做像素级隐写,这个特性本身就是线索。音频隐写则分得更细:用Audacity打开看频谱图、倒放试听、调慢速度试听、十六进制查看文件末尾追加数据。不同手法对应不同的工具和观察习惯。
这一节要传达的就一个观念:隐写不是玄学,而是有据可循的信号处理。你不需要背每个工具的每个按钮,你只需要知道“出题人想藏东西,就得往文件的某个冗余区域写数据”,然后顺着这条线去找。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 图片隐写的核心技术与识别特征
2.1 文件拼接与附加数据:binwalk的用法
图片隐写里最简单的招数,就是在图片尾部直接追加一个压缩包或者文本。很多新手不知道,图片文件的显示是按照文件头格式解析的,尾部多出来几百个字节并不会影响图片显示,这就给了出题人操作空间。
对付这种隐写,首选工具是binwalk。它的原理是扫描文件里的内嵌文件签名,比如ZIP压缩包的文件头PK、RAR的头、其他图片文件头等。基本命令就两个:
bash复制binwalk flag.png
或者更常用的是直接分离:
bash复制binwalk -e flag.png
第一次用的时候你会被它的扫描结果震惊。一张看起来只有几百KB的图片,可能里面藏着一个ZIP,甚至藏着一整张GIF。这里要注意一个细节:binwalk -e有时候会自动提取出文件,但提取出来的文件可能还需要二次处理,比如压缩包有密码。你还需要配合foremost来做高容错的文件提取,因为部分刻意破坏文件头的题目里,binwalk的效果不如foremost好。
bash复制foremost flag.png
foremost会把提取出来的文件放到output目录下,它会根据文件签名自动分类。如果遇到文件头被改掉的情况,比如原来的JPG文件头被改成了PNG,binwalk可能会识别错,这时候可以用文本编辑器或010 Editor手动修文件头。JPG文件头是FF D8 FF,PNG是89 50 4E 47,GIF是47 49 46 38。修完文件头再重新看,Flag可能就出来了。
另外一个冷门技巧:查看文件的十六进制末尾。命令行里直接执行:
bash复制strings flag.png | tail -20
strings命令本身就是隐写初赛的常客,它会把文件里可打印的字符串提取出来。出题人有时候会把Flag直接以明文形式追到文件末尾,或者塞进IDAT数据块的注释字段里。先用strings扫一遍再考虑高深的方法,能帮你省掉大量时间。记住:永远先做最简单的检查,再做复杂的像素级分析,这是Misc最重要的答题顺序。
2.2 EXIF信息与文件头伪装
第二类常见手法是藏在EXIF信息里。EXIF是JPEG/HEIC等图片格式自带的元数据,原本用来记录拍摄参数,比如相机型号、曝光时间、GPS坐标、拍摄软件等。出题人会在这些字段里写入Flag,或者把Flag拆成几段塞进不同的字段。
查看EXIF信息不需要太复杂的工具,Python的PIL库就能做到:
python复制from PIL import Image
img = Image.open('flag.jpg')
exif = img._getexif()
for tag_id, value in exif.items():
print(tag_id, value)
但更实用的是直接拉出命令行工具exiftool:
bash复制exiftool flag.jpg
它会列出所有EXIF字段。常见藏匿位置是Comment、ImageDescription、Software、Artist、XResolution、YResolution。有些出题人还会把坐标值拿来当提示,比如GPS坐标是某个地名的经纬度,再用坐标去定位。这种就属于附带一道地理题的隐写。
文件头伪装则是另一个完全不同的思路:出题人把图片的真实格式改了。比如明明是ZIP压缩包,却把文件头改成PNG图片的签名,然后命名为flag.png。你双击这张图可能能正常打开,因为有些看图软件会忽略文件头直接按扩展名解析。但当你执行file命令时,真相就露出来了:
bash复制file flag.png
如果输出不是PNG image data,而是Zip archive data,那这个文件本质上就是压缩包。处理办法很简单:把扩展名改成zip,然后解压。这个手法治好了很多“只会双击看图”的新手,也让大家养成了事事先file的好习惯。
2.3 LSB隐写:PNG与BMP的像素魔法
LSB(Least Significant Bit,最低有效位)是图片隐写里含金量最高、出场率最高的一类。它的原理特别符合直觉:图片每个像素点的颜色由RGB三个通道组成,每个通道的取值范围是0到255,用二进制表示就是8位。你修改最低位,比如把128变成129,视觉上根本看不出来区别,但每个像素的最低 bit 组合起来足够塞入大量信息。
说白一点,图像就是一个巨大的容器,你只需要把Flag的每一bit依次写入像素颜色值的最低位。既然原理清楚了,工具就好选了。最经典的是StegSolve,一个Java写的老牌隐写分析工具。它最大的价值是支持通道调整、图片拼接和逐帧分析。用法是打开文件后点击Analyse -> Data Extract,然后选择需要提取的通道,比如RGB三通道的0位,勾选LSB First,点击Preview,如果图上出现明显的文字信息,那就成功了。
但StegSolve只能“看图”,真正的批量提取和自动化还是得靠Python。比如用zsteg,一个专门检测PNG和BMP的LSB隐写的Ruby工具:
bash复制zsteg flag.png
zsteg会自动尝试常见的通道顺序和bit位组合,直接把可能隐藏的信息输出出来。大部分LSB题用这条命令就能秒杀。如果zsteg扫不出来,再上Python PIL脚本:
python复制from PIL import Image
img = Image.open('flag.png')
width, height = img.size
lsb_text = ''
for y in range(height):
for x in range(width):
r, g, b = img.getpixel((x, y))
lsb_text += str(r & 1)
lsb_text += str(g & 1)
lsb_text += str(b & 1)
然后把这个01字符串每8位转成ASCII,再去拼接Flag。实际比赛里LSB题目喜欢把文字横着写,有时需要按列扫描,有时需要逆序,有时需要先从蓝色通道取一部分再从红色通道取另一部分,这些排列组合靠脚本试,测试的时候可以多换几种读取模式。
还有一个坑必须提醒:LSB隐写不光能藏文字,还能藏图片。有时候数据提取出来是一张灰度图,需要把提取的二进制数据保存成PNG文件才能看见。StegSolve的Data Extract里如果发现提取出来的数据开头有PNG或者BM等图片文件头,不要直接当文本读,而是Save Bin把数据存成文件,再用图片查看器打开。
2.4 二维码、GIF与动态图的隐藏信息
Misc题目里二维码也是重灾区。常见考法有三种:
第一种是删掉二维码的一部分定位角,让你自己补全。这种题目考验的是对二维码结构的理解。二维码一共有三个大的定位角图案,分别位于左上、右上、左下。右下角的小方块是定位图案的一部分。如果发现某一块被涂黑或扣掉,用画图工具补回去,能扫出来就行。
第二种是把二维码的背景颜色做文章。比如二维码的黑色模块并不是纯黑,而是RGB值略有差异。这种用普通扫码工具是没用的,要把图片放到StegSolve里逐通道查看,或者用脚本统计黑色模块的颜色值差异。还有一种更狠的:把二维码反白、旋转、加马赛克,这就需要先还原再扫描。
GIF的隐写思路和静态图片完全不同。GIF本质是多帧图片,每帧画面可能藏着不同的信息,或者部分帧缺失。最常用的工具仍然是StegSolve,它的Frame Browser功能可以一帧一帧地查看GIF里的画面。有的出题人会在某一帧插入一个二维码,而正常播放速度下根本看不到;有的会在连续帧里把文字分成几段,每帧显示一部分。所以拿到GIF文件后第一件事就是拆帧看全部内容,不要只盯着动态效果。
拆帧也可以命令行处理:
bash复制convert flag.gif frame_%02d.png
或者用ffmpeg:
bash复制ffmpeg -i flag.gif frame_%04d.png
拆出来的每一帧再依次做图片隐写检查,Flag往往就在其中某一帧里。
3. 音频隐写的核心技术与工具链
3.1 频谱图与倒频谱隐藏
音频隐写里最容易让新人兴奋的,就是在频谱图里直接显示文字。原理不复杂:音频文件在频域上有大量冗余空间,出题人把文字或图案以特定频率的波形放进音频里,你用Audacity打开频谱视图就看见了。
操作流程非常简单,我却见很多新手栽在工具设置上。第一步,用Audacity打开音频;第二步,点击音频波形左上角的下拉菜单,视图从波形切换到频谱图;第三步,调整频谱图的频率范围。默认情况下频谱图可能把低频部分压缩得很小,而文字通常藏在人耳不敏感的中高频区间,所以把频率范围调到最高几千赫兹以上,文字就会像水印一样浮出来。
还有一种变体是把摩斯码先录成音调,再调成频谱可见的形式。你看到的频谱图可能是几段虚线,每一段分别是一横或一点。这时候你需要先把这些图像转换成摩斯码,再对照摩斯码表解出明文。很多题目不会直接给你文字,而是给你一串“点划组合”,这是音频隐写和密码学的结合题。
真有看不清的时候,把音频截图放大,调整对比度,或者用Audacity的“频谱图设置”里的窗口大小设置改成能看清的范围。我个人经验是把com 时间拖到最小,非要去跑到上万赫兹范围去抠细节。
3.2 摩斯码与慢速播放
摩斯码在Misc音频里出现频率奇高,因为它简单、隐蔽、容易出思路。出题人会把一段正常的音乐替换成滴滴答答的声音,或者把音乐加速/变调以后,在里面混入一段摩斯码。这时候直接用耳朵听很难听清节奏,需要工具辅助。
思路有两种。第一种是波形分析。用Audacity打开音频,放大到波形非常适合看电平高低的地方,你会发现信号有明显的长音和短音,长音是“-”,短音是“.”,间隔也要记下来。这个方式对清晰码型很有效。第二种是借助在线摩斯码解码器,手动把点划敲进去,得到明文。但有些题目里摩斯码频率很高,这时候可以用DTMF工具或写短Python脚本识别频率和时长。
音频处理里还有一个洁癖级小技巧:先把声音做降噪,再把信号整体放大,摩斯码的“点”和“划”时间比例会更明显;点大约是1个单位时长,划是3个单位时长,字符内间隔1个单位,字符间间隔3个单位,单词间间隔7个单位。对照这个比例,你就知道为什么有些录音听起来那么鬼畜但规则很强。
慢速播放和反向播放也是音频隐写的两大杀器。出题人喜欢把一段话加速到两倍甚至四倍,让你听不清;或者干脆反向播放,让语音变成外星语。处理办法还是Audacity:找到效果菜单里的“改变速度”或“反向”,先反向再放慢。有些Flag直接就是一句英文,听到以后按格式补上大小写和花括号即可。慢速播放时建议从0.5倍开始试,如果还听不清就循环,能降多低保真度足够听清为止。
3.3 LSB音频与差分编码
音频里的LSB和图片里的LSB思路完全一致。WAV格式是无损音频格式,采样值用16bit或8bit表示,最低位同样可以藏数据。工具上首推stegolsb或者一个叫WavSteg的脚本,但很多平台的ctf入门选手还是更喜欢直接手动分析。
手动分析WAV文件的LSB,需要利用Python的wave模块:
python复制import wave
def extract_lsb(filename):
wav = wave.open(filename, mode='rb')
frames = wav.readframes(wav.getnframes())
data = [byte for byte in frames]
bits = ''
for byte in data:
bits += str(byte & 1)
text = ''
for i in range(0, len(bits), 8):
text += chr(int(bits[i:i+8], 2))
print(text)
extract_lsb('flag.wav')
这种盲提取出来的文本里可能有大量无意义乱码,但Flag字符一般是可见ASCII。如果脚本输出最后一段能看到flag{...},说明找对了。
音频还有一类题目隐藏的是差分编码。简单说,出题人让相邻两个采样点之间的差值变化来对应0和1,直接读采样值反而什么都看不见。如果常规LSB提取失败,可以试试计算相邻字节或相邻样本的差分序列,再观察它的最低位。这类题目比较少,但遇到时要能想到。
4. 实战流程:从裸文件到Flag的全过程
4.1 拿到的第一个命令应该是什么
很多人拿到题目附件,第一反应是打开看一眼,看完没事干就发懵。我建议把检查顺序固定下来,形成肌肉记忆:先file,再strings,再binwalk,再十六进制高级查看。下面是我常用的流程:
bash复制file flag.png
strings flag.png | head -40
binwalk flag.png
exiftool flag.png
如果这几步把Flag找出来了,恭喜,这题是送分题。如果没有,再进入下一步图像分析。这个顺序最大的优点是:低成本、高覆盖。先用命令行排除掉最基础的情况,后面再上图形工具也不迟。
这里再分享一个坑:不要把strings输出直接扔到终端看,内容太多会刷屏。可以先存下来:
bash复制strings flag.png > strings.txt
grep -i flag strings.txt
grep直接过滤出带flag字眼的行。如果输出包含大括号,就算不是完整Flag,也是极有用的线索。比如ZmxhZ3s=这种,是Base64编码,解码后就能得到Flag。
4.2 图片隐写实操复现
你手头有一张1.jpg和一个题目描述“图片中隐藏了秘密”。按流程走完,binwalk显示没有附加文件,strings也没有可疑内容,exiftool评论字段有句“look at the pixels”。这时候基本上锁定了LSB隐写。
我会上StegSolve,打开图片,选择Analyse -> Data Extract。Bit Order选LSB First,Bit Planes选RGB的最低位,也就是R的0位、G的0位、B的0位全部勾上,然后Preview。如果显示出一串乱码但里面混着“flag{”字样,那就要把提取结果输出成文件进一步分析。还有一种情况是预览是黑白的,但能看到二维码轮廓,那就直接点击Save Bin,把数据存成图片,再用扫码工具识别。
如果你的题目载体是BMP格式,LSB隐写概率更大。因为我前面说过BMP无压缩,修改像素最方便。可以用zsteg快速测试:
bash复制zsteg -a secret.bmp
-a参数会尝试所有极端组合,包括不同的通道和位顺序,输出结果会比默认模式多很多。我的经验是:大多数BMP题目用zsteg -a都能扫出来,扫不出来再自己写脚本。
还有一种情况比较阴:图片看起来没有任何异样,但bmp或png的宽高被改了。比如图片宽度原本是400,被改成300,导致显示时看着是歪的或者后面少了一段。这类题用PIL或pyzbar识别不出来,需要调整图片宽高比。解决办法是用十六进制编辑器找到PNG文件里的IHDR块,宽度字段对应文件头偏移8个字节后的4个字节,高度在宽度后面4个字节。根据CRC校验值爆破正确宽高,或者用pngcheck命令检查报错,会提示哪一行CRC不对。
4.3 音频隐写实操复现
音频题常见附件格式是WAV、MP3、M4A。当你拿到一段音频附件,不要一开始就去解码摩斯码,而是先看文件大小。如果文件大小出奇的大,比如几秒的音频却有几十MB,很可能里面藏了额外数据。直接binwalk甚至能扫出ZIP包。
先用Audacity打开,把声道切换成波形显示,快速播放一遍,感受有没有异常:比如突然有一长串“哔”声、明显的快慢变化、或者背景里有不像音乐的声音。如果觉得有,立刻切到频谱图。频谱图模式看一遍全频段,有可疑文字就截图。
举个例子,某道题给了一首正常钢琴曲,播放完全不违和,但频谱图在8000Hz附近有无规则的横线。放大以后发现是两行字,第一行是“password: passw0rd”,第二行是“flag{...}”。这就同时考了频谱图和密码使用。我处理这类题时,习惯把Audacity频谱分辨率调到4096以上,频率显示范围改成linear,然后拖选可疑区域,用“Plot Spectrum”进一步观察峰值频率。
还有一道题是把Flag藏在声道里。音频是双声道,左声道正常音乐,右声道有一段反向语音,播放时你完全不知道右边有内容。在Audacity里点击音轨左侧的下拉箭头,把显示模式改成“Split Stereo Track”,分离左右声道,单独播放右声道就能听到。处理完右声道再反向,字正腔圆的Flag就出来了。
5. 常见问题与排查技巧实录
5.1 排查思路速查表
我把自己在CTF题库里刷过的隐写题排查思路整理成了个速查表,不一定覆盖所有冷门题目,但应付绝大多数常规Misc足够了。
| 症状 | 优先检查 | 推荐工具/命令 |
|---|---|---|
| 图片能正常显示但总觉得怪 | 文件尾部附加数据、文件头伪装 | file、binwalk、foremost |
| 图片有文字提示像像素问题 | LSB隐写、通道提取 | StegSolve、zsteg、PIL脚本 |
| 图片打不开或显示不全 | 文件头缺失、宽高被改、CRC错误 | 010 Editor、pngcheck |
| 图片是GIF | 逐帧分析、二维码藏在某一帧 | StegSolve Frame Browser、ffmpeg拆帧 |
| 音频放出来是乱码感 | 反向播放、变速播放 | Audacity效果菜单 |
| 音频波形有明显长音短音 | 摩斯码解码 | Audacity波形、在线摩斯码表 |
| 音频文件偏大 | 文件尾部嵌入压缩包 | binwalk、foremost |
| 音频频谱图有图案 | 频谱隐写 | Audacity频谱视图 |
| 播放正常但信息找不到 | 声道分离、左右声道独立试听 | Audacity Split Stereo Track |
| 文字乱码但中间有BASE64头 | 进去解码 | base64 -d或CyberChef |
这张表充当的是你解题时的线索优先级。如果一道题你完全没方向,就按照从上到下的顺序一条条试,多数情况下会在前三条里解决。
5.2 避坑心得和独家技巧
第一,永远不要忽略文件末尾那几个字节。我见过不少人在binwalk、LSB里折腾一个小时,最后发现Flag就在图片文件尾部的注释区。遇到可疑字节串,先复制到CyberChef里看Hex转ASCII、Base64解码、URL解码。CyberChef的Recipe可以串联多种解码方式,比命令行一个个试方便得多。
第二,strings搜不到不代表没有,可能是字符被拆成多段或多字节编码。有的题目把Flag拆成几段,分别藏在EXIF的多个字段里,字段顺序还不连续。这种玩法很考验你的拼图能力,发现有分段的文字时,把所有可疑字段全部提取出来,按文件里的偏移顺序排好,再拼成完整Flag。
第三,音频隐写不要只用听觉。人的耳朵对高频和反向信息非常不敏感,但波形和频谱图不会骗人。处理任何音频题时,先打开频谱图全局扫一圈,再考虑变速和反向,这会帮你省下大量时间。如果一段音频是纯音乐但时长很长,不要从头到尾循环,直接跳到靠近结尾的40%段落,很多出题人习惯把秘密放在结尾空音频里。
第四,善用010 Editor的模板功能。010 Editor对PNG、JPEG、GIF、WAV等格式都有官方模板,打开文件就能结构化地看到每一块的位置和值。如果你需要手工修复文件头或宽高,用它比记事本改十六进制靠谱得多。改完宽高后如果图片还是打不开,记得检查CRC。
第五,别在刚拿到附件时就过度依赖自动化解包工具,比如以为binwalk -e一定能提取出东西。binwalk对不规则附加数据偶尔会漏报,foremost又可能把图片本身的内容误当成隐藏文件提取出来。两者要交叉使用,手动查看十六进制作为兜底。
结尾
我自己在CTF比赛里刷Misc题踩过最大的坑,就是总想一步到位,比如上来就写Python脚本扫LSB,结果漏掉了文件头伪装这种基础题。后来我养成了一个习惯:每次拿到题都强制按“file -> strings -> binwalk -> exiftool -> 图形工具”的顺序做一遍检查,不跳步。这个习惯看着笨,但成功率极高。
最后再分享一个小技巧:把StegSolve、zsteg、Audacity、010 Editor、CyebrChef这几个工具放到一个固定目录里,命令行配好alias,比如alias steg='java -jar /tools/StegSolve.jar'。比赛时间非常紧张,减少重复敲路径的时间,都是实打实的收益。
Misc隐写不是一个需要背代码的领域,它更像培养一种“文件敏感度”:拿到任何文件,你心里要清楚哪些地方可以藏东西,哪些地方是格式规定的冗余区域。多刷几道BUUCTF的Misc题,从图片隐写一路做到音频隐写,你会发现大部分套路都是相通的。如果这个系列对你有帮助,下一篇可以接着聊流量包分析和压缩包伪加密,那个方向也特别有意思。
