逆向工程实验——pre9(可执行文件的加密MD5碰撞lab)
Posted 大灬白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了逆向工程实验——pre9(可执行文件的加密MD5碰撞lab)相关的知识,希望对你有一定的参考价值。
目录标题
1.阅读
C语言随机数的实现:mingw与glibc
https://blog.csdn.net/elicococoo/article/details/41851473
2.阅读
java 代码混淆原理
https://blog.csdn.net/u013378306/article/details/63682945
ProGuard代码混淆技术详解
http://www.cnblogs.com/cr330326/p/5534915.html
3、阅读
结构体(对齐规则及举例)
https://www.cnblogs.com/wsq-888/p/jie-gou-ti-dui-qi-gui-ze-ji-ju-li.html
4、阅读下面这三篇文章:
UAF学习–原理及利用
https://www.cnblogs.com/alert123/p/4918041.html
Linux内核提权漏洞的分析和利用(CVE-2016-0728)
http://www.freebuf.com/vuls/93799.html
通过UseAfterFree实现命令执行
https://bbs.pediy.com/thread-221537.htm
这个上个学期软件安全课已经写过了,利用UAF漏洞,实现GOT表覆盖,从而实现命令执行。
5、MD5 Collision Attack Lab
其中Task 4选做
旧链接:http://www.cis.syr.edu/~wedu/seed/Labs_16.04/Crypto/Crypto_MD5_Collision/
新链接:https://seedsecuritylabs.org/Labs_16.04/Crypto/Crypto_MD5_Collision/
Task 1:使用相同的MD5哈希值生成两个不同的文件
在这个任务中,我们将生成两个具有相同MD5哈希值的不同文件。这两个文件的开始部分需要相同,即它们共享相同的前缀。我们可以使用md5collgen程序实现这一点,它允许我们提供具有任意内容的前缀文件。程序是如何工作的如图1所示。
下面的命令生成两个输出文件out1.bin和out2.bin,对于给定的前缀文件prefix.txt:
$ md5collgen -p prefix.txt -o out1.bin out2.bin
我们可以使用diff命令检查输出文件是否不同。我们还可以使用md5sum命令检查每个输出文件的MD5哈希值。请参见以下命令。
$ diff out1.bin out2.bin
$ md5sum out1.bin
$ md5sum out2.bin
由于outl1.bin和 ou2.bin 是二进制的,我们不能使用文本查看程序来查看它们,比如 cat或更多;我们需要使用二进制编辑器来查看(和编辑)它们。我们已经在VM中安装了一个名为 bless的十六进制编辑器软件。请使用这样的编辑器来查看这两个输出文件,并描述您的观察结果。
将两个输出文件进行文件比较:
可以看到两个输出文件的二进制内容并不是完全一样的,但它们的md5值是一样的。
此外,你还应回答以下问题:
问题1:如果前缀文件的长度不是64的倍数,会发生什么?
diff命令显示两个输出文件是不同的,但它们的md5值相同
13字节全为0的前缀文件生成的两个输出文件的前64字节都是0,后面再接上128字节的数据。说明当前缀不是64字节的倍数时会自动补0补齐64字节。
问题2:创建一个恰好64字节的前缀文件,然后再次运行碰撞工具,看看会发生什么。
diff命令显示两个输出文件是不同的,但它们的md5值相同
恰好64字节全为0的前缀文件生成的两个输出文件的前64字节都是0,后面再接上128字节的数据。
128字节全为A的前缀文件生成的两个输出文件的前128字节都是A,后面再接上128字节的数据。
问题3:md5collgen 生成的数据(128字节)对于两个输出文件是否完全不同?请识别所有不同的字节。
两个输出文件大部分字节都相同,只有少数几个字节不同,上面这两个文件是有7个字节不同,其他的输出文件不同的字节数可能不一样。
所以到这,我们也知道了md5collgen的工作原理,就是当输入文件不是64字节的倍数时先补0到64字节的倍数,再添加两个128字节的数据分别生成两个MD5值相同的输出文件。
Task 2:理解MD5的属性
在本任务中,我们将尝试理解MD5算法的一些属性。这些特性对我们在这个实验室中进行进一步的工作非常重要。MD5是一个相当复杂的算法,但从非常高的水平来看,它并不那么复杂。如图2所示:
MD5将输入数据分成64字节的块,然后在这些块上迭代计算散列。MD5 算法的核心是一个压缩函数,它接受两个输入,一个64字节的数据块和前一次迭代的结果。压缩函数产生一个128位的 IHV,代表“中间哈希值”;然后将此输出输入到下一次迭代中。如果当前迭代是最后一次迭代,则IHV将是最终的哈希值。第一次迭代的HV输入(IHV0)是一个固定值。
根据MD5算法的工作原理,我们可以推导出MD5算法的以下属性:输入M和N,如果MD5(M) = MD5(N),即M和N的MD5哈希值相同,则对于任何输入T, MD5(M k T) = MD5(N k T),其中k表示拼接。也就是说,如果输入M和N具有相同的散列,向它们添加相同的后缀T将产生两个输出它们有相同的散列值。这个属性不仅适用于MD5散列算法,而且适用于许多其他算法其他的散列算法。你们的任务是设计一个实验来证明这个特性适用于MD5。
可以使用cat命令将两个文件(二进制文件或文本文件)连接为一个。以下命令将file2的内容连接到file1的内容,并将结果放在file3中。
$ cat file1 file2 > file3
因为out1.bin和out2.bin的md5值相同,我们将它们连接到同一个out3.bin,理论上连接后生成的两个文件的md5值也相同。
将out1.bin的内容连接到out3.bin的内容生成out13.bin,out2.bin的内容连接到out3.bin的内容生成out23.bin:
生成的out13.bin的md5和out23.bin的md5的值相同。
Task 3:生成两个具有相同MD5哈希值的可执行文件
在这个任务中,您将得到以下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");
}
您可以选择在源代码级别工作,即生成上述C程序的两个版本,编译后,它们对应的可执行文件具有相同的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字节的倍数。图3演示了文件是如何划分的。
我们可以在前缀上运行mdScollgen 来生成两个具有相同MD5散列值的输出文件。让我们使用P和Q来分别表示这两个输出的第二部分(每个有128字节)作为前缀后面的部分。因此,我们有以下:
MD5 (prefix || P) = MD5 (prefix || Q)
根据MD5的属性,我们知道,如果在上述两个输出中添加相同的后缀,生成的数据也将具有相同的散列值。基本上,以下是适用于任何后缀的:
MD5 (prefix || P || suffix) = MD5 (prefix || Q || suffix)
因此,我们只需要使用P和Q来替换128字节的数组(在两个分界点之间),就可以创建两个具有相同哈希值的二进制程序。它们的结果是不同的,因为它们各自打印出具有不同内容的自己的数组。
工具。您可以使用bless 来查看二进制可执行文件并找到数组的位置。为了划分二进制文件,我们可以使用一些工具来从特定位置划分文件。head和tail指令是非常有用的工具。你可以看说明书学习如何使用它们。下面我们举三个例子:
$ head -c 3200 a.out > prefix
$ tail -c 100 a.out > suffix
$ tail -c +3300 a.out > suffix
上面的第一个命令保存a的 前3200字节到prefix文件。第二个命令保存a的最后100个字节到suffix文件。第三个命令将数据从第3300个字节到文件结束保存到suffix文件。通过这两个命令,我们可以从任何位置将一个二进制文件分割成若干块。
如果我们需要将一些碎片粘在一起,我们可以使用cat命令。如果你用祝福来复制粘贴一块数据从一个二进制文件到另一个文件,该菜单项“编辑—>选择范围”是很方便的,因为你可以选择一块数据使用一个起点和一个范围,而不是手动计算有多少字节选中。
源代码:
#include <stdio.h>
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,
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,
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,
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,
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,
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,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
};
int main()
{
int i;
for (i=0; i<200; i++){
printf("%x", xyz[i]);
}
printf("\\n");
}
编译成二进制文件:
在二进制文件中找到xyz数组的位置:
所以我们可以前1080h(4224)个字节作为前缀prefix文件,中间1080h到10C0h作为中间的128字节文件,后面第10C0h(4353)个字节到文件结束作为后缀suffix文件。
前缀prefix文件:
后缀suffix文件:
用前缀prefix文件上运行mdScollgen 来生成两个具有相同MD5散列值的输出:
md5collgen -p prefix -o out1.bin out2.bin
$ diff out1.bin out2.bin
$ md5sum out1.bin
$ md5sum out2.bin
这时候out1.bin和out2.bin,就是由前缀prefix文件加上两个不同的128字节数据生成的具有相同md5值的文件:
MD5 (prefix || P) = MD5 (out1.bin) = MD5 (out2.bin) = MD5 (prefix || Q)
我们再把后缀文件加上:
得到两个可执行文件out1和out2,运行它们输出的字符串不同:
但是它们的MD5值相同。
MD5 (prefix || P || suffix) = MD5 (out1.bin || suffix)= MD5 (out1) = MD5 (out2) = MD5 (out2.bin || suffix) = MD5 (prefix || Q || suffix)
其实,现在的两个MD5的相同的可执行文件和原来的二进制可执行文件,只是中间的128字节数据不同而已:
Task 4:使两个程序行为不同
在前面的任务中,我们成功地创建了两个具有相同MD5散列的程序,但是它们的行为不同。然而,它们的区别仅仅在于打印出来的数据;它们仍然执行相同的指令序列。在这项任务中,我们希望实现更有意义更有意义的事情。
假设您已经创建了一个做有益事情的软件。您将软件发送到一个可信任的权威机构进行认证。权威机构对您的软件进行全面的测试,并得出结论,您的软件确实在做有益的事情。权威机构会给你颁发-份证书,证明你的程序是正确的。为了防止您在获得证书后更改程序,您的程序的MD5哈希值也包含在证书中;证书是由权威机构签名的,因此您不能更改证书或程序上的任何内容而不使签名无效。
您希望通过权威机构认证您的恶意软件,但是如果您只是将恶意软件发送给权威机构,那么您实现这一目标的机会为零。但是,您已经注意到,该机构使用MD5来生成散列值。你有主意了。你计划准备两个不同的程序。一个程序总是执行良性指令并做有益的事情,而另一个程序则执行恶意指令并造成损害。您可以找到一种方法使这两个程序共享相同的MD5散列值。
然后将良性版本发送给权威机构进行认证。由于这个版本做了有益的事情,它将通过认证,您将获得一个包含良性程序的散列值的证书。由于您的恶意程序具有相同的哈希值,因此此证书对您的恶意程序也有效。因此,您已经成功地为您的恶意程序获得了有效的证书。如果其他人信任权威机构颁发的证书,他们就会下载您的恶意程序。
这项任务的目的是发动上述攻击。也就是说,你需要创建两个共享相同MD5散列的程序。然而,一个程序将始终执行良性指令,而另一个程序将执行恶意指令。在你的工作中,执行什么良性/恶性指令并不重要;这足以证明这两个程序执行的指令是不同的。
指导方针:创建两个产生相同MD5散列值的完全不同的程序相当困难。md5collgen生成的两个hash-collision程序需要共享同一个前缀;此外,正如我们从前面的任务中可以看到的,如果我们需要向md5collgen生成的输出添加一些有意义的后缀,那么添加到两个程序的后缀也需要相同。这些是我们使用的MD5碰撞生成程序的限制。虽然有其他更复杂和更高级的工具,可以提升的一些局限性,如接受两种不同的前缀[2],它们需要更多的计算能力,所以它们不在这个实验室的研究范围之内。我们需要找到在限制范围内生成两个不同程序的方法
有很多方法可以实现上述目标。我们提供一种方法作为参考,但鼓励学生提出自己的想法。老师可能会考虑奖励学生自己的想法。在我们的方法中,我们创建两个数组X和Y。我们比较这两个数组的内容;如果他们都是一样的,执行的是良性代码;否则,将执行恶意代码。看到下面的伪代码:
Array X;
Array Y;
main()
{
if(X’s contents and Y’s contents are the same)
run benign code;
else
run malicious code;
return;
}
我们可以用一些值初始化数组X和Y,这些值可以帮助我们在可执行二进制文件中找到它们的位置。我们的工作是更改这两个数组的内容,这样就可以生成具有相同MD5散列的两个不同版本。在一个版本中,X和Y的内容是相同的,所以良性代码执行;在另一个版本中,X和Y的内容不同,所以恶意代码被执行。我们可以使用类似于Task3中使用的技术来实现这个目标。图4演示了程序的两个版本。
从图4可知,只要P和Q相应地生成,这两个二进制文件具有相同的MD5哈希值。在第一个版本中,我们使数组X和Y的内容相同,而在第二个版本中,我们使它们的内容不同。因此,我们唯一需要更改的是这两个数组的内容,而不需要更改程序的逻辑。
我们先按照上面的思路,写一个比较X和Y数组的代码task4.c:
#include <stdio.h>
#include <string.h>
unsigned char X[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,
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,
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,
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,
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,
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,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
};
unsigned char Y[200] = {
0x41, 0x41, 0x41以上是关于逆向工程实验——pre9(可执行文件的加密MD5碰撞lab)的主要内容,如果未能解决你的问题,请参考以下文章
软件安全实验——pre9(堆栈保护机制return-to-libc预习)
Crimsonland 血腥大地 逆向无敌通关分析报告配置文件加密Dll隐藏MD5检测补丁反调试函数反反调试