Challenge
This is an encoded message, the only tip you get is ‘I am the not of a file’.
Solution
Analysis
curl -sL -b "$HTS_COOKIE" -o 12.bmp \ 'https://www.hackthissite.org/missions/stego/lvl/12.bmp'file 12.bmp12.bmp: PC bitmap, Windows 3.x format, 123 x 1 x 8, image size 126, resolution 2834 x 2834 px/m, cbSize 1204, bits offset 1078一张 123×1、只有 1 个像素高的 8 bpp(调色板) BMP,整个文件才 1204 字节。
先把 BMP 头字段算清楚:
import struct
d = open("12.bmp", "rb").read()bf_type, bf_size, _, _, off = struct.unpack_from("<2sIHHI", d, 0)hsz, w, h, planes, bpp, comp, img_size = struct.unpack_from("<IiiHHII", d, 14)row = ((w * bpp + 31) // 32) * 4 # 8bpp 每行按 4 字节对齐print("bfType", bf_type, "bfSize", bf_size, "dataOffset", off)print("w", w, "h", h, "bpp", bpp, "biSizeImage", img_size)print("row_bytes", row, "pixel_bytes", row * h)print("palette_entries", (off - 14 - hsz) // 4)print("trailing", len(d) - off - row * h)bfType b'BM' bfSize 1204 dataOffset 1078w 123 h 1 bpp 8 biSizeImage 126row_bytes 124 pixel_bytes 124palette_entries 256trailing 2dataOffset=1078 正好等于 ,
即文件头 14 字节 + 信息头 40 字节 + 256 项调色板(每项 4 字节 = 1024)。
像素区是 字节(123 个像素 + 1 个对齐填充),像素之后只剩两个 0000 填充,
没有尾部附加文件。信息只能在像素值里。
确认这个调色板的内容:
import struct
d = open("12.bmp", "rb").read()off = struct.unpack_from("<I", d, 10)[0]pal = d[14 + 40:off]gray = all(tuple(pal[i:i + 3]) == (i // 4, i // 4, i // 4) for i in range(0, len(pal), 4))print("palette is grayscale identity:", gray)palette is grayscale identity: True调色板是恒等灰度斜坡:第 i 项就是 (i, i, i)。所以像素索引值等于它显示出来的灰度,
每字节一个像素,直接读原始字节即可。
题面 I am the not of a file 里的 not 就是按位取反:255 - x。
把 124 个像素字节逐个取反:
pixel bytes : af b4 fc fb eb ff ff ff ff ff 8b 7f 04 c7 a5 b5 ...NOT (255-x): 50 4b 03 04 14 00 00 00 00 00 74 80 fb 38 5a 4a ...取反后的头四个字节是 50 4b 03 04(PK\x03\x04):一个 ZIP 归档的本地文件头。
原来这张图片的像素值,本身就是某个文件按位取反后的样子,正如提示所说:
它就是某个文件的 not。
把取反结果当作 ZIP 打开:
import ioimport structimport zipfile
d = open("12.bmp", "rb").read()off = struct.unpack_from("<I", d, 10)[0]w, h, planes, bpp = struct.unpack_from("<iiHH", d, 18)row = ((w * bpp + 31) // 32) * 4px = d[off:off + row * h]decoded = bytes(255 - b for b in px) # bitwise NOTwith zipfile.ZipFile(io.BytesIO(decoded)) as z: print(z.namelist()) print(z.read("pass.txt").decode())6ae4nt5TB