CTF文件隐写实战:图片、压缩包与音频排查
文件隐写和CTF隐写这两个词第一次接触的人往往会低估它的难度——以为无非是把字符串塞进图片里拿个工具一跑就出来了。等你真正在赛场上连续卡上三四个小时看着一张正常的风景照就是不出flag才会明白文件隐写为什么被称为CTF里最考验耐心的方向之一。它不像Web那样有明确的漏洞点也不像逆向那样有清晰的函数调用链它考的是你对文件格式二进制结构的熟悉程度以及面对什么都看不出来时的排查纪律。这篇就聊聊文件隐写这件事从图片、压缩包到音频把每种载体的藏数据缝隙、排查顺序、以及我自己踩过的坑整理一遍。不管你是刚入门Misc的新手还是已经做过几道隐写题但总是漏掉关键步骤的老手下面这些内容应该都能用得上。1. 隐写与加密的分野先搞清楚你在找什么很多人拿到一道隐写题第一反应是这是个解密题吧然后一头扎进各种编码转换里base64、十六进制、凯撒密码轮着来最后发现方向完全错了。这个误区的根源是没有分清隐写和加密解决的是两件不同的事。1.1 隐写藏的是存在性加密藏的是内容加密的核心目标是让内容不可读你明知道一段密文在那里但没有密钥就打不开。隐写的核心目标是让有东西这件事本身不被发现数据被嵌进一张图片、一段音频的冗余空间里从外观上看和普通文件没有任何区别。打个生活化的比方。加密像是把一封信锁进保险箱所有人都知道保险箱里有一封信但没密码打不开。隐写像是把暗号写在报纸某一页每个字的第一个笔画上报纸就那么摆在桌上没人会怀疑里面藏了东西。在CTF里这两种技术经常组合使用——先用隐写把密文嵌进图片再对提取出来的密文做解密。所以排查顺序永远是先找藏了什么再处理怎么解开顺序反了就是白费力气。1.2 CTF隐写题的常见载体分布我把近些年Misc方向出现的隐写载体梳理了一下大致是这么分布的载体类型占比感受典型出题方式图片(PNG/JPG/BMP/GIF)最高LSB、附加数据、EXIF、尺寸篡改压缩包(ZIP/RAR)高伪加密、明文攻击、CRC碰撞音频(WAV/MP3)中频谱图、波形、LSB、静默段文档(PDF/DOCX)中隐藏图层、注释、白字视频(MP4)较低帧拼接、音轨分离流量包(PCAP)中协议字段、分片重组图片是绝对主力原因很实际图片文件格式的冗余空间大视觉上又不容易察觉改动出题人操作起来也方便。所以下文会花最多篇幅在图片上其他载体逐个过。提示拿到题目先别急着上工具。第一步应该是用file命令确认真实文件类型很多时候题目会故意改后缀扩展名是jpg但实际是png或者zip这一步能省掉大量弯路。2. 图片格式的二进制结构数据到底能藏在哪几个位置想做好图片隐写光会用工具是不够的得知道每种图片格式的字节是怎么排列的才能判断哪里还能塞东西。下面按PNG、JPG、BMP、GIF分别说。2.1 PNG的分块结构是藏数据的重灾区PNG文件由一个个数据块(chunk)组成每个块的格式固定4字节长度、4字节类型标识、数据区、4字节CRC校验。标准的块类型有IHDR(图像头信息)、IDAT(图像数据)、IEND(结束标记)。关键点在于PNG规范允许存在辅助块和私有块。出题人可以在IEND之后继续追加数据很多解析器读到这里就停了后面全是空白插入tEXt、zTXt、iTXt这类文本块直接写关键词自定义一个块塞进压缩后的数据排查命令pngcheck -v target.png这条命令会列出所有chunk如果看到不认识的块类型或者长度异常基本就是切入点。我之前遇到过一道题IEND后面跟了一大段base64用010 Editor直接拉到底部就能看到提取出来解一下就出了。另外PNG的IDAT数据是经过zlib压缩的有时候题目会在这里做文章比如把两张图叠在一起。这种情况可以用zlib-flate或者写几行Python解压看内容。2.2 JPG的段结构与EXIF字段JPG(JPEG)是另一种结构由若干个段(segment)组成每个段以FF加一个标识字节开头。常见的段有SOI(文件开头FFD8)、APP0、APP1、SOS(扫描数据开始)、EOI(文件结束FFD9)。隐写的切入点主要有两处一是EXIF信息。APP1段里可以存大量元数据包括拍摄设备、时间、GPS还有最常被利用的Comment和UserComment字段。很多人拿exiftool看一圈就以为看全了其实要重点关注有没有异常的字段名。exiftool target.jpg二是EOI之后的数据。和PNG一样FFD9之后如果有内容多半是附加的文件。用binwalk或者直接看十六进制就能发现。我个人的习惯拿到JPG先跑binwalk再跑exiftool最后用十六进制编辑器确认FFD9之后有没有东西。三步下来基本能覆盖JPG的常规出题点。2.3 BMP与GIF的两个特殊位置BMP的结构相对简单文件头之后是像素数组。它的特点是像素数组的字节顺序和调色板(如果有的话)之间存在冗余。LSB隐写在BMP上做起来最干净因为没有压缩直接改最低位就行。如果你在BMP上做LSB提取发现乱码可以试试换通道顺序。GIF就更有意思了它支持多帧和调色板。常用的藏数据位置有两个多帧之间的差异把flag拆成多帧肉眼看是动图逐帧提取才能拼出来调色板(GCT)的排列调色板颜色值的低位可以藏信息GIF题有个坑很多工具默认只处理第一帧所以提取不到flag时要记得手动指定帧号或者把GIF拆成单帧再逐个分析。我用convert或者Python的PIL库拆帧比较多。from PIL import Image, ImageSequence img Image.open(target.gif) for i, frame in enumerate(ImageSequence.Iterator(img)): frame.convert(RGB).save(fframe_{i}.png)这段话就是CTF里很常见的拆分藏图手法的操作核心拆完再对每帧做后续分析。说到这最近赛场上流行的一种ctf jpeg隐写题其实就是把数据藏进JPEG的量化表或者DCT系数里这类题光靠工具很难直接出得理解JPEG的压缩流程后面单独讲。3. LSB位平面分析最经典也最容易翻车LSB(最低有效位)隐写是图片隐写里出现频率最高的手法原理一句话就能说清把像素每个颜色通道值的最低位替换成要隐藏的二进制数据。因为最低位对颜色影响微乎其微比如红色通道247变成246肉眼根本看不出所以藏进去完全不显眼。3.1 位平面分析的原理与观察方法一张24位色的图片每个像素由R、G、B三个通道各8位组成。把这8位按位拆分就得到8个位平面。最低位平面(bit 0)如果被替换成有规律的文本数据看起来会是明显的噪点而正常图片的最低位平面应该也接近随机——但有结构的数据会呈现块状、条纹状。用工具观察Stegsolve(Java版)是最经典的打开图片后通过Analyse - Data Extract可以选择通道和位平面实时预览提取结果。很多人一开始只会点默认的RGBbit0组合其实要挨个试R/G/B单通道 × bit0RGB交错 × bit0bit0到bit7逐层反转位序我遇到过的翻车情况就是数据藏在bit1而不是bit0因为出题人故意把bit0填了随机噪声干扰。所以逐层排查是纪律不是可选项。3.2 命令行与脚本化的高效提取Stegsolve直观但批量处理不方便。PNG的LSB我基本都用zstegzsteg -a target.png-a参数会把所有可能的提取组合跑一遍输出里出现text、flag、base64等关键字就说明命中了。这个工具的好处是自动尝试了各种位序和通道组合省去手动点几十次。WAV音频的LSB也可以用zsteg或者用wavSteg。需要自己写脚本时核心逻辑就那么几行from PIL import Image img Image.open(target.png).convert(RGB) bits for pixel in img.getdata(): for channel in pixel: bits str(channel 1) # 按8位一组还原 data bytes(int(bits[i:i8], 2) for i in range(0, len(bits)//8*8, 8)) print(data)这段代码是理解LSB本质的关键自己跑一遍比用十次工具都管用因为你能清楚地知道数据是按什么顺序读的——是按行、按列还是通道交错。顺序错了提取出来的就是乱码这时候就要调整读取顺序这也是LSB题常见的坑点之一。注意LSB提取的字符串开头如果是PK说明后面跟着的是ZIP文件继续把二进制保存成文件再解压如果是iVBORw那是PNG文件这些隐写套娃在比赛里特别常见提取一次往往不是终点。3.3 遇到非标准LSB时的思路有些题不是简单的每通道一位而是变体比如只改R通道、隔像素藏一位、或者是把数据藏在通道值的高位(MSB)。判断方法还是看位平面——高位的改动会明显影响颜色所以如果图片局部有肉眼可见的色块可能是MSB隐写。另一种变体是通道间差值隐写用R和B通道的差值来编码。这种用常规工具提不出来得先做通道运算。我在某次比赛里就栽在这类题上后来是把三个通道分离出来做差值才看到规律。4. 文件头尾与附加数据binwalk之外的手工活附加数据(append)是最朴素的隐写方式——把一个文件直接拼在另一个文件后面利用解析器只读前半部分的特性。听上去简单但真正做起来识别和切分这两步都有讲究。4.1 常见文件头的magic bytes速查判断一段数据是什么文件最快的方法是对照文件头的magic bytes。把这个表记熟能让排查速度快很多文件类型十六进制文件头ASCII特征PNG89 50 4E 47.PNGJPGFF D8 FF无GIF47 49 46 38GIF8ZIP50 4B 03 04PK..RAR52 61 72 21Rar!PDF25 50 44 46%PDFBMP42 4DBM7z37 7A BC AF7z..在十六进制编辑器里搜索50 4B 03 04就能定位到附加的ZIP搜索FF D8定位JPG这是手工切分的基本功。4.2 binwalk与foremost的使用边界binwalk会自动扫描并识别嵌套的文件binwalk -e target.png-e参数是自动提取。但有几个坑要注意第一自动提取有时会把一个完整文件切成几段尤其是文件本身还带压缩或加密的时候。这时候要看它生成的提取目录手动拼回来。第二binwalk对某些格式不敏感比如被修改过文件头的文件就识别不出来。这时候要用手工方法在十六进制里找到文件结束标记(JPEG的FF D9、PNG的IEND块)标记之后的数据全割出来另存。第三foremost和binwalk定位的粒度不同foremost基于magic文件规则更细遇到疑难文件可以两个都试。我习惯的操作是先binwalk扫一遍看全貌再用十六进制编辑器确认每个边界最后手工dd切分。自动化工具负责提示人工负责确认这样最稳。dd iftarget.png ofextracted.zip bs1 skip偏移量4.3 图片尺寸篡改这个隐蔽的坑有一类题特别阴出题人修改PNG的IHDR里记录的高度或宽度把图片底部或右侧的一部分裁掉真正的flag就藏在被裁掉的部分里。因为解析器按修改后的尺寸渲染看起来完全正常。判断方法PNG的IHDR后面有CRC校验。如果尺寸被改过CRC就对不上。pngcheck会直接报错CRC error: 0xXXXX (calculated 0xYYYY)修复方式是算出正确尺寸再改回去或者用脚本爆破宽高组合直到CRC校验通过。这类题我第一次遇到时完全没思路以为是普通图片白白浪费了时间。记住这个特征PNG尺寸异常 CRC报错 高度宽度被改。5. 压缩包类隐写伪加密只是入门压缩包本身不是把数据藏起来但它经常作为隐写的第二层载体出现——从图片里提取出一个ZIP然后这个ZIP还有机关。所以压缩包的处理能力是隐写题的必修课。5.1 ZIP伪加密的识别与破解ZIP的本地文件头和中央目录里各有一个通用位标记字段。正常加密的ZIP这两个字段的加密标志位(bit 0)都是1而伪加密只改其中一个导致解压软件提示需要密码实际上数据根本没加密。识别方法看本地文件头的偏移6-7字节和中央目录对应字节是否一致。用010 Editor打开定位到50 4B 01 02(中央目录)找到加密标志位改回00或者直接改本地文件头的标志位保存后就能正常解压。命令行下用zipinfo -v能看到每个文件的加密状态结合十六进制编辑器修改几秒钟就能解决。5.2 明文攻击与CRC碰撞的实际应用真正的ZIP加密如果密码复杂爆破是不现实的。这时候有两个方向明文攻击如果你知道ZIP里某个文件的内容(ZIP使用的传统加密算法存在已知明文弱点)可以用工具pkcrack结合已知文件恢复密钥。CTF里常给一个提示文件内容和ZIP里某个文件一样就是给你做明文攻击用的。CRC碰撞ZIP里每个文件记录了一个CRC32校验值。如果压缩包很小(比如只压了4个字节)可以通过暴力枚举所有可能的4字节内容匹配CRC值来还原原文。这需要写脚本思路是遍历候选字符空间。import binascii # 假设目标CRC已知字符集有限 target_crc 0x12345678 import itertools for combo in itertools.product(abcdefghijklmnopqrstuvwxyz, repeat4): s .join(combo).encode() if binascii.crc32(s) 0xffffffff target_crc: print(s) break这个手法在只压缩极短内容的题目里非常好用因为搜索空间可控。5.3 压缩包里的其他小机关还有些值得注意的点ZIP里可以藏注释(在中央目录末尾)有时flag就写在注释里unzip -z或者直接看十六进制能找到RAR可以设置文件头注释;7z的头部结构也能塞数据。另外压缩包里的文件名本身可能就是信息——比如文件名是各种字符按特定规则排序后拼出flag。我见过一道题文件名是打乱的数字暗示你要按数字顺序重命名再拼接内容。这类题考的是观察力不是工具。6. 音频、文档与冷门载体少有人走的路径当图片和压缩包都排查完还没思路时就该把注意力转向其他载体了。这些方向的题量少但一旦出现不熟悉的人很容易全军覆没。6.1 音频隐写的三大类手法音频隐写主要有这么几个方向频谱图隐写把信息编码成特定频率的波形在频谱图里能看到文字或者图案。工具用Audacity或者Sonic Visualiser把音频切到频谱视图很多题直接把flag画在频谱里。这是音频题里出场率最高的。波形隐写直接在时域波形上做文章比如用摩尔斯电码的长短波形表示点划。看波形要用放大到样本级别。LSB与静默段WAV的PCM数据每个采样点16位最低位同样可以藏信息。还有一种是在音频的静默段(听不见的部分)藏数据用工具把静默段放大就能看到。# 用sox查看音频信息 sox target.wav -n stat我处理音频题的顺序是先看频谱再看波形最后才考虑解LSB。因为前两种最直观花时间最少。6.2 PDF与Office文档的隐藏位置PDF的隐藏手法包括隐藏图层、注释、白色文字、对象流里的额外内容。pdfid和pdf-parser能列出所有对象重点看有没有异常的JavaScript或附件流。用文本编辑器搜索flag、key这类词也常有收获因为白色文字在源码里是明文。Office文档(docx、xlsx)本质是ZIP改了后缀用解压工具打开能看到内部的XML和媒体文件。隐藏文字、批注、超链接都可能在XML里留下痕迹。把docx改成zip解压翻document.xml是常规操作。6.3 二维码与其他编码类隐写二维码题一般是图片里有多个二维码需要拼接或者修复才能扫或者二维码被反色、遮挡、扭曲需要用工具修复。zbarimg是命令行扫二维码的利器zbarimg target.png如果扫不出来试试图像预处理反色、二值化、矫正透视。这类题不难但需要耐心。一个容易被忽略的点二维码有时藏在音频的频谱图里或者图片的alpha通道里。遇到图片看起来是纯色但文件很大的情况优先检查alpha通道。7. 我的排查决策流程与工具组合讲了这么多手法最后把整个排查思路串成一条线。隐写题最怕的就是东打一枪西打一枪有条理的排查顺序能极大提升效率。拿到一道题我会按下面的顺序走第一步确认真实类型。file命令 十六进制看文件头判断后缀是否被改。第二步基础信息扫描。binwalk看嵌套strings找可读文本exiftool看元数据。这三步能覆盖掉相当一部分简单题。第三步针对载体深入。图片就看位平面和chunk音频看频谱压缩包试伪加密。第四步检查附加数据。对照文件结束标记确认尾部有没有拼接内容。第五步处理提取物。提取出的数据可能是另一个文件回到第一步继续。常用的工具我整理成一张表用途工具关键命令/参数文件类型识别filefile *嵌套文件扫描binwalkbinwalk -e可读字符串stringsstrings -n 6元数据exiftoolexiftoolPNG位平面zstegzsteg -a通用位平面StegsolveGUI操作PNG结构pngcheckpngcheck -v二进制查看010 Editor / xxd手工定位音频分析Audacity / sox频谱视图二维码zbarimgzbarimg7.1 几个反复踩到的坑说几个我自己翻过车的点都是技巧性不强但特别耗时间的第一通道顺序盲区。zsteg默认按RGB读如果数据是BGR顺序藏的就提不出来要手动调整参数。光这一点我浪费过好几次时间。第二提取物保存格式错误。LSB提取出来的是二进制有人用文本模式读取保存结果文件被破坏。一定用二进制模式(wb)写文件。第三忽略文件结尾的空白。有些题把数据藏在文件末尾的一大段0字节之后工具扫描可能跳过需要手动拉到底部看。第四JPEG的量化表隐写。前面提到的ctf jpeg隐写题有相当一部分是把数据编码进量化表或者DCT系数里这种用常规LSB工具完全无效得解析JPEG的段结构和量化表对比正常图片找异常值。遇到怎么都提不出来的JPG题往这个方向想。7.2 关于工具选择的一点个人看法工具用哪个其实不是最重要的重要的是理解它背后的原理。我见过太多人手里工具一大堆binwalk -e一跑看没结果就放弃了其实数据就藏在binwalk识别不了的地方。工具是拿来验证想法的不是拿来替你思考的。真正拉开差距的是你对一个PNG文件从第几个字节到第几个字节是什么这种细节的掌握程度。当你脑子里能看见文件的二进制结构隐写题就不再是碰运气而是一道有明确解法的推理题。我在实际参赛和刷题的过程中最大的体会是隐写题的训练不是靠背工具命令而是靠一次次亲手用十六进制编辑器把文件拆开、对照、复原。每次做完一道题把文件的结构图在纸上画一遍比做十道题不总结要有用得多。如果你刚开始接触这个方向建议从一道最简单的PNG附加数据题入手全程用十六进制编辑器手工完成一次提取把每个字节看明白再逐渐上工具提效。这个顺序比上来就依赖自动化工具稳得多。