bomblab

开始之前

    开始紧张刺激的拆炸弹之旅之前,先了解一下以下几点:

1)如何使用gdb调试

    在解压后的炸弹目录下输入命令:

gdb bomb

2)如何查看反汇编代码

    方法一:进入gdb调试后,可以使用disassemble命令+函数名,可实时查看该函数的反汇编代码。
    这里写图片描述
    方法二:在终端输入:

objdump -d bomb>bomb.out

    会在当前目录下生成一个bomb.out文件,可用vim查看,文件内容就是整个bomb的反汇编代码。
    这里写图片描述

3)小技巧

    1.爆炸的原理是在输入的答案经过判定不正确后调用了一个函数名为explode_bomb的爆炸函数。
    进入gdb调试后,输入:

b explode_bomb

    即在爆炸函数打断点,这样在爆炸前程序执行就会中断。
    2.遇见传入一个地址值的mov语句,就用p或者x命令查看一下这个地址上的内容,要么直接就是答案要么是要输入的答案格式要么就是和答案有关的内容。
    3.通常看到调用的一些函数,可以直接看函数名,然后结合c语言的函数推测函数的作用和返回值。

phase_1

<main>部分代码: 
 8048a1f:   e8 f2 07 00 00          call   8049216 <read_line>
                                             //读取一行输入的内容(字符串形式),返回首地址
 8048a24:   89 04 24                mov    %eax,(%esp)//输入内容的首地址压栈

这里写图片描述

 8048a27:   e8 b4 00 00 00          call   8048ae0 <phase_1>

这里写图片描述

08048ae0 <phase_1>:
 8048ae0:   55                      push   %ebp

这里写图片描述

 8048ae1:   89 e5                   mov    %esp,%ebp
 8048ae3:   83 ec 18                sub    $0x18,%esp

这里写图片描述
(此后会省略以上重复的内容)

 8048ae6:   c7 44 24 04 50 a1 04    movl   $0x804a150,0x4(%esp)
 8048aed:   08 
 8048aee:   8b 45 08                mov    0x8(%ebp),%eax 
 8048af1:   89 04 24                mov    %eax,(%esp)

这里写图片描述

 8048af4:   e8 69 04 00 00          call   8048f62 <strings_not_equal>
 //调用字符串比较函数strings_not_equal
 8048af9:   85 c0                   test   %eax,%eax
 //根据eax中返回值是否为0设置标志位
 8048afb:   74 05                   je     8048b02 <phase_1+0x22>
 //若返回值为零则跳转,否则执行下一句调用爆炸函数
 8048afd:   e8 83 06 00 00          call   8049185 <explode_bomb>
 8048b02:   c9                      leave  
 8048b03:   c3                      ret

即比较了地址0x804a150上的内容和输入的内容是否一致,直接扫描内存即可。

phase_2

08048b04 <phase_2>:
 8048b04:   55                      push   %ebp
 8048b05:   89 e5                   mov    %esp,%ebp
 8048b07:   53                      push   %ebx
 8048b08:   83 ec 34                sub    $0x34,%esp
 8048b0b:   8d 45 e0                lea    -0x20(%ebp),%eax
 8048b0e:   89 44 24 04             mov    %eax,0x4(%esp)
 8048b12:   8b 45 08                mov    0x8(%ebp),%eax
 8048b15:   89 04 24                mov    %eax,(%esp)
 8048b18:   e8 aa 06 00 00          call   80491c7 <read_six_numbers>

这里写图片描述
由此可知read_six_number的参数除输入的内容外,还有一个ebp-0x20,结合后面代码以及考虑到栈中还有很多空间开辟后未使用,可推出读取的六个数字以类似数组形式存储在了ebp-0x20开始的连续空间中(具体可看read_six_number的代码)。

 8048b1d:   83 7d e0 00             cmpl   $0x0,-0x20(%ebp)
 8048b21:   79 22                   jns    8048b45 <phase_2+0x41>
 8048b23:   e8 5d 06 00 00          call   8049185 <explode_bomb>
 8048b28:   eb 1b                   jmp    8048b45 <phase_2+0x41>
 /*循环体*/
 8048b2a:   89 d8                   mov    %ebx,%eax  //ebx=eax
 8048b2c:   03 44 5d dc             add    -0x24(%ebp,%ebx,2),%eax
                                         //eax+=*(ebp+2*ebx-0x24)
 8048b30:   39 44 5d e0             cmp    %eax,-0x20(%ebp,%ebx,2)
                                         //比较eax 和 *(ebp+2*ebx-0x20)
 8048b34:   74 05                   je     8048b3b <phase_2+0x37>
 8048b36:   e8 4a 06 00 00          call   8049185 <explode_bomb>
 8048b3b:   83 c3 02                add    $0x2,%ebx  //ebx+=2
 8048b3e:   83 fb 0c                cmp    $0xc,%ebx  //比较ebx和0xc
 8048b41:   75 e7                   jne    8048b2a <phase_2+0x26>
                                                     //若ebx不等于12,继续循环

 /*循环初始化*/
 8048b43:   eb 07                   jmp    8048b4c <phase_2+0x48>
 8048b45:   bb 02 00 00 00          mov    $0x2,%ebx  //初始化ebx=2

 /*循环结束*/
 8048b4a:   eb de                   jmp    8048b2a <phase_2+0x26>
 8048b4c:   83 c4 34                add    $0x34,%esp
 8048b4f:   5b                      pop    %ebx
 8048b50:   5d                      pop    %ebp
 8048b51:   c3                      ret

