threebody.bmp
    下载下来图片打开,
    image.png
    stegsolve看下,在red 0通道可以看到一些信息:
    image.png
    “你们都是虫子”,但是并没有什么用。
    让我们再回到原始图片,放大看其中的细节:
    image.png
    可见图片中红黄蓝绿交错,有老式电视像素点的感觉。
    通过010Editor查看像素点数值:
    image.png

    可见相邻像素点的RGB值都差异巨大,正常情况下相邻像素点的RGB值应该差不多才对。 再仔细观察可以发现,如果以4为周期的话相邻像素的数值就差不多了,考虑正常情况下该图片的像素点应该是每4个一组。相当于原始图片是四维的,这里被“降维打击”成了三维。我们这里需要做的,便是对图片进行升维处理。 BMP格式的头部有个biBitCount字节是控制每个像素点所占的比特数,现在为24,也就是3个字节,我们将这个字段改为4个字节对应的32,得到正常图像:

    image.png
    stegsolve再次打开,在Blue 0通道看到信息:
    image.png
    data extract一下,看到了其中隐藏的信息:
    image.png
    接着换成Column方式再看看,又发现了新的信息:
    image.png
    通过搜索得知此人是知名的数学家大卫·希尔伯特
    这些信息暂时还用不了,接着010 editor再次打开,发现rgbReserved字段有值,而根据BMP文件结构,BYTE rgbReserved字段需要保留,必须为0,那么就可以猜测我们所需要的flag藏在这些字段中。
    image.png
    写出脚本提取出来rgbReserved字段的值,并与rgbBlue的值进行替换

    1. with open('threebody.bmp', 'rb') as f:
    2. d = f.read()
    3. w = 580
    4. h = 435
    5. b = 4
    6. l = bytearray(d)
    7. off = l[10]
    8. for i in range(h):
    9. for j in range(w):
    10. l[off+j*b+i*b*w] = l[off+j*b+i*b*w+3]
    11. with open('threebody_new.bmp', 'wb') as f:
    12. f.write(l)

    这里我原本像尝试用和wp不一样的方法来提取,但是用PIL处理总是会得到镜像上下翻转的图片,迷惑。

    得到新图片后,继续stegsolve,看到新图片
    image.png

    可见中间是一个黑白相间的正方形,其中左上角部分比其他部分颜色要深一些。 大家肯定能想到这里的黑白便是01序列,所以这是一个二维的二进制数组。考虑将这些二进制数组逐行保存成二进制文件,无果,并且如果是逐行保存的话不应该出现左上角的区域与其他区域密度明显不同的情况。 那这个二维数组里的信息是如何储存的呢? 这又要回到刚才提到的数学家希尔伯特了。希尔伯特提出过一种希尔伯特曲线,是一个高维到低维的映射。我们现在得到的二维二进制数组,可以通过这种方式进行降维处理转化成一串一维的二进制流。

    并且,希尔伯特曲线的一个特性便是如果从低维还原成高维,则低维中相邻的点在高维中也是相邻的。这就解释了为什么会出现某一块区域密度与其他区域密度不同的情况。

    这里用到的就是刚才得到的数学家的名字,希尔伯特,前些阵子正好b站上也刷到了降维打击的视频:https://www.bilibili.com/video/BV1Sf4y147J9
    剩下的就是构造出合适的希尔伯特曲线,使其可以填充所有像素点,最后二维降至一维。
    因为正中间的正方形大小是128x128,128=2^7,所以需要7阶希尔伯特矩阵。 1.gif (36.19MB)用b站上那个视频给的源码演示一下降维打击大概长这样。
    不过还是要写脚本提取数据,抄下wp的脚本

    1. import numpy as np
    2. from PIL import Image
    3. from hilbertcurve.hilbertcurve import HilbertCurve
    4. with Image.open('threebody_new.bmp') as img:
    5. arr = np.asarray(img)
    6. arr = np.vectorize(lambda x: x&1)(arr[:,:,2])
    7. for x1 in range(np.size(arr,0)):
    8. if sum(arr[x1])>0:
    9. break
    10. for x2 in reversed(range(np.size(arr,0))):
    11. if sum(arr[x2])>0:
    12. break
    13. for y1 in range(np.size(arr,1)):
    14. if sum(arr[:,y1])>0:
    15. break
    16. for y2 in reversed(range(np.size(arr,1))):
    17. if sum(arr[:,y2])>0:
    18. break
    19. arr = arr[x1:x2+1, y1:y2+1]
    20. hilbert_curve = HilbertCurve(7, 2)
    21. s = ''
    22. for i in range(np.size(arr)):
    23. [x,y] = hilbert_curve.point_from_distance(i)
    24. s += str(arr[127-y][x])
    25. with open('output', 'wb') as f:
    26. f.write(int(s,2).to_bytes(2048, 'big'))

    最终得到output文件
    sublime打开发现一些很奇怪的空行
    image.png
    原以为是莫斯编码,但是不符合规则,实际上是由空格和TAB组成的01字符串。
    通过把空格替换成0、Tab替换成1,可得到字符串

    1. 01100110011011000110000101100111011110110100010000110001011011010100010101101110001101010110100100110000011011100100000101101100010111110101000001110010001100000011011000110001011001010110110101111101

    解码得到flag
    image.png