CTF这道坑,我跳了也有三年了。每次比赛打开Misc题,总有人以为是最简单的送分题,结果一蹲就是一下午,最后连flag的影子都没见到。Misc杂项里最大的一个分支就是隐写术,说白了就是"把秘密藏在大家都能看到的东西里,但你偏偏看不见"。我做这类题的最深感受是:隐写术考的不是你会多少高深算法,而是你对文件格式、编码原理、工具特性的熟悉程度,外加一点"脑洞"——很多flag就藏在最不起眼的地方。这篇总结我打算把图片、音频、压缩包、流量这几大类逐个拆开讲,重点放在实际解题时的切入思路和工具选用逻辑上,希望能帮新手少走点弯路。
1. 隐写术题目的五种常见形态:先搞清楚出题人在哪一层藏东西
拿到一道隐写题,第一件事不是急着上工具,而是先判断题目属于哪个类型。我按自己的做题习惯,把隐写题粗分成五类,这五类的解题思路完全不同,工具链也基本不重合。
- 文件结构隐写:秘密藏在文件格式的冗余区里。比如JPEG的EXIF信息、PNG的IHDR块、GIF的注释区,甚至文件末尾追加的额外数据。这类题最简单,属于"眼力题"。
- 像素数据隐写:秘密藏在图片的像素值里。典型的就是LSB隐写(把RGB分量最低位替换成秘密信息),以及各种变体如LSB匹配、随机间隔采样等。这类题需要脚本或专用工具处理。
- 压缩包隐写:秘密往往和压缩包捆绑出场。一种是伪加密(改标志位骗过解压软件),一种是真加密需要爆破,还有一种明文攻击场景——你手里有压缩包里的一个已知文件,就能推导出压缩包密码。
- 音视频隐写:秘密藏在音频频谱图、波形间隙、声道相位差、视频帧序列里。最经典的就是用Audacity看频谱图,或者用MMSSTV解码慢扫描电视信号。
- 网络流量隐写:秘密藏在PCAP流量包中。可能是某个HTTP请求的参数、DNS查询的域名子串、图片传输的完整PNG外加大段注释、USB键盘流量里的击键记录。这类题综合性强,常常和Web、Forensics交叉。
我在实际比赛中遇到的情况是:大概四成隐写题走的是"图片像素+文件结构"路线,三成走压缩包,剩下三成均匀分布在音频、流量和其他脑洞题上。 所以你要是时间紧,优先把图片类和压缩包类的工具练熟,性价比最高。
还有一个很重要的经验:很多隐写题是几层嵌套的。 文件尾部藏了一个压缩包,压缩包里有一张新图片,新图片里又有一串Base64,解码后才是flag。所以解题时不要找到一个线索就停下,要习惯性地对每个中间产物继续做同一套检查流程。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 图片隐写:从文件尾部、EXIF到LSB像素位
2.1 第一板斧:file、strings、binwalk三板斧
图片类的题,我不管三七二十一一上来先跑三条命令,这三条命令跑完基本能排除一半的简单题。
bash复制file image.png
strings image.png | head -50
binwalk image.png
file告诉你这个文件真正的格式。很多时候出题人会把一个zip改名成png扔给你,或者把一个PNG改成jpg,file命令一眼就能看穿。
strings用来提取文件中的可打印字符串。flag通常以flag{}、ctf{}这类格式出现,所以我看strings的输出时直接grep关键字:
bash复制strings image.png | grep -iE "flag|ctf|key|secret"
如果直接能搜到,那这题就是送分题。但更多的坑是flag被拆成了几段藏在不同的位置,或者被编码过,所以strings只是第一步,不是终点。
binwalk是检测文件是否内嵌了其他文件的利器。它会扫描文件里的文件签名(magic bytes),比如在PNG文件末尾追加了一个ZIP,binwalk能直接探测出来。
bash复制binwalk image.png
# 如果检测到ZIP,可以用-e参数直接提取
binwalk -e image.png
我个人的经验是:binwalk -e 的结果经常是残缺的,特别是遇到一些非标准的文件拼接方式,提取出来的文件根本打不开。 更可靠的替代方案是foremost,它能按文件签名去Carving,对数据恢复式的提取更稳。两条命令都跑一遍,哪个能用用哪个。
2.2 EXIF信息与GPS坐标:别小看出题人塞进去的备注
有些题不会专门让你用工具,flag直接写在图片的属性里。这个"属性"在Linux下可以用exiftool看全:
bash复制exiftool image.jpg
重点看这几项:Comment(注释)、Artist(作者)、Copyright(版权)、ImageDescription(图像描述)。CTF出题人很喜欢往Artist或Comment字段塞Base64字符串或者一段看起来毫无意义的话。
有一道印象很深的题,图片的EXIF里有一串Copyright: YmFzZTY0,我当时没反应过来这是个Base64的"base64",后来解出来是flag{...}。所以看到EXIF里有奇怪的字段值,先别急着忽略,把可疑字符串丢进CyberChef或者随波逐流工具里试一遍Base64、Hex、ROT13,标准的编码方式一套组合拳就能解出来。
GPS坐标也有考过。图片EXIF里带了经纬度,拿去地图一查,定位到一个地方,flag就是那个地点的中文拼音或者坐标本身某种变形。这种题属于冷门,但一旦遇到,解法非常固定。
2.3 像素级操作:zsteg、Steghide与LSB的完整认识
像素级的隐写是图片隐写里真正有技术含量的一部分。核心原理很简单:图片每个像素有RGB三个通道,每个通道用一个字节(0-255)表示。如果你把每个字节的最低位替换成秘密信息,人的肉眼完全分辨不出差别,但一字节的秘密就能藏在三个像素里(每个像素提供3bit),一张100x100的图片足够藏3700字节。
处理这类题,我常用的工具按场景分三类:
场景一:PNG图片LSB隐写
zsteg可以说是PNG杀手锏,它能把PNG/BMP里的LSB隐写数据自动探测并提取出来:
bash复制zsteg -a image.png
-a参数会尝试所有通道组合和位平面顺序,包括RGB、RGBA、以及各种bit提取顺序。大多数LSB题一条命令直接出flag。
场景二:JPG图片隐写
JPEG的压缩过程会丢失部分信息,所以LSB应用在JPEG上效果不好,出题人更倾向于用Steghide这种工具。Steghide的隐写原理是把秘密信息嵌入到DCT系数中,对JPEG这类有损压缩格式有较好的鲁棒性。
bash复制# 提取时可能需要密码,如果出题人没给,直接回车试空密码
steghide extract -sf image.jpg
很多新手不知道的是,Steghide嵌入时是可以设密码的,出题人常常把密码放在题目描述里、文件名里,或者藏在上一层的某个线索中。解Steghide时如果默认密码不对,想想题面有没有出现过像password: 123456这类信息。
场景三:需要手工写脚本的LSB变种
标准LSB工具搞不定的情况,是出题人改了LSB的规则。比如:
- 不只取最低位,而是取每个字节的低两位或低四位
- 提取顺序不是从左到右,而是按某个随机种子跳跃取像素
- 三个通道不是都用来藏数据,而是只有G通道或B通道
- 像素选择范围限定在图片的某个区域
这时候就得写Python脚本,用PIL或cv2读像素,按规则提取。我遇到过一个题,flag信息藏在R通道最低位,但提取方向是从右下角往左上角走,脚本逻辑如下:
python复制from PIL import Image
img = Image.open('challenge.png')
pixels = img.load()
width, height = img.size
bits = []
for y in range(height - 1, -1, -1): # 自下而上
for x in range(width - 1, -1, -1): # 自右向左
r, g, b = pixels[x, y]
bits.append(r & 1) # 只取R通道最低位
# 每8位一组转字符
data = ''
for i in range(0, len(bits), 8):
byte = 0
for bit in bits[i:i+8]:
byte = (byte << 1) | bit
if byte == 0: # 遇到0字节往往代表结束
break
data += chr(byte)
print(data)
写脚本的经验是:先搞清楚目标信息量有多大。 如果flag一般是30字节左右,那就是240个bit,你只需要提取前240个有效位,不需要把整张图的所有bit都转成字符。所以脚本里加个break条件很有必要,否则图像数据本身会生成一堆乱码,反而看不清flag。
2.4 二维码与多图组合:脑洞题的大本营
图片隐写里还有一类"脑洞题",典型的就是二维码。
二维码的常规考点有几种:
- 二维码残缺:用PPT或PhotoShop把缺失的定位角、校正图形补回去,手机直接扫
- 二维码反色:把黑白对调,需要先用工具反转颜色再扫
- 二维码掩码:二维码本身有8种掩码方式,出题人可能改了掩码结果,需要逐种掩码尝试,用
zxing库或在线工具验证哪个能扫出来 - 二维码像素替换:把QR码的模块(黑白方块)替换成其他有意义的图案,但保留了模块的明暗关系,你需要从中重构出一个标准的QR码
多图组合常见的是两张图叠在一起(比如一张图用Photoshop的正片叠底、滤色等混合模式处理过),或者一张动图的每一帧藏一段信息、一个帧序列隐含一段完整的图。这类题的处理思路是:把图片当作数据载体,想尽一切办法做数学变换。 加减乘除、异或、位运算,两两处理,看结果里有没有出现有意义的字符或者图像轮廓。
一个比较实用的方法是用stegsolve的analyse功能——这个工具是隐写题必装的老牌神器。里面的File Format、Data Extract、Frame Browser三个功能分别对应文件格式分析、像素位提取、GIF帧查看。尤其是Data Extract,可以手动勾选R/G/B通道的每一个bit plane,然后逐个查看是否有信息出现。标准LSB题如果zsteg抽风,用stegsolve手点也能解出来。
3. 压缩包攻击:伪加密、真爆破与明文攻击
3.1 ZIP伪加密:一个标志位骗过所有解压软件
热搜词里"misc zip伪加密"出现频率极高,可见这是新手最容易卡住的地方之一。伪加密的原理非常巧妙:ZIP文件有一个general purpose bit flag字段,第0位(值为1)表示文件被加密。出题人把ZIP里某个文件条目的这个标志位改成1,但实际上并没有真正加密文件内容——文件内容根本没有被加密处理,你只是被标志位"骗"了,以为它加密了,解压时不管输什么密码都会报错。
检测伪加密的办法,最直观的是用binwalk或010 Editor看ZIP的二进制结构,找到对应文件的general purpose bit flag是00 09还是00 00。真正的加密,标志位是00 09(加密标志加描述符标志),伪加密则是00 00或00 01,同时文件头的其它字段显示normal。
但更快捷的方式是用工具:
bash复制# Linux下用zipinfo查看加密标志
zipinfo encrypted.zip
# 如果显示文件的加密方式为"none",但解压时要密码,那就是伪加密
手工修复伪加密最直接的办法:用010 Editor或WinHex把general purpose bit flag从01 00改回00 00,保存后重新解压就能直接拿到文件。Python的zipfile模块也可以绕过伪加密,但有些变体需要修改多个地方,直接用16进制编辑器改最稳。
3.2 真爆破:字典优先于纯暴力
真加密的ZIP就只能爆破。我推荐的工具是john the ripper的zip2john模块,或者专门的fcrackzip。
bash复制zip2john encrypted.zip > hash.txt
john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
为什么字典优先?因为CTF出题人设密码有个通病——密码通常是英文单词、键盘相邻键组合、题目描述里的某个词,或者有意义的数字串。纯6位数字暴力可以接受,但如果密码是8位以上混合大小写加符号,纯暴力在比赛时间内几乎不可能跑出来。
我遇到过几道题,密码分别是123456、password、题目里的某个英文单词、还有一次是键盘上斜着排列的字符串qwertyuiop。这些都出现在常用字典里。
ROCKYOU字典不够用的情况下,还可以自己写一个定制字典:把题面描述里的单词、文件名、作者名、图片里出现的文字全部收集起来,组合生成一个小字典去撞。很多时候出题人的密码逻辑是"题目里某段线索的拼音",这种是不在通用字典里的,需要你自己把线索转成拼音后加进字典里。
3.3 明文攻击:当你知道压缩包里某个文件的内容时
明文攻击(Known-plaintext attack)是另一个高频考点。原理是:ZIP的标准加密算法(ZipCrypto,不是AES)在加密时使用了一个由密码派生的密钥,如果你知道压缩包内某个文件未加密的内容,就可以通过已知明文和对应密文的异或关系反推密钥流,进而恢复出随机生成的文件加密密钥,最终解出整个压缩包。
工具用pkcrack或bkcrack。
bash复制# bkcrack用法示例
# 已知明文文件为plain.txt,对应压缩包内文件为plain.txt
bkcrack -C encrypted.zip -c "path/to/plain.txt" -p plain.txt
明文攻击有效的前提:
- 压缩包内至少有一个文件的内容你完全知道(比如题目附带的说明文件、readme.txt)
- 压缩算法用的是传统ZipCrypto而非AES加密(新版WinRAR默认AES时明文攻击无效)
做题经验:如果压缩包里有个文件很小,内容在题面里贴出来了,或者文件名直接叫hint.txt、readme.txt,第一反应就应该是明文攻击。 有个经典套路是:ZIP里藏着一张图片,但你手里有一张几乎一模一样的原图,只是文件大小略有不同——这时候试着对ZIP里的图片做CRC32比对,如果CRC对得上,说明内容完全相同,就能当已知明文来攻击。
3.4 压缩包爆破之外的旁门左道:CRC32碰撞
还有一种专门应对"小文件加密压缩"的技巧——CRC32碰撞。当压缩包里的文件很小(比如只有几个字节)时,即使密码爆破不出来,也可以直接用CRC32逆推出文件内容。因为文件很小,你只需要枚举所有可能的极短内容,计算CRC32值和压缩包里的CRC32比对,匹配上就说明内容一致。
python复制import binascii
import string
import itertools
target = 0x5E3B5E3B # 从zip文件头里读出的CRC32
charset = string.printable
for length in range(1, 5):
for candidate in itertools.product(charset, repeat=length):
s = ''.join(candidate).encode()
if binascii.crc32(s) & 0xffffffff == target:
print(s)
break
CRC32碰撞理论上只能用于极短内容的还原,但如果压缩包里的单个文件内容本身就只有4-6个字符,这个方法非常有效,能在几秒内跑出来。
4. 音频隐写:隐藏在声波里的数字密码
4.1 频谱图:用眼睛"听"声音
音频隐写里最常见的解法是看频谱图。工具就是Audacity,开源免费,CTF必备。打开音频文件后,通过菜单Analyze -> Plot Spectrum查看频谱,或者直接切换视图模式为Spectrogram显示频谱图。
秘密信息通常以两种形式出现在频谱图里:
- 文字形式:flag的字母直接以固定频率的色块形式出现在频谱中,肉眼可见
- 图像形式:整幅二维码或图片被嵌进某一频段,需要导出来才能扫描
处理的关键是调节频谱图的参数。我在Audacity里看频谱图时一般这样做:
- 将视图设置为
Spectrogram(频谱图)模式 - 把窗口大小(Window Size)调大,比如2048或4096,频率分辨率更高
- 调节灰度或色阶范围(在频谱图设置里),让对比度更明显,隐藏信息更容易看清
如果看不到任何明显的文字或图像,可以尝试改变音量、缩放频率轴。有些题把秘密信息放在高频区域(比如14kHz以上),人耳听不到,但频谱图里一清二楚。
4.2 摩斯电码与DTMF双音多频
音频里藏摩斯电码是老题型了。一段音频听起来"滴滴答答"的,那就是摩斯电码。把音频导入Audacity后放大波形观察:
- 短促的beep是"点"(.)
- 长音是"划"(-)
- 间隔规律长短不一
手工记录太慢。我的做法是先听一遍,把节奏转换成点划序列,然后用Python或在线工具解码。也可以用audacity的标签轨功能,边听边Ctrl+M打点标注,精度高很多。
DTMF(双音多频)也考过,就是电话按键的声音,一段音频里不同的双音组合代表不同的数字或符号,把按键序列还原出来再转ASCII或解码。
还有更进阶的,双声道相位隐写——左声道和右声道存在反相关系时,相减之后能还原出隐藏的声音。在Audacity里选中音轨后通过Effect -> Invert(反向)再合并左右声道(Tracks -> Mix -> Mix Stereo down to Mono),往往能听到隐藏的信息。
4.3 慢扫描电视(SSTV):用声音传图
SSTV(Slow-Scan Television)是一种用音频信号传输静态图像的技术,在CTF里也成了常客。音频听起来就是一阵阵刺耳的"滴——嘟——"声,用专用解码器就能还原成一张图片。
我用的是MMSSTV(Windows下)和qsstv(Linux下):
bash复制# 安装qsstv
apt install qsstv
# 播放音频时打开qsstv,选择正确的解码模式(通常是Robot 36或Martin M1)
解码出来的图片往往是flag所在的最终载体,或者是一张包含提示的图。注意解码前把音量调正,SSTV对信号幅度有一定要求,声音太小解码出来的图会花屏。
5. 流量包分析:把通信记录当线索库
5.1 WireShark的过滤与对象导出
流量分析类隐写题给的通常是一个.pcap或.pcapng文件。拿到流量包的第一反应是打开WireShark,但打开后别急着乱点。按我常用的检查顺序来:
查看流量概览 -> 筛选HTTP/HTTPS/DNS等常见协议 -> 追踪TCP流 -> 导出HTTP对象。
bash复制# 用命令行工具先做一次粗筛
tshark -r capture.pcap -Y "http.request" -T fields -e http.host -e http.uri
HTTP对象导出的操作路径是:File -> Export Objects -> HTTP,把传输过的图片、压缩包、文件全部导出来。这是流量题最重要的操作之一,很多时候flag就在传输的某个文件里。
5.2 从USB流量还原击键记录
流量包里也可能藏着USB键盘的HID报文。原理是USB键盘每次按键都会发送一个8字节的HID report,其中第二个字节是按键的键码。把时序上连续的HID report提取出来,将键码映射回字符,就能还原出键盘敲击内容。
bash复制# 提取usb hid report
tshark -r usb.pcap -Y "usb.capdata" -T fields -e usb.capdata
然后把输出的十六进制键码,用现成的HID键码对应表解析成字符串。这个活儿有专门的工具UsbKeyboardDataHacker,也可以用Python写一个映射表来手动解析。
我当时第一次做USB流量题,卡在了一个细节上:Shift按键会改变键码的含义,所以在解析时需要额外处理Shift的状态位,否则大小写全乱,flag根本看不出来。
5.3 DNS与ICMP的隐写通道
DNS和ICMP也是常见的隐写载体。DNS隐写最常见的形态是:查询的域名子域名由大写十六进制/Base64字符拼接而成,把DNS查询包里的子域名按顺序拼出来再解码就能得flag。
bash复制tshark -r dns.pcap -Y "dns.qr == 0" -T fields -e dns.qry.name
ICMP隐写则花样更多:ICMP的payload字段可以携带任意数据,出题人会把flag藏在ICMP echo request/reply的数据部分。用tshark提取icmp.data即可:
bash复制tshark -r icmp.pcap -Y "ip.proto == 1" -T fields -e data.data
5.4 特殊情况:需要重组的数据流
有些流量题不是简单导对象就能解决的。比如flag被切分成片段分布在多个HTTP请求的响应中,或者藏在TCP流中的特定偏移位置。遇到这种情况,可以用tcpflow把所有TCP流重组成原始文件:
bash复制tcpflow -r capture.pcap -o output_dir
然后在output_dir里逐文件查看,file命令识别文件类型,strings检查内容。TCP重组的核心价值在于:它能帮你还原出正常的文件传输内容,尤其是图片或二进制文件,比在WireShark里看十六进制数据直观得多。
6. 一条综合题的完整推演:从图片到压缩包再到音频的嵌套链条
光讲工具不练题,等于纸上谈兵。我完整复盘一道自己做过的综合题,把上面的知识点串起来。
题目给了一个misc_challenge.png和一段题目描述:"好好看,好好听。"
第一步:常规检查。file命令确认是PNG,strings输出里没有任何flag相关字符串,binwalk也没有检测到内嵌文件。走入了死胡同。
第二步:转去看EXIF。exiftool显示图片的Comment字段有一串base64:emlwIGlzIG5vdCBhIHppcA==。随手一解,结果是zip is not a zip。这明显是提示往ZIP方向想。
**第三步:回到图片本身。**既然是PNG,试试LSB路线。zsteg -a跑完后输出了一行似乎在说图片里藏着另一张图片的提示。用stegsolve的Data Extract逐个bit plane查看,在R通道的第0位平面看到了一张模糊的小图轮廓,继续调整,把图像提取出来,是一张新的PNG,这个PNG打开后只有几个像素,但文件大小异常地大。
**第四步:怀疑内嵌。**对提取出的新PNG跑binwalk,这次检测出一个ZIP文件附加在PNG数据之后。foremost提取出这个ZIP,尝试解压,提示需要密码。
**第五步:压缩包攻击。**先试伪加密——用zipinfo查看标志位,发现确实打开了加密标志。用010 Editor把标志位改回00 00后重新解压,这次成功!说明这道题的ZIP是伪加密。
**第六步:压缩包里的内容。**解压得到一个.wav文件。按音频隐写流程,导入Audacity,查看频谱图。在14kHz附近看到了一行淡淡的字符。放大后辨认出是`flag{h3ll0_st3g4n0gr4phy}``
这道题其实是三层嵌套:PNG的LSB藏了一张PNG -> PNG尾部拼了一个伪加密ZIP -> ZIP里藏了一段音频频谱图。每一步都不算高深,但每一步都需要用对工具、判断对方向。综合题的解题核心是:每拿到一个新文件,都当作独立题目重新走一遍完整检查流程,绝不因为上一个文件是图片就只检查图片属性。
7. 隐写题的翻车点自查清单与工具装包
最后把踩过的坑和常用工具整理一份清单,做题前对照一下,能节省大量试错时间。
最容易翻车的五个细节:
- 文件后缀名不可信。 任何文件都用
file命令先确认真实格式。曾经有题把ELF可执行文件改名成txt,直接双击记事本打开的人全都懵了。 - Base64解码结果里可能有尾部填充。 有些题把flag拆成几段分别编码,拼接时要注意Base64的填充字符
=不规范会导致解码失败,这时候要人工检查填充长度是否合理。 - binwalk提取的文件常常是垃圾。 当binwalk检测到多个内嵌文件时,不代表每个都能提取成功,需要逐个用
file检查提取结果的真伪。 - LSB提取顺序不是固定的。 标准工具提取不出来的,不要死磕,立刻切换到脚本路线,手动控制顺序和bit plane。
- 音频题的音量一定要调。 频谱图里隐藏信息的可见度严重依赖音量设置和窗口尺寸,我第一次做音频题时就是音量太小,隐藏文字完全看不到。
工具装包清单(按优先级排序):
| 工具 | 用途 | 安装方式 |
|---|---|---|
| binwalk | 文件内嵌检测与提取 | apt install binwalk |
| foremost | 文件Carving恢复 | apt install foremost |
| zsteg | PNG/BMP的LSB检测 | gem install zsteg |
| exiftool | EXIF元数据查看 | apt install libimage-exiftool-perl |
| stegsolve | 像素位平面手动分析 | 下载jar包或使用在线版 |
| steghide | JPG隐写提取 | apt install steghide |
| Audacity | 音频波形/频谱分析 | apt install audacity |
| qsstv/MMSSTV | SSTV音频解码 | apt install qsstv |
| john/zip2john | ZIP密码爆破 | apt install john |
| bkcrack | ZIP明文攻击 | GitHub下载 |
| WireShark/tshark | 流量分析 | apt install wireshark |
| tcpflow | TCP流重组 | apt install tcpflow |
我在实际比赛里最常被绊倒的地方,往往是那种"明明工具都装了,就是没想到用"的瞬间。隐写术的本质是"在常规视野之外找信息",做题的节奏感比单纯堆工具重要得多。每拿到一个文件,先问自己三个问题:这是什么格式?有没有多余的元数据?有没有可能在看似无关的地方藏着另一层信息?把这几个问题问完,再动手跑命令。
这道题解完之后,每次比赛遇到Misc隐写题我都多留了一个心眼:不仅检查常规位置,还会顺手看一眼文件的时间戳、CRC校验值、以及图片不同颜色通道之间的差值矩阵——这些奇奇怪怪的角落恰恰是出题人最喜欢埋雷的地方。希望这份总结能让你在下次遇到隐写题时少绕几个弯,直接踩到点上。