上述注释结合起来就是:

for ebx != 12 
    eax = ebx
    eax += *(ebp + 2 * ebx - 0x24)
if eax == *(ebp + 2 * ebx - 0x20) then
    ebx += 2
else explode
end for

转化为C语言代码:

for(ebx = 2; ebx != 12) {
    eax = ebx;
    eax += a[ebx/2 - 1];
    if(eax == a[ebx/2]) 
        ebx += 2;
    else explode();
}

即输入的六个数为首项大于等于0且符合a[i]-a[i-1]=2*i(i>0)的二阶等差数列。

phase_3

 8048a5b:   e8 b6 07 00 00          call   8049216 <read_line>
 8048a60:   89 04 24                mov    %eax,(%esp)
 8048a63:   e8 ea 00 00 00          call   8048b52 <phase_3>


08048b52 <phase_3>:
 8048b52:   55                      push   %ebp
 8048b53:   89 e5                   mov    %esp,%ebp
 8048b55:   83 ec 28                sub    $0x28,%esp
 8048b58:   8d 45 f0                lea    -0x10(%ebp),%eax
 8048b5b:   89 44 24 0c             mov    %eax,0xc(%esp)
 8048b5f:   8d 45 f4                lea    -0xc(%ebp),%eax
 8048b62:   89 44 24 08             mov    %eax,0x8(%esp)
 8048b66:   c7 44 24 04 b1 a3 04    movl   $0x804a3b1,0x4(%esp)
 8048b6d:   08 
 8048b6e:   8b 45 08                mov    0x8(%ebp),%eax
 8048b71:   89 04 24                mov    %eax,(%esp)
 8048b74:   e8 67 fc ff ff          call   80487e0 <__isoc99_sscanf@plt>

这里写图片描述
__isoc99_sscanf@plt有四个参数,扫描可得有一个参数内容为”%d %d”,然后联想C语言sscanf函数的作用,可知要输入两个整数,这两个整数会存在ebp-0xc和ebp-0x10位置上(注意压栈和参数顺序的关系)。

 8048b79:   83 f8 01                cmp    $0x1,%eax
 8048b7c:   7f 05                   jg     8048b83 <phase_3+0x31>
 //__isoc99_sscanf@plt读出的参数个数应大于1
 8048b7e:   e8 02 06 00 00          call   8049185 <explode_bomb>
 8048b83:   83 7d f4 07             cmpl   $0x7,-0xc(%ebp)
 8048b87:   77 5b                   ja     8048be4 <phase_3+0x92>
 //第一个数不大于7且大于等于0
 8048b89:   8b 45 f4                mov    -0xc(%ebp),%eax
 8048b8c:   ff 24 85 b0 a1 04 08    jmp    *0x804a1b0(,%eax,4)
 //根据第一个数选择跳转的地址(注意*号和没有*号的区别)
 8048b93:   b8 00 00 00 00          mov    $0x0,%eax
 8048b98:   eb 05                   jmp    8048b9f <phase_3+0x4d>
 8048b9a:   b8 9e 03 00 00          mov    $0x39e,%eax
 8048b9f:   2d 5e 03 00 00          sub    $0x35e,%eax
 8048ba4:   eb 05                   jmp    8048bab <phase_3+0x59>
 8048ba6:   b8 00 00 00 00          mov    $0x0,%eax
 8048bab:   05 d3 02 00 00          add    $0x2d3,%eax
 8048bb0:   eb 05                   jmp    8048bb7 <phase_3+0x65>
 8048bb2:   b8 00 00 00 00          mov    $0x0,%eax
 8048bb7:   83 e8 4e                sub    $0x4e,%eax
 8048bba:   eb 05                   jmp    8048bc1 <phase_3+0x6f>
 8048bbc:   b8 00 00 00 00          mov    $0x0,%eax
 8048bc1:   83 c0 4e                add    $0x4e,%eax
 8048bc4:   eb 05                   jmp    8048bcb <phase_3+0x79>
 8048bc6:   b8 00 00 00 00          mov    $0x0,%eax
 8048bcb:   83 e8 4e                sub    $0x4e,%eax
 8048bce:   eb 05                   jmp    8048bd5 <phase_3+0x83>
 8048bd0:   b8 00 00 00 00          mov    $0x0,%eax
 8048bd5:   83 c0 4e                add    $0x4e,%eax
 8048bd8:   eb 05                   jmp    8048bdf <phase_3+0x8d>
 8048bda:   b8 00 00 00 00          mov    $0x0,%eax
 8048bdf:   83 e8 4e                sub    $0x4e,%eax
 8048be2:   eb 0a                   jmp    8048bee <phase_3+0x9c>

 8048be4:   e8 9c 05 00 00          call   8049185 <explode_bomb>
 8048be9:   b8 00 00 00 00          mov    $0x0,%eax
 8048bee:   83 7d f4 05             cmpl   $0x5,-0xc(%ebp)
 8048bf2:   7f 05                   jg     8048bf9 <phase_3+0xa7>
 //第一个数不大于5
 8048bf4:   3b 45 f0                cmp    -0x10(%ebp),%eax
 8048bf7:   74 05                   je     8048bfe <phase_3+0xac>
 //第二个数要等于eax
 8048bf9:   e8 87 05 00 00          call   8049185 <explode_bomb>
 8048bfe:   c9                      leave  
 8048bff:   90                      nop
 8048c00:   c3                      ret

