首先来看题目:
看到了题目里的“MD5 hash collision”,意思是MD5哈希碰撞,作为一个学过密码学的人,还有点小激动,毕竟是我接触过的东西,可以不用查概念了。
跟上道题一样,先连上服务器再说:
可以看到,还是只有三个文件,那么就继续从C语言文件下手,代码如下:
#include
#include
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}
这段代码的功能就是:传入一个长为20的字符串,然后把字符串转化成整型数组,再把数组里面的数相加,最后如果等于0x21DD09EC,则可得到flag。
举个例子,假如输入的字符串是20个9(大佬的例子,偷个懒),那么字符串就是:
[9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9], 而9的ascii码是39,所以字符串在内存中的存放方式就是:
39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39
然后,一波操作把好好的字符串变成了整型数组,周所周知,一个int数的长度为4字节,一个char的长度为1字节。所以,这波操作过后,把原来有20个字符的字符串变成了有5个整型常数的数组,每个数的值都是0x39393939(十进制960051513),即:
[960051513,960051513,960051513,960051513,960051513]
最后就是把这5个数加起来,但是这里有个重点,int数的最大值是0x7FFFFFFF,而不是0xFFFFFFFF,因为它的第一位是符号位,如果第一位是1,表示这是负数。所以最后相加的结果会比相加之前还小。
知道了这个程序的功能,那么下一步就是思考怎么构造符合要求的字符串了。我的思路是,既然最后是做加法,那就找五个数加起来是0x21DD09EC就可以了。
最简单的当然是[0x21DD09EC,0,0,0,0],但是这样是不行的,因为ascii码里的0是null,所以输入之后只有4个字符,不符合条件要求。
那既然每个字节都不能是00,那就来个第二简单的,后面16个字节都是01,那么第一个数就是0x21DD09EC-0x01010101*4=0x1DD905E8,对应数组就是[0x1DD905E8,0x01010101,0x01010101,0x01010101,0x01010101]。
由于目标是小端机,所以要按小端机的顺序输入,即:
./col `perl -e 'print "\xe8\x05\xd9\x1d"."\x01"x16'`
` `:这里是反引号` `,不是引号‘ ’。反引号的意思是,反引号里面的命令优先执行,且把输出暂存,在需要的时候输出。这里就是把后面命令里的输出暂存,在执行col文件时作为参数传入。
perl:是一种编程语言,我也没有了解太多,就介绍一下这里用到的:
perl -e:是指在命令行执行后面的代码,而无需建立文件。
运算符:这里用到了两种运算符,‘.’和‘x’。其中,‘.'运算符是将两个字符串连接起来;’x‘运算符后面是字符串重复的次数。但是有个小问题,就是我在测试的时候发现,
"\xe8\x05\xd9\x1d"."\x01"x16
这种写法可以,但是反过来:
”\x01"x16. "\xe8\x05\xd9\x1d"
就不行了,可能是两个运算符连用的结果,也不知道怎么解决。就先不管他吧。
输入上述命令后的结果:
搞定!下面开始写exp:
from pwn import *
import os
pwn_ssh=ssh(host='pwnable.kr',user='col',password='guest',port=2222)
print(pwn_ssh.connected())
sh=pwn_ssh.process(argv=['collision','\xe8\x05\xd9\x1d'+'\x01'*16],executable='./col')
print(sh.recvall())
用的东西跟上题一样,就不解释了。
这道题做完了,突然感觉少点啥。思考了一下,发现原来题目里的提示没用上。就构造了个字符串就搞定了,跟哈希碰撞有什么关系呢?其实还是有点关系的,但是首先应该明白两个概念,哈希算法和哈希碰撞:
哈希算法(hash):哈希算法的一大特征就是可以把任意长度的输入转化为固定长度的输出,可以说符合这一特征的算法都可以称为哈希算法。这种算法经常用在加密或者数字签名中,比如著名的SHA1、MD5。
哈希碰撞(hash collision):由于哈希算法的特征,相当于是把无数的输入可能压缩为有限的固定长度输出,所以必然存在一种情况,就是一个输出对应无数个输入。如果两个输入对应同一个输出,那么可以说这两个输入构成了“碰撞”。
为啥说这道题跟哈希碰撞有关系呢,因为这道题的算法就是把一个20字节的输入转化为4字节的输出,可以说是一个简单的哈希算法。而这道题的目标是构造一个字符串得到一个已知的输出,也就是用不同的输入得到同样的输出,所以也可以说是对这个简单哈希算法的碰撞进行研究。
虽然这道题在不了解哈希碰撞的时候也可以做,属于比较简单的题目,但是不得不说,如果在学习哈希碰撞的时候,用这道题目做例题的话,也是个不错的选择。题目虽然简单,但是非常直观,又特别符合哈希算法和哈希碰撞的特征,非常有助于理解。