bomblab-0x12攻略

计算机组成原理的一个实验,在做bomblab之前需要一些基础知识,包括x86的一些指令寄存器(寄存器知识不需要详解,这篇文章的寄存器部分就足够了),以及gdb相关

用到的一部分gdb调试指令:
终端输入 gdb file_name,进入文件。
输入layout asm, 进入调试窗口。
b func_name, 在func_name设置断点。
r 运行; c 继续运行; q 退出。
x/s $reg_name ,查看某寄存器值。
p/x *(int*)(address)@len ,查看从address开始len长的数组值。
delete breakpoint_name, 删除断点。 

phase_1 :

观察代码:
在这里插入图片描述
其中text %eax, %eax意义为判定eax==0是否成立,eax是上一个函数的返回值,所以应该是判断字符串是否相等,并且返回相等。
因此,进入strings_not_equal
在这里插入图片描述
我们可以发现rsi, rdi是两个参数,查看这两个寄存器的值,结果即为答案:
在这里插入图片描述
所以答案为:I can see Russia from my house!

在这里插入图片描述

phase_2 :

在这里插入图片描述
这部分应该是是读取数据,看到了read_six_numbers可以猜想是读了六个数字,但是具体按照什么格式读取需要进入read_six_numbers查看:
在这里插入图片描述
这些代码中,mov $0x4025a3, %esi比较有用(来自百度),这个可以查看输入格式:输入x/s 0x4025a3
在这里插入图片描述
因此是六个按照空格分隔的数字。继续观察代码:
在这里插入图片描述
可以看到第一行比较了args[0] == 1如果相等则跳过炸弹。因此我们确定第一个值为1。并且rbp = args[0x14/4] = args[5]即输入参数的最后一个。
接下来是核心代码:
在这里插入图片描述

由于phase_2+70跳转到了phase_2+49,可以看出这是一个循环。循环的跳出条件为rbp == rbx。也就是说当rbx = args[5]时结束循环,在这之前每次循环都不能引爆炸弹。将这个伪代码用cpp表示:

在这里插入图片描述

分析其作用,应该是判断第一个数的二倍是否为第二个数,第二个数的二倍是否为第三个数······直到第五个和第六个进行比较,rbx被赋值为args[5]即第六个数,代码跳出循环。

在这里插入图片描述
跳出循环之后再无炸弹,便可以放心。因此我们知道,第一个数必定为1,之后每一个数为第一个数的2倍,总共有六个数,答案即为:1 2 4 8 16 32

在这里插入图片描述


phase_3 :

观察代码:
在这里插入图片描述
查看输入格式,得到:
在这里插入图片描述
因此要输入两个数字。
下一部分代码:
在这里插入图片描述
这部分除去输入格式判断,第一个有用的是cmpl $0x7, (%rsp)。这句话决定了第一个数字不能超过7 。假设该数字为3。接着就把3赋给了eax。最后一行进行了一次跳转,查看跳转地址x/s *(0x402420+3*8)得到:
在这里插入图片描述
可以得到跳转到了phase_3+78的位置。查看这个位置代码:
在这里插入图片描述
此时赋值eax = 0x2b1并跳转到phase_3+130。观察phase_3+130
在这里插入图片描述
直接进行了args[1] == eax的判断并且相等则跳过炸弹,释放栈空间。因此args[1] = eax = 0x2b1。答案即为:3 689。经过尝试,应该是有7种答案,又试了一个arg[0] = 2的答案为:2 690
在这里插入图片描述


phase_4:

首先设置断点观察phase_4的代码,前几行预处理代码如下:

在这里插入图片描述

查看输入格式。输入x/s 0x4025af得到:

在这里插入图片描述

可以看出要输入的是两个数字。

观察剩余的代码:

在这里插入图片描述

可以看出edx = e; esi = 0; edi = args[0](输入的第一个值)。接着调用了func4并返回eax(返回值专用寄存器)。然后比较eax == 0x2d如果为false则引爆炸弹,因此func4 return 0x2d。然后又有args[1] == 0x2d的比较,若不等则不跳转,引爆炸弹,可以看出第二个参数为0x2d,第一个参数为经过func4返回0x2d的值。

使用cpp写出来func4的代码:

在这里插入图片描述

分析如上, 代码如下:

int func4(int esi, int edx, int edi)
{
    int eax = edx;                          
    eax -= esi;                             
    int ebx = eax;                          
    ebx = ebx>0?0:1;                        
    eax += ebx;                             
    eax /= 2;                               
    ebx = esi + eax;                        
    if(ebx > edi)                           
    {
        edx = ebx - 1;                      
        int ret1 = func4(esi, edx, edi);    
        return ret1+ebx;
    }
    else if(edi <= ebx) return ebx;
    else
    {
        esi = ebx + 1;
        int ret2 = func4(esi, edx, edi);
        return ret2+ebx;
    }
}

接着循环找出哪个数字返回0x2d,我做了1到100的循环找到了14 。所以可以确定答案是14 0x2d,即14 45

在这里插入图片描述

phase_5 :

在这里插入图片描述
和4的思路一样,首先查看0x4025af,确定输入格式:

在这里插入图片描述

可以看出这次的输入仍然是两个数字。

这次剩余的代码较为复杂:

在这里插入图片描述
phase_5+48phase_5+67为第一部分,通过这部分可以看出使用eax作为中间寄存器对args[0]进行了与0xf的按位与,并且比较了0xf == args[0]&0xf,也就是说第一个输入值不能是大于等于16的值。看到后面的映射之后可以发现这是为了避免非法访问。