即要输入的第一个数需要大于等于0且小于等于5的数,然后根据第一个数会选择一个跳转目的地址(比如第一个数为0,则跳转的地址就是0x804a1b0上的内容),然后会给eax一个值,之后顺序执行,会对eax做一系列的加减法,最后eax的值要和第二个数相等。

phase_4

08048c5f <phase_4>:
 8048c5f:   55                      push   %ebp 
 8048c60:   89 e5                   mov    %esp,%ebp    
 8048c62:   83 ec 28                sub    $0x28,%esp  
 8048c65:   8d 45 f0                lea    -0x10(%ebp),%eax
 8048c68:   89 44 24 0c             mov    %eax,0xc(%esp)
 8048c6c:   8d 45 f4                lea    -0xc(%ebp),%eax
 8048c6f:   89 44 24 08             mov    %eax,0x8(%esp)
 8048c73:   c7 44 24 04 b1 a3 04    movl   $0x804a3b1,0x4(%esp)
 8048c7a:   08 
 8048c7b:   8b 45 08                mov    0x8(%ebp),%eax
 8048c7e:   89 04 24                mov    %eax,(%esp)
 8048c81:   e8 5a fb ff ff          call     80487e0 <__isoc99_sscanf@plt>  

可以看到目前为止都和phase3的代码一致,唯一差别为压栈的地址值不同。同样扫描这个地址得到内容为:“%d %d”,因此这里同样是要输入两个整数。

 8048c86:   83 f8 02                cmp    $0x2,%eax
 8048c89:   75 06                   jne    8048c91 <phase_4+0x32>
                                        //获取到的参数个数应等于2
 8048c8b:   83 7d f4 0e             cmpl   $0xe,-0xc(%ebp)
 8048c8f:   76 05                   jbe    8048c96 <phase_4+0x37>
                                        //第一个数应小于等于0xe
 8048c91:   e8 ef 04 00 00          call   8049185 <explode_bomb>
 8048c96:   c7 44 24 08 0e 00 00    movl   $0xe,0x8(%esp)
 8048c9d:   00                          //0xe压栈
 8048c9e:   c7 44 24 04 00 00 00    movl   $0x0,0x4(%esp)
 8048ca5:   00                          //0x0压栈
 8048ca6:   8b 45 f4                mov    -0xc(%ebp),%eax
 8048ca9:   89 04 24                mov    %eax,(%esp)
                                        //第一个数压栈
 8048cac:   e8 50 ff ff ff          call   8048c01 <func4>//调用func4
 8048cb1:   83 f8 07                cmp    $0x7,%eax   
 8048cb4:   75 06                   jne    8048cbc <phase_4+0x5d>
                                        //返回值应当为7
 8048cb6:   83 7d f0 07             cmpl   $0x7,-0x10(%ebp)
 8048cba:   74 05                   je     8048cc1 <phase_4+0x62>
                                        //第二个数应当为7
 8048cbc:   e8 c4 04 00 00          call   8049185 <explode_bomb>
 8048cc1:   c9                      leave  
 8048cc2:   c3                      ret 

根据以上部分代码可知:第一个数应当在[0,14]的区间内,第二个数为7,同时还需要满足func4(x,0,14)的返回值为7(设第一个数为x)。因此接下来分析func4的作用,根据返回值反向推出第一个数即可。

