☰
ZIP文件头与LSB隐写:从文件识别到PNG提取的CTF Misc实战解析
2026/10/17 1:46:11 网站建设 项目流程

前几天在某个CTF练习平台上刷Misc题,遇到一道叫“赛博朋克记录”的题目。附件是一个没有扩展名的文件,题目描述倒挺有氛围:这是一段在赛博世界里被篡改过的记忆档案。我一开始以为会碰上一堆花里胡哨的加解密,结果老老实实做完才发现,整道题最关键的就两个基础知识点:ZIP文件文件头协议和LSB隐写。分数不高,但把两个Misc入门路上经常出现的内容揉在一道题里,反而比很多高难度的题更适合写复盘。

这篇文章就按我实际做题的顺序来写:从拿到无后缀文件开始,到用file和xxd识破伪装,到手动修复ZIP文件头,再到从PNG里提取LSB隐写数据,最后把一串看起来像乱码的东西变成flag。如果你刚开始接触CTF的Misc方向,或者对文件识别和图片隐写还处于“听说过但没实操过”的状态,顺着这篇走一遍会顺畅很多。

1. 这道题表面花哨,内核却很收敛

1.1 先说题目背景和考点分布

题目叫“赛博朋克记录”,听名字像是做复杂分析的,但打开附件后你会发现它给的其实是一个非常典型的外带文件:无扩展名、无头文件信息提示、只有一个孤零零的文件。这类出题思路在Misc里很常见,它不会好心地给你一个flag.zip或者stego.png,而是先把扩展名抹掉、把文件头改坏、或者把多个东西拼在一起,考验你能不能从二进制底层把文件“认”出来。

这道题的核心考点拆开只有三块:

  • ZIP压缩包的文件类型识别与文件头协议判断;
  • 通过文件尾的EOCD结构确认真实格式;
  • PNG图片的LSB最低有效位隐写提取。

前两点都属于“文件结构”范畴,第三点属于“图片隐写”范畴。它们之间靠一条线索串联:先把一个被破坏的ZIP修复,解压出一张PNG,再从PNG里挖flag。

1.2 拿到附件后我不急着解题,先走一遍通用流程

做Misc题,尤其是拿到“不知道是什么文件”的附件时,我一般会按一套固定流程来:

  1. file命令看文件类型;
  2. xxd或hexdump看十六进制开头和结尾;
  3. binwalk扫描文件内是否嵌了其他文件;
  4. strings提取可打印字符串,快速找线索。

这套流程看起来简单,但能解决掉相当一部分“伪装类”题目。因为很多文件类型识别就是靠文件头部固定字节,也就是常说的魔数(magic number)。你不需要把文件完整读完,只要盯着头部那几个字节,心里基本就有数了。

这道题的关键恰恰卡在“文件头协议”上:文件真实类型是ZIP,但是头部被改了一个字节,导致各种工具完全不认。这一步如果没想明白,后面全部白搭。

2. 先说ZIP文件头:这其实就是一张“快递标签”

2.1 文件头是“魔数”,不是扩展名

很多初学者有个根深蒂固的误解:文件类型是由扩展名决定的。但实际情况是,操作系统和识别工具更信任文件内部的魔数。所谓魔数,就是文件开头一段固定的字节序列,用来标记文件格式。比如:

  • PNG图片头部通常是89 50 4E 47(对应ASCII字符.PNG);
  • JPEG图片头部通常是FF D8 FF E0;
  • ZIP压缩包头部通常是50 4B 03 04。

file命令的原理就是读取这些头部字节,去匹配内置的魔数库,然后告诉你它是什么类型。扩展名只是给人看的,骗不过file。所以每题拿到手,第一件事永远是看文件头,不是看名字。

ZIP文件的头部签名50 4B 03 04里,50 4B正是ASCII字符PK,这也是为什么你用十六进制编辑器或者xxd看ZIP文件时,右侧字符区域经常能看到“PK”两个字符。这两个字母和ZIP格式的发明者姓名缩写有关系,算是一个历史遗留彩蛋。

2.2 ZIP结构里的三次“PK”签名

ZIP不是只有文件开头一个签名。一个结构完整的ZIP压缩包,内部至少包含三个关键区域,每个区域都有自己的“PK”签名:

区域十六进制签名ASCII作用
本地文件头50 4B 03 04PK\x03\x04每个压缩条目开始处,记录文件名、压缩方式、CRC32等信息
中央目录50 4B 01 02PK\x01\x02压缩包的文件索引,记录所有条目的偏移和属性
EOCD(中央目录结尾记录)50 4B 05 06PK\x05\x06位于文件末尾,记录中央目录的位置、条目总数等

