利用MD5碰撞,生成两张显示不同,但是MD5相同的GIF图片

0x00 起因

几年前在网上见过一个md5碰撞生成工具(fastcoll),当时也没感觉有什么用处。不过不久前看见了一篇工具作者的paper,才大概明白工具的用处:

Hash collisions and exploitations

看见知乎上也有人拿gif搞了个生成显示自己md5值图片的工具,稍微研究了下md5格式,实现了不同图案相同md5值:

其实类似的思路还可以实现相同md5值,打印不同数据的exe(这里就不展开了,大家可以自己思考下怎么做)下载这两个exe文件

0x01 先研究下大佬的paper

在读了一遍Hash collisions and exploitations这篇文章后,大致了解了fastcoll这个工具的用处

fastcoll可以根据已知的数据头,在其后面分别附加两组128字节的数据(如果没对齐,还会加上其他数据),生成两个文件,并保证两个文件的MD5值相同。

并且根据MD5的特性:在两个MD5相同的文件后,如果附加上MD5相同的数据,生成的两个文件MD5依旧相同(虽然MD5会变,但是两个文件MD5相同)。就是文章中给出的这个公式:

hash(A) = hash(B) -> hash(A + C) = hash(B + C)

根据这个结论,我们可以得到如下结论:

利用fastcoll,可以对固定开头和固定结尾的数据,生成两组不同的数据(数据内容不可控)拼接上头和尾,分别生成两个文件,保证他们的MD5相同。

0x02 分析fastcoll生成的数据

如果是写一个exe的话,利用起来比较简单,按规则判断下内存区域的哪部分数据就好了(也就是上面举的例子)。但是因为数据内容不可控,所以gif利用起来有些困难。

不过在分析了fastcoll生成的数据后,发现工具只会在几个区域内产生不同的数据(只是位置确定,数据内容依旧不可控),如图:

经过多次实验,发现最后一位数据,两组输出的值相差总是为0x60。可以利用这一点做些文章

0x03 分析下GIF格式

可以看一遍glib出的文章:What’s In A GIF

如果打不开上面的链接,可以看我备份的页面:What’s In A GIF backup

根据格式,我们可以利用Comment Extension来包住fastcoll生成的“乱码”,然后利用上最后一位字节数据。

如果我们将fastcoll生成的数据第123位(最后一处的不同位置),数据值用xy代替(x<y),那么大体处理方式如下:

                   +----+
   length below    | 21 |       explaination below
                   +----+
                   | ef |
                   +----+
                   |lenA| <--+ A区域的长度(填充数据+123字节)
            +----> +----+
            |      |....| <-----+可能有的0x00填充数据  (实际确实有)
            |      |....|
            |      +----+ <----------------------------------+
            |      |    |                                    |
            |      |    |                                    |
          A |      |    |                                    |
            |      |    |                                    |
            |      |....|                                    | fastcoll生成的128字节碰撞数据
            |      |....|                                    |
            |      |    |                                    |
            |      |    |                                    |
            +----> +----+                                    |
                   |0x??| <-----+生成数据的第124位,           |
  +---+-------+--> +----+        就是上面讨论过的要利用的那个字节 |
  |   | 4bytes|    |....|        会被当作接下来的注释长度用      |
  |   |       +--> +----+ <----------------------------------+
  | x |            |    |
  |   |            |mess| <-----+垃圾数据,凑数用的
  |   |            |    |
  |   +----------> +----+
  |                |0x00| <-----+注释结束字符
  |                +----+
  |                |    |
  |                |    |
  |                |    |         实际的图片数据
  |                |pic | <-----+ 一张图会显示
  |                |data|         另一张图会忽略
y |                |    |
  |                |    |
  |                +----+ 
  |                |0x00| <-----+ 图片结束
  |                +----+ <-----+
  |                | 21 |       |
  |                +----+       |
  |                | ef |       |
  |                +----+       |
  |                |lenA|       | 纯属填充用的一片注释区域
  |                +----+       |
  |                |....|       |
  |                |....|       |
  +--------------> +----+ <-----+
                   |0x00| <-----+注释结束字符
                   +----+

总的来说,就是一个文件会把图片数据包在注释里当注释处理,而另一个文件会把它当正常图片数据处理

