2026软件系统安全赛 · Misc WP
misc
第一层
第一步
题目给的是一个 misc.zip,解压后只有一个没有后缀的文件:
1 | steganography_challenge |
先不要急着改后缀,直接看文件头。
用十六进制工具或者脚本检查后可以发现:
- 文件开头前 35 字节不是正常图片头
- 在偏移
35处出现了标准 PNG 文件头:
1 | 89 50 4E 47 0D 0A 1A 0A |
说明这其实不是“完全未知文件”,而是一个前面塞了垃圾数据的 PNG。
所以第一层第一步要做的事就是:
- 定位 PNG 文件头
- 从 PNG 头开始把真正图片数据抠出来
- 找到
IEND,把完整 PNG 单独导出
也就是说,本质上是在做一次文件雕刻。
第二步
把 PNG 抠出来之后,继续检查结构。
可以读到 IHDR:
- 宽高:
1024 × 1024 - 色彩模式:
RGB - 位深:
8
但这张图并不是正常 PNG,因为:
- 普通图片查看器打不开
IEND后面还有额外数据IDAT解压后的长度不对
把所有 IDAT 数据拼起来后解压,得到:
1 | 3147255 bytes |
而正常的 1024×1024 RGB PNG 图像流长度应该是:
1 | (1 + 1024 * 3) * 1024 = 3146752 |
也就是说,多了:
1 | 503 bytes |
这说明这张 PNG 不是单纯“坏了”,而是图像数据流里被人为插入了额外内容。
同时,IEND 后面的尾部还能看到字符串:
1 | layer2.png |
所以这里已经可以判断:
- 题目在故意把你往“图层 / 隐写 / 套娃”方向引
- 真正重点不是把这张 PNG 完全修复,而是从里面继续挖隐藏层
第三步
虽然 PNG 本身有问题,但仍然可以做一个“近似恢复”:
- 按 PNG 扫描行方式解码
- 遇到非法 filter byte 时强行按
0处理 - 先把大致图像内容还原出来
恢复出的 approx.png 已经足够继续分析。
这一步的意义不是直接出 flag,而是为了拿到后面真正的隐写载体。
也就是说,第一层最后得到的是:
1 | approx.png |
第二层
第一步
对 approx.png 做隐写分析。
这里重点检查的是 LSB(最低位隐写)。
把整张图的 RGB 三个通道最低位依次取出,按 bit 流重新拼成字节流后,可以在偏移 4 处发现标准 ZIP 文件头:
1 | PK\x03\x04 |
说明图片里真正藏的是一个压缩包。
所以第二层第一步要做的事是:
- 读取
approx.png - 取每个像素
(r, g, b)的最低位 - 每 8 位拼成 1 字节
- 搜索 ZIP 头和 ZIP 尾
- 把这段数据单独导出为一个新的 ZIP 文件
导出后得到:
1 | lsb_embedded.zip |
第二步
解开 lsb_embedded.zip,可以看到里面有:
1 | flag.zip |
这时题目的结构已经很明显了:
flag.zip里藏最终结果pass1.zip ~ pass6.zip用来给出密码
继续检查这些压缩包内部信息,可以发现:
- 每个
pass*.zip里只有一个data*.txt - 每个
data*.txt的原始大小都是:
1 | 4 bytes |
- 全部都被加密
- 压缩方式是:
1 | store |
- 同时给出了对应的
CRC32
也就是说,这里并不是让你真的去盲爆复杂密码,而是在告诉你:
明文特别短,只有 4 字节,可以直接根据 CRC32 反推原文。
第三步
根据 data1.txt ~ data6.txt 的 CRC32,可以把 6 段 4 字节明文全部反出来:
1 | data1.txt = pass |
按顺序拼接后得到提示语:
1 | pass is c1!xxtLf%fXYPkaA |
所以真正要取的不是整句话,而是后面的密码:
1 | c1!xxtLf%fXYPkaA |
也就是说,第二层最后的结论就是:
1 | flag.zip 的密码 = c1!xxtLf%fXYPkaA |
第三层
第一步
用刚才得到的密码解开 flag.zip,拿到:
1 | flag.txt |
但 flag.txt 里并不是直接明文 flag,而是类似这样的内容:
1 | flag is here + 一长串看不见的字符 |
这些“看不见的字符”不是空白,而是零宽字符隐写。
这里能识别出两种字符:
\u200b:零宽空格\u200c:零宽非连接符
所以第三层第一步要做的事就是:
- 读取
flag.txt - 去掉前面的普通提示文本
- 提取后面的零宽字符序列
- 按二进制方式解释它们
第二步
把零宽字符映射为二进制:
1 | \u200b -> 0 |
然后每 8 位转成一个字节,再还原成字符串。
最终得到:
1 | dart{bf4100d9-cc8d-48f6-a095-54cbfad189e1} |
最终 flag
1 | bf4100d9-cc8d-48f6-a095-54cbfad189e1 |