08048c01 <func4>:
 8048c01:   55                      push   %ebp 
 8048c02:   89 e5                   mov    %esp,%ebp    
 8048c04:   56                      push   %esi 
 8048c05:   53                      push   %ebx
 8048c06:   83 ec 10                sub    $0x10,%esp
 8048c09:   8b 55 08                mov    0x8(%ebp),%edx   //第一个参数赋值给edx
 8048c0c:   8b 45 0c                mov    0xc(%ebp),%eax//第二个参数赋值给eax
 8048c0f:   8b 75 10                mov    0x10(%ebp),%esi//第三个参数赋值给esi
 8048c12:   89 f1                   mov    %esi,%ecx    //ecx = esi
 8048c14:   29 c1                   sub    %eax,%ecx    //ecx -= eax
 8048c16:   89 cb                   mov    %ecx,%ebx    //ebx = ecx
 8048c18:   c1 eb 1f                shr    $0x1f,%ebx  //ebx逻辑右移31位
 8048c1b:   01 d9                   add    %ebx,%ecx    //ecx +=ebx
 8048c1d:   d1 f9                   sar    %ecx //ecx算术右移1位
 8048c1f:   8d 1c 01                lea    (%ecx,%eax,1),%ebx//ebx = ecx + eax
 8048c22:   39 d3                   cmp    %edx,%ebx    //比较edx和ebx
 8048c24:   7e 17                   jle    8048c3d <func4+0x3c>

 /*ebx > edx*/
 8048c26:   8d 4b ff                lea    -0x1(%ebx),%ecx  //ecx = ebx - 1
 8048c29:   89 4c 24 08             mov    %ecx,0x8(%esp)   //ecx压栈
 8048c2d:   89 44 24 04             mov    %eax,0x4(%esp)   //eax压栈
 8048c31:   89 14 24                mov    %edx,(%esp)  //edx压栈
 8048c34:   e8 c8 ff ff ff          call   8048c01 <func4>//调用func4
 8048c39:   01 d8                   add    %ebx,%eax    //eax += ebx
 8048c3b:   eb 1b                   jmp    8048c58 <func4+0x57>

 /*ebx <= edx*/
 8048c3d:   89 d8                   mov    %ebx,%eax    //eax = ebx
 8048c3f:   39 d3                   cmp    %edx,%ebx    //比较edx和ebx
 8048c41:   7d 15                   jge    8048c58 <func4+0x57>

 /*ebx < edx*/
 8048c43:   89 74 24 08             mov    %esi,0x8(%esp)   //esi压栈
 8048c47:   8d 43 01                lea    0x1(%ebx),%eax   //eax = ebx + 1
 8048c4a:   89 44 24 04             mov    %eax,0x4(%esp) //eax压栈
 8048c4e:   89 14 24                mov    %edx,(%esp)  //edx压栈
 8048c51:   e8 ab ff ff ff          call   8048c01 <func4>//调用func4
 8048c56:   01 d8                   add    %ebx,%eax    //eax += ebx

 /*恢复调用现场*/
 8048c58:   83 c4 10                add    $0x10,%esp  //释放栈空间
 8048c5b:   5b                      pop    %ebx
 8048c5c:   5e                      pop    %esi
 8048c5d:   5d                      pop    %ebp
 8048c5e:   c3                      ret  

上述注释内容整合起来就是:

edx = a
eax = b
esi = c
ecx = esi
ecx -= eax
ebx =ecx
ebx >> 0x1f //逻辑右移
ecx += ebx
ecx >> 1    //算术右移
ebx = ecx + eax
if ebx <= edx then
    eax = ebx
    if ebx >= edx then
        return
    else return func4(edx,ebx + 1,esi) + ebx
    end if
else return func4(edx,eax,ecx-1) + ebx
end if

其中a、b、c依次表示func4的三个参数。
由于数字的范围可以确定是[0,14]的子集,则逻辑右移31位的结果一定为0,算术右移1位可以等效于/2操作。可以列表手动运行,得到函数返回前各个寄存器的内容如下:

registerval
edxa
eaxb
esic
ecx[c-b+0]/2
ebx[c-b+0]/2+b

则func4转化为C语言后如下:

int func4(a,b,c){
    if((c-b)/2+b < a)
        return func4(a,(c-b)/2+b+1,c) + (c-b)/2+b;
    else if((c-b)/2+b = a)
        return a;
    else if((c-b)/2+b > a)
        return func4(a,b,(c-b)/2-1) + (c-b)/2+b;
}

实质上func4就是一个递归实现的二分搜索过程,每一次划分都会将最终的返回值加上作为划分标志的数(比如[8,14]的中间数为11,则返回值会加11),最终查找到a后,返回值再加a。

二分搜索树结构如下:

              7
        3            11
    1      5      9       13
0    2   4   6   8  10   12  14

各自对应的返回值为:

                 7
        10               18
    11      15        27       31
11    13   9   21   35  37   43   45

即第一个数就是7

phase_5

08048cc3 <phase_5>:
 8048cc3:   55                      push   %ebp
 8048cc4:   89 e5                   mov    %esp,%ebp
 8048cc6:   53                      push   %ebx
 8048cc7:   83 ec 24                sub    $0x24,%esp  
 8048cca:   8b 5d 08                mov    0x8(%ebp),%ebx
                                    //将输入的内容的首地址传递给ebx
 8048ccd:   89 1c 24                mov    %ebx,(%esp)  
 8048cd0:   e8 6b 02 00 00          call   8048f40 <string_length>
                                    //获取输入的字符串长度
 8048cd5:   83 f8 06                cmp    $0x6,%eax   
 8048cd8:   74 48                   je     8048d22 <phase_5+0x5f>
                                    //长度应为6
 8048cda:   e8 a6 04 00 00          call   8049185 <explode_bomb>
 8048cdf:   90                      nop
 8048ce0:   eb 40                   jmp    8048d22 <phase_5+0x5f>
                                     //跳转至循环初始化部分

