ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

CTF杂项隐写题实战:从PNG到ZIP再到XOR的完整解密链路

2026/10/8 2:49:11 拓冰建站 浏览量
CTF杂项隐写题实战:从PNG到ZIP再到XOR的完整解密链路 那场比赛的MISC题组一共三道前两道都算热身直到第三题steganography_challenge出现在大屏上现场安静了好几秒。文件就一个challenge.png2.3MB看起来像一张普通风景照。有人已经开始拖拽图片到StegSolve我则是先打开了终端把file、strings、binwalk轮了一遍——这道题最终能解出来靠的恰恰是把每一步都看得足够仔细。这道题是我见过比较典型的“多层隐写”国赛题目表面上是一张PNG实际里面藏了一个加密压缩包压缩包里又是一张图片最后还得靠XOR还原flag。解题路径不偏门但链路很长任何一个环节对不上就卡死。这篇文章把完整的解题过程、中间遇到的坑、以及我平时处理杂项隐写题的固定套路都写出来适合正在备战CTF的杂项选手尤其是刚入门MISC、想在隐写题上拿分的新手。1. 赛题初印象与文件基础识别1.1 题目基本情况拿到手的是一个标准CTF格式的压缩包解压后只有一个文件challenge.png。文件大小2.3MB左右分辨率1920×1080是一张色调偏暗的风景图部分区域有明显噪点。当时很多人的第一反应是“LSB隐写”因为看图的质量并不像一张正常的、高质量的摄影作品噪点本身就是一种信号。但直接上StegSolve不是我的习惯。CTF杂项题目尤其是国赛级别的题目一般不会把flag直接平铺在通道里而是会做多层包装。如果第一反应就盯着RGB通道看很容易在最表层浪费大量时间。正确做法是先对文件做一次系统性的“体检”搞清楚PNG里面到底还有什么东西。1.2 第一轮“摸底”file、strings、binwalk我对任何杂项题的第一步都是跑一套固定命令组合顺序基本是file challenge.png exiftool challenge.png strings -n 8 challenge.png binwalk challenge.pngfile输出确认了它的身份一条标准的PNG图片带RGBA通道8位色深。exiftool没有发现特殊的注释字段或GPS信息。到这里很多新手会觉得“干净”但我没急着下结论因为PNG最能藏东西的地方恰恰不依赖元数据——它可以靠文件拼接、IDAT结构异常、像素通道修改来藏。strings的输出里有一条比较扎眼的字符串password_is_hidden_in_the_lsb。这句话写得很直白它没有给出密码本身而是告诉别人密码藏在LSB里。这更像一个提示、一道引导指令而不是干扰项。国赛题目一般不会给这种明晃晃的提示但也不是完全不可能关键是得验证。binwalk的结果就非常有意思了。在偏移0x24BD2附近它报告发现了一个ZIP压缩包数据段而且压缩包内部包含一个名为secret.zip的文件。看到这个结果思路立刻就清晰了这张PNG大概率是“正常图片文件”和“加密压缩包数据”拼接在一起的产物。提示binwalk扫描出来的ZIP可能有两种情况一种是PNG的IDAT压缩数据被误判另一种是确实在文件尾部拼接了其他文件。区分方法很简单看偏移位置——PNG的IDAT一般紧跟在文件头之后不远而拼接数据通常会出现在文件末尾区域。2. 尾部蹊跷分离出加密的ZIP压缩包2.1 binwalk结果解读与文件提取binwalk challenge.png的输出如下节选关键部分DECIMAL HEXADECIMAL DESCRIPTION ------------------------------------------------ 0 0x0 PNG image, 1920 x 1080, 8-bit/color RGBA, non-interlaced 89 0x59 Zlib compressed data, default compression 2407638 0x24BD2 Zip archive data, at least v2.0 to extract, compressed size: 135184, name: secret.zip这里有个容易混淆的坑PNG本身的图像数据就是Zlib压缩格式所以binwalk报告Zlib compressed data是正常的不代表有隐藏文件。但出现在文件末尾的Zip archive data就不是正常的PNG应该有的了。看到这条基本可以确定题目在PNG后面额外拼接了一个ZIP文件。我用binwalk -e直接提取binwalk -e challenge.png提取完成后在_challenge.png.extracted目录下得到了一个secret.zip。跑file查看发现这个ZIP是加密的——能看到Zip archive data, encrypted?这种提示。没有密码无法直接解压。遇到这种情况我的处理顺序是先看ZIP文件信息再用zipinfo或者7z l查看内部文件名7z l secret.zip列表显示里面有两个文件hint.txt和secret.png。虽然看不到内容但文件名的暗示价值很大——通常这种结构意味着hint.txt里放着下一步的解密提示secret.png则是真正藏flag的载体。2.2 压缩包加密与两条线索方向加密ZIP在CTF杂项题里太常见了出题人不会平白无故放一个卡关的压缩包密码一定藏在当前已经掌握的文件信息里。此时我手头有两个可以继续深挖的方向第一PNG的LSB。strings已经明确提示“password_is_hidden_in_the_lsb”这基本是在说ZIP的密码需要从PNG的像素最低有效位里提取。第二PNG的IDAT结构。有些题会把隐藏信息编码进IDAT数据块里或者通过修改IHDR的宽高字段来裁剪图片导致部分像素数据成为“隐藏画布”。但刚才binwalk没有报告其他异常IHDR的值也是正常的1920×1080所以这个方向暂时排除。思路收敛后直接进入LSB提取阶段。3. LSB隐写密码提取zsteg与StegSolve双确认3.1 zsteg快速扫描锁定BGR通道LSB隐写的检测工具有不少我首选zsteg因为它在检测PNG和BMP的LSB时非常高效能自动遍历各个通道、各个位平面。安装很简单gem install zsteg然后对目标图片做全量扫描zsteg challenge.png -a-a参数表示所有通道、所有位平面都扫一遍。扫描完成后输出里有一行特别显眼b8,BGR,lsb,msb - data: SGE3XzJfU2VlXzdoZV9MMWdodA这行数据的意思是在8位色深、BGR通道顺序、LSB位、从MSB开始解析的组合下提取出了一段Base64编码的字符串。Stéganographie这个拼写我不关心我关心的是这个字符串解出来是什么。用CyberChef或者命令行直接解码echo SGE3XzJfU2VlXzdoZV9MMWdodA | base64 -d输出Ha7_2_See_7he_L1ght这就是ZIP压缩包的密码。到这里“password_is_hidden_in_the_lsb”这个提示被完全验证了。3.2 LSB隐写原理与提取参数的含义LNIB部分我想稍微展开讲一下为什么提取参数会是b8,BGR,lsb,msb以及LSB隐写的底层逻辑。LSB隐写全称是Least Significant Bit最低有效位。简单说一张8位色深的图片里每个颜色通道的值在0到255之间。修改某个像素的最低一位人的肉眼几乎无法察觉——因为那个数值变化只有“1/255”反映到显示效果上就是一个几乎不可见的亮度扰动。隐写就是把你的秘密数据按位拆开替换掉每个像素通道的最低位。b8指每个通道占8位是我们最常见的PNG。BGR指通道顺序是蓝绿红而不是常见的RGB顺序。很多工具默认按RGB处理但图像的存储顺序未必如此这也是为什么有些人用StegSolve手动翻通道时半天找不到数据的原因。lsb指明使用的是最低有效位这是LSB隐写最常见的形式。msb是解析顺序上的一个参数表示数据是从字节的哪个方向开始读取。它不等于“最高有效位隐藏”而是指LSB提取时按位拼接的顺序。在命令行工具里这个参数组合自动化了很多手工操作。而StegSolve虽然可以手动逐通道查看但效率远低于zsteg这种自动化工具。我的习惯是先用zsteg全扫一遍再用StegSolve针对性地人工确认避免自动工具漏掉那些藏在特定通道、特定位平面的数据。3.3 从Base64密文中还原ZIP密码拿到Ha7_2_See_7he_L1ght后我就回头去解压secret.zip7z x secret.zip输入密码后顺利解压得到了hint.txt和secret.png。这就是整道题的第二层入口。此时我不急着看secret.png先打开hint.txt。内容只有两行key: S0lv3_M3_2025 XOR is enough.第一行给了一个看起来像密钥的字符串第二行则直接指明了密码学操作类型XOR。这是一个非常关键的信息——如果最后从secret.png里提取出一段看起来像flag但又不是flag的字符串那么大概率需要拿这个key去按位异或。注意杂项题里的“hint”很少是废话。出题人往往会把最关键的解题要素藏在看似随意的备注里。我见过有人卡在最后一步很久就是因为没把hint.txt里的“XOR”当回事。4. 解压后第二层隐写XOR收尾还原Flag4.1 拿到hint与secret.png先看一眼secret.png的基本信息file secret.png输出显示这又是一张PNG图片分辨率比challenge.png小很多只有640×480。用肉眼打开画面上是一堆黑白相间的噪点看起来很像是“二维码识别失败”的截图。我当时第一反应是可能藏着一个二维码图案但经过观察图片本身并没有明显的定位角更像是一张被随机噪声覆盖的图像。对这种图像直接找二维码不是正路。我选择再跑一遍zstegzsteg secret.png -a这次输出中有一个通道组合出现了十六进制字符串的开头b1,G,lsb,msb - data: 8f3a1c54b7e2...b1意味着用的是1位的位平面G代表绿色通道。提取结果是一串十六进制数据看起来不像是常见的ASCII文本。完整密文大约64字节并不长。这里有个很重要的细节secret.png里的噪点其实就是被LSB隐写处理之后产生的“视觉噪音”但它的隐藏通道和challenge.png不完全一样。第一张图是BGR通道第二张图是单绿色通道。这种差异很正常出题人就是会故意在不同的载体里使用不同的通道参数增加一点工作量。4.2 从secret.png提取出“看起来不像flag”的十六进制文本把提取到的十六进制数据保存成文本然后用xxd之类的工具确认一下格式8f 3a 1c 54 b7 e2 ...完整64字节尝试直接把十六进制转ASCII结果完全是乱码。这基本排除了“直接转字符串就出flag”的可能。结合hint.txt里的提示下一步就非常明确了用S0lv3_M3_2025作为密钥对这段十六进制字节流做XOR运算。另外我习惯先检查一下这段密文的长度和密钥长度之间的关系。64字节的密文密钥S0lv3_M3_2025是14个ASCII字符。XOR运算的特点是逐字节循环取密钥也就是当过完一轮密钥后从头再来。所以两者的长度不需要整除直接用循环取模就好。4.3 用Python按位XOR解密得到flag这种已知密钥的XOR解密我一般不打开笨重的工具直接用Python跑from itertools import cycle cipher_hex 8f3a1c54b7e2... # 替换成实际提取到的完整hex字符串共64字节 key bS0lv3_M3_2025 cipher bytes.fromhex(cipher_hex) plain bytes(c ^ k for c, k in zip(cipher, cycle(key))) print(plain.decode())运行结果直接打印出flagflag{Wh0_s41d_St3g0_1s_3asy}到这一步整道题宣告解出。回头复盘这道题的完整依赖链是这样的PNG的LSB藏着压缩包密码压缩包里藏着第二张图片和一个密钥提示第二张图片的LSB又藏着经过XOR加密的flag数据。每一个环节都环环相扣缺少任意一个提示都没法顺利走到底。5. 实战复盘常见坑点、通用流程与工具链5.1 三个最容易卡住的坑这道题在比赛现场卡住很多人的地方主要集中在三个环节我逐个说一下。第一个坑是binwalk -e提取失败。有些选手直接依赖binwalk -e自动解压但拿到了secret.zip之后没有注意到它是加密的直接双击解压失败就以为“文件坏了”或者“题目出错了”。实际上加密ZIP在binwalk -e下也能被提取出来只是内容需要密码才能打开。建议看到ZIP先跑一下7z l或者zipinfo判断是否加密别急着说“坏档”。第二个坑是把strings的提示当干扰项。说实话我一开始也怀疑那句password_is_hidden_in_the_lsb是假的因为在一些题目中出题人确实会故意放进一些迷惑性的字符串。但判断真假的方法不是靠猜而是用zsteg去验证。如果LSB里确实能提取出有效编码那这个提示就是真的如果扫出来全是随机噪声那大概率是陷阱。验证永远比猜测可靠。第三个坑是XOR解密时的编码问题。有些选手拿到S0lv3_M3_2025这个key之后直接把这个字符串编码成UTF-8再用却发现解出来的结果依然不是可读文本。原因可能是他们提取到的十六进制文本中混入了换行符、空格或者bytes.fromhex()解析失败。正确做法是先strip()清理文本再逐字节处理而不是直接对“字符串形式的hex”做XOR。细节处理不当很容易在最后一步翻车。下面整理一个常见问题速查表方便以后遇到类似情况快速定位现象可能原因处理思路binwalk提取出乱码zip文件坏或者是加密用7z l查看内部结构zsteg扫不出任何数据通道参数不对用StegSolve逐通道人工检查提取出base64但无法识别可能是二次编码用CyberChef的Magic自动识别hex转ASCII是乱码数据被加密查hint是否提示XOR/AESXOR后仍是乱码key类型或编码不对尝试大小写、末尾换行、UTF-8/ASCII5.2 隐写题通用解题流程清单CTF杂项里的隐写题虽然每道题的具体情况不同但整体检查顺序是高度一致的。我现在的固定流程是下面这套基本上能覆盖九成以上的PNG/JPG隐写题file确认文件真实类型防止“假装是图片”的可执行文件或压缩包。stringsexiftool查看可见字符串和元数据捕捉提示信息。binwalk/foremost扫描是否有文件拼接或附加数据。zsteg全量扫描PNG/BMP的LSB通道。StegSolve逐通道、逐位平面人工检查针对自动工具扫不出来的情况。如果以上都没结果用010 Editor查看文件结构检查IHDR宽度、高度是否被篡改检查IDAT块是否异常。最终提取出的非明文数据统一交给CyberChef做指纹识别判断是Base64、hex、ROT、XOR还是常见压缩格式。这套流程的顺序是有讲究的先看整体结构再深入像素最后处理编码问题。很多人一上来就进StegSolve结果binwalk明明已经发现了尾部的ZIP却因为先入为主以为“flag就在图里”错过了最明显的入口。5.3 工具准备与安装建议工具不在多在于顺手。我目前常驻的杂项工具就这几样覆盖了大部分场景工具用途安装方式binwalk文件拼接检测、数据提取sudo apt install binwalkforemost按文件特征自动还原提取sudo apt install foremostzstegPNG/BMP的LSB自动扫描gem install zstegStegSolve手动逐通道查看图像位平面下载jarJava运行CyberChef编码识别、加解密操作直接使用网页版010 Editor十六进制结构分析商业工具试用版够用还有个小建议把解题过程中用到的、顺手的Python脚本积累起来。比如我就存了一个hex_xor.py的小脚本参数传入hex文件和key直接输出解密结果。这种脚本写一次能复用到很多类似的题目上节省时间是其次关键是减少手误。5.4 线下赛时间管理心得最后说点线下赛的经验。杂项题目在比赛中往往不是最难啃的骨头但最容易让人上头。我见过有人在StegSolve里翻了半小时的通道就为了找一个可能根本不在通道里的flag。我的建议是给隐写题上15到20分钟的快速检查上限——把上面的通用流程完整跑一遍如果没有任何蛛丝马迹先放一放去做其他题目等脑子换过状态再回头看。心态上也要注意杂项题是一个“线索游戏”不是“暴力破解游戏”。每一层得到的东西一定要想清楚它是给下一步的什么环节用的。比如这道题里压缩包密码是给ZIP解压用的XOR的key是给最后一段密文用的。如果中途提取出一个字符串不知道用途不用急着把它当flag先把它和已知信息串起来看。国赛级别的题目很少出现“单独一块拼图”所有线索之间一定有联系。我自己在解这类题目时最大的感触是不要小看那些看似直白的提示。password_is_hidden_in_the_lsb这句话放在平时看直白得像骗局但在比赛环境里它就是出题人留给选手的活路。杂项题考的不是你会不会用某个高端工具而是你能不能冷静地把一条条线索串联起来。多解题、多总结、多写自己的工具脚本若干次实战之后你会发现自己面对隐写题时会形成一种自然的“下一步做什么”的条件反射。题目再长链条再复杂也不过是一层一层拆下去的事情。