解压工具拿到ZIP后,会先扫描文件末尾的EOCD,找到中央目录的位置,再根据索引去读每个本地文件头,最后解压数据。也就是说,ZIP不是简单地把压缩数据堆在一起,而是有一个“目录系统”在管理。

这个结构特点非常关键:就算你把文件开头的本地文件头改得面目全非,只要EOCD还在文件尾部,这个文件依然可以被确认是ZIP格式。对于CTF题来说,这就是翻盘的机会。

2.3 文件尾的EOCD是怎么“救场”的

我在拿到那份无后缀附件后,第一时间就看了尾部字节,发现末尾出现50 4B 05 06。再加上文件中部有50 4B 01 02,三个签名里出现了两个,我心里基本可以确定:这就是一个ZIP压缩包,只不过头部被人动了手脚。

EOCD还有一个好处:它记录了中央目录的偏移位置和条目数量。就算你前面全部乱掉,只要EOCD里的偏移信息没被破坏,理论上都能把ZIP重新拼回来。这就像一栋楼的入口指示牌被人涂了,但楼后面的出口指示牌还在,管理员看一眼出口就知道这是哪栋楼。做题时遇到解压失败,别急着删除重下,先看尾部。

3. 修复被篡改的ZIP文件头:一次完整的排查过程

3.1 第一步侦察结果

我在终端里执行了最基础的两个命令:

$ file cyberpunk cyberpunk: data $ xxd cyberpunk | head -n 3 00000000: 504b 0305 1400 0200 0800 9b8e 4512 a0b4 PK..........E... 00000010: ed5c 2a1f 7a9f 25c1 b804 2e90 3412 8f00 .\*.z.%.....4...

file直接输出data,意思是它认不出这是什么。但看一眼十六进制内容,开头四个字节是50 4B 03 05,而ZIP的标准本地文件头是50 4B 03 04。差别非常小,只有最后一位从04变成了05,但魔数不匹配,所以file不认、unzip也大概率拒绝处理。

我再用tail或者直接看文件末尾:

$ xxd cyberpunk | tail -n 2 000004f0: 0101 2020 4e4f 5445 2a2a 2a2a 2a2a 2a2a ... 00000520: 504b 0506 0000 0000 0100 0100 4800 0000 PK........H...

这里出现了50 4B 05 06,也就是EOCD签名。结合前面的50 4B 01 02中夹着的信息,判断这是ZIP已经是板上钉钉的事。

3.2 为什么一个字节之差会让工具“失明”

有人可能会问:50 4B 03 05和50 4B 03 04就差1,至于吗?

至于。签名识别就是严格匹配一整段魔数,不是模糊匹配。很多工具看到头部不是标准签名,就会直接认为“这不是ZIP”,或者报告“signature not found”。这也是出题人最喜欢利用的点:不重写整个文件,只改一个字节,就能让几乎所有常规工具失灵。

注意到本地文件头里的第四字节04其实也和“版本号”概念有一定关系,如果被改成其他值,工具会认为版本信息异常。但最稳妥的做法就是把它还原回04,不要去猜别的。在做这类修复题时,有一个原则叫“最小改动原则”:只在确认是错误的地方做修改,别乱动偏移、压缩方式、CRC等字段。

我用Python写了一小段修复脚本:

data = bytearray(open("cyberpunk", "rb").read()) print("before:", data[:4].hex()) if data[:4] == bytes.fromhex("504b0305"): data[:4] = bytes.fromhex("504b0304") with open("cyberpunk_fixed.zip", "wb") as f: f.write(data) print("after:", data[:4].hex())

输出:

before: 504b0305 after: 504b0304

如果你在Windows上,用010 Editor或者WinHex手动改两个字节也一样,核心思路是把文件头恢复成标准签名。改完后再跑一次:

$ file cyberpunk_fixed.zip cyberpunk_fixed.zip: Zip archive data, at least v1.0 to extract $ unzip cyberpunk_fixed.zip Archive: cyberpunk_fixed.zip inflating: readme.txt inflating: cyberpunk.png

到这里,文件头协议的第一个关键点就解决了。

3.3 解压后的第一个收获:提示文件

ZIP里有两个文件:readme.txt和cyberpunk.png。

readme.txt内容大意是:

图层的画面看起来一切正常,但系统日志显示位平面存在可疑抖动,解码顺序参考BGR。

这就是出题人留下的路线图。它明确告诉你两件事:

  • 图片里有隐写数据;
  • 提取时通道顺序不是常规的RGB,而是BGR。

