misc

第一层

第一步

题目给的是一个 misc.zip,解压后只有一个没有后缀的文件:

1
steganography_challenge

先不要急着改后缀,直接看文件头。

用十六进制工具或者脚本检查后可以发现:

  • 文件开头前 35 字节不是正常图片头
  • 在偏移 35 处出现了标准 PNG 文件头:
1
89 50 4E 47 0D 0A 1A 0A

说明这其实不是“完全未知文件”,而是一个前面塞了垃圾数据的 PNG。

所以第一层第一步要做的事就是:

  1. 定位 PNG 文件头
  2. 从 PNG 头开始把真正图片数据抠出来
  3. 找到 IEND,把完整 PNG 单独导出

也就是说,本质上是在做一次文件雕刻。


第二步

把 PNG 抠出来之后,继续检查结构。

可以读到 IHDR:

  • 宽高:1024 × 1024
  • 色彩模式:RGB
  • 位深:8

但这张图并不是正常 PNG,因为:

  1. 普通图片查看器打不开
  2. IEND 后面还有额外数据
  3. 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

说明图片里真正藏的是一个压缩包。

所以第二层第一步要做的事是:

  1. 读取 approx.png
  2. 取每个像素 (r, g, b) 的最低位
  3. 每 8 位拼成 1 字节
  4. 搜索 ZIP 头和 ZIP 尾
  5. 把这段数据单独导出为一个新的 ZIP 文件

导出后得到:

1
lsb_embedded.zip

第二步

解开 lsb_embedded.zip,可以看到里面有:

1
2
3
4
5
6
7
flag.zip
pass1.zip
pass2.zip
pass3.zip
pass4.zip
pass5.zip
pass6.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
2
3
4
5
6
data1.txt = pass
data2.txt = is
data3.txt = c1!x
data4.txt = xtLf
data5.txt = %fXY
data6.txt = PkaA

按顺序拼接后得到提示语:

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:零宽非连接符

所以第三层第一步要做的事就是:

  1. 读取 flag.txt
  2. 去掉前面的普通提示文本
  3. 提取后面的零宽字符序列
  4. 按二进制方式解释它们

第二步

把零宽字符映射为二进制:

1
2
\u200b -> 0
\u200c -> 1

然后每 8 位转成一个字节,再还原成字符串。

最终得到:

1
dart{bf4100d9-cc8d-48f6-a095-54cbfad189e1}

最终 flag

1
bf4100d9-cc8d-48f6-a095-54cbfad189e1