
刷杂项题最怕的不是没思路而是每个知识点都“好像见过”一到手就卡住。这道名叫“赛博朋克”的题目我前前后后折腾了将近二十分钟最后发现自己其实栽在最基础的两件事上ZIP文件的文件头协议没吃透LSB隐写只听说过没用熟。题目本身不算刁钻但知识点覆盖得非常典型——压缩包伪装、伪加密、图片低位隐写全都在MISC方向里高频出现。这篇就把完整拆解思路和实操过程倒出来给还在刷题入门的朋友一个可以直接照做的参考。建议手边准备这几个东西一个十六进制编辑器一个支持无损格式的图片查看器一个能跑Python的环境。工具不在多关键是每一步都知道自己在看什么、改什么。1. 拿到赛博朋克题先别急着解压很多人在杂项题上效率低不是因为工具不会用而是拿到文件就默认它是什么就是什么。题目给的资源叫“赛博朋克”后缀可能是ZIP也可能是图片甚至可能是个没有后缀的二进制块。第一步永远不是解压而是先确认这个文件的真实身份。这道题的实际场景是这样的我下载下来一个名为“赛博朋克.zip”的文件大小只有几十KB第一反应是直接双击解压。但WinRAR弹了个错误说文件头损坏需要密码。这个“需要密码”的提示其实已经很不寻常了——正常压缩包即使有密码文件头也应该能被正确解析只是解压的时候才会要求输入密码。一个连文件头都被判定为“损坏”的包大概率是被动过手脚。这时候我的习惯是分四步走用file命令看一眼真实文件类型用binwalk扫描文件里是否存在内嵌的其他文件用十六进制工具打开直接看文件头签名根据签名判断是用错扩展名、伪加密、还是整包被塞进了其他文件里。这个顺序看起来很基础但能过滤掉九成以上的“假压缩包”问题。赛博朋克这道题就是典型例子file显示它是一个ZIP压缩包binwalk也扫出了ZIP的签名但解压程序却死死咬住“需要密码”。问题不在文件是不是ZIP而在ZIP内部的结构被动了手脚。1.1 工具清单与实际分工我常用的组合并不复杂都是公开、常见的工具工具用途说明file识别文件真实类型系统自带先做一次粗判断binwalk扫描文件内的隐藏数据能快速发现ZIP、PNG等签名010 Editor / WinHex查看和修改二进制内容关键字段的定位和修复都靠它7-Zip解压测试、检验修复结果对ZIP结构的报错信息比WinRAR更直白StegSolve查看位平面、提取LSB隐写杂项题必备的Java图形工具Python PIL自定义提取LSB处理StegSolve无法覆盖的细节情况工具不在多StegSolve和010 Editor是这题的核心。前者负责后半段的隐写提取后者负责前半段的ZIP结构修复。两者的切换逻辑非常清晰先二进制修包再像素提取。1.2 为什么不能盲信扩展名扩展名是操作系统给人看的标签不是文件本身。一个文件叫“赛博朋克.zip”它完全可以是PNG图片、PDF文档甚至是纯文本。判断文件真实格式最可靠的依据是文件头签名也就是一组特定的十六进制字节。常见格式的文件头签名我都记了一些方便快速对照ZIP50 4B 03 04也可能是50 4B 05 06这种空压缩包PNG89 50 4E 47 0D 0A 1A 0AJPEGFF D8 FFGIF47 49 46 38PDF25 50 44 46ELF可执行文件7F 45 4C 46。这些签名只要瞄一眼就能知道文件到底是什么。赛博朋克这题的文件头是50 4B 03 04所以确认是ZIP但ZIP的“文件头协议”并不仅仅指开头这两个字节后面一大段结构都有讲究。下一节重点讲这个。2. ZIP文件头协议压缩包最容易被忽略的细节很多人对ZIP的理解停留在“能压缩”“能加密”“能解压”但一旦碰到“压缩包损坏”或者“伪加密”就完全摸不着方向。ZIP文件头协议本质是一套二进制的元数据格式它描述了文件在压缩包内如何存储、是否加密、压缩算法是什么、文件名多长、数据在哪个偏移位置。CTF题目最喜欢动的手脚就在这个结构里。2.1 ZIP的局部文件头长什么样ZIP文件由三大部分组成若干个局部文件头Local File Header、中央目录区Central Directory、以及目录结束标记End of Central Directory Record。每个被压缩的文件都会先有一个局部文件头再跟一段压缩数据。局部文件头的关键字段是这样的偏移量长度内容典型值0x004字节文件头签名50 4B 03 040x042字节解压所需版本14 000x062字节通用位标志00 00或00 010x082字节压缩方法00 00存储、08 00Deflate0x0A2字节文件修改时间无固定值0x0C2字节文件修改日期无固定值0x0E4字节CRC32校验值无固定值0x124字节压缩后数据大小无固定值0x164字节未压缩数据大小无固定值0x1A2字节文件名长度无固定值0x1C2字节扩展字段长度无固定值0x1EN字节文件名ASCII字符串这里最需要留意的就是偏移量0x06的通用位标志。它的Bit0如果为1表示这个文件是加密的如果为0表示不加密。CTF里的伪加密通常就是把这个bit改成1让解压工具认为文件需要密码但实际上压缩数据根本没有被加密。换句话说这就像一个假锁看着锁上了实际上门根本没关。2.2 伪加密一个bit如何拦住所有人用十六进制工具打开正常的ZIP包局部文件头第7和第8个字节偏移0x06和0x07通常是00 00。伪加密时这里会变成01 00也就是第一个字节最低位被置为1。但这里有一个非常坑的点ZIP文件里同一个文件的加密标志并不只出现在局部文件头还出现在中央目录区Central Directory File Header签名50 4B 01 02中。中央目录区内同样有一个通用位标志字段。有些题目只改局部文件头有些只改中央目录有些两个都改。如果只修复其中一个另一个区域的标志没有清除解压工具依然可能报需要密码。判断到底是不是伪加密有一个非常朴素的方法把两个区域的加密标志位都改成0然后用解压工具测试。如果修改之后直接解压成功说明原本就是伪加密如果提示密码错误或者数据出错说明是真加密那就要考虑其他思路了。赛博朋克这道题中局部文件头的0x06位置显示01 00中央目录区中的对应标志也是01 00。我先把这两个位置都记录下原始字节然后逐一改成00 00再用7-Zip测试。修改后解压非常顺畅没有要求输入密码这就确认了是伪加密。2.3 修复伪加密的具体操作用010 Editor打开ZIP文件后定位到局部文件头签名50 4B 03 04之后直接看第7个字节。也可以搜索50 4B 01 02定位中央目录区检查同样位置的字段。以赛博朋克这题为例我搜到的是局部文件头签名后第7字节为01中央目录区签名后第7字节也为01。在010 Editor里逐个改成00保存后退出。再用7-Zip测试时已经没有任何密码提示。修改时我习惯先复制一份原始文件避免改坏后没有退路。伪加密修复只修改标志位一个字节理论上风险很低但谁也不能保证文件里还有其他陷阱。2.4 其他常见的ZIP文件头伪装除了伪加密ZIP还经常被改扩展名、改签名、甚至把文件头整个抠掉。比如一个压缩包被改名为PNG图片后文件头还是50 4B 03 04此时用图片查看器会直接报错。反过来有些题目会把一个ZIP文件头50 4B 03 04篡改成50 4B 03 05之类的无效值让压缩包“文件头损坏”。处理这类题目的通用步骤是用binwalk扫描原始文件看它能否识别出内嵌的ZIP签名。如果能直接提取出来如果不能手动搜索50 4B 03 04或者50 4B 01 02在十六进制工具中定位修正错误的字节。3. LSB隐写藏在图片最低位里的暗道修复完ZIP之后解压出来一张PNG图片图片本身看起来就像一张赛博朋克风格的城市夜景图。到这里很多人以为结束了但杂项题不会把flag直接放在压缩包里。这张图片大概率还藏着一层隐写。常见的图片隐写包括LSB隐写、EXIF信息、文件尾附加数据、颜色通道异常而赛博朋克这道题考的是LSB。3.1 为什么PNG图片能藏数据PNG是一种无损格式图片的每个像素颜色由RGB三个分量组成每个分量通常占8个bit取值范围0到255。人的眼睛对颜色的细微变化非常不敏感尤其是对最低位的改变。一个像素的红色分量从240变成241肉眼几乎看不出任何区别。LSB隐写利用的就是这种感知盲区。它把秘密信息的二进制数据逐bit拆开替换掉图片像素分量中的最低位。比如原始像素的红色分量是240二进制是11110000如果把最低位改成1就变成11110001也就是241。多个像素的冗余空间足以塞下一段完整的flag。用一个生活类比来理解这就像在一面白色墙壁上用几乎透明的颜料写了一段文字。正常光线下去看墙壁还是白的但只要换一个特定角度的光文字就浮现出来了。LSB隐写就是这种“特定角度”的数字实现。3.2 用StegSolve逐层检查位平面StegSolve是一个专门用来做图片隐写分析的小工具打开图片后进入Analyse菜单选择Data Extract。界面里可以分别勾选Red、Green、Blue三个通道的位平面Bit Plane从0到7共8个层级。正常情况下一个普通图片的0号位平面看起来是均匀的彩色噪声没有任何几何图案或文字轮廓。如果某个通道的最低几位呈现出明显的规律性或者能隐约看出文字的形状说明这里被塞了数据。StegSolve左下角的预览窗口会实时显示提取结果。赛博朋克这张图在Bit Order选择“LSB First”、勾选RGB三个通道的0号位平面之后左下角直接出现了一段可读的文字。这个现象非常典型隐藏信息未经任何二次加密直接以ASCII明文字节流写入最低位所以用位平面视图就能轻松看到。3.3 自己写脚本提取LSBStegSolve能解决的问题只是“肉眼看到”如果隐藏信息不是按常规顺序写入或者数据被拆分在多个通道的不同bit位就需要用脚本精确提取。我自己写Python时通常用PIL库逐像素读取RGB值把想要的分量最低bit拼接起来再按8bit一组还原成字节。from PIL import Image img Image.open(cyberpunk.png) pixels img.load() width, height img.size bits [] for y in range(height): for x in range(width): r, g, b pixels[x, y][:3] # 提取三个通道的最低bit按RGB顺序排列 bits.append(r 1) bits.append(g 1) bits.append(b 1) # 每8个bit转成一个字节 data bytearray() for i in range(0, len(bits) - 7, 8): byte 0 for j in range(8): byte (byte 1) | bits[i j] data.append(byte) # 过滤可打印字符并输出 print(data.decode(latin-1, errorsignore))这个脚本的提取顺序是“逐像素、每像素内按RGB、每个bit按高位在前”。实际运行后我在输出里直接看到了形如flag{...}的字符串。需要特别注意的是不同题目写入LSB时的顺序可能不一样。有些按RGB顺序有些按BGR有些低位在前有些高位在前有些按行优先有些按列优先。因此一个脚本提不到正常文字不代表没有隐写可能只是顺序不对。这种情况下我建议把三个变量做成可配置项逐个尝试。4. 完整实操从赛博朋克题目到flag前面两节把原理讲清楚了这一节把整道题的完整流程走一遍。所有关键步骤和细节都会写出来方便直接对照操作。4.1 第一步识别文件身份拿到赛博朋克.zip后我先在终端里执行file 赛博朋克.zip输出是赛博朋克.zip: Zip archive data, at least v2.0 to extract确认是ZIP包。然后执行binwalk扫描binwalk 赛博朋克.zip扫描结果显示在偏移0的位置发现了ZIP签名50 4B 03 04没有发现内嵌的其他文件。这说明文件就是一个独立的ZIP压缩包没有用图片包着ZIP也不是ZIP包着图片的复合结构。4.2 第二步查看ZIP文件头并定位标志位用010 Editor打开ZIP搜索十六进制串50 4B 03 04很快就定位到了第一个局部文件头。在010 Editor的模板视图里可以看到通用位标志字段的值为0x0001Bit0是1。也就是说ZIP的局部文件头认为自己“加密了”。接着搜索50 4B 01 02找到中央目录区对应的通用位标志同样为0x0001。两个区域都带了加密标志位这就是伪加密的典型形态。4.3 第三步修复伪加密字节在010 Editor里把局部文件头偏移0x09和中央目录区偏移0x09处的01都改成00。保存之后我用7-Zip打开压缩包文件列表正常显示不再提示输入密码。双击里面的图片文件可以正常解压。这里有一点要强调修复磁盘上第二个01的时候不要盲目全局替换所有01为00。ZIP内部有很多字段都可能包含01这个字节比如压缩方法、文件名内容。只修改通用位标志字段其他一律不动。4.4 第四步解压并检查内容物用7-Zip解压后得到一张名为cyberpunk.png的图片。先用file确认它是PNGcyberpunk.png: PNG image data, 800 x 600, 8-bit/color RGB, non-interlaced图片是一张赛博朋克风格的街景图霓虹灯、雨夜、高楼感官上完全正常。普通看图软件打开没有任何异常。这时候就需要怀疑LSB隐写。4.5 第五步StegSolve提取LSB数据打开StegSolve加载PNG图片进入Analyse菜单的Data Extract。在Bit Plane选项里把Red、Green、Blue都勾选为0Bit Order选LSB First然后点击Preview。左下角预览窗口出现了文字开头的几个字符是flag{。整段文字直接显示在预览区域里。由于隐藏信息没有加密只是逐bit嵌入StegSolve的预览功能足以完整还原。如果预览窗口发现文字是倒序或者乱序可以把Bit Order改成MSB First或者调整通道顺序再试。不同工具写LSB隐写的顺序不同StegSolve的可视化选项恰好覆盖了大部分情况。4.6 第六步整理Flag并提交从StegSolve预览窗口复制出整段flag{...}内容。提交到CTF平台时注意不要有多余的空格或换行。这道题的flag是一串很短的字符串比如flag{cyberpunk_lsb_zip_protocol}这种格式。实际做题时我复制完直接粘贴到提交框一次通过。整个流程下来真正耗时间的不是提取操作而是前期识别伪加密和分析图片位平面的过程。5. 常见问题与排查技巧刷题多了会发现同样的知识点换个出题方式就变了个坑。这里把做题过程中最常见的几种卡点整理出来方便以后遇到类似问题时快速排查。5.1 压缩包提示文件头损坏怎么办“文件头损坏”这个提示在CTF杂项题里的出现率非常高。可能的原因有三种一是扩展名被改错文件本身不是ZIP二是ZIP签名被别人改成无效值三是文件被截断缺少目录结束记录。排查顺序是先用file和binwalk看真实类型和签名位置再用十六进制工具搜索50 4B 03 04和50 4B 01 02如果签名正常但解压失败检查文件末尾是否有50 4B 05 06。缺少结束记录时可以用7-Zip的“修复压缩文件”功能或者手工在文件末尾补一段目录结束结构。后者比较麻烦但好在绝大部分CTF题目只要恢复签名就能解决。5.2 伪加密和真加密如何区分区分方法其实很简单把加密标志位改成0然后尝试解压。如果文件正常解压说明原本是伪加密如果提示密码错误或者解压出来的文件CRC校验失败说明是真加密数据和文件头都做了密码处理。还有一种辅助判断方法看压缩数据的熵。真加密的ZIP压缩数据部分看起来是完全均匀的随机字节伪加密的ZIP压缩数据部分还是正常的Deflate流包含大量可识别的结构特征。把标志位改成0后如果解压得到一堆乱码文件可能是压缩方法或者数据偏移量也被改动过不只是伪加密的问题。5.3 LSB提取出来是乱码怎么办用StegSolve能看到位平面有异常但提取出来的字符串是乱码通常是因为顺序不对。我遇到最多的情况是通道顺序不是RGB而是BGRbit组合顺序是LSB First但脚本写成了MSB First数据写入时按列优先而不是按行优先每个像素不只取最低位而是取了连续两个bit或多个bit。遇到乱码时我会调整Python脚本的这几个参数组合尝试。比如把通道顺序改成BGR把字节拼接改成小端模式或者把步长改成2。大部分情况尝试几次就能得到明文。5.4 StegSolve异常或图片显示不了位平面StegSolve对JPEG格式支持不好因为JPEG是有损压缩像素值经过离散余弦变换后并不直接等于原始RGB值LSB位早就被破坏掉了。如果题目给的图片是JPG建议先转换成PNG或BMP再分析。转换过程也要用无损格式不要用截图或者再保存一次JPEG否则会进一步破坏低位信息。如果StegSolve打不开可能是Java环境问题也可以直接用zsteg这个命令行工具检测。它能自动尝试多个通道和多个位平面组合一条命令输出所有可能的隐藏信息。5.5 一张图多重隐写的扩展思路有些进阶题会在同一张图里既放LSB隐写又在文件尾部追加隐藏文件或者修改图片的宽高比隐藏信息。赛博朋克这道题没有那么多花活但做题时如果发现LSB提取结果只是线索而不是最终flag就需要回头再看一遍图片的原始二进制数据。检查图片末尾是否有50 4B开头的ZIP签名检查PNG的IHDR块里宽度和高度是否被修改过检查EXIF段是否包含字符串。这些都能作为二次排查的方向。不过这些都是后话先把ZIP文件头协议和LSB隐写本身掌握扎实才是基础中的基础。具体到赛博朋克这题我最大的收获不是多认识了一个工具而是重新理解了“伪加密”这个概念的判定逻辑。以前遇到要密码的压缩包第一反应就是找密码、试爆破其实只要看一眼通用位标志可能就只是一个bit的事。同理看到图片就想StegSolve自动提取也需要注意提取顺序是否匹配写入顺序。这两件事都不难但每一个“不难”的背后都需要真正理解底层结构才能做到心里有数。以后再看到类似“文件头损坏”或“位平面有规律噪声”的提示至少知道该往哪个方向排查了。