如果你不动脑子直接上工具,很可能按照默认的RGB顺序提取出一堆乱码,然后卡很久。这也是这道题比单纯考“你会不会用StegSolve”更值得写复盘的原因:它逼着你真去读提示、调参数,而不是只会点按钮。

4. LSB隐写到底藏了什么:原理、检测与提取

4.1 为什么最低有效位适合藏信息

LSB的全称是Least Significant Bit,最低有效位。一张PNG图片的每个像素通常由R、G、B三个颜色通道组成,每个通道是一个0到255的整数,在二进制里就是8位。比如某个像素的红色分量是0b10110010,最后一位就是0。把最后一位改成1,红色分量从178变成179,变化只有1/255,人的肉眼根本分辨不出来。

LSB隐写就是把你的秘密信息拆成单个bit,按顺序写进每个颜色通道的最低位。载体越大,能藏的数据就越多。PNG是无损压缩格式,压缩过程中不会破坏这些最低位的信息,所以LSB隐写在PNG里非常常见。

可以用一个很生活化的类比来理解:房间门牌号都写在门牌上,如果只是把门牌号的最后一位数字“稍作涂改”,从101改成100,路人不会觉得有什么不对劲,但住户自己知道这代表了一个暗号。LSB隐写也是一样,表面的颜色几乎没有变化,内部却藏着完整的信息。

4.2 用StegSolve做可视化确认

拿到cyberpunk.png之后,我第一件事是用StegSolve打开,逐个查看颜色通道的位平面。

StegSolve是CTF图片隐写最常用的图形化工具之一,操作路径是:

  • File→Open File打开cyberpunk.png;
  • 点击图片下方或右侧的通道切换,查看Red plane 0、Green plane 0、Blue plane 0等视图。

正常照片的某个位平面应该只是明暗变化,不会出现太明显的规律性图案。如果图片里藏了LSB信息,那么在对应的位平面上往往会出现一些规律性的噪点、条纹、甚至直接浮现文字轮廓。我切到其中一个通道的plane 0之后,看到了明显的规律性色块,确认这张图里有LSB隐写数据。

然后进入Analyse→Data Extract面板,这里有两个关键参数需要设置:

  • Bit Order:选择LSB First,因为是按最低位提取;
  • Bit Plane Order:常规思路选RGB,但这题的提示说要用BGR,所以我改成BGR通道顺序。

参数调好之后,可以先点击Preview预览,如果能直接看到可打印字符或规律内容,说明方向对了。然后点Save Bin保存提取出的原始二进制数据。

4.3 为什么还要手写Python脚本提取

StegSolve能胜任大多数简单情况,但它也有局限:它把所有通道顺序和bit位组合都做成枚举选项,参数一旦错了,输出就是乱码,而且不太方便做进一步处理。所以我更习惯在StegSolve确认“有隐写数据”之后,再用Python脚本按自己指定的顺序精确提取。

这里贴出我的提取脚本:

from PIL import Image img = Image.open("cyberpunk.png").convert("RGB") width, height = img.size bits = [] for y in range(height): for x in range(width): r, g, b = img.getpixel((x, y)) # 题目提示是BGR顺序,所以先b,再g,再r bits.append(b & 1) bits.append(g & 1) bits.append(r & 1) # 每8个bit拼成一个字节 data = bytearray() for i in range(0, len(bits) - (len(bits) % 8), 8): byte = 0 for j in range(8): byte = (byte << 1) | bits[i + j] data.append(byte) with open("extracted.bin", "wb") as f: f.write(data) print(f"extracted {len(data)} bytes")

脚本逻辑不复杂:遍历所有像素,按BGR顺序依次取每个通道的最低位,把所有bit收集起来,再每8位组成一个字节,写到一个文件里。执行完之后生成了extracted.bin。

之所以写成“提取到文件”而不是直接打印,是因为隐写内容可能是文本、可能是压缩包、可能是图片数据,直接打印容易把终端搞乱。先存成文件,再用其他命令去判断内容形态,更稳妥。

5. 从一串“乱码”到flag:最后一公里

5.1 先看看提取出来的东西长什么样

$ xxd extracted.bin | head -n 5 00000000: 5a6d 7868 5a33 6c7a 5a58 5a76 ... ZmxhZ3lz...

看到ZmxhZ3...这串字符,第一反应就应该是Base64编码。因为Base64的字符集是A-Z、a-z、0-9、+、/,以及末尾可能的=,提取结果正好是一串能直接看懂的ASCII文本,同时明显符合Base64的长度规律。