易知这里是要输入长度为6的字符串,同时注意ebx中存储了输入的字符串的首地址,在下面的代码分析中会用到。

 /*循环体*/
 8048ce2:   0f b6 14 03             movzbl (%ebx,%eax,1),%edx
                                    //将第eax个字符存入edx
 8048ce6:   83 e2 0f                and    $0xf,%edx   //取edx低4位
 8048ce9:   0f b6 92 d0 a1 04 08    movzbl 0x804a1d0(%edx),%edx
                            //将0x804a1d0地址上的第edx个元素存入edx
 8048cf0:   88 54 05 f1             mov    %dl,-0xf(%ebp,%eax,1)
                        //将edx低8位传递到从ebp-0xf位置开始的第eax个空间
                        //ebp-0xf可以看成是另一个数组的首地址
 8048cf4:   83 c0 01                add    $0x1,%eax   
 8048cf7:   83 f8 06                cmp    $0x6,%eax
 8048cfa:   75 e6                   jne    8048ce2 <phase_5+0x1f>
                                    //eax自增,当eax不等于6的时候,继续循环
                                    //即循环6次,eax取值为0~6

 /*检验答案*/
 8048cfc:   c6 45 f7 00             movb   $0x0,-0x9(%ebp) 
                                    //添加终止符
 8048d00:   c7 44 24 04 a6 a1 04    movl   $0x804a1a6,0x4(%esp)
         8048d07:   08                          //将0x804a1a6压栈
 8048d08:   8d 45 f1                lea    -0xf(%ebp),%eax
 8048d0b:   89 04 24                mov    %eax,(%esp)
                                    //将ebp-0xf压栈(意义见循环体)
 8048d0e:   e8 4f 02 00 00          call   8048f62 <strings_not_equal>
 8048d13:   85 c0                   test   %eax,%eax
 8048d15:   74 12                   je     8048d29 <phase_5+0x66>
                                    //0x804a1a6和ebp-0xf位置上的内容应相等
 8048d17:   e8 69 04 00 00          call   8049185 <explode_bomb>
 8048d1c:   8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi
 8048d20:   eb 07                   jmp    8048d29 <phase_5+0x66>

 /*循环初始化*/
 8048d22:   b8 00 00 00 00          mov    $0x0,%eax   //初始化eax为0
 8048d27:   eb b9                   jmp    8048ce2 <phase_5+0x1f>

 /*函数结束*/
 8048d29:   83 c4 24                add    $0x24,%esp
 8048d2c:   5b                      pop    %ebx
 8048d2d:   5d                      pop    %ebp
 8048d2e:   c3                      ret  

循环体的作用就是依次对字符串的每个字符进行两次变换,其中一次变换需要用到数组A(首地址为0x804a1d0),然后存入另一个数组B(首地址为ebp-0xf)对应的位置,要求是数组B中的内容要和指定数组C(首地址0x804a1a6)的内容相同。简单来说就是有一个函数关系:C = f(input) = A[input & 0xf] & 0xff,已知C和f,求input。

PS:ascll码中会有多个字符低4位相同,因此答案不唯一

phase_6

本关循环较多,跳转多,汇编代码看起来比较绕,但还是能清晰分为几块(所以C语言转换就不写了ㄟ( ▔, ▔ )ㄏ,看注释再结合几个图应该很好理解了)。

整个过程整理如下:
这里写图片描述

扫描代码中唯一出现的地址,结果如下:
这里写图片描述
可以看到每个node都有三个部分:一个值(val)、编号(id)、还有一个地址(next)
即本关的数据结构为链表,有几点看注释前需要先注意:
1)仔细看可以发现链表原本是按id顺序排好的。
2)假设ebp为某个结点地址,则0x8(%ebp)等效于ebp→next

8048ab5:    e8 5c 07 00 00          call   8049216 <read_line>
 8048aba:   89 04 24                mov    %eax,(%esp)
 8048abd:   e8 6d 02 00 00          call   8048d2f <phase_6>


08048d2f <phase_6>:
 8048d2f:   55                      push   %ebp
 8048d30:   89 e5                   mov    %esp,%ebp
 8048d32:   56                      push   %esi 
 8048d33:   53                      push   %ebx 
 8048d34:   83 ec 40                sub    $0x40,%esp  
 8048d37:   8d 45 e0                lea    -0x20(%ebp),%eax 
 8048d3a:   89 44 24 04             mov    %eax,0x4(%esp)
 8048d3e:   8b 45 08                mov    0x8(%ebp),%eax
 8048d41:   89 04 24                mov    %eax,(%esp)
 8048d44:   e8 7e 04 00 00          call   80491c7 <read_six_numbers>
                                    //以上部分同第二关类似
 /*初步检测输入是否符合要求*/
 /*外层循环,检测输入数字大小范围*/
 8048d49:   be 00 00 00 00          mov    $0x0,%esi   //初始化esi=0
 8048d4e:   8b 44 b5 e0             mov    -0x20(%ebp,%esi,4),%eax
                                    //取数组的第esi个元素给eax
 8048d52:   83 e8 01                sub    $0x1,%eax   //eax-=1
 8048d55:   83 f8 05                cmp    $0x5,%eax   
 8048d58:   76 05                   jbe    8048d5f <phase_6+0x30>
                                    //eax的值应在[0,5]的区间
                                    //即输入的每个数都应在[1,6]的区间
 8048d5a:   e8 26 04 00 00          call   8049185 <explode_bomb>
 8048d5f:   83 c6 01                add    $0x1,%esi   //esi加1
 8048d62:   83 fe 06                cmp    $0x6,%esi
 8048d65:   75 07                   jne    8048d6e <phase_6+0x3f>
                                    //若esi不等于6,继续循环

 //esi==6
 8048d67:   bb 00 00 00 00          mov    $0x0,%ebx   //初始化ebx=0
 8048d6c:   eb 38                   jmp    8048da6 <phase_6+0x77>
                                    //结束循环

 //esi!=6,内层循环,检测数字是否各不相同
 8048d6e:   89 f3                   mov    %esi,%ebx    //初始化ebx=esi
 8048d70:   8b 44 9d e0             mov    -0x20(%ebp,%ebx,4),%eax
                                    //取数组的第ebx个元素给eax
 8048d74:   39 44 b5 dc             cmp    %eax,-0x24(%ebp,%esi,4)
 8048d78:   75 05                   jne    8048d7f <phase_6+0x50>
                                    //第esi个元素应当和第ebx个元素值不相等
 8048d7a:   e8 06 04 00 00          call   8049185 <explode_bomb>
 8048d7f:   83 c3 01                add    $0x1,%ebx   //ebx自增
 8048d82:   83 fb 05                cmp    $0x5,%ebx   
 8048d85:   7e e9                   jle    8048d70 <phase_6+0x41>
 8048d87:   eb c5                   jmp    8048d4e <phase_6+0x1f>
                                    //若ebx大于5,则结束内层循环

