题目描述
给出一个名为 steganography.png 的图片文件。图片无法正常打开,也无法拖入 Stegsolve 等工具中进行分析。需要从中提取隐藏的 flag。
解题思路与步骤
1. PNG 文件结构修复 (IDAT 块异常)
首先,尝试将图片拖入 Stegsolve 时会报错。使用十六进制编辑器 (如 010 Editor) 或 Python 脚本检查图片的二进制结构,发现 PNG 的区块 (Chunks) 链被破坏。
具体来说,在正常的 PNG 文件中,一个数据块的结尾 (包含 4 字节的 CRC 校验码) 应该紧接着下一个数据块的开头 (4 字节的长度字段)。但在这个文件中,多个 IDAT 数据块之间被恶意插入了额外的字节,导致图片解析器因为遇到未知的长度和类型而无法继续解析。
通过脚本提取出这些被插入的额外字节,并在原文件中将它们删除。同时,为确保修复后的文件严格符合 PNG 标准,可以顺便重新计算并修复所有块的 CRC 验证值。
修复后得到一个正常的 PNG 文件 fixed2.png,现在它可以被 Stegsolve 正常打开了。
顺便一提:修复过程中提取出的多余字节为:a61ade 6428b2 1091025592d7 6c7f2c bcd754 a82fd7
组合起来:a61ade6428b21091025592d76c7f2cbcd754a82fd7
(这在后续步骤中没有用到,但属于常见的隐写思路。)
2. LSB 隐写提取 ZIP
图片修复后使用 Stegsolve 打开,利用 LSB (最低有效位) 隐写分析功能,在勾选了Red、Green和Blue的0通道后发现有PK(50 4b 03 04)和flag.zip的16进制数据,使用 Save Bin 提取 0 通道的数据,提取出了一个 ZIP 压缩包 flag.zip。
注意:使用工具提取出的文件通常包含了原文件末尾的大量垃圾数据。因为真正的 ZIP 文件结尾以 50 4b 05 06 (EOCD 标志) 结束,可以使用脚本将其后面的冗余数据截断,进而得到一个结构完全正常的 flag_fixed.zip。
3. ZIP 伪层叠与 CRC32 爆破
解压修复后的 flag_fixed.zip,发现里面包含了 7 个同样需要密码的压缩包:
flag.zippass1.zip~pass6.zip
查看 pass1 到 pass6 这 6 个压缩包的属性,发现里面的明文文件(data1.txt ~ data6.txt)的 大小全部仅有 4 个字节 。
对于这种加密压缩包已知明文极短的情况,可以直接利用明文的 CRC32 校验值进行爆破,无需密码即可知道明文内容。
通过 Python 脚本组合所有可打印的 ASCII 字符,对这 6 个 4字节长度文件的 CRC32 进行碰撞运算,结果如下:
pass1.zip(CRC:0xce70d424) -> 爆破结果:passpass2.zip(CRC:0xf90c8a70) -> 爆破结果:ispass3.zip(CRC:0xff3fe4bb) -> 爆破结果:c1!xpass4.zip(CRC:0x242a5387) -> 爆破结果:xtLfpass5.zip(CRC:0x9a27098e) -> 爆破结果:%fXYpass6.zip(CRC:0xd3f6df9f) -> 爆破结果:PkaA
拼接后得到重要提示(即最终密码):pass is c1!xxtLf%fXYPkaA
4. 零宽字符隐写解密
利用获取到的密码 c1!xxtLf%fXYPkaA 解压加密的 flag.zip,里面包含了一个 flag.txt 文件。
用文本编辑器打开 flag.txt,发现里面只有似乎是常规的一段话 flag is here。
但实际上该文本内隐藏了不可见的“零宽字符”。利用脚本对文本进行读取遍历,分析发现含有大量的:
- 零宽空格 (
\u200b) - 零宽不连字 (
\u200c)
这是一种典型的零宽隐写 (Zero-Width Steganography)。由于只存在两种不可见字符,将其理解为二进制串。
将 \u200b 替换为 0,将 \u200c 替换为 1,把整段编码转换为二进制字符串后,按每 8 位转换为对应的 ASCII 字符,成功还原出暗藏的 flag。
最终 Flag
dart{bf4100d9-cc8d-48f6-a095-54cbfad189e1}