当我如何使用相同的 MD5 哈希创建两个 PHP 文件时,有人问我一个问题:它适用于编译的二进制文件吗?
答案是肯定的,事实上,这就是我在这个演示中第一次得到这个想法的地方。
该示例使用 C 程序作为目标,并执行二进制操作,这略微模糊了它的工作方式。它还利用了 Wang 攻击的一个非常缓慢的旧实现来生成碰撞。为了更好、更快速地展示它是如何为即将到来的演讲工作的,我创建了一个非常简单的示例,使用 PHP 脚本在编译后操作二进制文件。
我已经把这里使用的所有代码都放在了github上。
下面是一个超级简单的 C 程序,它比较两个字符串,如果它们不匹配,它会打印出天使的 ASCII 艺术图片。如果它们确实匹配,你会得到一张魔鬼的照片。
| 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 | #include <string.h> #include <stdio.h> #define DUMMY "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" \  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" \  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" \  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" \  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" \  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" int angel(); int devil(); char *dummya = DUMMY "A"; char *dummyb = DUMMY "B"; int main() {   if (strcmp(dummya, dummyb) != 0) {     return angel();   } else {     return devil();   } } int angel() {   fprintf(stdout, ".                                       ,\n");   fprintf(stdout, ")).               -===-               ,((\n");   fprintf(stdout, "))).                                 ,(((\n");   fprintf(stdout, "))))).            .:::.           ,((((((\n");   fprintf(stdout, "))))))))).        :. .:        ,(((((((('\n");   fprintf(stdout, "`))))))))))).     : - :    ,((((((((((((\n");   fprintf(stdout, "))))))))))))))))_:' ':_((((((((((((((('\n");   fprintf(stdout, " `)))))))))))).-' \\___/ '-._(((((((((((\n");   fprintf(stdout, "  `))))_._.-' __)(     )(_  '-._._(((('\n");   fprintf(stdout, "   `))'---)___)))'\\_ _/'((((__(---'(('\n");   fprintf(stdout, "     `))))))))))))|' '|(((((((((((('\n");   fprintf(stdout, "      `)))))))))/'   '\\((((((((('\n");   fprintf(stdout, "         `)))))))|     |((((((('\n");   fprintf(stdout, "          `))))))|     |(((((('\n");   fprintf(stdout, "                /'     '\\\n");   fprintf(stdout, "               /'       '\\\n");   fprintf(stdout, "              /'         '\\\n");   fprintf(stdout, "             /'           '\\\n");   fprintf(stdout, "             '---..___..---'\\\n");   return 0; } int devil() {   fprintf(stdout, "        _.---**""**-.\n");   fprintf(stdout, "._   .-'           /|`.\n");   fprintf(stdout, " \`.'             / |  `.\n");   fprintf(stdout, "  V              (  ;    \\\n");   fprintf(stdout, "  L       _.-  -. `'      \\\n");   fprintf(stdout, " / `-. _.'       \\         ;\n");   fprintf(stdout, ":            __   ;    _   |\n");   fprintf(stdout, ":`-.___.+-*\"': `  ;  .' `. |\n");   fprintf(stdout, " |`-/     `--*'   /  /  /`.\\|\n");   fprintf(stdout, ": :              \\    :`.| ;\n");   fprintf(stdout, "| |   .           ;/ .' ' /\n");   fprintf(stdout, ": :  / `             :__.'\n");   fprintf(stdout, " \`._.-'       /     |\n");   fprintf(stdout, "  : )         :      ;\n");   fprintf(stdout, "  :----.._    |     /\n");   fprintf(stdout, " : .-.    `.       /\n");   fprintf(stdout, "  \\     `._       /\n");   fprintf(stdout, "  /`-            /\n");   fprintf(stdout, " :             .'\n");   fprintf(stdout, "  \\ )       .-'\n");    fprintf(stdout, "   `-----*\"'\n");   return 0; } | 
它可以用 gcc 编译并执行,只需执行程序将打印出天使,因为两个字符串在最后一个字母中不同。
现在我们有了编译的二进制文件,我们需要对它做一些处理。我们要做的是将 MD5 冲突插入到虚拟文本的长串 A 中。我们只需要插入两个 64 字节的块,但我们需要将其插入到块的开头,即当字节长度是 64 字节的倍数时。
| 1 2 3 | longEgg$ gcc -o demo ./demo.c longEgg$ chmod a+x demo longEgg$ ./demo | 
| 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 | <?php include __DIR__.'/MD5.php'; $inFile = __DIR__.'/demo'; $dummyText = str_pad('', 64, 'A'); function replaceDummyText($input, $replacment, $position) {     return substr_replace($input, $replacment, $position, strlen($replacment)); } function findDummyText($filestring, $dummyText) {        $pos = 0;     $chunks = str_split($filestring, 64);     foreach ($chunks as $chunk) {         if ($chunk == $dummyText)  {             break 1;         }         $pos++;     }     return $pos*64; } // read in the original binary file in $filestring = file_get_contents($inFile); // find the place where we have the dummy string and its at start of a 64 byte block $pos = findDummyText($filestring, $dummyText); printf('I want to replace %d bytes at position %d in %s'.PHP_EOL, 128, $pos, $inFile); $firstPart = substr($filestring, 0, $pos); //find the IV up to the point we want to insert then print that out $iv = md5_hash($firstPart); printf('Chaining variable up to that point is %s'.PHP_EOL, $iv); if (!file_exists(__DIR__.'/a')) {     print('Run fastcoll to generate a 2 block collision in MD5'.PHP_EOL);     return; } // replace the dummy text at the correct location $good = replaceDummyText($filestring, file_get_contents(__DIR__.'/a'), $pos); $bad  = replaceDummyText($filestring, file_get_contents(__DIR__.'/b'), $pos); // find the secod dummy string $secondDummyTextStart = strpos($good, str_pad('', 191, 'A')); // serach back from where we inserted the collision first time so we can grab the whole // 192 bytes and use it to replace the second string while ('A' == substr($filestring, $pos-1, 1)) {     --$pos; } //the 192 butes of str1 $replacement = substr($good, $pos, 192); // replace str1 with 192 bytes cut from of the files // the file it came from will then compare str1 and str2 to 0 $good = replaceDummyText($good, $replacement, $secondDummyTextStart); file_put_contents(__DIR__.'/devil', $good); printf('Just output new file %s with hash %s'.PHP_EOL, __DIR__.'/devil', md5($good)); $bad = replaceDummyText($bad, $replacement, $secondDummyTextStart); file_put_contents(__DIR__.'/angel', $bad); printf('Just output new file %s with hash %s'.PHP_EOL, __DIR__.'/angel', md5($bad)); | 
当我们第一次在它上运行 php 脚本时,它会找到这样的位置并计算文件中该点 MD5 的四个链接变量的值。它打印出连接在一起的十六进制值作为哈希值。
| 1 2 3 4 | $ php long_egg.php I want to replace 128 bytes at position 3008 in /var/www/html/md5collisions/longEgg/demo Chainring variable up to that point is <strong>a2330164cee76a94d8779f4a2bdfa6a2</strong> Run fastcoll to generate a 2 block collision in MD5 | 
现在,我们可以获取该值并搜索与该初始状态的 MD5 冲突。最好的 MD5 碰撞查找器是 Marc Stevens fastcoll。它通常可以在几秒钟内使用 Wang 攻击的变体找到碰撞。下载后,您需要对其进行编译。github 上的代码中应该有一个 Makefile。运行它指定初始状态和输出文件如下所示。
-o 选项指定输出文件,因此将创建两个新文件 a 和 b,其中包含 2 个二进制数据块。此时,这些块仅作为二进制文件中的 MD5 冲突起作用。第二次运行 php 脚本将创建原始编译二进制文件的两个副本,并将冲突插入到适当的位置。
所以现在我们又创建了两个文件,天使和魔鬼。运行其中每个都应该给出不同的输出。
但它们应该具有相同的 MD5 值。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $ wget https://www.win.tue.nl/hashclash/fastcoll_v1.0.0.5-1_source.zip $ unzip fastcoll_v1.0.0.5-1_source.zip $ make $ chmod a+x fastcoll $  ./fastcoll -i <strong>a2330164cee76a94d8779f4a2bdfa6a2</strong> -o a b MD5 collision generator v1.5 by Marc Stevens (http://www.win.tue.nl/hashclash/) Using output filenames: 'a' and 'b' Using initial value: a2330164cee76a94d8779f4a2bdfa6a2 Generating first block: ................................ Generating second block: S11................................... Running time: 7.81061 s 再次运行 long_egg.php 会产生 devil 和 angel 文件 | 
| 1 2 3 4 5 | $ php long_egg.php I want to replace 128 bytes at position 3008 in /var/www/html/md5collisions/longEgg/demo Chainring variable up to that point is <strong>a2330164cee76a94d8779f4a2bdfa6a2</strong> Just output new file /var/www/html/md5collisions/longEgg/devil with hash 7422a71a481c82148d65fe6ef473ae66 Just output new file /var/www/html/md5collisions/longEgg/angel with hash 7422a71a481c82148d65fe6ef473ae66 | 
$ chmod +x angel devil

| 1 2 3 4 5 6 7 | $ openssl md5 angel devil MD5(angel)= 7422a71a481c82148d65fe6ef473ae66 MD5(devil)= 7422a71a481c82148d65fe6ef473ae66 $ openssl sha1 angel devil SHA1(angel)= 1f331a6428652fda09cbd6da559c869eb68ca55e SHA1(devil)= 0830389dad66341145289b77359a320c6606f620 | 
如果 运行 fastcoll 缺少库 libboost_filesystem.so.1.75.0 ,需要 找到库,加入路径:
| 1 | export LD_LIBRARY_PATH=/usr/local/lib/:$LD_LIBRARY_PATH | 
