章节大纲

  • 在此任务中,我们提供了下面的 C 程序。你的任务是基于该程序创建两个不同的版本,它们的 xyz 数组的内容不同,但是可执行文件的哈希值却是相同的。

    #include <stdio.h>
    
    unsigned char xyz[200] = {
     /* The actual contents of this array are up to you */
    };
    
    int main()
    {
      int i;
      for (i=0; i<200; i++){
        printf("%x", xyz[i]);
      }
      printf("\n");
    }

    你可以选择在源代码层面完成这项工作,也就是写两个版本,使得在编译后,它们对应的可执行文件有相同的 MD5 哈希值。但是直接在编译后生成的二进制文件上操作可能会更加简单。你可以在 xyz 数组中放任意的一些数,然后你可以用一个十六进制编辑器来直接在二进制文件中修改 xyz 的内容。找出这个数组存放在二进制文件中的哪个部分并不容易。然而,如果我们在数组中填入一些固定的值,我们就能容易地在二进制文件中找到它们。例如,下面的代码中数组被填满了 0x41 , 即字母 A 的 ASCII 数值。在二进制文件中找到 200 个 A 的位置不会很难。

    unsigned char xyz[200] = {
      0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
      0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
      0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
      ... (omitted) ...
      0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
    }



    实验指导。在数组内部,我们可以找到两个位置将可执行文件分为三个部分:前缀、 128 字节区域和后缀。前缀的长度必须是 64 个字节的倍数。有关如何分割文件的说明,请参见下图:
     
     

    我们可以在前缀上运行 md5collgen 生成两个有相同 MD5 哈希值的输出。让我们用 P 和 Q 来表示这些输出的第二个部分(即前缀之后,128 字节的部分)。这样我们就有下面的条件:
    MD5 (prefix ‖ P) = MD5 (prefix ‖ Q)

    根据 MD5 的性质,我们知道,如果将相同的后缀附加到上述两个输出中,得到的结果也将具有相同的哈希值。下面的结论适用于所有后缀:
    MD5 (prefix ‖ P ‖ suffix) = MD5 (prefix ‖ Q ‖ suffix)

    因此,我们只需要使用 P 和 Q 替换数组中两个分隔点之间的 128 个字节,然后我们就可以创建两个有相同哈希值的二进制文件。这两个程序的结果是不同的,因为它们分别输出它们自己的数组,而数组有不同的内容。



    工具。你可以使用 ghex 工具来查看二进制可执行文件,找到数组的位置。我们有一些工具可以用来在特定的位置分割一个二进制文件。 head 和 tail 命令都是很有用的工具。你可以查看手册来了解如何使用它们。我们在下面给出三个示例:

    $ head -c 3200 a.out > prefix
    $ tail -c 100 a.out > suffix
    $ tail -c +3300 a.out > suffix
    第一个命令把  a.out 的前 3200 个字节保存到 prefix 文件中。第二个命令把 a.out 的最后 100 个字节保存到 suffix 文件中。第三个命令把 a.out 从第 3300 个字节到末尾保存到 suffix 中。使用这两个命令,我们可以从任意位置将一个二进制文件分割成几份。如果我们需要把这几部分粘起来,我们可以使用 cat 命令。