此时栈的结构如下:
这里写图片描述

 /*结点按输入的id顺序重新将地址排序并记录*/
 8048d89:   8b 52 08                mov    0x8(%edx),%edx
                                    //edx=edx->next
 8048d8c:   83 c0 01                add    $0x1,%eax   //eax加一
         //PS:由于链表初始按id顺序排列,eax自增等效于下一个结点的id
 8048d8f:   39 c8                   cmp    %ecx,%eax
 8048d91:   75 f6                   jne    8048d89 <phase_6+0x5a>
                                //遍历链表直到ecx=eax即找到特定id的结点
 8048d93:   eb 05                   jmp    8048d9a <phase_6+0x6b>
                                    //跳过下一句
 //id=1
 8048d95:   ba 54 c1 04 08          mov    $0x804c154,%edx
                                    //edx=0x804c154
 8048d9a:   89 54 b5 c8             mov    %edx,-0x38(%ebp,%esi,4)
                //将edx(即id为ecx结点的地址)记录在ebp-0x38开始的第esi个空间
 8048d9e:   83 c3 01                add    $0x1,%ebx   //ebx自增
 8048da1:   83 fb 06                cmp    $0x6,%ebx
 8048da4:   74 17                   je     8048dbd <phase_6+0x8e>
                                    //若ebx等于6则结束循环
 //获取下一个id
 8048da6:   89 de                   mov    %ebx,%esi    //esi=ebx
 8048da8:   8b 4c 9d e0             mov    -0x20(%ebp,%ebx,4),%ecx
                                    //将输入的数组第ebx个元素给ecx
 8048dac:   83 f9 01                cmp    $0x1,%ecx
 8048daf:   7e e4                   jle    8048d95 <phase_6+0x66>
                                    //根据结点id选择要执行的代码
 //id>1
 8048db1:   b8 01 00 00 00          mov    $0x1,%eax   //eax=1
 8048db6:   ba 54 c1 04 08          mov    $0x804c154,%edx
                                    //edx=0x804c154
 8048dbb:   eb cc                   jmp    8048d89 <phase_6+0x5a>
 /*重建链表*/
 //循环初始化
 8048dbd:   8b 5d c8                mov    -0x38(%ebp),%ebx 
                                    //将第一个结点地址给ebx
 8048dc0:   8d 45 cc                lea    -0x34(%ebp),%eax
                                //eax=ebp-0x34即指向第二个结点的地址
 8048dc3:   8d 75 e0                lea    -0x20(%ebp),%esi
                                    //esi=ebp-0x20即指针数组末尾
 8048dc6:   89 d9                   mov    %ebx,%ecx
                                        //ecx=ebx
 //循环体
 8048dc8:   8b 10                   mov    (%eax),%edx
                                    //取下一个结点地址给edx
 8048dca:   89 51 08                mov    %edx,0x8(%ecx)
                                    //ecx->next=edx
 8048dcd:   83 c0 04                add    $0x4,%eax   
                                    //eax+=4即指向下一个指针
 8048dd0:   39 f0                   cmp    %esi,%eax
 8048dd2:   74 04                   je     8048dd8 <phase_6+0xa9>
                                    //若eax=esi,结束循环
 8048dd4:   89 d1                   mov    %edx,%ecx    //ecx=edx
 8048dd6:   eb f0                   jmp    8048dc8 <phase_6+0x99>
 /*检测链表的val成员是否降序排列*/
 //循环初始化
 8048dd8:   c7 42 08 00 00 00 00    movl   $0x0,0x8(%edx)
 8048ddf:   be 05 00 00 00          mov    $0x5,%esi   

 //循环体
 8048de4:   8b 43 08                mov    0x8(%ebx),%eax
                                    //将ebx->next给eax
 8048de7:   8b 00                   mov    (%eax),%eax
                                    //取*eax即下个结点的val给eax
 8048de9:   39 03                   cmp    %eax,(%ebx)
 8048deb:   7d 05                   jge    8048df2 <phase_6+0xc3>
                                //ebx->val的值应当大于等于下个结点的val
 8048ded:   e8 93 03 00 00          call   8049185 <explode_bomb>
 8048df2:   8b 5b 08                mov    0x8(%ebx),%ebx
                                    //ebx=ebx->next
 8048df5:   83 ee 01                sub    $0x1,%esi   //esi自减
 8048df8:   75 ea                   jne    8048de4 <phase_6+0xb5>
                                    //esi为0时结束循环
 /*函数结束*/
 8048dfa:   83 c4 40                add    $0x40,%esp
 8048dfd:   5b                      pop    %ebx
 8048dfe:   5e                      pop    %esi
 8048dff:   5d                      pop    %ebp
 8048e00:   c3                      ret    