0x04 开工

具体代码见https://github.com/chenxuuu/different-gif-same-md5

代码里的md5_hmd5_s需要根据你是实际的图片内容进行重新生成(懒得写自动生成处理逻辑了)

根据代码生成的图片数据,需要从前半部分分割开来,删掉后面的全部数据,重新生成碰撞块数据。如图所示圈住的地方就是要去掉的部分:

然后根据生成后的开头数据,生成对应的碰撞块:

可以看到,我们需要的最后一位不同数据差值正好为128字节,我们可以最多显示一百多字节的不同图片数据

同时数据前面还填充了一堆0x00用于对齐,这些数据在写这堆代码的时候就考虑到了,不需要再理会,我们只需要把下面的碰撞代码拎出来就行:

//碰撞用的小数组
const unsigned char md5_s[] =
{
0x14,0xba,0x1f,0x3a,0x94,0x14,0xc7,0x5b,0x6e,0xb1,0x32,0xc3,0xbc,0x6f,0xb8,0x13,
0x58,0x9b,0xa0,0x44,0x73,0x42,0x09,0x4f,0x44,0x9e,0x78,0xe9,0xb7,0xbb,0x2f,0xc2,
0x5a,0xfd,0xee,0xc0,0xf2,0x62,0x2a,0x98,0x0b,0x5e,0xd3,0xf9,0xd3,0xf9,0x05,0x90,
0x94,0x22,0x1f,0xf9,0x56,0xaa,0x1e,0xd9,0xe3,0x20,0x74,0x19,0xca,0xa1,0x2a,0x0e,
0x29,0xaa,0xe8,0x7e,0x5c,0x4b,0xf0,0xec,0x4d,0xd0,0x7c,0x20,0xd9,0x1c,0xcc,0x9e,
0x2c,0xf6,0xb4,0x9a,0x18,0xbb,0xc0,0x20,0xc5,0xb7,0xf4,0xc0,0xd1,0x61,0x7d,0x1e,
0x71,0x59,0x96,0xda,0x26,0x84,0x73,0x22,0x77,0x53,0xbd,0xcf,0x50,0x51,0x53,0xc8,
0x7d,0x3c,0x17,0x72,0x7d,0x95,0x00,0xa0,0xcc,0x21,0x6d,0x25,0xcd,0x1b,0xd3,0x12,
// ------------------------------------------------------^^-----差异点
};
//碰撞用的大数组
const unsigned char md5_h[] =
{
0x14,0xba,0x1f,0x3a,0x94,0x14,0xc7,0x5b,0x6e,0xb1,0x32,0xc3,0xbc,0x6f,0xb8,0x13,
0x58,0x9b,0xa0,0xc4,0x73,0x42,0x09,0x4f,0x44,0x9e,0x78,0xe9,0xb7,0xbb,0x2f,0xc2,
0x5a,0xfd,0xee,0xc0,0xf2,0x62,0x2a,0x98,0x0b,0x5e,0xd3,0xf9,0xd3,0x79,0x05,0x90,
0x94,0x22,0x1f,0xf9,0x56,0xaa,0x1e,0xd9,0xe3,0x20,0x74,0x99,0xca,0xa1,0x2a,0x0e,
0x29,0xaa,0xe8,0x7e,0x5c,0x4b,0xf0,0xec,0x4d,0xd0,0x7c,0x20,0xd9,0x1c,0xcc,0x9e,
0x2c,0xf6,0xb4,0x1a,0x18,0xbb,0xc0,0x20,0xc5,0xb7,0xf4,0xc0,0xd1,0x61,0x7d,0x1e,
0x71,0x59,0x96,0xda,0x26,0x84,0x73,0x22,0x77,0x53,0xbd,0xcf,0x50,0xd1,0x53,0xc8,
0x7d,0x3c,0x17,0x72,0x7d,0x95,0x00,0xa0,0xcc,0x21,0x6d,0xa5,0xcd,0x1b,0xd3,0x12,
// ------------------------------------------------------^^-----差异点
};

最终就可以生成两张md5相同的图片了:

结束

是不是还可以做点其他更好玩的东西?

1 Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注