news 2026/10/17 2:15:31

CTF隐写入门:ZIP文件头修复与LSB隐写提取实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CTF隐写入门:ZIP文件头修复与LSB隐写提取实战

CTF里的隐写题,看着永远是最“无害”的一类——不考漏洞利用、不考内网渗透,但做起来经常让人对着一个图片发呆半小时,最后发现线索就在眼皮底下。这道BugkuCTF“赛博朋克”就是典型代表:题目名字看起来很赛博,压缩包解出来也是一张赛博朋克风格的图片,但核心考点其实只有两个词——ZIP文件头协议和LSB隐写。

这两个知识点本身都不复杂,但组合在一道题里,刚好能串起一整条隐写题的解题链路:先判断文件真实类型,再修复文件头,解压出真正的载体,最后从图像像素的最低有效位里把flag捞出来。不管你是刚入坑CTF的萌新,还是只会对着题解“照作业”的玩家,把这条链路吃透,以后遇到七八成隐写题都能自己往下走了。这篇我就以这道题为例,把ZIP文件头的结构、伪加密的识别修复、LSB的原理和提取脚本整个过一遍,附带我实际踩过的一些坑。

1. 出题思路拆解:为什么是ZIP文件头加LSB隐写

很多人拿到隐写题的第一反应是打开看图、用StegSolve扫,扫不出来就开始焦虑。这个顺序其实错了一半。真正高效的思路是:先搞清楚手头这个文件到底是什么,再考虑“里面能藏什么”。而这道题选ZIP文件头协议和LSB隐写这两个点,本身就代表了CTF隐写题最常见的两类出题手法。

1.1 组合题的出题逻辑

CTF隐写题里,纯单点考察越来越少,出题人更喜欢“套娃”:把一个信息经过多重隐藏,做成一条解题链。ZIP加图片的组合尤其常见,原因很实际。

ZIP文件头是可修改的。文件头协议里那几个魔数字节不是不可变的,改掉之后,系统就不会再把它当成ZIP文件,但里面的压缩数据仍然完好。这天然适合做成第一层障碍:下载下来一个文件,file命令识别不出来,直接双击也打不开,你得靠对二进制结构的熟悉程度把它“救回来”。

LSB隐写则适合做第二层障碍。它不需要额外生成文件,直接把信息埋在图片像素里,视觉上和原图几乎没有任何差异。尤其是赛博朋克风格的高饱和霓虹图片,暗部、噪点、半透明光效特别多,像素值本身就比较杂,埋点东西进去肉眼完全看不出异常。这就是为什么载体偏偏选赛博朋克风格的图片——不是为了好看,是为了让LSB痕迹更难被察觉。

所以这道题整体链路一般是:拿到一个无扩展名或伪装过的文件 → 修复ZIP文件头 → 解压得到一张图片 → 图片里有LSB隐写 → 提取出flag。理解了这条链路,你就知道每一步该干什么了。

1.2 第一步该做什么:文件类型识别三板斧

拿到任何未知文件,我建议先执行三个命令,成本极低,但能避免后面走弯路:

工具作用典型命令
file根据文件头识别真实类型file cyberpunk
hexdump查看文件开头十六进制字节hexdump -C cyberpunk | head -20
binwalk扫描文件里是否嵌入了其他文件binwalk cyberpunk

这里要强调一下,file命令识别的依据就是文件头内容。如果出题人把文件头前几个字节篡改了,file命令会给出错误结果,但hexdump能让你看到原始字节。我个人的习惯是:file结果和十六进制开头对照着看,如果发现“file说是JPEG,但开头却是50 4B”,那说明文件头被改过或者文件被拼接了,接下来就要按真正的类型去处理。

另外,先备份原文件再动手,这是血泪教训。修复文件头、改标志位这种操作是对二进制直接写入,写错了原文件就废了,从头再来还要重新下载。

2. ZIP文件头协议:从“PK”到伪加密

ZIP文件头协议是这道题绕不开的第一个技术点。很多人对ZIP的认知只有“能解压的压缩包”和“文件头是PK”,但做题时需要看得更细一些,至少要知道文件头里哪些字节决定了解压行为。

一个标准ZIP文件会包含三种关键签名,分别出现在文件不同位置:

签名十六进制位置作用
局部文件头50 4B 03 04每个压缩文件条目开始处
中央目录50 4B 01 02文件末尾区域的目录信息区
中央目录结尾50 4B 05 06整个ZIP文件的末尾标记

hexdump打开一个正常ZIP,通常能看到类似这样的结构:

00000000 50 4b 03 04 14 00 00 00 08 00 21 07 5a 7f 62 60 |PK........!.Z.bb| 00000010 b3 2f 3d 8f 4d 30 cb 0a 33 00 00 00 34 00 00 00 |./=.M0..3...4...|

开头的50 4b 03 04就是局部文件头签名,也就是网上常说的“PK”头部。紧跟着的字段里,有几个值得重点关注:

  • 偏移4-5字节(14 00):解压所需版本,做题时基本不用管。
  • 偏移6-7字节(00 00):通用标志位,这就是伪加密判断的核心,后面细说。
  • 偏移8-9字节(08 00):压缩算法。08表示Deflate,是ZIP最常见的压缩方式;00表示不压缩(Stored)。
  • 偏移18-21字节(33 00 00 00):压缩后大小。
  • 偏移22-25字节(34 00 00 00):原始大小。

做题时主要用的就是前10个字节。尤其是通用标志位,伪加密题百分之百靠它做文章。

2.1 伪加密的识别与修复

伪加密,英文叫Fake Encryption,意思是压缩数据本身并没有真正加密,只是把文件头里的加密标志位置成了1。当你尝试解压时,压缩软件看到加密标志就会要求输入密码,但实际上根本没有加密,把标志位改回去就能正常解压。

通用标志位具体怎么读?在局部文件头中,第6、7字节是十六进制值,它的bit0(最低位,0x01)代表“加密”,bit3(0x08)代表“数据描述符”。伪加密题的特征就是:bit0为1,但数据没有加密。

举一个真实常见的例子。用hexdump看到文件头是:

50 4b 03 04 14 00 09 00 08 00

这中间第6-7字节是09 00,二进制写作0000 1001。拆开看,bit0是1,bit3也是1。如果你尝试解压,会弹窗要密码,但你心里得打个问号:这密码是不是出题人故意的?此时验证方法很简单——不去爆破密码,直接改标志位。

用010 Editor或者HxD这类十六进制编辑器打开文件,定位到文件偏移6的位置(因为0-3是签名,4-5是版本,6-7是标志),把09 00改成00 00,保存后再解压。如果没有任何密码提示直接解出内容,那它就是伪加密。

这里要提醒一下:改的时候最好只动通用标志位那2个字节,后面的中央目录(中央目录里的对应标志位)如果主解压工具仍报错,可以一并改掉,大多数情况改局部文件头就够了。另外,7-Zip对伪加密的容忍度比WinRAR高,用7-Zip试一下也许直接就能打开。第二种判断方法是运行那个经典命令:

ZipCenOp.jar -r flag.zip

国外CTF圈子老工具ZipCenOp可以对ZIP的加密标志做批量重置,但它的使用环境依赖Java老版本,现在很多新机器跑不起来。所以我现在的首选方案仍是:先试7-Zip,再手动改字节,这两个方法基本覆盖90%的伪加密场景。

2.2 文件头被篡改时的修复技巧

除了伪加密,还有一种常见操作是把文件头整个换掉。比如题目文件的开头被改成50 4B 03 05或者干脆是一串和ZIP无关的字节,导致file命令识别为Data,Windows里也会直接报“文件已损坏”。

修复思路其实很简单:只要认出数据主体是ZIP结构,把开头改回50 4B 03 04即可。但前提是你要能从杂物里认出ZIP结构。我的判断顺序是:

  1. 用hexdump看前16字节。
  2. 如果是无扩展名文件,搜索整个文件里有没有50 4b 03 04或50 4b 01 02的重复出现。
  3. 用binwalk扫描,看有没有提示Zip archive data。

如果binwalk提示有ZIP数据但头部被破坏,修复时不要只改前4字节。要注意局部文件头和中央目录是否都完整。最稳的操作是:把文件头和中央目录签名都恢复成标准值,然后保存为新文件,再试解压。

常见的文件头魔数我也顺手整理了一份,遇到类似“改文件头”的题可以用到:

文件类型十六进制魔数ASCII文本
ZIP50 4B 03 04PK..
PNG89 50 4E 47.PNG
JPEGFF D8 FF E0....
GIF47 49 46 38GIF8
PDF25 50 44 46%PDF
BMP42 4DBM

修复脚本可以用Python写,几行就够。下面这个示例假设已知文件偏移0处应该修成ZIP头:

# fix_zip_header.py with open('cyberpunk', 'rb') as f: data = bytearray(f.read()) # 如果开头不是标准ZIP局部文件头,按标准字节修正 if data[:4] not in (b'PK\x03\x04',): data[:4] = b'PK\x03\x04' with open('fixed.zip', 'wb') as f: f.write(data) print('done -> fixed.zip')