总体来说读起来都知道是什么意思,就是跳转太多,逻辑上需要很多整理。

Secret_phase

    每一关都是对应的一个phase函数,隐藏关对应的就是secret_phase函数。在bomb.out中查找函数名,可以看到在phase_defused中调用了secret_phase函数。

 804936c:   8d 45 a8                lea    -0x58(%ebp),%eax
 804936f:   89 44 24 10             mov    %eax,0x10(%esp)
                                    //将ebp-0x58压栈
 8049373:   8d 45 a0                lea    -0x60(%ebp),%eax
 8049376:   89 44 24 0c             mov    %eax,0xc(%esp)
                                    //将ebp-0x60压栈
 804937a:   8d 45 a4                lea    -0x5c(%ebp),%eax
 804937d:   89 44 24 08             mov    %eax,0x8(%esp)
                                    //将ebp-0x5c压栈
 8049381:   c7 44 24 04 0b a4 04    movl   $0x804a40b,0x4(%esp)
 8049388:   08                      //将0x804a40b压栈
 8049389:   c7 04 24 f0 c8 04 08    movl   $0x804c8f0,(%esp)
                                    //将0x804c8f0压栈
 8049390:   e8 4b f4 ff ff          call  80487e0 <__isoc99_sscanf@plt>
                                    //调用__isoc99_sscanf@plt
 8049395:   83 f8 03                cmp    $0x3,%eax
 8049398:   75 34                   jne    80493ce <phase_defused+0x80> 
                                    //若输入的个数不为3,则跳过secret_phase
 804939a:   c7 44 24 04 14 a4 04    movl   $0x804a414,0x4(%esp)
 80493a1:   08                          
                                    //将0x804a414压栈
 80493a2:   8d 45 a8                lea    -0x58(%ebp),%eax
 80493a5:   89 04 24                mov    %eax,(%esp)
                                    //ebp-0x58压栈
 80493a8:   e8 b5 fb ff ff          call   8048f62 <strings_not_equal>
                                    //比较字符串
 80493ad:   85 c0                   test   %eax,%eax
 80493af:   75 1d                   jne   80493ce <phase_defused+0x80>
                                    //若比较结果不一样则跳过secret_phase

扫描以上出现的地址值,根据前几关的经验易知进入隐藏关需要输入一个正确的字符串。
同时还扫描出格式为”%d %d %s”。在前几关中,输入格式为”%d %d”的仅有第三、四两关,只需再在原有输入后添加正确的字符串即可在phase6之后进入隐藏关(具体是哪一关的原理待研究)。

首先看secret_phase代码

08048e54 <secret_phase>:
 8048e54:   55                      push   %ebp 
 8048e55:   89 e5                   mov    %esp,%ebp    
 8048e57:   53                      push   %ebx 
 8048e58:   83 ec 14                sub    $0x14,%esp  
 8048e5b:   e8 b6 03 00 00          call   8049216 <read_line>
 8048e60:   c7 44 24 08 0a 00 00    movl   $0xa,0x8(%esp)
 8048e67:   00                      //将0xa压栈
 8048e68:   c7 44 24 04 00 00 00    movl   $0x0,0x4(%esp)
 8048e6f:   00                      //将0x0压栈
 8048e70:   89 04 24                mov    %eax,(%esp)
 8048e73:   e8 c8 f9 ff ff          call   8048840 <strtol@plt>
                                    //调用strtol@plt(首地址,0,10)
                                    //函数作用自行百度
 8048e78:   89 c3                   mov    %eax,%ebx    
 8048e7a:   8d 40 ff                lea    -0x1(%eax),%eax  
 8048e7d:   3d e8 03 00 00          cmp    $0x3e8,%eax 
 8048e82:   76 05                   jbe    8048e89 <secret_phase+0x35>
                                    //eax的值要小于等于0x3e8
 8048e84:   e8 fc 02 00 00          call   8049185 <explode_bomb>
 8048e89:   89 5c 24 04             mov    %ebx,0x4(%esp)   
 8048e8d:   c7 04 24 a0 c0 04 08    movl   $0x804c0a0,(%esp)
                                    //将0x804c0a0压栈
 8048e94:   e8 68 ff ff ff          call   8048e01 <fun7>//调用fun7
 8048e99:   83 f8 04                cmp    $0x4,%eax
 8048e9c:   74 05                   je     8048ea3 <secret_phase+0x4f>
                                    //返回值要为4
 8048e9e:   e8 e2 02 00 00          call   8049185 <explode_bomb>
 8048ea3:   c7 04 24 80 a1 04 08    movl   $0x804a180,(%esp)
 8048eaa:   e8 d1 f8 ff ff          call   8048780 <puts@plt>
 8048eaf:   e8 9a 04 00 00          call   804934e <phase_defused>
                                    //输出提示信息并解除炸弹
 8048eb4:   83 c4 14                add    $0x14,%esp
 8048eb7:   5b                      pop    %ebx
 8048eb8:   5d                      pop    %ebp
 8048eb9:   c3                      ret    
 8048eba:   66 90                   xchg   %ax,%ax
 8048ebc:   66 90                   xchg   %ax,%ax
 8048ebe:   66 90                   xchg   %ax,%ax

