题目

This is a very simple, yet spectacular trick.
Can you see the hidden solution?

Note: The solution is bound to your session ID and consists of twelve letters.

Hidden Hint: http://wechall.blogspot.com/2007/11/steganabara-explained.html

Thanks go out to buttmonkey for creating the image :)

分析

  WeChall 网站使用了 cookie 和 session 来产生动态的答案,所以不同的人在每一次登录时,答案是不同的。我在这里贴出我的答案,你复制过去一般就是错误的答案,这样可以提防不动脑子只粘贴答案的人。

这道题目有链接提示,链接的颜色是 #EEE,接近背景色白色 #FFF,还是很明显看得出来的,看来作者没有有意隐藏这一提示。

家里网没能打开这个链接,原因还没查出,公司网可以打开。steganabara 原来是一个 .jar 包,这里下载 steganabara.jar,并运行程序 java -jar steganabara-1.1.1.jar ,将隐藏信息的图片拖进来。似乎是很正常的一张图片,LED 数码管显示了作者的大名 Gizmore。查了一下,gizmor 英文是小发明的意思。当然,这只是作者创作题目的署名,与解题没有任何关系。解题偶尔能有额外的收获。

LSB (Stegano, Image, Training) Write up

 这张图片是 RGB 格式,没有 alpha 通道,随便勾选 RGB 某一通道的某一位,共有 3*8=24 种单项选择(复合选择暂未考虑进来,从简单到复杂嘛~)。点击菜单 Filter -> Bit Mask,从低位到高位一个一个勾选盲试的话,很容易试出答案来。思路是这样的,你的答案不一定是这样的。注意勾上 Amplify 选项,否则很难辨认。

  借助事实分析和判断,点击菜单 Analyse -> Histogram 给出 RGB 各分量的柱状图,范围是 0~255,有 3 个连续分布图和 1 个离散分布(斑马带)图,可以怀疑出是哪个通道有问题。相邻柱子的间距是周期,可以推断出是哪个比特位被篡改,导致出现重复的模式。

LSB (Stegano, Image, Training) Write up

扩展

 LSB (least significant bit) 最低有效位,权重最小的位,也是修改最不容易引起注意的位;与之对应的是 MSB (most significant bit) 最高有效位,修改后很容易被察觉。上面之所以需要增强信号(Amplify) 就是因为隐藏的信息在 LSB 上不易被发觉。这个是图片与图片的操作,我想到了一种字符串与图片混合的办法。

  将字符串信息(也就是需要隐藏的信息 ASCII 码值)拆开成比特位,分别藏在图像中的连续像素中的最低位。思路是读取承载信息的图片,然后把需要隐藏的信息以比特流的形式连续覆盖图片 RGB 值的最低位,为了进一步增加难度,可以只覆盖其中某一个分量,可以不用放最低位,如果比特位用完了,信息可以从头循环再覆盖,直到覆盖所有。例如此题采用的是 Blue 通道的第 4 位。

#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>

#include "libpng16/png.h"

int main()
{
    png_image image;

    memset(&image, 0, sizeof image);
    image.version = PNG_IMAGE_VERSION;
    image.format = PNG_FORMAT_RGB;  // no alpha channel

    const char* message = "No 300 taels of silver buried here.";
    const int BITS = strlen(message) * CHAR_BIT;

    const char* original = "g2YMC634NSqoEhXe.png";
    const char* stegano = "stegano.png";
    if(png_image_begin_read_from_file(&image, original))
    {
        size_t size = PNG_IMAGE_SIZE(image);
        png_bytep buffer = (png_bytep)malloc(size);
        if(buffer != NULL)
        {
            // blend text bits to image's least significant bit
            int bit = 0;
            for(size_t i = 0; i < size; ++i)
            {
                char test = (original[bit/8] & (1<<(bit%8))) ?1:0;
                buffer[i] = (buffer[i]&0xFE) | test;
                    
                ++bit;
                if(bit >= BITS)
                    bit = 0;  // repeat message
            }

            if(png_image_finish_read(&image, NULL/*background*/, buffer,
                0/*row_stride*/, NULL/*colormap for PNG_FORMAT_FLAG_COLORMAP*/))
            {
                if(png_image_write_to_file(&image, stegano, 0/*convert_to_8bit*/,
                    buffer, 0/*row_stride*/, NULL/*no colormap*/))
                    fprintf(stdout, "write to file %s successfully.\n", stegano);
                else
                    fprintf(stderr, "write %s: %s\n", stegano, image.message);

                free(buffer);
            }
            else
            {
               fprintf(stderr, "read %s: %s\n", original, image.message);

               /* This is the only place where a 'free' is required; libpng does
                * the cleanup on error and success, but in this case we couldn't
                * complete the read because of running out of memory.
                */
               png_image_free(&image);
            }
        }
        else
            fprintf(stderr, "out of memory: %lu bytes\n",
                (unsigned long)PNG_IMAGE_SIZE(image));
    }
    else
        fprintf(stderr, "%s: %s\n", original, image.message);

    return 0;
}

转载:
wechall.net/stegano 解题心得 - KAlO2 - 博客园