0%

Whodunit:破译侦探游戏中的隐写信息

tl;dr

这个程序可以将位图中的隐藏信息破译出来。
不过,目前解码信息的规则很简单,因为噪点都是红色的,只需要将0000ff全部转变成ffffff,生成新的图片,即可看到被覆盖的隐写信息。

背景知识

一些儿童向的冒险小说里会夹杂这种类型的侦探卡片,覆盖在空无一物的图片上即可显现真实的信息。计算机程序所能做到的也是如此。

关于色彩

黑白色彩 = 1 bit = 1/0 分别表示白或黑 2 色

GIF :支持 8 bit color
BMP/JPEG/PNG :都支持 24-bit color
BMP 实际上可以支持 1-, 4-, 8-, 16-, 24-, 32-bit color

24 bit color 中的 RGB color:
8 red R
8 green G
8 blue B

关于 BMP

bitmap - by MS

BMP文件在文件开始会包含一些 metadata:
14 byte 大小的结构体 BITMAPFILEHEADER
40 byte 大小的结构体 BITMAPINFOHEADER
一共 54 byte

BITMAP HEADER
(在1-, 4-,16-bit BMP中,还有代表“色彩饱和度”的 RGBQUAD )

其中 BMP 对色彩 triple 的存储方法都是完全逆向 BGR,有些甚至是每一个像素点逆向存储的。

PS:BMP 4.0 的 magic number 是0x4d42

关于 padding

微软开发者制定了规则,每一行 scanline 需要是 32-bit DWORD的倍数
所以 scanline 不能被 4 整除的话就需要增加 padding。

其他注意点

  1. 异常情况判断,错误抛出
  2. 读取原始位图内容、padding调整、按照解密规则替换像素颜色、输出结果

Code Solution

[whodunit.c] []
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

#include <stdio.h>
#include <stdlib.h>
#include "bmp.h"

int main(int argc, char *argv[])
{
// ensure:program should accept exactly two command-line arguments
if (argc != 3)
{
// remind the user of correct usage,
fprintf(stderr, "Usage: change infile outfile\n");
return 1;
}

// remember filenames
char *infile = argv[1];
char *outfile = argv[2];

// open input file
FILE *inptr = fopen(infile, "r");
// if input file cannot be opened for reading
// return 2 and reming
if (inptr == NULL)
{
fprintf(stderr, "Could not open %s.\n", infile);
return 2;
}

// open output file
FILE *outptr = fopen(outfile, "w");
// if the output file cannot be opened for writing
// return 3 and remind
if (outptr == NULL)
{
fclose(inptr);
fprintf(stderr, "Could not create %s.\n", outfile);
return 3;
}

// read infile's BITMAPFILEHEADER
BITMAPFILEHEADER bf;
fread(&bf, sizeof(BITMAPFILEHEADER), 1, inptr);

// read infile's BITMAPINFOHEADER
BITMAPINFOHEADER bi;
fread(&bi, sizeof(BITMAPINFOHEADER), 1, inptr);


// ensure infile is (likely) a 24-bit uncompressed BMP 4.0
if (bf.bfType != 0x4d42 || bf.bfOffBits != 54 || bi.biSize != 40 ||
bi.biBitCount != 24 || bi.biCompression != 0)
{
fclose(outptr);
fclose(inptr);
fprintf(stderr, "Unsupported file format.\n");
return 4;
}
// ensure infile is a 24-bit uncompressed BMP 4.0
if (bf.bfType != 0x4d42 || bf.bfOffBits != 54 || bi.biSize != 40 ||
bi.biBitCount != 24 || bi.biCompression != 0)
{
fclose(outptr);
fclose(inptr);
fprintf(stderr, "Unsupported file format.\n");
return 4;
}

// write outfile's BITMAPFILEHEADER
fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, outptr);

// write outfile's BITMAPINFOHEADER
fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, outptr);

// determine padding for scanlines
int padding = (4 - (bi.biWidth * sizeof(RGBTRIPLE)) % 4) % 4;

// iterate over infile's scanlines
for (int i = 0, biHeight = abs(bi.biHeight); i < biHeight; i++)
{
// iterate over pixels in scanline
for (int j = 0; j < bi.biWidth; j++)
{
// temporary storage
RGBTRIPLE triple;

// read RGB triple from infile
fread(&triple, sizeof(RGBTRIPLE), 1, inptr);

//TODO: change the color of red to white
if (triple.rgbtBlue == 0x00 && triple.rgbtGreen == 0x00 && triple.rgbtRed == 0xff)
{
triple.rgbtBlue = 0xff;
triple.rgbtGreen = 0xff;
}

// write RGB triple to outfile
fwrite(&triple, sizeof(RGBTRIPLE), 1, outptr);
}

// skip over padding, if any
fseek(inptr, padding, SEEK_CUR);

// then add it back (to demonstrate how)
for (int k = 0; k < padding; k++)
{
fputc(0x00, outptr);
}
}

// close infile
fclose(inptr);

// close outfile
fclose(outptr);

// success
return 0;
}

[bmp.h] []
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// BMP-related data types based on Microsoft's own

#include <stdint.h>

// aliases for C/C++ primitive data types
// https://msdn.microsoft.com/en-us/library/cc230309.aspx
typedef uint8_t BYTE;
typedef uint32_t DWORD;
typedef int32_t LONG;
typedef uint16_t WORD;

// information about the type, size, and layout of a file
// https://msdn.microsoft.com/en-us/library/dd183374(v=vs.85).aspx
typedef struct
{
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} __attribute__((__packed__))
BITMAPFILEHEADER;

// information about the dimensions and color format
// https://msdn.microsoft.com/en-us/library/dd183376(v=vs.85).aspx
typedef struct
{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} __attribute__((__packed__))
BITMAPINFOHEADER;

// relative intensities of red, green, and blue
// https://msdn.microsoft.com/en-us/library/dd162939(v=vs.85).aspx
typedef struct
{
BYTE rgbtBlue;
BYTE rgbtGreen;
BYTE rgbtRed;
} __attribute__((__packed__))
RGBTRIPLE;