更保险的做法是不覆盖原文件,始终输出到新文件。等你熟练了可以直接用十六进制编辑器改,但对新手来说,保留原始文件、另存修复结果能避免很多误操作。

3. LSB隐写:藏在像素最低位里的信息

解决完ZIP文件头,解压出来的图片才是第二层战场。如果打开图片肉眼看不到任何异常,别急着在脑门上写“扫不出来”,先想想LSB。

3.1 LSB原理:为什么肉眼看不出来

LSB全称Least Significant Bit,最低有效位。数字图像中每个像素由红、绿、蓝三个通道组成,每个通道通常用8位二进制表示,取值范围0到255。

把255写成二进制是1111 1111,把254写成1111 1110,两者只差最后一位。而这一位恰恰对颜色数值影响最小——最多让亮度变化1/255,人眼几乎无法察觉。LSB隐写干的事情,就是把要隐藏的信息的二进制位,逐个替换掉像素值的最低一位。

比如要隐藏字符“A”,它的ASCII码是65,二进制是01000001。假如某个像素的红色分量是10001010,把最后一位替换成0,它变成10001010不变;如果下一位需要1,但对应的绿色分量最后一位正好是0,就把它改成1。这样逐位替换,所有像素的RGB三个通道就能承载任意二进制数据。一行字符、一张小图片,甚至一个压缩包,只要体积允许,都可以塞进去。

用生活化的例子来理解:这就像在整面纯色墙纸上,用颜色差别只有1%的涂料写了一段文字。你站在三米外看,墙纸就是纯色的;凑到跟前对比色号,才能看出有些地方颜色微微不一样。LSB隐写就是这个思路,只不过“涂料的色号差别”体现在二进制最后一位。

之所以图片载体喜欢选PNG、BMP,是因为它们是无损格式,像素值原样保存;如果是JPEG这种有损压缩格式,压缩过程中像素值会被重新量化,LSB里嵌入的信息大概率会被破坏。所以做题时看到题目给的是PNG图片,LSB几乎是必查项。

3.2 用StegSolve和zsteg提取

提取LSB最老牌的工具是StegSolve,一个Java写的GUI小工具,适合手动查看和逐通道提取。打开图片后,操作路径是:

  1. 菜单栏选Analyse。
  2. 选择Data Extract。
  3. 面板里有R、G、B三个通道,每个通道有0到7共8个位平面。
  4. 只勾选R、G、B的0位(也就是每个通道的最低位),点Extract或Preview。

如果看到提取出来的左侧预览区出现一串ASCII字符或者明显的文件头,那基本就是flag了。把结果保存成文本或者文件即可。

命令行选手更推荐zsteg,一条命令扫完所有常见隐写组合:

zsteg -a cyberpunk.png

-a参数会遍历所有通道、所有位平面组合,输出可能的隐藏内容。如果flag是明文,屏幕上直接就能看到;如果隐藏内容是一张图片,zsteg会提示检测到了PNG/JPEG之类的东西。

我实际使用中,两个工具配合最有效:先用zsteg快速出结果,如果没出,再开StegSolve手动调整通道组合。因为有些题目会只隐藏在一个通道里,或者需要按特定顺序读取,zsteg的默认组合不一定覆盖到,StegSolve的手动勾选反而更灵活。

3.3 手写一个Python提取脚本

工具再好用,面试时、比赛断网时,手写脚本的能力就是底牌。用Python配合PIL库,提取LSB其实非常短。

下面这个脚本按“先红通道、再绿通道、再蓝通道,从上到下逐行扫描”的顺序,取出每个像素三个通道的最低有效位,拼成二进制串,再转成ASCII字符串:

# extract_lsb.py from PIL import Image def extract_text_from_lsb(image_path): img = Image.open(image_path) pixels = img.load() width, height = img.size bits = [] # 按顺序遍历像素:每个像素取R、G、B通道的最低位 for y in range(height): for x in range(width): r, g, b = pixels[x, y][:3] bits.append(r & 1) bits.append(g & 1) bits.append(b & 1) # 每8位拼成一个字节,再转字符 chars = [] for i in range(0, len(bits) - 7, 8): byte = 0 for j in range(8): byte = (byte << 1) | bits[i + j] chars.append(chr(byte)) # 提取结果是完整文本 + 大量空字节噪声,只输出可读部分 text = ''.join(chars) printable = ''.join(c for c in text if 32 <= ord(c) <= 126) return printable if __name__ == '__main__': print(extract_text_from_lsb('cyberpunk.png'))

