HackThisSite - Steganography Mission 8
Challenge
Thank you t_n83
关卡页在
https://www.hackthissite.org/missions/playit/stego/8/,数据文件是
https://www.hackthissite.org/missions/stego/lvl/stego8.bmp。题面只有一句作者的致谢,
没有任何提示,答案大小写敏感。
状态:live 已通关(verified)。提交后 profile 的
Stego: 一行出现 (8), 关卡页回执为
You have already done this mission.。
Sample
1 | curl -sL -b "$HTS_COOKIE" -o stego8.bmp \ |
1 | stego8.bmp: PC bitmap, Windows 3.x format, 157 x 97 x 24, image size 45784, |
一张 157×97 的 24 位 BMP,体积很小。直接打开只看到一幅白底图上印着一行字和一条下划线。
Solution
BMP 的隐写常见路径只有几条:尾部附加数据、头部声明尺寸和真实长度不符、调色板里的垃圾项。 先排除这几条常见路径:
1 | import struct |
1 | bfType b'BM' bfSize 45838 dataOffset 54 |
像素区正好 472 × 97 = 45784
字节,文件在像素之后没有任何多余数据(trailing=0); 24 bpp
的 BMP 没有调色板,palette
这条线也不用查。信息只能在像素值里。
分别统计 R、G、B 三个通道的取值分布:
1 | import numpy as np |
1 | R uniq = 26 top = [(14849, 255), (293, 0), (20, 240), (15, 124)] |
图像呈灰度外观,但通道统计显示:R 通道比 G、B 多出一批取值。进一步比对三个通道:
1 | import numpy as np |
1 | R!=G: 15 G!=B: 0 |
绝大多数像素满足 R == G == B(真正的灰度),只有
15 个像素例外,而且全部 集中在同一行
y=50、x 从 68 到 82
的一段连续像素上。这 15 个点就是全部秘密。
这 15 个像素的形态是
(R, 0, 0):绿、蓝被清零,红通道独自保留一个值。
把这一行原样打印出来:
1 | import numpy as np |
1 | 66 (255, 255, 255) |
肉眼没觉得是字,是因为这些值不构成图案,它们本身就是字节。 把每个红通道值当 ASCII 码读出来:
1 | 112 97 115 115 119 111 114 100 61 89 114 82 111 116 55 |
连起来是 password=YrRot7。
一串代码把上面几步串起来即可,提取结果:
1 | import numpy as np |
1 | password=YrRot7 |
注意等号前的 password= 只是标签,真正的密码是等号之后的
6 个字符,大小写敏感。
Key points
- 这是一次伪灰度 +
单通道编码:整幅图看是灰度,价值信息只出现在红通道少数像素上。
定位方法和通道统计强绑定:先做三通道取值分布对比,再看多出来的那些值落在哪些坐标,
一步就能定位到
y=50那 15 个像素。 - 它不是 LSB
位平面隐写:每个载体的红通道里存的是一整个可打印 ASCII 字节
(
112='p'、97='a'等),而不是打散到低位。形状上也没有点阵文字的图案。 信息在数值里而不在几何形状里,所以strings也读不出(字节区间不连续、 旁边没有可见的连贯字符串),必须按坐标取红通道值再转字符。 - 排除顺序仍然是:结构(头/尾/调色板)→ 通道分布 → 坐标取值。第 1、2 步快速排掉 尾部附加与头部尺寸不符;第 3 步把红独有像素选出来按 x 排序,直接得到明文字节流。
- 提交:
/missions/stego/template.php,表单字段为formkey(每次加载都变)、lvl、pass, 并且必须带Referer: .../missions/playit/stego/8/,否则后端拒绝。答案大小写敏感。