这里有个非常常见也值得记住的特征:Base64编码后的文本长度一定是4的倍数,如果原文长度不是3的倍数,末尾会补充=或==。字符串开头如果是ZmxhZ3,解码出来就是flag。我看了提取结果,开头正好是ZmxhZ3,基本可以直接说:题目稳了。

5.2 解码命令与最终输出

在Linux终端里直接解码:

$ base64 -d extracted.bin flag{...}

如果提取出来的是纯文本,base64 -d会直接把结果打到标准输出。要是内容比较长,也可以存成文件再看:

$ base64 -d extracted.bin > flag.txt $ cat flag.txt

我这边解出来的是一个flag{...}格式的字符串,主题正好和“赛博朋克记忆”对应。完整提交到平台之后,也验证了解题链路没有问题。

这道题的完整处理流程可以总结成下面这张表:

阶段输入主要处理输出
文件识别无后缀附件file、xxd、检查尾部EOCD判断文件是ZIP
文件头修复损坏的ZIP文件将50 4B 03 05改为50 4B 03 04可解压的ZIP
解压修复后的ZIPunzipreadme.txt、cyberpunk.png
LSB提取PNG图片StegSolve/自定义脚本按BGR提取extracted.bin
编码解码extracted.binBase64解码最终flag

可以看到每一步的输出都是下一步的输入,这就是Misc题典型的串联式结构:文件头给你“门”,图片隐写给你“钥匙”,编码给你“最后一道锁”。

6. 踩坑记录与可复用的Misc解题清单

6.1 我在这道题里踩过的三个坑

第一个坑是差点把文件当垃圾数据处理。看到file输出data,一开始我以为附件是一段随机数据,甚至想跳过。幸好习惯性地多看了一眼尾部,才找到EOCD签名。经验:file不认识不代表文件没用,它可能只是头被改了或者被拼接了。

第二个坑是调用StegSolve时默认按RGB顺序提取。如果只看题目提示“提取顺序参考BGR”,可能意识不到这个细节有多重要。我一开始按常规顺序提取,得到的是乱码,后来改成BGR顺序才正常。隐写题里的通道顺序就是出题人故意埋的第二个坑,你不调对顺序,就算数据躺在那里也读不出来。

第三个坑是字节对齐问题。LSB提取时如果选成MSB First,或者每8位的拼接顺序反了,最终也会全部错位。判断方法很简单:先看提取出来的内容是否呈现可读的ASCII字符形态,如果全是“雪花”一样的乱码,先检查Bit Order,再检查通道顺序,别盲目继续解析。

6.2 值得存进笔记的工具速查

整理一份我自己常用的Misc命令和工具速查表,给新手指引方向:

命令/工具用途使用时机
file 文件名识别真实文件类型拿到任何未知附件的第一步
xxd 文件名查看十六进制内容需要确认文件头、文件尾时
binwalk 文件名扫描文件内嵌文件怀疑文件拼接或隐藏文件时
strings 文件名提取可打印字符串快速寻找提示、Base64串、flag片段
unzip/7z解压压缩包文件头修复后执行解压
StegSolve图片隐写可视化查看位平面、提取LSB数据
zsteg自动化扫描LSB快速验证PNG/BMP有无LSB隐写
base64 -dBase64解码处理提取出的编码数据

zsteg是一个针对PNG/BMP的Ruby工具,自动枚举多种通道组合,有时候一步就能出结果。但这道题里我更建议手动调参,因为题目明确给了BGR提示,手动操作反而能加深对隐写原理的理解。

6.3 这两个考点还能怎么变着考

ZIP文件头协议相关的题目不止“改一个字节”这一种玩法。常见的变体还有:

  • 伪加密:通过修改通用标志位让ZIP看起来需要密码,实际上没有加密;
  • ZIP注释隐写:在EOCD的注释区域藏明文信息;
  • 文件合并:把ZIP藏在PNG尾部或头部,用binwalk分离;
  • 中央目录偏移篡改:让解压工具无法找到正确的文件索引。

LSB隐写也不只存在于PNG单张图里。GIF分帧截取、调色板索引隐写、多张图片平均后的加权隐写,都是同一类思想的不同载体。只要理解了“把信息塞进不易察觉的冗余位”这个本质,换工具换格式都不难。

做完这道题之后,我把自己本地的“魔数速查表”补了一行:ZIP本地文件头50 4B 03 04。以后不管遇到多奇怪的附件,我都会先跑一遍file和xxd,先确认真实类型,再谈下一步。ZIP文件头协议和LSB隐写这两个知识点,看起来基础,却在Misc里反复出现。希望这篇记录能帮你少走几步弯路,下次刷到类似题目时,能直接想起“先看头、再看尾、最后按提示调通道顺序”这条路径。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询