拆弹实验

近期重读CSAPP,记得第三章后有一实验,二进制炸弹,找出以前下载的目标文件(不记得在哪里下的了,就不贴原链接了),开始了拆弹实验。

实验分六个关卡,每个关卡通过一个phase_x函数来判断输入的正确性,因此先查看phase_1的代码,disas phase_1:

在<+22>处,判断strings_not_equal的返回值,从字面意义及在<+6><+11>出传入的参数来看,这个函数判断传入的两个参数是否相等,在<+11>的0x8(%ebp)表示phase_1的第一个参数,因此查看0x8049678处的值:x/s 0x8049678

可推测第一个关卡的答案就是"The future will be better tomorrow.",运行文件,在第一关处输入这个句子,问题解决。

 

查看phase_2的代码:disas phase_2

在<+8>行,声明了一个数组的地址,在<+15>处调用了read_six_numbers函数,推断为将phase_2的含有6个数字的输入字符串读入6个元素的数组,在read_six_numbers后栈帧的情况如下:

 在<+23>处,比较a[0]与0x1,推断数组的首元素是1。在<+64>处,程序跳到<+42>处执行,因此推断这是一个循环,在<+34><+39>处完成初始化工作,可以将这个流程用以下代码来描述:

if (a[0] != 1)
    explode_bomb()
i = 2;
do {
    j = i;
    j = i * a[i-2]
    if (j != a[i-1])
        explode_bomb()
} while (i != 7)

 可推断以下表达式:

a[i-1] = i * a[i-2]    //i from 2 to 6

 表明当前数组元素的值等于前一个元素的值乘以当前的下标加1,所以推断出这6个数分别是1,2 ,6,24,120,720:

 

查看phase_3的代码,比较长,先截出第一部分:

在<+22>处,调用了sscanf函数,它的参数分别是:phase_3传入的字符串,0x8049968处的字符串,分别相对于%ebp的-0x8和-0x4的地址,查看0x8049968处的内容:x/s 0x8049968显示为"%d %d",则知输入的是两个整数。显示第二部分的代码如下:

据代码结构可以推断这是一个switch-case结构,在<+46>处将读入的第一个数作为分支判断的条件,在<+49>处跳到相应的分支代码段执行。在<+40>处有比较第一个数于0x7,可以判断分支共有8个,显示<+49>处0x80496cc及往下的8个跳转代码的地址是:

它们分别位于<+98><+105><+91><+84><+77><+70><+63><+56>处。在这之后又分别跳到<+110><+115><+120><+125><+130><+135><+140>处,在这几处并没有跳转指令,可以推断这是一个没有break的switch结构。

根据流程推测处代码如下:

result = 0x0;
switch (i) {
    case 1:
    case 0: result = 0x359;
            result -= 0x1df;
    case 2: result += 0x2bd;
    case 3: result -= 0x2db;
    case 4: result += 0xf2;
    case 5: result -= 0x86;
    case 6: result += 0x86;
    case 7: result -= 0x19b;
}

 在<+157>处判断了第一个数与5的关系,可知第一个数可以取从0到5这几个数,然后将switch后的结果与第二个数比较,取第一个数为5,则第二个数等于:0x0-0x19b,等于-411,取4则等于-169,将两个结果代入输入可通关

 

查看phase_4的代码:

查看0x804996b的内容:x/s 0x804996b,为"%d",知通关密码为一个数。再看<+45>及<+53>知以这个数为参数调用func4的返回值为0x90,那么就要查看func4的代码

发现在<+48>处跳转到<+25>处,判断有一个循环,而在循环中的<+32>处又调用了func4,推断有一个递归。在%ebx处存放的是传入的参数值,而在循环中的递归调用前将这个数减了1(在<+28>处),之后将参数减2<+37>,将func4的返回值累加<+40>,所以推测相应代码如下:

int func4(int n)
{
    int v1 = 0;
    if (n <= 1)
        return 1;
    do {
        v1 += func4(n-1);
        n -= 2;
    } while (n > 1); return v1+1; }

 要使得func4的返回值为0x90即144,写个测试代码:

for (i = 0; i < 20; i++)
    printf("%d: %d\n", i, func4(i));

 得到结果为

所以这一关的通关密码是:11

 

查看第五关代码:

在第<+11>及<+19>处,检查输入串的长度必须为6,在<+7>处将串首地址存入%ebx,在<+34>处开辟了新的数组空间将首地址放入%ecx,在 <+37>到<+59>处是一个循环,将输入串的各个字符经过一定的处理后放入到新开辟的数组空间中。然后在<+74>处比较这个处理后的串与0x80496c2处的异同,显示0x80496c2处的内容为:

 那么经过处理后的字串为"titans"。再看看处理过程是什么:首先在<+37>处取出当前字符,再与0xf 位与,即只需要最低4位的值,在<+45>处将基地址0x804a5c0加上%eax中的偏移得到了目标字符,我们看看0x804a5c0处的内容:

 发现刚好有16个字符,对应"titans"分别在13、0、13、5、11、1处,因%eax中是当前字符的低4位,所以只要保证输入串字符的低四位表示0xD、0x0、0xD、0x5、0xB、0x1就满足条件了,组合其中一组结果为:

 

查看phase_6的代码:

在<+16>处将输入的参数转换成长整形。然后调用fun6,在<+41>处可以判断fun6的返回值是一个地址,那么可以不用进入fun6,而在<+41>处查看%eax即可知道fun6的返回值。单步运行到<+41>处然后查看%eax的内容为:

在<+41>及<+45>之间进行8次这样的循环:取%eax(地址),将它加上0x8,再取这个地址上的值(还是一个地址),那么可以用一个表来表示这样的过程:

测试最后的地址上的值:

可知通关密码为:198,测试一下:

 

哈哈,解决了。

 

转载于:https://www.cnblogs.com/justvi/archive/2011/11/22/2258984.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值