最近迷上了 www.wechall.net 网站,里面都是些与计算机相关的题目挑战。题目又分很多类型,例如:加密与解密、隐写术、网络攻防、趣味编程、数学逻辑等。题目有的简单,有的很难,需要一些知识和技巧。与其他题目挑战的网站不同的是,在其他类似性质的网站注册的用户可以绑定到 WeChall 网站,然后 WeChall 提供排名信息,而且也分得很细,什么按总分全球排名、什么在自己国家的排名、什么解答某种语言网站题目的排名等。可以从解题的人数判断题目的难易程度,有兴趣的朋友可以去注册,解题中也能学到很多知识。国内的学校和公司也有举办网络攻防大赛。与玩 ACM 解题不同的是:ACM 提供给定的输入和输出,中间的黑盒部分需要你来完成,需要提供一种更好更快的算法;这类攻防挑战题目可能需要更多的计算机知识,从一堆垃圾数据中发现有用的信息、越过不同的障碍终于发现答案等。
注:Steganography 是隐写术,它与 Cryptography (密码术)是不同的。
题目1 stegano1 给了一张彩色的 BMP 图片,让你从中发现答案。
不管是图片、音频文件,还是二进制文件,首先都可以尝试以文本文件打开,查看是否存在可疑的字符串。文件太大了就不要这么做了,鼠标会转圈好久的。
图片很小,才 102 bytes。将图片文件拖到文本编辑器里,就可以看到答案了。Notepad++ 是 Windows 上最好的文本编辑器,没有之一。体积小巧,功能齐全。
Linux 上用 gedit 打开,或者使用 vim 命令,输入 :%!xxd 切换到二进制模式,答案显而易见。
xxd 是另外一个命令,以十六进制现实内容。再输入 :%!xxd -r 命令执行,切换到原来的摸样(r 是 reverse )。
注意 vim 的 -b 选项,是否以二进制模式打开。图片是二进制文件,所以要加此选项,否则处理会有问题。
还有一个命令 strings,来的更快,直接输出文件中可打印的字符,通常用来显示二进制中的字符串常量。
strings 命令的选项 -n VALUE 或 --bytes=VALUE 选项控制输出连续字符的最短长度,这个的默认值 VALUE 是 4。
还有 -t RADIX 或 --radix=RADIX 选项以后也会用上的,用来显示每个字符串的偏移量。C++ 的 static 字符串变量需要这个偏移量索引字符串。
你我都是做题的,但是怎么出题呢?破解别人的软件,写一个序列号注册机相对容易。但是如何写一个加壳、混淆的难以破解的程序就有点难度了,毕竟建房子拆房子简单。
我们需要了解一下 BMP 文件格式,BMP 图片很占空间,没有压缩,或者用 RLE (Run-Length Encoding)压缩一下,体积大所以很少在网络上传播。 BITMAPFILEHEADER,BITMAPINFOHEADER,
const char* buffer = "put your message here"; FILE *file = fopen(path, "wb"); if (!file) { fprintf(stderr, "can‘t open %s\n", path); return false; } BITMAPFILEHEADER file_header; BITMAPINFOHEADER info_header; file_header.bfType = (WORD)(‘B‘|‘M‘<<8); // Windows BMP file tag file_header.bfSize = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+len; file_header.bfReserved1 = 0; file_header.bfReserved2 = 0; file_header.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER); info_header.biSize = sizeof(BITMAPINFOHEADER); info_header.biWidth = width; info_header.biHeight = height; info_header.biPlanes = 1; info_header.biBitCount = 24; info_header.biCompression = 0; // BI_RGB, An uncompressed format. info_header.biSizeImage = len; // size in bytes. This may be set to zero for BI_RGB bitmaps info_header.biXPelsPerMeter = 0; info_header.biYPelsPerMeter = 0; info_header.biClrUsed = 0; info_header.biClrImportant = 0; fwrite(&file_header, sizeof(file_header), 1, file); fwrite(&info_header, sizeof(info_header), 1, file); fwrite(buffer, len, 1, file); fclose(file);
这样写,太容易被发现了。我们可以通过对字符串先加密再隐写,或者用循环移位、异或操作增加难度。
知道 BMP 格式支持颜色索引模式(GIF、PNG 格式也支持),图片里会有一个调色板用来存储使用的颜色值,相当于一个数组存储 RGB 值,然后图片只用数组索引值找到对应的颜色并显示出来,于是你也可以这样隐藏你的信息到图片中。我们用两种颜色作图,在白纸写黑字,存储为索引模式图,于是调色板会有两个值 #000000 和 #FFFFFF,颜色只需一位 0/1 就可以表示黑白颜色了,将数组中的 #000000 值篡改成 #FFFFFF,也就是黑色改成白色,其他数据不用动,保存后用再打开图片看发现,字消失了!写一些想说的话,然后把图片发给暗恋对象,对方会说你怎么发了一张空白图片。说了这么多,自己动手尝试一下吧。
题目2 Stegano Attachment
答案在图片中吗?不,答案在附件(attachment)里。等你做出了这道题,就明白前面一句话的意思。有时候,题目会给出提示容易忽视的信息,而这个可能就是解题的关键。
稍微留心一下图片 http://www.wechall.net/challenge/training/stegano/attachment/attachment.php ,是不是很坏?后缀被人改成 PHP 网页后缀。用文本编辑软件打开,发现一堆乱码。不同文件会有不同的文件头,即不同的 Magic Number,如上面的 BMP 格式图片的 “BM” 标志,PNG 图片有八字节头 “\211
P
N
G
\r
\n
\032
\n
”,用 Visual Studio 调式 C++ 代码出现很多的“烫烫烫烫”,其实是编译期初始化栈空间为 “\xCC\xCC\xCC\xCC”(0xCC 对应 x86 的 INT 3 中断指令,你应该知道为什么这么做),编译 Java 文件生成的 .class 文件的文件头有 “CAFEBABE”。从 attachment.php 里我们看到了字符 "JFIF",应该就是 JPG 图片了。更好的方法是用 linux 上的 file 命令查看文件类型,文件名后缀随你怎么该。改成 .jpg 后缀后,打开图片后,是电影 Ghostbusters (1984) 的海报,我们追到了这里,电影我没看过,看介绍好像很不错的样子。一边下载一边解题吧~
[email protected]:~/Desktop$ file attachment.php
attachment.php: JPEG image data, JFIF standard 1.01
我们稍微了解一下 JPEG 格式规范。JPEG 文件以 “\xFF\xD8” 打头,以 “\xFF\xD9” 结尾。JPEG/JFIF 文件会出现 “JFIF” 字符,JPEG/Exif 文件会出现 "Exif" 字符。
jpg 图片采用有损压缩,不支持 alpha 通道。编码时,颜色从 RGB 色彩空间转到 YUV 色彩空间,根据颜色分量的重要性和人眼的感知剔除不重要的信息,缩减采样 4:4:4 4:2:2 4:2:0,接着 分割图片成 8x8 子区域进行离散余弦变换,量化编码,支流系数用差分编码,交流系数用 RLE 编码。
我们发现图片是以 "\xFF\xD9" 开头的,但是结尾却不是 "\xFF\xD9",好像明白了什么。
我们用题目1学到的 strings 命令搜到了可疑字符串 solution.txt 和 solution.txtPK,恰恰是放文件最后的。
PK 字眼熟悉不?最常用的 ZIP 格式压缩文件头的 magic number 是 “PK\x03\x04”,而且文件中确实存在这四个字节。而且这四个字节的前面也恰好是 "\xFF\xD9"。想想之前的题目 attachment。看来,是谁把一个 ZIP 压缩文件追加到一个 JEPG 图像文件的后面了。我们需要提取出 ZIP 文件,答案离我们不远了。。
ZIP 文件的偏移量用题目1学到的 vim :%!xxd 搜索定位查看并计算,或者用 grep 命令搜索下。
我们需要复制从 0004f06 到最后 0004f8d (长度 0x88=136)到新的文件里去。
复制粘贴文件或者段落用 Ctrl+C、Ctrl+V,可是复制二进制文件流呢,也可以这么做吗?好像不行。
Notepad++ 不愧是最好用最实用的文本编辑器,选择菜单 Edit -> Paste Special,二级菜单中出现了 Copy Binary Cotent | Cut Binary Content。
或者使用 linux 的 dd 命令。
[email protected]:~/Desktop$ printf "%d\n" 0x0004f06
20230
[email protected]:~/Desktop$ dd if=attachment.jpg of=solution.zip skip=20230 bs=1 count=136
136+0 records in
136+0 records out
136 bytes (136 B) copied, 0.0021692 s, 62.7 kB/s
[email protected]:~/Desktop$ unzip solution.zip
Archive: solution.zip
inflating: solution.txt
打开 solution.txt 文件,就是最终的答案了。
从中,我们学习了另外一种隐藏文件的方法,将一个文件附属到另外一个文件末尾,打开的时候只解析第一个文件,附属文件被忽略了。
Windows 控制台下,将信息 message.txt 隐藏到图片 carrier.jpg 中:copy carrier.jpg /b + message.txt /a mixed.jpg
/A /B 分别代表文本文件和二进制文件。
题3 LSB - The least significant bit
to be continued...