在这里插入图片描述

phase_5+72phase_5+98为第二部分,由于phase_5+89跳转到了phase_5+72,因此这部分应该是一个循环。其中循环结束条件为eax == 0xf。但是之后在phase_5+98也比较了edx == 0xf,如果不相等就爆炸。而edx在初始值为0并且只在循环中改变值。所以正确的跳出循环条件为eax == 0xf && edx == 0xf。循环中除了这两个之外还有一个寄存器叫做ecx,其初始值为0。观察phase_5+77行,可以看出该行为一个映射复制,查看以0x402460为起始地址的数组:

在这里插入图片描述

至此就很清楚了:该循环使用一个固定的数组进行多次映射,eax = args[0]为映射初始值,每次映射之后更新eax;使edx+=1;并且使ecx+=eax。所以ecx相当于求和,edx==0xf表明循环进行了15次,但由于是映射之后再加上eax,因此eax初始值并没有算进去(第二次才发现这个坑)。15次映射之后的eax应该是0xf,因此采用倒推的方法,从0xf向前映射15次,观察初始值。对应的cpp代码:

int reg[] = {10, 2, 14, 7, 8, 12, 15, 11, 0, 4, 1, 13, 3, 9, 6, 5};
int eax = 15, ecx = 15;
for(int edx=0; edx<15; edx++)
    for(int index=0; index<15; index++)
        if(reg[index] == eax)
    {
        eax = index;
        ecx += eax;
        cout<<index<<' ';
        break;
    }
ecx -= eax;
cout<<endl<<ecx;

输出结果

在这里插入图片描述

由此得到ecx = 115; args[0] = 5

在这里插入图片描述

第三部分为phase_5+103phase_5+139。这部分除去释放栈空间等处理,有用的在第一句上,第一句比较了ecx == args[1],相等跳过炸弹,因此第二个参数确定为ecx即115。答案即为:5 115

在这里插入图片描述


phase_6 :

查看代码:

在这里插入图片描述

由于read_six_numbers是之前已经能查看过的函数,可以直接判定这次也是读入六个空格分隔数字。

在这里插入图片描述

由于在最后一行看到了跳转至phase_6+48,可以看出这是一个循环,循环的结束很有意思,不在最后而在中间,也就是类似于whlie(1){··· if(xxx) break; ···}的循环体。可以看出跳出循环的条件在phase_6+76,为r14d == 0x6。在这之前又有r14d += 1,所以猜想r14d也许是一个计数器,每次加一,六次跳出,即:for(r14d = 0; r14d!=6; r14d++)。每次循环做的事情是从phase_6+48phase_6+109之间的事,观察到phase_6+103处还有个跳转,因此是一个小循环。尝试写出循环的cpp伪代码:

在这里插入图片描述

分析伪代码的作用,我们发现在第一个跳过炸弹的时候判定的是某一个数是否小于等于6,也就是说,args[i]<=6。接着在第二个循环里,我们发现本质上是比较args[r14d+1] == args[r14d],也就是说相邻两个数不能像等,我们得到了这六个数都小于等于6且各不相同。接着:

在这里插入图片描述

发现这也是一个循环,首先得初始化阶段为edx = 7,跳转条件为r12 != rcx。而在上一部分,我们知道r12表示的是rsp的首地址,又因为phase_6+131r12做了自增操作,因此r12应该是指示每一个值,也就是说对输入的六位数组进行遍历,对每一项的操作为:rax = edx; eax -= *(r12); *(r12)= eax这三句的意思是*(r12) = 7-*(r12)。也就是说每个数对7取补。

在这里插入图片描述这段代码是超多循环的集合,尝试对应的伪代码:

在这里插入图片描述

很遗憾,因为下面的循环是大循环,上面的循环是小循环,也就是说,晚开始循环的反而包括了早开始的循环体。在不使用goto语句似乎无法写出。观察这个伪代码,是一种奇怪的扫描方式,是通过一个地址找到另一个地址的方式,类似于c++里的链表格式。并且出现了一个让人在意的地址:0x6032f0,猜想这个应该是初始地址。查看初始地址里的值:

在这里插入图片描述
果然找到了另外一个地址,并且和这个地址相邻,基本可以确实是链表。通过伪代码可以看出来前三个值比较有用,分别为:data, index, address。最后一个也就是ecx。由跳出条件,我们需要一直找到ecx==0的状态:

在这里插入图片描述
正好到第六个地址为0,跳出循环。接下来还是有很多循环,下一个循环:

在这里插入图片描述

phase_6+211phase_6+229为一个循环,循环跳出条件为rax == rsi循环体中有rax += 8,初始为rax = *(rsp+0x20); rsi = *(rsp+0x48)。所以共循环5次,也就是遍历所有值。每次循环操作为*(rcx+8) = rcx = *(rax+8)。这个循环要和下一个循环结合理解:这是最后一个循环:

在这里插入图片描述

这个循环的跳出很奇怪,cmpphase_6+250,跳出在phase_6+252phase_6+266,应该是一个判断决定两个跳转。而第一个判断是躲炸弹的,因此必须要求每次都有*(rbx) >= eax。观察循环,可以发现eax = *(rax) = *(rbx+8),也就是说,*(rbx) > *(rbx+8)必须成立,即这个数组应该是降序排列。回想之前得到的链表,我们可以发现,data降序排列时的index值为:1 4 3 5 6 2,由于进行过一次对7取补,真正的序列为 : 6 3 4 2 1 5

在这里插入图片描述
至此全部解决,给出运行截图:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值