[pwnable.kr]passcode

在这里插入图片描述
题目描述里提到c编译的时候有警告,但是没报错

在这里插入图片描述
先看c源代码

#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");

        welcome();
        login();

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

发现login函数里面两次调用scanf函数,
传进去的参数不是&passcode1和&passcode2
而不加&的的话,
传进去的不是变量passcode1和passcode2的地址,
而是他们的值本身,
这样scanf就可能写到奇怪的地方去

就会导致出错
在这里插入图片描述
同时login函数中并没有对passcode1和passcode2的值进行初始化,
意味着,
如果我们能够控制passcode1和passcode2所在的那一片内存的值,
就可以让这两个变量成为指向内存中某处的指针,
从而通过scanf对那片内存进行修改

首先要考虑如何控制passcode1和passcode2的值,
注意到main函数中连续调用了welcome和login函数,并且都没有传入参数,
意味着他们的栈底将会是同一个位置,
并且welcome在login函数之前,
welcome中还有一个长度100的局部变量字符串,

先写入这个字符串,
此时passcode1和passcode2将来所处的位置就会有初始值,
welcome函数返回后,
main函数再调用login函数,
而login函数没有对passcode1和passcode2变量进行初始化,
即意味着我们能够控制passcode1和passcode2的值

最理想的情况当然是同时控制passcode1和passcode2,
这样在login函数中的if判断就会成立,
直接读取到flag

使用objdump -d查看elf文件,
在这里插入图片描述
welcome函数在8048639处通过plt表跳转调用了scanf,
结合栈的结构,判断此前的两条mov指令是在把参数写到栈中,
那么之前的804862f处通过lea指令向edx寄存器装入的地址应该就是第二个参数,
即长度为100的char数组的首地址为ebp-0x70,

再看login函数
在这里插入图片描述
8048586和80485b4两次调用了scanf,
结合c源代码中看到的程序流程,
判断第一次跟passcode1有关,第二次跟passcode2有关,
看804857c处,passcode1的地址是ebp-0x10,
看80485aa处,passcode2的地址是ebp-0xc,

welcome和login在执行过程中的ebp应该是一样的,
name[100]的地址和passcode1相差96个byte,跟passcode2相差100个byte,
又因为passcode1是int型,四个byte,
所以name[100]的最后四个byte的位置刚好就是passcode1的位置,
因此通过name[100]只能控制到passcode1,对passcode2无能为力,

既然如此,想要让login中if判断为真的想法就泡汤了,


参考https://zhuanlan.zhihu.com/p/130271689,
简单复习一下plt表和got表,

plt和got用在动态链接过程中,
获取数据段存放函数地址的那一小段代码称为PLT(Procedure Linkage Table)过程链接表,
存放函数地址的数据段称为GOT(Global Offset Table)全局偏移表,

除了plt[0]和got[0]、got[1]、got[2]有特殊作用外,
plt表和got表一一对应,
例如plt[1]对应got[3];

除第一项外,plt中的每个表项都是三条指令,以现在这个passcode程序为例,
在这里插入图片描述
例如调用printf时,call printf@plt就会来到8048420,
8048420是jmp指令,跳转到804a000处存储的地址,

而804a000就是got[3]所在的位置,

当目标函数没被调用过时,
got中存储的是对应的plt表项的第二条指令,

以这个题目中的printf为例,
在还没调用过printf时,
got[3],即804a000处实际存的值是8048426,即plt[1]中第二条指令的位置,

在第一次调用时,
call printf@plt来到8048420,是jmp指令,
随后jmp到got[3]保存的地址,
相当于jmp到8048426,

接着往下执行,
第二条指令通过push压入所需的参数,
然后第三条指令jmp到plt[0]

plt[0]中两条指令,
第一条压栈,
第二条跳转到got[2]处所存的地址,即是动态链接函数的入口地址,
而动态链接函数会根据已经压入栈的参数,
找到共享库中对应函数的地址,写回到got[3],
此后再调用printf函数,
过程就是call printf@plt -->plt[1]第一条指令jmp *got[3] --> printf函数入口,

相比之下,
第一次调用时则是,
call printf@plt -->plt[1]第一条指令jmp *got[3] --> plt[1]第二条指令压栈 --> plt[1]第三条指令jmp plt[0] --> plt[0]第一条指令再压栈 --> plt[0]第二条指令jmp *got[2] --> 进入动态链接函数 --> 回写got表&调用目标函数


现在就有完整的思路了

通过welcome函数控制passcode1的初始值,使其值为got[3]的地址
->
利用login函数中第一次scanf函数,将got[3]的值改成login函数中调用system函数处的地址
->
login函数中第二次调用printf时,由于got[3]已经被我们篡改,就会跳转到login中system函数调用

落实到细节,
got[3]的地址是0x0804a000,
考虑小端法存储,
那么welcome中的name就应该是任意96个字符+\x00\xa0\x04\x08,

在这里插入图片描述
而在login函数中,
调用system函数的地址是80485ea,
但实际上应该考虑到前一条将参数压栈的指令,
也就是0x080485e3,
scanf用的%d,
需要转成十进制,即134514154

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值