【pwnable.kr】Toddler‘s Bottle-[collision]


打开题目审题


image-20211107214527901

Daddy told me about cool MD5 hash collision today.
I wanna do something like that too!

ssh [email protected] -p2222 (pw:guest)

题目中提到MD5哈希碰撞,可以基本确定方向

输入命令回车,输入密码:guest 即可登录服务器。

image-20211107214836342


找到突破口


照例先 ls -la

col@pwnable:~$ ls -la
total 36
drwxr-x---   5 root    col     4096 Oct 23  2016 .
drwxr-xr-x 115 root    root    4096 Dec 22  2020 ..
d---------   2 root    root    4096 Jun 12  2014 .bash_history
-r-sr-x---   1 col_pwn col     7341 Jun 11  2014 col
-rw-r--r--   1 root    root     555 Jun 12  2014 col.c
-r--r-----   1 col_pwn col_pwn   52 Jun 11  2014 flag
dr-xr-xr-x   2 root    root    4096 Aug 20  2014 .irssi
drwxr-xr-x   2 root    root    4096 Oct 23  2016 .pwntools-cache

发现flag文件,尝试查看(肯定看不了

fd@pwnable:~$ cat flag
cat: flag: Permission denied
fd@pwnable:~$

果然不能看。看下别的,文件flag肯定是最后的答案,所以需要看看其他文件找寻突破口。

还有一个c语言源文件col.c和可执行文件col

那没什么说的,看看col.c。

col@pwnable:~$ cat col.c
#include <stdio.h>
#include <string.h>
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 bytesn");
                return 0;
        }

        if(hashcode == check_password( argv[1] )){
                system("/bin/cat flag");
                return 0;
        }
        else
                printf("wrong passcode.n");
        return 0;
}

找到重点 system("/bin/cat flag");

看到源码大概已经明白了,我们没有查看文件的权限,但是fd有,所以只要分析如何让代码执行system("/bin/cat flag");即可。


源代码分析


col@pwnable:~$ cat col.c
#include <stdio.h>
#include <string.h>
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;
        }
    	// 第一个参数长度必须是20字节
        if(strlen(argv[1]) != 20){
                printf("passcode length should be 20 bytesn");
                return 0;
        }
		// 对argv[1]进行check_password 结果与hashcode 相等
        if(hashcode == check_password( argv[1] )){
                system("/bin/cat flag");
                return 0;
        }
        else
                printf("wrong passcode.n");
        return 0;
}

单独分析unsigned long check_password(const char* p)

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;
}

res 就是将 参数每四个字节相加后的结果,联系main函数,可知我们输入参数每四个字节相加必须hashcode = 0x21DD09EC


解题步骤


其实就是计算五个数,五个数相加等于 0x21DD09EC 算一下这五个数。 设为 a、a、a、a、a + b

image-20211107220856130

继续计算器计算:0x06C5CEC8 * 5 = 0x21DD09E8 与原数相差 4

五个数就计算出来了

0x06C5CEC8、0x06C5CEC8、0x06C5CEC8、0x06C5CEC8、0x06C5CECC

但是我们如何保证我们输入的数正好以我们想要的形式存在呢。直接作为参数输入的话。

col@pwnable:~$ ./col 06C5CEC806C5CEC806C5CEC806C5CEC806C5CECC
passcode length should be 20 bytes
col@pwnable:~$

明显是不对的,会把每个字符读作一个字节,这样就有40字节了。

我们实际要将这些值直接写到内存里,也即这些值是已经对应好的ascii值。并且是大端模式,对应数据的高字节存放在低地址。【可以使用GDB进行调试或者两种模式都输入尝试】print 从高地址向低地址写,因此综合与我们书写相反。【大小端应该都懂】

那么问题来了,如何将这些值直接写到内存里呢? 使用命令:

"print 'xCCxCExC5x06'+'xC8xCExC5x06'*4"

构造输入命令:

./col $(python -c "print 'xCCxCExC5x06'+'xC8xCExC5x06'*4")

python -c是指用python解释器执行print ‘xCCxCExC5x06’+’xC8xCExC5x06’*4

$()指令是括号内的字符串被shell解释为命令行,在执行时,shell首先执行该命令行,并以它的标准输出结果取代整个$(),整个语句的意思是将参数

‘xCCxCExC5x06xC8xCExC5x06xC8xCExC5x06xC8xCExC5x06xC8xCExC5x06’

直接写入到内存。

col@pwnable:~$ ./col $(python -c "print 'xCCxCExC5x06'+'xC8xCExC5x06'*4")
daddy! I just managed to create a hash collision :)

daddy! I just managed to create a hash collision ?

验证即可

image-20211107223434299


相关知识


介绍与上一篇有重复

MD5哈希碰撞

md5是一种被广泛使用的密码散列函数,可以产生一个128位的(16进制)散列值,2004年,我国中科院院士王小云证实md5算法无法防止碰撞,因此,不适用于安全性认证。

MD5作为文件校验方法已经不可靠了,可以人为制造碰撞。

int main( int argc , char * argv[ ] ,char * envp[ ])

main函数的参数列表保存了输入参数的信息:

第一个参数argc记录了输入参数的个数。且argc是包括程序本身在内的参数个数。如本题目,直接./fd运行,该参数实际上是1,而不是0。

第二个参数是字符串数组的,字符串数组的每个单元是char*类型的,指向一个c风格字符串,arg[ ]指向的数组中至少有一个字符指针,即arg[0].他通常指向程序中的可执行文件的文件名。

第三个参数是用来取得系统的环境变量,如:在DOS下,有一个PATH变量。当你在DOS提示符下输入一个命令的时候,DOS会首先在当前目录下找这个命令的执行文件。如果找不到,则到PATH定义的路径下去找,找到则执行,找不到返回Bad command or file name 。在DOS命令提示符下键入set可查看系统的环境变量。

$(python -c “print ‘xCCxCExC5x06’+’xC8xCExC5x06’ * 4”)详解

$(linux命令) 在执行时,shell首先执行该命令行,并以它的标准输出结果取代整个$()

python -c 可以在命令行中调用 python 代码, 实际上 -c 就是 command 的意思

print str 将字符串写入到内存中


参考:

https://blog.csdn.net/jxz_dz/article/details/102755923

https://zhuanlan.zhihu.com/p/131283811

https://www.pythonheidong.com/blog/article/147292/6142802b1e89b9028188/

http://blog.chinaunix.net/uid-28463456-id-4090821.html

https://www.cnblogs.com/chnmig/p/14207100.html

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>