分析得本关的答案为一个整数,结合第四关易知需要使fun7返回值为4,即fun7(0x804c0a0,输入的值)=4。

08048e01 <fun7>:
 8048e01:   55                      push   %ebp 
 8048e02:   89 e5                   mov    %esp,%ebp
 8048e04:   53                      push   %ebx 
 8048e05:   83 ec 14                sub    $0x14,%esp  
 8048e08:   8b 55 08                mov    0x8(%ebp),%edx   
                                    //第一个参数赋值给edx
 8048e0b:   8b 4d 0c                mov    0xc(%ebp),%ecx
                                    //第二个参数赋值给ecx
 8048e0e:   85 d2                   test   %edx,%edx
 8048e10:   74 37                   je     8048e49 <fun7+0x48>
                                    //若edx为0,则跳转返回-1

 /*edx!=0*/
 8048e12:   8b 1a                   mov    (%edx),%ebx 
                                    //将*edx赋值给ebx
 8048e14:   39 cb                   cmp    %ecx,%ebx    //比较ecx和ebx
 8048e16:   7e 13                   jle    8048e2b <fun7+0x2a>

 /*ebx>ecx*/
 8048e18:   89 4c 24 04             mov    %ecx,0x4(%esp)   //ecx压栈
 8048e1c:   8b 42 04                mov    0x4(%edx),%eax   
                                    //将*(edx+0x4)压栈
 8048e1f:   89 04 24                mov    %eax,(%esp)  //eax压栈
 8048e22:   e8 da ff ff ff          call   8048e01 <fun7>//递归调用
 8048e27:   01 c0                   add    %eax,%eax        
                                    //设置返回值为2*fun7
 8048e29:   eb 23                   jmp    8048e4e <fun7+0x4d>

 /*ebx<=ecx*/
 8048e2b:   b8 00 00 00 00          mov    $0x0,%eax   //设置返回值为0
 8048e30:   39 cb                   cmp    %ecx,%ebx    //比较ecx和ebx
 8048e32:   74 1a                   je     8048e4e <fun7+0x4d>
                                    //若ecx==ebx,则返回0
 8048e34:   89 4c 24 04             mov    %ecx,0x4(%esp)   //ecx压栈
 8048e38:   8b 42 08                mov    0x8(%edx),%eax   
                                    //将*(edx+0x8)压栈
 8048e3b:   89 04 24                mov    %eax,(%esp)  //eax压栈
 8048e3e:   e8 be ff ff ff          call   8048e01 <fun7>//递归调用
 8048e43:   8d 44 00 01             lea    0x1(%eax,%eax,1),%eax
                                    //设置返回值为2*fun7+1
 8048e47:   eb 05                   jmp    8048e4e <fun7+0x4d>

 /*edx==0*/
 8048e49:   b8 ff ff ff ff          mov    $0xffffffff,%eax
                                    //设置返回值为-1

 /*恢复调用现场*/
 8048e4e:   83 c4 14                add    $0x14,%esp
 8048e51:   5b                      pop    %ebx
 8048e52:   5d                      pop    %ebp
 8048e53:   c3                      ret  

结合第4关和第6关来看,这里的代码应该都比较好理解。这里作为参数的地址扫描结果如下
这里写图片描述
可以看到这里的结构体也有三个成员:一个数值和两个地址值。分析数据可以发现每个地址值都是另外一个结点的地址且互不相同,且最终都可追踪到一个没有后续结点的结点,由此推出这是一个二叉树结构。

将fun7转化为C语言:

int fun7(addr, x){
    if (addr == 0)
        return -1;
    //比较结点的值和x的大小
    else {
        if (addr->val > x)//若大于,访问左孩子
            return 2 * fun7(addr->leftchild, x);//返回的为偶数
        else if (addr->val == x)//若相等,则返回0
            return 0;
        else //若小于,访问右孩子
            return 2 * fun7(addr->rightchild, x) + 1;//返回的为奇数
    }
}

根据返回值的奇偶性以及二叉树高度为4的条件可以反推出遍历二叉树的过程。比如这里返回值要为4,则返回值的递归计算式一定为2 x (2 x (2 x 0 + 1)),比较结点的值和x的结果一定依次是:大于、大于、小于、等于。遍历方式为左、左、右。


待补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值