pwnable学习心得(二)

时间:2021-12-08 14:00:24

0x04  flag

通过反汇编工具IDA PRO打开

pwnable学习心得(二)

文件打开入口看起来很奇怪,东西很少,可能是加壳过的,在linux环境下用upx –d flag命令脱壳,脱壳后文件大小从300+k到800+k,此时再用IDA打开,通过View->Open subview->String查看字符串得到flag或者跟进flag找到字符串

pwnable学习心得(二)

 

0x05  passcode

#include <stdio.h>
#include <stdlib.h>

void login(){
        int passcode1;
        int passcode2;

        printf("enter passcode1 : ");
        scanf("%d", passcode1);
        fflush(stdin);

        // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
        printf("enter passcode2 : ");
        scanf("%d", passcode2);

        printf("checking...\n");
        if(passcode1==338150 && passcode2==13371337){
                printf("Login OK!\n");
                system("/bin/cat flag");
        }
        else{
                printf("Login Failed!\n");
                exit(0);
        }
}

void welcome(){
        char name[100];
        printf("enter you name : ");
        scanf("%100s", name);
        printf("Welcome %s!\n", name);
}

int main(){
        printf("Toddler's Secure Login System 1.0 beta.\n");

        //两个函数挨着,用的同一个栈底(ebp)
        welcome();
        login();

        // something after login...
        printf("Now I can safely trust you that you have credential :)\n");
        return 0;
}

思路:

1. 在login函数中在调用scanf函数输入passcode变量的时候,没有加&符号,后果是在调用scanf的时候,程序会从栈上去4个字节作为passcode变量的地址,将我们输入的整形放入到这个地址中,也就是说我们实际输入的数据就不一定保存在栈上了,但是这两个passcode的地址一定在栈上。

2.再看welcome函数输入100个字节,看似没有什么漏洞可以利用,但观察主函数里,login函数和welcome函数的调用紧挨着且welcome函数在前,这就导致这两个函数的函数栈帧是同用一个栈底(ebp)的,所以调用welcome函数输入100个字节后,残留下来的数据可以发挥作用。

3.我们用gdb调试,来查看passcode1和passcode2到底是用的栈上哪个地方的数据做地址

 

如图,这是第一次调用scanf,因为passcode1没加&符号所以用的是(ebp-0x10)这个地址上存放的4个字节的数据作为地址

pwnable学习心得(二)

 

如图,这是第二次调用scanf,passcode2也没加符号&,所以用的是(ebp-0xc)这个地址上存放的4个字节数据作为地址。

pwnable学习心得(二)

 

再看welcome函数里数组name的首地址为(ebp-0x70)

pwnable学习心得(二)

 

所以调用login函数时,栈上的数据分布如下:(灰色为上个栈帧的残留数据)

 

pwnable学习心得(二)

 

其实正常来说根据函数中定义变量的先后顺序,应该是passcode2在相对较低的字节(先定义先分配栈空间),也就是说原来这两个变量的值是保存在栈上的,而后面的if()语句正是用这两个栈空间内的值来判定,scanf没加&只是导致我们把内容输入到别的地址去了。所以能不能直接覆盖passcode1和passcode2在栈上的内容,结果是不行的。原因有两点:

1)       passcode1和passcode2定义了之后就没有其他动作了(比如赋值或者其他操作)这样我们在gdb反汇编它就没办法确定它们的位置,上图的两个passcode地址是由于scanf()的使用不当引起的把栈上数据内容当地址的行为,那两个地址并不是login函数一开始定义的两个变量的地址,后面if的判断也不是用的图上两个地址的数据

2)       其次即使能确定地址,welcome中的name数组的输入是没有任何漏洞,所以如果需要覆盖的数据超出了100字节的返回,在数组外的区域没有办法覆盖,比如上图,虽然两个变量是紧挨着的但有一个在数组外,想靠数组覆盖无能为力。

 

所以我们采用got表覆写的方式:

  1.用一个got中的函数地址去覆盖scanf取的地址也就是上图passcode1的地方

  2.再用执行system(‘/bin/sh’)这条指令的地址通过scanf写入到1.中的地址去

   下一次执行到这个got表中函数时,正常情况是跳到got表地址读取指令,而此时指令以及被我们覆盖,就执行system(‘/bin/sh’)了

注意不论是name数组的构造,还是scanf这个漏洞导致覆写got表的行为,本质都是在构造,真正触发漏洞的是这个got表函数在程序里第一次加载的时候,调到got表的这个行为。

pwnable学习心得(二)

之所以有两个\n是因为对应两次输入的回车代表输入结束。