运行后输出的可读字符串里如果能看到flag{...},那就成了。如果输出是乱码,不要慌,大概率是位序或读取顺序不对。常见的变体包括:

  • 字节内位序是反的,需要从第7位往第0位读,而不是从第0位往第7位。
  • 只取某一个通道,比如先全图取R通道,再全图取G通道,而不是每个像素交替取三通道。
  • 隐藏的不是文本,而是另一张图片或ZIP文件。那提取出来的二进制串转成字节后,前几个字节会显示出PNG或PK的文件头,此时需要把整个二进制流按字节保存成文件,再用file命令识别。

提取二进制转文件的核心代码,本质上就是一个“比特收集器”加一个bytes()拼接:

# 假设bits已经收集好 byte_values = [] for i in range(0, len(bits) - 7, 8): byte = 0 for j in range(8): byte = (byte << 1) | bits[i + j] byte_values.append(byte) data = bytes(byte_values) with open('hidden_out', 'wb') as f: f.write(data)

保存之后用file hidden_out查看真实类型,再按类型做后续处理。这一步在CTF里很常见,可能一个图里藏了另一个小图,小图里还有一层。

4. 模拟解题流程:神秘文件变成flag的完整过程

前面原理讲了一堆,接下来我用一个模拟题场景,把从拿到文件到拿到flag的完整流程走一遍。为了符合“赛博朋克”这道题的背景,我假设题目下载下来一个没有扩展名的文件,文件名就叫cyberpunk。

4.1 初始分析:file、binwalk、hexdump

拿到文件后,我的习惯顺序是:

ls -la cyberpunk file cyberpunk binwalk cyberpunk hexdump -C cyberpunk | head -20

假设file输出的是data,说明系统没识别出真实类型。binwalk扫描结果可能显示ZIP archive data,只是头部信息不对。再用hexdump看前20个字节,此时看到的可能是:

00000000 50 4b 03 05 14 00 00 00 08 00 21 07 5a 7f 62 60 |PK........!.Z.bb|

50 4b 03 05和标准ZIP的50 4b 03 04就差最后一个字节,这是出题人故意“改坏”的典型手法。认出这一点,修复方向就明确了。

4.2 修复ZIP头并破解伪加密

这里有两种路径,取决于binwalk能不能直接提取。如果binwalk已经把里面的ZIP部分分离出来了,那直接处理提取出的文件;如果binwalk只提示ZIP结构存在但没分离,就手动修复。

修复步骤:

  1. 复制一份:cp cyberpunk cyberpunk.bak。
  2. 用010 Editor打开cyberpunk,把偏移0处的50 4b 03 05改成50 4b 03 04。
  3. 保存为cyberpunk_fixed.zip。
  4. 尝试: 7z x cyberpunk_fixed.zip,或者直接双击。

此时如果出现“需要密码”的提示,就怀疑伪加密。重复前面说的:hexdump查看偏移6-7字节的通用标志位,发现是09 00或01 00之类的加密标志,直接改成00 00再保存。

伪加密修复之后,解压得到cyberpunk.png。图片是赛博朋克风格的高饱和霓虹夜景图,肉眼完全看不出异常。不要光看,直接进入LSB步骤。

4.3 在解压出的图片里提取LSB内容

拿到cyberpunk.png,先跑zsteg:

zsteg -a cyberpunk.png

如果运气好,输出里直接有flag{...}。但为了演示手写脚本的用法,也可以把自己写的extract_lsb.py跑一下:

python3 extract_lsb.py

输出里能看到一串可读字符,其中有类似flag{zip_lsb_...}的字段。到这里,这道题就算解完了。

4.4 解题流程总览

把上面整个过程简单总结成一张表,方便以后遇到类似题目时对照:

阶段动作工具产出
识别类型file / binwalk / hexdumpKali自带命令确认文件实际是ZIP
修复文件头把错误魔数改回50 4B 03 04010 Editor / Python可被识别的ZIP文件
处理伪加密通用标志位bit0清零十六进制编辑器 / 7-Zip可免密解压的ZIP
解压解压出图片7z / unzip赛博朋克风PNG图片
提取隐写内容扫描LSB位并还原数据zsteg / StegSolve / Pythonflag字符串

这个流程不是只对“赛博朋克”这一道题有效。换成任何“ZIP+图片”的隐写组合题,只要把文件名和图片风格换掉,解题路径几乎是一致的。

5. 常见问题与避坑经验

这一章我把实际做隐写题过程中踩过的坑整理一下,按“症状→原因→解法”的方式列出来,方便大家真的卡住的时候有据可查。

5.1 排查问题速查表

症状可能原因排查方向
file命令报data,打不开文件头被篡改hexdump看前16字节,对照文件头魔数表
解压ZIP时报“需要密码”伪加密查看偏移6-7字节的flag位,bit0为1则改为0
7-Zip打开提示“文件头错误”头部损坏或文件被拼接用binwalk下一层分离,或搜索全文找PK签名
解压后得到图片,肉眼无异常隐写不在可见区域跑LSB提取扫描,不要用眼睛判断
zsteg无输出位序/通道顺序特殊StegSolve手动勾选不同位平面组合
提取出来是乱码读取顺序或编码不一致尝试交换逐像素通道顺序、反转字节内位序
提取出来是文件头而非文本隐藏的是文件而不是字符串把二进制流保存为文件,用file识别类型
strings看不到flagflag做了编码或反向注意看提取结果是否base64、十六进制,多试几次方向

5.2 实际操作中的几点心得

第一个经验:看到什么类型,不要只信file命令的输出。file是拿文件头比对魔数库,文件头被改了它就“撒谎”。比赛里最容易被骗的就是这一步。每次我都用hexdump再确认一下前4个字节,最多10秒钟,能省后面半小时。

第二个经验:解压ZIP报错时,别急着上密码爆破工具。很多新手一看到压缩包有密码就去跑字典,但伪加密题的密码根本不存在,跑几小时也是白费。判断ZIP是否为伪加密,其实只要看通用标志位这一个字段。先花30秒做静态检查,比闷头爆破高效太多了。

第三个经验:LSB提取没有固定顺序。不同的隐藏脚本可能按“每个像素RGB”“先R后G后B”,也可能按“全图R通道扫完再G通道”,甚至可能是MSB顺序。工具扫不出来的时候,我建议手写脚本多试几个排列组合,比反复开GUI点鼠标快。这也是为什么我推荐大家至少会手写一个提取脚本,不是为了炫技,而是排查问题时它是最可控的手段。

第四个经验:培养“备份原文件”的肌肉记忆。修文件头、改标志位都是直接对二进制动手,写错一个字节文件就是废的。我现在只要对原始文件做修改,一定先复制一份.bak,改坏了可以用原文件重来。这习惯让我少交了很多份“重做作业”。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 2:14:44

基于MP2642GR-Z与STM32F756ZG的两串电池均衡方案设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 2:12:36

AIRI Web 版使用指南:从对话提供商接入到语音与角色卡配置

人工智能AI AgentMCP 服务工具调用GUI 自动化语音数字人交互助手 【免费下载链接】airi &#x1f496;&#x1f9f8; 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器&#xff0c;目标是接近 Neuro-sama 的高度&#xff1b;支持实时语音聊天、Minecraft 和 F…

作者头像 李华
网站建设 2026/10/17 2:12:21

达梦DM8主备切换实战:从原理到Switchover/Failover操作指南

作为 DBA&#xff0c;最怕听到的一句话就是“把库切一下吧”。“切库”在达梦数据库 DM8 的日常运维里&#xff0c;既可能是一次计划内的主备切换&#xff0c;也可能是一场毫无征兆的故障应急。我最早接触 DM8 切库时也踩了不少坑&#xff0c;总觉得它是高危操作&#xff0c;后…

作者头像 李华
网站建设 2026/10/17 2:12:15

5分钟跑通Qwen3.8-27B-NVFP4:vLLM快速开始完全教程

人工智能大模型模型量化多模态 【免费下载链接】Qwen3.8-27B-NVFP4 项目地址&#xff1a; https://ai.gitcode.com/hf_mirrors/nvidia/Qwen3.8-27B-NVFP4 点击查看 免费下载 Qwen3.8-27B-NVFP4 是 NVIDIA 基于 Model Optimizer 对 Qwen3.8-27B 大模型进行 NVFP4 量化后的版本&…

作者头像 李华
网站建设 2026/10/17 2:11:46

Cocos Creator特效包工程化落地:从导入到性能调优全指南

简介&#xff1a;这份 Cocos Creator 特效包面向 2D 游戏开发者与特效学习者&#xff0c;集中解决粒子效果素材零散、调试成本高的问题。包内共 149 个文件&#xff0c;以 81 个 meta 元数据、55 张 png 粒子贴图、11 个 prefab 预制体为主&#xff0c;另含 json 配置与 fire 场…

作者头像 李华