计算机系统_bomblab

Lab2-Bomblab

一.实验题目及目的

1.实验题目 程序运行在linux环境中。程序运行中有6个关卡(6个phase),每个phase需要用户在终端上输入特定的字符或者数字才能通关,否则将会引爆炸弹。需要通过分析汇编代码,使用gdb调试等方式找到正确的字符。

2.实验目的 熟悉汇编代码的分析方法,c程序的机器级表达以及控制,过程等相关知识,熟悉gdb调试的方法及过程。

二.准备工作

1.打开bomb.c文件

发现main函数依次调用了phase_1~phase_6六个函数,但函数的具体代码被隐藏。可以知道从命令行输入的内容必须和phase函数里面的一样,否则炸弹爆炸。

2.反汇编可执行文件bomb objdump -d bomb > bomb.txt 这样就可以再bomb.txt文件里面看到整个文件(主要是phase函数)的汇编代码了

三.拆除炸弹

phase_1

下面是phase_1的具体汇编代码及分析

08048b60 <phase_1>:
 8048b60:    83 ec 1c                 sub    $0x1c,%esp
 8048b63:    c7 44 24 04 84 a1 04     movl   $0x804a184,0x4(%esp)                    #
 8048b6a:    08 
 8048b6b:    8b 44 24 20              mov    0x20(%esp),%eax
 8048b6f:    89 04 24                 mov    %eax,(%esp)
 8048b72:    e8 3d 04 00 00           call   8048fb4 <strings_not_equal>
 8048b77:    85 c0                    test   %eax,%eax                               #按位相与,如果eax为0,zf=1,否则zf=0
 8048b79:    74 05                    je     8048b80 <phase_1+0x20>                  #zf=1,即eax=0时,跳转到8048b80
 8048b7b:    e8 46 05 00 00           call   80490c6 <explode_bomb>                  #eax=1,炸弹爆炸
 8048b80:    83 c4 1c                 add    $0x1c,%esp
 8048b83:    c3                       ret    

其中调用了8048fb4< string_not_equal >这个函数,其具体的汇编代码和分析如下:

08048fb4 <strings_not_equal>:                                                        #第一题,判断字符串是否相等
 8048fb4:    83 ec 10                 sub    $0x10,%esp
 8048fb7:    89 5c 24 04              mov    %ebx,0x4(%esp)
 8048fbb:    89 74 24 08              mov    %esi,0x8(%esp)
 8048fbf:    89 7c 24 0c              mov    %edi,0xc(%esp)
 8048fc3:    8b 5c 24 14              mov    0x14(%esp),%ebx                         #第一个字符串的指针
 8048fc7:    8b 74 24 18              mov    0x18(%esp),%esi                         #第二个字符串的指针
 8048fcb:    89 1c 24                 mov    %ebx,(%esp)
 8048fce:    e8 c8 ff ff ff           call   8048f9b <string_length>                 #计算第一个字符串的长度
 8048fd3:    89 c7                    mov    %eax,%edi                               #把结果保存在edi中
 8048fd5:    89 34 24                 mov    %esi,(%esp)
 8048fd8:    e8 be ff ff ff           call   8048f9b <string_length>                 #计算第二个字符串的长度,结果保存在eax中
 8048fdd:    ba 01 00 00 00           mov    $0x1,%edx                                                
 8048fe2:    39 c7                    cmp    %eax,%edi                               #比较两个字符串的长度
 8048fe4:    75 33                    jne    8049019 <strings_not_equal+0x65>        #不相等跳转,相等继续执行
 8048fe6:    0f b6 03                 movzbl (%ebx),%eax                             #第一个字符串的第一个字符?                                
 8048fe9:    b2 00                    mov    $0x0,%dl
 8048feb:    84 c0                    test   %al,%al                                 #eax的低8位是否为0
 8048fed:    74 2a                    je     8049019 <strings_not_equal+0x65>        #为0跳转
 8048fef:    b2 01                    mov    $0x1,%dl
 8048ff1:    3a 06                    cmp    (%esi),%al                              #比较两个字符串的第一个字符?
 8048ff3:    75 24                    jne    8049019 <strings_not_equal+0x65>        #不相等直接结束
 8048ff5:    b8 00 00 00 00           mov    $0x0,%eax                               #相等eax置0
 8048ffa:    eb 08                    jmp    8049004 <strings_not_equal+0x50>
 8048ffc:    83 c0 01                 add    $0x1,%eax                               #循环开始
 8048fff:    3a 14 06                 cmp    (%esi,%eax,1),%dl                       #比较两个字符?dl是8位,一个字符也是八位
 8049002:    75 10                    jne    8049014 <strings_not_equal+0x60>        #不相等直接eax=1
 8049004:    0f b6 54 03 01           movzbl     0x1(%ebx,%eax,1),%edx               #edx更新,大概是起到遍历字符的作用
 8049009:    84 d2                    test   %dl,%dl                                 #判断字符串是否比较完毕
 804900b:    75 ef                    jne    8048ffc <strings_not_equal+0x48>        #如果dl为0,循环结束
 804900d:    ba 00 00 00 00           mov    $0x0,%edx                               #eax置0
 8049012:    eb 05                    jmp    8049019 <strings_not_equal+0x65>
 8049014:    ba 01 00 00 00           mov    $0x1,%edx
 8049019:    89 d0                    mov    %edx,%eax                               #eax=1(两个字符串的长度不相等/字符串不同)
 804901b:    8b 5c 24 04              mov    0x4(%esp),%ebx
 804901f:    8b 74 24 08              mov    0x8(%esp),%esi
 8049023:    8b 7c 24 0c              mov    0xc(%esp),%edi
 8049027:    83 c4 10                 add    $0x10,%esp
 804902a:    c3                       ret  

这个函数主要实现的功能是判断两个字符串是否相等,判断的具体方法为先判断两个字符串的长度是否一致,如果不一致,将zf置1,炸弹爆炸。如果长度相等,则循环比较每一个字符是否相等,若出现不相等的情况炸弹也会直接爆炸。比较的两个字符串其一是输入的字符串,另外是程序中原先设置好的。我们注意到两个字符串的指针分别是0x8(%esp)和0x4(%esp),而phrse_1函数中有这样一条指令:

8048b63:    c7 44 24 04 84 a1 04     movl   $0x804a184,0x4(%esp)

用gdb查看0x804a184地址中的内容(x/s 0x804a184),得到以下字符串:

When I get angry, Mr. Bigglesworth gets upset.

用gdb输入这个字符串进行验证,结果正确,第一关通过。

phase_2

下面是phase_2的具体汇编代码及分析

08048b84 <phase_2>:
 8048b84:    53                       push   %ebx
 8048b85:    83 ec 38                 sub    $0x38,%esp
 8048b88:    8d 44 24 18              lea    0x18(%esp),%eax
 8048b8c:    89 44 24 04              mov    %eax,0x4(%esp)
 8048b90:    8b 44 24 40              mov    0x40(%esp),%eax
 8048b94:    89 04 24                 mov    %eax,(%esp)
 8048b97:    e8 5f 06 00 00           call   80491fb <read_six_numbers>                 #输入应为6个数字
 8048b9c:    83 7c 24 18 00           cmpl   $0x0,0x18(%esp)                            #0x18>0时,跳转
 8048ba1:    79 05                    jns    8048ba8 <phase_2+0x24>
 8048ba3:    e8 1e 05 00 00           call   80490c6 <explode_bomb>                     #否则爆炸
 8048ba8:    bb 01 00 00 00           mov    $0x1,%ebx
 8048bad:    89 d8                    mov    %ebx,%eax                                  #循环开始
 8048baf:    03 44 9c 14              add    0x14(%esp,%ebx,4),%eax                     #地址对应的值加ebx
 8048bb3:    39 44 9c 18              cmp    %eax,0x18(%esp,%ebx,4)                     #比较  x+1
 8048bb7:    74 05                    je     8048bbe <phase_2+0x3a>                     #相等跳转
 8048bb9:    e8 08 05 00 00           call   80490c6 <explode_bomb>                     #两数不相等爆炸
 8048bbe:    83 c3 01                 add    $0x1,%ebx                                  #ebx++
 8048bc1:    83 fb 06                 cmp    $0x6,%ebx                                        
 8048bc4:    75 e7                    jne    8048bad <phase_2+0x29>                     #ebx!=6时进入循环
 8048bc6:    83 c4 38                 add    $0x38,%esp
 8048bc9:    5b                       pop    %ebx
 8048bca:    c3                       ret    

根据代码可以猜测这一关需要输入的是六个数字,分析read_six_number这个函数发现其功能就是读取六个数字,所以剩下的有用的代码其实只有很小一段。我们接着来分析剩下的代码:

8048b9c:    83 7c 24 18 00           cmpl   $0x0,0x18(%esp)                                #0x18>0时,跳转
8048ba1:    79 05                    jns    8048ba8 <phase_2+0x24>
8048ba3:    e8 1e 05 00 00           call   80490c6 <explode_bomb>

如果输入的第一个数大于0,才会继续执行,否则将直接引爆炸弹。

接下来的代码是这一关的核心代码:

 8048bad:    89 d8                    mov    %ebx,%eax                                         #循环开始
 8048baf:    03 44 9c 14              add    0x14(%esp,%ebx,4),%eax                            #地址对应的值加ebx
 8048bb3:    39 44 9c 18              cmp    %eax,0x18(%esp,%ebx,4)                            #比较  x+1
 8048bb7:    74 05                    je     8048bbe <phase_2+0x3a>                            #相等跳转
 8048bb9:    e8 08 05 00 00           call   80490c6 <explode_bomb>                            #两数不相等爆炸
 8048bbe:    83 c3 01                 add    $0x1,%ebx                                         #ebx++
 8048bc1:    83 fb 06                 cmp    $0x6,%ebx                                        
 8048bc4:    75 e7                    jne    8048bad <phase_2+0x29>                            #ebx!=6时进入循环

寄存器eax将加上输入的值,如果eax+ebx不等于下一个输入的值,炸弹将直接爆炸,而ebx会在循环中每次加一。换而言之,我们输入的6个数字第二个数字应为第一个数字加一,第三个数字应为第二个数字加二,以此类推。因此,我们可以得出最终的答案有如下形式:

x x+1 x+1+2 x+1+2+3 x+1+2+3+4 x+1+2+3+4+5 最终我们试得答案可以为:1 2 4 7 11 16

我们接着测试了另外一组满足上面形式的数字:11 12 14 17 21 26

这组数字同样能够通过测试,因此,我们认为,凡是满足上述形式且第一个数字>0的一组(六个) 数字,均为正确答案。

phrse_3

下面是phase_3的具体汇编代码及分析

08048bcb <phase_3>:
 8048bcb:    83 ec 2c                 sub    $0x2c,%esp
 8048bce:    8d 44 24 1c              lea    0x1c(%esp),%eax
 8048bd2:    89 44 24 0c              mov    %eax,0xc(%esp)
 8048bd6:    8d 44 24 18              lea    0x18(%esp),%eax
 8048bda:    89 44 24 08              mov    %eax,0x8(%esp)
 8048bde:    c7 44 24 04 a3 a3 04     movl   $0x804a3a3,0x4(%esp)                              #两个整数
 8048be5:    08 
 8048be6:    8b 44 24 30              mov    0x30(%esp),%eax
 8048bea:    89 04 24                 mov    %eax,(%esp)
 8048bed:    e8 7e fc ff ff           call   8048870 <__isoc99_sscanf@plt>
 8048bf2:    83 f8 01                 cmp    $0x1,%eax                                         #
 8048bf5:    7f 05                    jg     8048bfc <phase_3+0x31>                            #eax大于1跳转
 8048bf7:    e8 ca 04 00 00           call   80490c6 <explode_bomb>                            #否则爆炸---输入的数的个数应大于1
 8048bfc:    83 7c 24 18 07           cmpl   $0x7,0x18(%esp)                                   #0x18(%esp)为输入的第一个数
 8048c01:    77 3c                    ja     8048c3f <phase_3+0x74>                            #无符号大于跳转,爆炸
 8048c03:    8b 44 24 18              mov    0x18(%esp),%eax
 8048c07:    ff 24 85 e0 a1 04 08     jmp    *0x804a1e0(,%eax,4)                               #这个地址里的内容就是下一行的地址?跳转表
 8048c0e:    b8 ea 01 00 00           mov    $0x1ea,%eax                                       #答案零:0 490
 8048c13:    eb 3b                    jmp    8048c50 <phase_3+0x85>                            
 8048c15:    b8 f9 00 00 00           mov    $0xf9,%eax                                        #答案二:2 249
 8048c1a:    eb 34                    jmp    8048c50 <phase_3+0x85>
 8048c1c:    b8 e2 00 00 00           mov    $0xe2,%eax                                        #答案三:3 226
 8048c21:    eb 2d                    jmp    8048c50 <phase_3+0x85>
 8048c23:    b8 58 03 00 00           mov    $0x358,%eax                                       #答案四:4 856
 8048c28:    eb 26                    jmp    8048c50 <phase_3+0x85>
 8048c2a:    b8 ef 00 00 00           mov    $0xef,%eax                                        #答案五:5 239
 8048c2f:    eb 1f                    jmp    8048c50 <phase_3+0x85>
 8048c31:    b8 97 02 00 00           mov    $0x297,%eax                                       #答案六:6 663
 8048c36:    eb 18                    jmp    8048c50 <phase_3+0x85>
 8048c38:    b8 22 02 00 00           mov    $0x222,%eax                                       #答案七:7 546
 8048c3d:    eb 11                    jmp    8048c50 <phase_3+0x85>
 8048c3f:    e8 82 04 00 00           call   80490c6 <explode_bomb>
 8048c44:    b8 00 00 00 00           mov    $0x0,%eax                                         #答案八? default???
 8048c49:    eb 05                    jmp    8048c50 <phase_3+0x85>
 8048c4b:    b8 9f 02 00 00           mov    $0x29f,%eax                                       #答案一:1 671
 8048c50:    3b 44 24 1c              cmp    0x1c(%esp),%eax                                   #eax和esp+28比较    
 8048c54:    74 05                    je     8048c5b <phase_3+0x90>                            #相等跳转
 8048c56:    e8 6b 04 00 00           call   80490c6 <explode_bomb>                            #不相等爆炸
 8048c5b:    83 c4 2c                 add    $0x2c,%esp
 8048c5e:    c3                       ret    

当第一眼看到这一关的代码时,最先吸引我的就是下面这行代码:

movl   $0x804a3a3,0x4(%esp)

它直接将一个地址中的内容传入esp+4的地址。在第一关中我们就是查看一个可疑的地址从而拿到了答案,这里我们同样在gdb中查看地址中的内容(x/s 0x804a3a3)

得到的结果为:%d %d

可以猜测,要求我们输入的为两个整数,接下来的代码调用了这样一个函数isoc99_sscanf@plt。其返回值需要大于1才不会引发爆炸,故推测isoc99_sscanf@plt函数的功能就是判断输入的数字个数是否符合要求。

8048bfc:    83 7c 24 18 07          cmpl   $0x7,0x18(%esp)                                    
8048c01:    77 3c                    ja     8048c3f <phase_3+0x74>
8048c03:    8b 44 24 18              mov    0x18(%esp),%eax

这里esp+0x18为输入的第一个数,第一个数如果大于7将爆炸,用的jump指令为ja,为无符号数所使用,故输入的第一个数应小于等于7并且大于等于0,取值范围即[0,7],之后将第一个数移到寄存器eax。

接下来又有一个地址暴露在外,我们查看其内容得到:

(gdb) x/23xw 0x804a1e0
0x804a1e0:      0x08048c0e      0x08048c4b      0x08048c15      0x08048c1c
0x804a1f0:      0x08048c23      0x08048c2a      0x08048c31      0x08048c38

这是一个跳转表,其中存储的内容十分眼熟,它们对应接下来的代码的地址:

 8048c07:    ff 24 85 e0 a1 04 08     jmp    *0x804a1e0(,%eax,4)
 8048c0e:    b8 ea 01 00 00           mov    $0x1ea,%eax                                       #答案零:0 490
 8048c13:    eb 3b                    jmp    8048c50 <phase_3+0x85>                            
 8048c15:    b8 f9 00 00 00           mov    $0xf9,%eax                                        #答案二:2 249
 8048c1a:    eb 34                    jmp    8048c50 <phase_3+0x85>
 8048c1c:    b8 e2 00 00 00           mov    $0xe2,%eax                                        #答案三:3 226
 8048c21:    eb 2d                    jmp    8048c50 <phase_3+0x85>
 8048c23:    b8 58 03 00 00           mov    $0x358,%eax                                       #答案四:4 856
 8048c28:    eb 26                    jmp    8048c50 <phase_3+0x85>
 8048c2a:    b8 ef 00 00 00           mov    $0xef,%eax                                        #答案五:5 239
 8048c2f:    eb 1f                    jmp    8048c50 <phase_3+0x85>
 8048c31:    b8 97 02 00 00           mov    $0x297,%eax                                       #答案六:6 663
 8048c36:    eb 18                    jmp    8048c50 <phase_3+0x85>
 8048c38:    b8 22 02 00 00           mov    $0x222,%eax                                       #答案七:7 546
 8048c3d:    eb 11                    jmp    8048c50 <phase_3+0x85>
 8048c3f:    e8 82 04 00 00           call   80490c6 <explode_bomb>
 8048c44:    b8 00 00 00 00           mov    $0x0,%eax                                         #答案八? default???
 8048c49:    eb 05                    jmp    8048c50 <phase_3+0x85>
 8048c4b:    b8 9f 02 00 00           mov    $0x29f,%eax                                       #答案一:1 671

我们先跳过这一部分,分析最后的几行代码:

 8048c50:    3b 44 24 1c              cmp    0x1c(%esp),%eax                                   #eax和esp+28比较    
 8048c54:    74 05                    je     8048c5b <phase_3+0x90>                            #相等跳转
 8048c56:    e8 6b 04 00 00           call   80490c6 <explode_bomb>

esp+0x1c为输入的第二个数的值,这里将eax与输入的第二个数的值相比,如果不相等就爆炸。而eax在上面的代码中存储的是一些固定的值,例如在地址0x8048c0e中,eax的值就是0x1ea。于是我们再回过去看那一大块代码,执行的操作基本都是eax赋值,eax赋什么值则取决于我们我们输入的第一个数字,这里的逻辑很像一个switch语句。

我们将gdb查看出来的内容整理一下,可以得到以下的组合:

0 490    1 671    2 249    3 226    
4 856    5 239    6 663    7 546

经过测试,以上8组数字均能过关。

phrse_4

下面是phase_4的具体汇编代码及分析

08048ccc <phase_4>:
 8048ccc:    83 ec 2c                 sub    $0x2c,%esp
 8048ccf:    8d 44 24 1c              lea    0x1c(%esp),%eax
 8048cd3:    89 44 24 0c              mov    %eax,0xc(%esp)
 8048cd7:    8d 44 24 18              lea    0x18(%esp),%eax
 8048cdb:    89 44 24 08              mov    %eax,0x8(%esp)
 8048cdf:    c7 44 24 04 a3 a3 04     movl   $0x804a3a3,0x4(%esp)                                  #两个整数
 8048ce6:    08 
 8048ce7:    8b 44 24 30              mov    0x30(%esp),%eax
 8048ceb:    89 04 24                 mov    %eax,(%esp)
 8048cee:    e8 7d fb ff ff           call   8048870 <__isoc99_sscanf@plt>                         #判断输入的个数
 8048cf3:    83 f8 02                 cmp    $0x2,%eax
 8048cf6:    75 0d                    jne    8048d05 <phase_4+0x39>                                #不等于二直接爆炸(输入的数字只能是两个)
 8048cf8:    8b 44 24 18              mov    0x18(%esp),%eax                                       #0x18为输入的第一个数
 8048cfc:    85 c0                    test   %eax,%eax                                                        
 8048cfe:    78 05                    js     8048d05 <phase_4+0x39>                                #eax为负数直接爆炸
 8048d00:    83 f8 0e                 cmp    $0xe,%eax                                             #和14比较
 8048d03:    7e 05                    jle    8048d0a <phase_4+0x3e>                                #小于等于就跳转
 8048d05:    e8 bc 03 00 00           call   80490c6 <explode_bomb>                                #否则爆炸(第一个数的范围:[0-14])
 8048d0a:    c7 44 24 08 0e 00 00     movl   $0xe,0x8(%esp)                                        #参数-12
 8048d11:    00 
 8048d12:    c7 44 24 04 00 00 00     movl   $0x0,0x4(%esp)                                        #参数-0
 8048d19:    00 
 8048d1a:    8b 44 24 18              mov    0x18(%esp),%eax
 8048d1e:    89 04 24                 mov    %eax,(%esp)                                           #参数-输入
 8048d21:    e8 39 ff ff ff           call   8048c5f <func4>
 8048d26:    83 f8 05                 cmp    $0x5,%eax                                                        
 8048d29:    75 07                    jne    8048d32 <phase_4+0x66>                                #eax不等于5爆炸
 8048d2b:    83 7c 24 1c 05           cmpl   $0x5,0x1c(%esp)                                            
 8048d30:    74 05                    je     8048d37 <phase_4+0x6b>                                #输入的第二个数必须是5
 8048d32:    e8 8f 03 00 00           call   80490c6 <explode_bomb>
 8048d37:    83 c4 2c                 add    $0x2c,%esp
 8048d3a:    c3                       ret    

根据前面三关的经验,我们可以认为输入的第一个数在 0x1c(%esp)中,第二个数在0x18(%esp)中,在gdb中查看地址中的内容(x/s 0x804a3a3),得到的结果为:%d %d。

很好地验证了我们的经验。这一题的输入为两个整数。

这一题同样调用了 isoc_ssanf @plt 这个函数,随后的代码的功能:检查输入的数字的个数是否为2,否则直接爆炸。

接下来一段代码是对输入的第一个数进行限制(esp+0x18为输入的第一个数):

 8048cf8:    8b 44 24 18              mov    0x18(%esp),%eax                                       #0x18为输入的第一个数
 8048cfc:    85 c0                    test   %eax,%eax                                                        
 8048cfe:    78 05                    js     8048d05 <phase_4+0x39>                                #eax为负数直接爆炸
 8048d00:    83 f8 0e                 cmp    $0xe,%eax                                             #和14比较
 8048d03:    7e 05                    jle    8048d0a <phase_4+0x3e>                                #小于等于就跳转
 8048d05:    e8 bc 03 00 00           call   80490c6 <explode_bomb>

输入如果小于0,则直接爆炸,同样地,eax如果大于14,也会直接爆炸。

因此,我们可以得出,第一个输入的值的范围为[0,14]。

 8048d0a:    c7 44 24 08 0e 00 00     movl   $0xe,0x8(%esp)                                        #参数-12
 8048d11:    00 
 8048d12:    c7 44 24 04 00 00 00     movl   $0x0,0x4(%esp)                                        #参数-0
 8048d19:    00 
 8048d1a:    8b 44 24 18              mov    0x18(%esp),%eax
 8048d1e:    89 04 24                 mov    %eax,(%esp)                                           #参数-输入
 8048d21:    e8 39 ff ff ff           call   8048c5f <func4>
 8048d26:    83 f8 05                 cmp    $0x5,%eax                                                        
 8048d29:    75 07                    jne    8048d32 <phase_4+0x66>

这一段代码调用了func4这个函数,同时传入的3个参数,分别是:第一个输入,0,14。

最后的返回值eax如果不等于5,直接爆炸。

8048d2b:    83 7c 24 1c 05           cmpl   $0x5,0x1c(%esp)                                            
8048d30:    74 05                    je     8048d37 <phase_4+0x6b>                                 #输入的第二个数必须是5
8048d32:    e8 8f 03 00 00           call   80490c6 <explode_bomb>

这里判断输入的第二个数(esp+0x1c),如果不是5则直接爆炸,因此第二个输入只能为5。

这里我们完全可以不用管func4中发生了什么,范围已经缩小到足够小了。

第一个数:[0,14],第二个数:5。

最终试出来答案只有一个:

10 5

现在回看func4这个函数,其原代码的结构应该是这样的:

int func4(int x,int y,int z)
{
    int res=0;
    int mid=(y+z)/2;
    if(mid>x)
        return func4(x,y,mid-1)*2;
    else if(mid<x)
        return func4(x,mid+1,z)*2+1;
    else
        return res;
}

我们已经知道返回值是5,且第一个输入是10。

即res=5,第一个参数为10,接下来我们开始验证。

参数mid值
第一次(10,0,14)7
第二次(10,8,14)11
第三次(10,8,10)9
第四次(10,10,10)10

以上是递归中func4函数的参数和局部变量mid值的变化,调用的顺序是从上到下的。

因此,我们计算实际的结果应该从下往上。

第四次返回的res值为0;

第三次返回的res值为2*0+1=1;

第二次返回的res值为1*2=2;

第一次(最终)返回的res值为2*2+1=5;

人工验证结果正确。

接下来我们让电脑帮我们验证一下,编写如下的c++代码:

#include <iostream>
using namespace std;

int func4(int x,int y,int z)
{
    int res=0;
    int mid=(y+z)/2;
    if(mid>x)
        return func4(x,y,mid-1)*2;
    else if(mid<x)
        return func4(x,mid+1,z)*2+1;
    else
        return res;
}

int main()
{
    for(int i=0;i<=14;i++)
        cout<<func4(i,0,14)<<" ";
    return 0;
}

输出结果为:

可以看到0-14的范围中只有x=10有输出为5,其他均不符合条件。

因此,我们可以断定原代码大概就是如此。

(这里不再给出原函数的具体推测过程了,我这是一点一点猜出来的,不好从何说起)

phrse_5

下面是phase_5的具体汇编代码及分析

08048d3b <phase_5>:
 8048d3b:    53                       push   %ebx
 8048d3c:    83 ec 18                 sub    $0x18,%esp
 8048d3f:    8b 5c 24 20              mov    0x20(%esp),%ebx
 8048d43:    89 1c 24                 mov    %ebx,(%esp)
 8048d46:    e8 50 02 00 00           call   8048f9b <string_length>
 8048d4b:    83 f8 06                 cmp    $0x6,%eax
 8048d4e:    74 05                    je     8048d55 <phase_5+0x1a>                            #字符串长度等于6
 8048d50:    e8 71 03 00 00           call   80490c6 <explode_bomb>
 8048d55:    ba 00 00 00 00           mov    $0x0,%edx
 8048d5a:    b8 00 00 00 00           mov    $0x0,%eax
 8048d5f:    0f be 0c 03              movsbl (%ebx,%eax,1),%ecx                                #循环开始
 8048d63:    83 e1 0f                 and    $0xf,%ecx                                         #ecx=0000 0000 0000 0000 0000 ?
 8048d66:    03 14 8d 00 a2 04 08     add    0x804a200(,%ecx,4),%edx
 8048d6d:    83 c0 01                 add    $0x1,%eax
 8048d70:    83 f8 06                 cmp    $0x6,%eax
 8048d73:    75 ea                    jne    8048d5f <phase_5+0x24>                            #eax不为6进入循环
 8048d75:    83 fa 33                 cmp    $0x33,%edx
 8048d78:    74 05                    je     8048d7f <phase_5+0x44>                            #edx不为51就爆炸
 8048d7a:    e8 47 03 00 00           call   80490c6 <explode_bomb>
 8048d7f:    83 c4 18                 add    $0x18,%esp
 8048d82:    5b                       pop    %ebx
 8048d83:    c3                       ret    

同样,根据经验,我们可以推断输入应该被保存在esp+0x20,并且输入的应该是一个字符串,因为后面调用了string_length这个函数,其返回值,也就是输入的字符串的长度如果不等于6,则炸弹爆炸。也就是说,我们的输入应该是一个长度为6的字符串。

接下来的代码将edx和eax两个寄存器置零。

下面的代码是一个循环:

8048d5f:    0f be 0c 03              movsbl (%ebx,%eax,1),%ecx                                 #循环开始
8048d63:    83 e1 0f                 and    $0xf,%ecx                                          #ecx=0000 0000 0000 0000 0000 ?
8048d66:    03 14 8d 00 a2 04 08     add    0x804a200(,%ecx,4),%edx
8048d6d:    83 c0 01                 add    $0x1,%eax
8048d70:    83 f8 06                 cmp    $0x6,%eax
8048d73:    75 ea                    jne    8048d5f <phase_5+0x24>

其中将ebx+eax的值传入ecx,然后拿到ecx的低4位,之后将0x804a200+4*ecx的值加到edx中。接着eax+1,eax和6作比较,如果不相等就重新回到这段代码的开始处。我们注意到,在这段代码运行之前,edx和eax的值都被置零了。那么这段代码应该被循环运行了6次,edx也加上了6个数字的值。

8048d75:    83 fa 33                 cmp    $0x33,%edx
8048d78:    74 05                    je     8048d7f <phase_5+0x44>                             #edx不为51就爆炸
8048d7a:    e8 47 03 00 00           call   80490c6 <explode_bomb>

这最后一部分代码中,将edx的值与51相比较,如果不相等则炸弹爆炸。那么我们可以知道,上面那段循环的代码中,edx为6个数的和,和必须为51。剩下我们要解决的就是怎么把edx加到51了。

我们注意到这样一个地址:0x804a200

8048d66:    03 14 8d 00 a2 04 08     add    0x804a200(,%ecx,4),%edx

直接在gdb中查看(x/128bx 0x804a200)得到如下结果:

0x804a200 <array.2999>:         0x02    0x00    0x00    0x00    0x0a    0x00    0x00    0x00
0x804a208 <array.2999+8>:       0x06    0x00    0x00    0x00    0x01    0x00    0x00    0x00
0x804a210 <array.2999+16>:      0x0c    0x00    0x00    0x00    0x10    0x00    0x00    0x00
0x804a218 <array.2999+24>:      0x09    0x00    0x00    0x00    0x03    0x00    0x00    0x00
0x804a220 <array.2999+32>:      0x04    0x00    0x00    0x00    0x07    0x00    0x00    0x00
0x804a228 <array.2999+40>:      0x0e    0x00    0x00    0x00    0x05    0x00    0x00    0x00
0x804a230 <array.2999+48>:      0x0b    0x00    0x00    0x00    0x08    0x00    0x00    0x00
0x804a238 <array.2999+56>:      0x0f    0x00    0x00    0x00    0x0d    0x00    0x00    0x00

因此我们知道,edx加的数值只能是上面所列出来的,并且ecx的值对应一个数值,将上面的内容整理一下并和ecx的值整合起来,可以得到如下表(个人感觉像个数组):

下标  0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15
数值  2   10  6   1   12  16  9   3   4   7   14  5   11  8   15  13

我们的目标是让edx等于51,这里我们发现下标456789对应的数值加起来为:

12+16+9+3+4+7=51,这可能就是我们所找的解。

接下来,我们要让ecx的低4位等于4 5 6 7 8 9。ecx的值为ebx+eax对应的内容,其中ebx的值为我们的输入。那么ecx的值其实就是我们输入的6个字符,即:我们输入的6个字符的低四位应该分别是4 5 6 7 8 9,接下来我们去查ascii码表有如下发现:

二进制十进制十六进制字符
0011010052344
0011010153355
0011011054366
0011011155377
0011100056388
0011100157399

我们发现字符456789正好满足条件,在gdb中验证通过:

此外,这里还测试了其他满足条件的字符串,例如DEFGHIdefghi,这两个答案也能通过这一关,因此认为,凡是ascii码的后四位依次为4 5 6 7 8 9的字符串均符合条件。

DEFGHIdefghi的ascii码表如下:

二进制十进制十六进制字符
010001006844D
010001016945E
010001107046F
010001117147G
010010007248H
010010017349I
0110010010064d
0110010110165e
0110011010266f
0110011110367g
0110100010468h
0110100110569i

phase_6

这里由于代码太长,就不把整个代码放出来了。

我将整个代码分成了5个部分,每个部分之间换行相间隔,我们一个部分一个部分地来看。

第一部分

 8048d84:    56                       push   %esi
 8048d85:    53                       push   %ebx
 8048d86:    83 ec 44                 sub    $0x44,%esp
 8048d89:    8d 44 24 10              lea    0x10(%esp),%eax
 8048d8d:    89 44 24 04              mov    %eax,0x4(%esp)
 8048d91:    8b 44 24 50              mov    0x50(%esp),%eax
 8048d95:    89 04 24                 mov    %eax,(%esp)
 8048d98:    e8 5e 04 00 00           call   80491fb <read_six_numbers>                    #输入6个数字

第一个部分为输入,输入为6个数字,esp+0x10就是我们的输入,这里不多作解释了。

第二部分

 8048d9d:    be 00 00 00 00           mov    $0x0,%esi
 8048da2:    8b 44 b4 10              mov    0x10(%esp,%esi,4),%eax                        #第二个循环从这里开始
 8048da6:    83 e8 01                 sub    $0x1,%eax
 8048da9:    83 f8 05                 cmp    $0x5,%eax
 8048dac:    76 05                    jbe    8048db3 <phase_6+0x2f>                        #eax小于等于5跳转
 8048dae:    e8 13 03 00 00           call   80490c6 <explode_bomb>
 8048db3:    83 c6 01                 add    $0x1,%esi                                     #esi+1
 8048db6:    83 fe 06                 cmp    $0x6,%esi                                                
 8048db9:    74 33                    je     8048dee <phase_6+0x6a>                        #esi=6跳转,此时完成所有遍历
 8048dbb:    89 f3                    mov    %esi,%ebx                                     #!
 8048dbd:    8b 44 9c 10              mov    0x10(%esp,%ebx,4),%eax                        #输入的第ebx(2-6)个数字?
 8048dc1:    39 44 b4 0c              cmp    %eax,0xc(%esp,%esi,4)                         #和第1个数字比较?
 8048dc5:    75 05                    jne    8048dcc <phase_6+0x48>
 8048dc7:    e8 fa 02 00 00           call   80490c6 <explode_bomb>                        #相等爆炸
 8048dcc:    83 c3 01                 add    $0x1,%ebx                                     #不相等继续遍历
 8048dcf:    83 fb 05                 cmp    $0x5,%ebx
 8048dd2:    7e e9                    jle    8048dbd <phase_6+0x39>
 8048dd4:    eb cc                    jmp    8048da2 <phase_6+0x1e>                        #又他妈一个循环
 #6个数字两层循环,像冒泡那样比较两个数字是否相等,这里相当于约束了6个数字不能两两相等

第二个部分中包含一个双层循环。

这里先将esi赋0,这里eax中的内容为输入的(第一个)数字,因为后面eax将不断自减一并和5比较,如果大于5就会直接爆炸。

8048da9:    83 f8 05                 cmp    $0x5,%eax
8048dac:    76 05                    jbe    8048db3 <phase_6+0x2f>                        #eax小于等于5跳转
8048dae:    e8 13 03 00 00           call   80490c6 <explode_bomb>

这里是无符号数比较,说明输入为无符号数,即大于等于0。

由于num[i]-1<=5,所以num[i]<=6。

之后esi自加一,并不断与6比较,如果相等就跳转,分析完这一部分后我们会知道这里是跳出循环。接下来比较esp+0x10+4esi和esp+0xc+4esi,即比较两个相邻的输入的数字,如果相等就直接爆炸,不相等就继续遍历,一个内循环完成后就会切换到下一个数字继续遍历,具体的过程很想冒泡排序中的遍历方法。

总结一下就是:这一段代码限定了我们输入的6个数字。

1.这6个数字两两不能相等。

2.这六个数字都是在[0,6]这个区间内。

(嫌麻烦的话分析到这里就可以了,组合共7!种,虽然有点多,但应该还是能试,应该?)

第三部分

 8048dd6:    8b 52 08                 mov    0x8(%edx),%edx                                #esi=6,eax=0,ebx=6
 8048dd9:    83 c0 01                 add    $0x1,%eax
 8048ddc:    39 c8                    cmp    %ecx,%eax
 8048dde:    75 f6                    jne    8048dd6 <phase_6+0x52>                        #令eax=ecx
 8048de0:    89 54 b4 28              mov    %edx,0x28(%esp,%esi,4)
 8048de4:    83 c3 01                 add    $0x1,%ebx
 8048de7:    83 fb 06                 cmp    $0x6,%ebx
 8048dea:    75 07                    jne    8048df3 <phase_6+0x6f>                        #ebx不等于6跳转8048df3
 8048dec:    eb 1c                    jmp    8048e0a <phase_6+0x86>
 8048dee:    bb 00 00 00 00           mov    $0x0,%ebx                                     #跳转点
 8048df3:    89 de                    mov    %ebx,%esi                                     #esi=ebx
 8048df5:    8b 4c 9c 10              mov    0x10(%esp,%ebx,4),%ecx                        #ecx=第一个数字
 8048df9:    b8 01 00 00 00           mov    $0x1,%eax
 8048dfe:    ba 3c c1 04 08           mov    $0x804c13c,%edx
 8048e03:    83 f9 01                 cmp    $0x1,%ecx                                                
 8048e06:    7f ce                    jg     8048dd6 <phase_6+0x52>                        #第ebx个数字>1跳转    
 8048e08:    eb d6                    jmp    8048de0 <phase_6+0x5c>                        #

这段代码中有一个可以查看的地址,用gdb查看(x/32a 0x804c13c)得到:

0x804c13c <node1>:      0x3db   0x1     0x804c148 <node2>       0x2fc
0x804c14c <node2+4>:    0x2     0x804c154 <node3>       0xb8    0x3
0x804c15c <node3+8>:    0x804c160 <node4>       0x3ba   0x4     0x804c16c <node5>
0x804c16c <node5>:      0xdd    0x5     0x804c178 <node6>       0x2c6
0x804c17c <node6+4>:    0x6     0x0     0x99    0x0
0x804c18c:      0x0     0x0     0x0     0x0
0x804c19c:      0x0     0x804a3b9       0x0     0x0
0x804c1ac <host_table+12>:      0x0     0x0     0x0     0x0

我的第一感觉就是这些玩意很像链表,数据以3个为一组,第一个数据推测为存储的值,即我们的输入值,第二个数据逐个加一,推测为节点的编号,第三个数据则为下一个节点的地址。我们访问下一个节点的地址,例如node1中的0x804c148,得到以下内容:

(gdb) x/32a 0x804c148
0x804c148 <node2>:      0x2fc   0x2     0x804c154 <node3>       0xb8
0x804c158 <node3+4>:    0x3     0x804c160 <node4>       0x3ba   0x4
0x804c168 <node4+8>:    0x804c16c <node5>       0xdd    0x5     0x804c178 <node6>
0x804c178 <node6>:      0x2c6   0x6     0x0

这证明了我们的猜想是正确的。那第三部分代码具体做了什么?

这部分代码用循环的方式根据输入数将链表中对应的第输入数个结点的地址复制到 esp+0x28 开始的栈中。

第四部分

 8048e0a:    8b 5c 24 28              mov    0x28(%esp),%ebx                                    #ebx等于6跳到这来,第一个输入的值对应的节点
 8048e0e:    8b 44 24 2c              mov    0x2c(%esp),%eax                                    #eax,第二个输入的值对应的节点
 8048e12:    89 43 08                 mov    %eax,0x8(%ebx)
 8048e15:    8b 54 24 30              mov    0x30(%esp),%edx
 8048e19:    89 50 08                 mov    %edx,0x8(%eax)
 8048e1c:    8b 44 24 34              mov    0x34(%esp),%eax
 8048e20:    89 42 08                 mov    %eax,0x8(%edx)
 8048e23:    8b 54 24 38              mov    0x38(%esp),%edx
 8048e27:    89 50 08                 mov    %edx,0x8(%eax)
 8048e2a:    8b 44 24 3c              mov    0x3c(%esp),%eax
 8048e2e:    89 42 08                 mov    %eax,0x8(%edx)
 8048e31:    c7 40 08 00 00 00 00     movl   $0x0,0x8(%eax)
 8048e38:    be 05 00 00 00           mov    $0x5,%esi
 #大概是构建链表?

这部分代码全是mov指令,猜测是在构架链表,不作细致分析。

第五部分

 8048e3d:    8b 43 08                 mov    0x8(%ebx),%eax
 8048e40:    8b 10                    mov    (%eax),%edx
 8048e42:    39 13                    cmp    %edx,(%ebx)
 8048e44:    7e 05                    jle    8048e4b <phase_6+0xc7>                        #ebx小于等于edx跳转
 8048e46:    e8 7b 02 00 00           call   80490c6 <explode_bomb>                        #否则爆炸
 8048e4b:    8b 5b 08                 mov    0x8(%ebx),%ebx
 8048e4e:    83 ee 01                 sub    $0x1,%esi
 8048e51:    75 ea                    jne    8048e3d <phase_6+0xb9>
 8048e53:    83 c4 44                 add    $0x44,%esp
 8048e56:    5b                       pop    %ebx
 8048e57:    5e                       pop    %esi
 8048e58:    c3                       ret    

这里将ebx+0x8移到edx中,将ebx+0x8与ebx比较,如果ebx小于等于ebx+0x8,则跳转,否则爆炸。这里猜测ebx是链表的第一个值,ebx+8是链表的第二个值,对应的也是我们输入的第一个和第二个数字。之后就是将计数的esi减一,并将第二个和第三个输入移到ebx和ebx+0x8的位置上继续比较,直到esi=1(进入这部分代码时esi的值为5),即比较5次后跳出循环,代码结束。

总结一下,这部分代码要求我们的输入是递增的。最后我们回到第三部分得到的链表中:

0x804c13c <node1>:      0x3db   0x1     0x804c148 <node2>       0x2fc
0x804c14c <node2+4>:    0x2     0x804c154 <node3>       0xb8    0x3
0x804c15c <node3+8>:    0x804c160 <node4>       0x3ba   0x4     0x804c16c <node5>
0x804c16c <node5>:      0xdd    0x5     0x804c178 <node6>       0x2c6
0x804c17c <node6+4>:    0x6    

整理一下可以得到:

编号123456
0x3db0x2fc0xb80x3ba0xdd0x2c6

将其按从小到大排序后得到的编号顺序为:

3 5 6 2 4 1

在gdb中验证此结果,通过:

 

secret_phase

我们在汇编代码中发现这样一句注释:

并且,我们在.c代码中可以看到:

/* Wow, they got it!  But isn't something... missing?  Perhaps
     * something they overlooked?  Mua ha ha ha ha! */

由此,我们可以断定这个炸弹之中是存在隐藏关卡的。

我们来找找汇编代码中哪些地方调用了这个secret_parse函数,在txt文件中ctrl+f搜索secret_parse,发现phase_defused调用了这个函数,继续搜索phase_defused,发现在main函数中,之前的所有关卡调用完毕后都调用了这个函数。

下面是phase_defused的具体汇编代码,我们分成三个部分来分析:

part1

0804924b <phase_defused>:
 804924b:    81 ec 8c 00 00 00        sub    $0x8c,%esp
 8049251:    65 a1 14 00 00 00        mov    %gs:0x14,%eax
 8049257:    89 44 24 7c              mov    %eax,0x7c(%esp)
 804925b:    31 c0                    xor    %eax,%eax
 804925d:    83 3d cc c3 04 08 06     cmpl   $0x6,0x804c3cc
 8049264:    75 72                    jne    80492d8 <phase_defused+0x8d>

这里0x804c3cc下的内容如果不等于6,直接跳转到最下方,则secret_phase无法进入。于是我们猜测,这里是需要我们完成前6关才能进入隐藏关卡。

接着我们在gdb的前6关中都各设置一个断点,依次查看这个地址下的内容,发现在第i关中的断点下查看这个地址,这个地址的值就是i。因此,我们的猜测应该是基本正确的:进入secret_phase的则先决条件是:完成phase 1 - 6,0x804c3cc代表的是关卡的完成数。

part_2

 8049266:    8d 44 24 2c              lea    0x2c(%esp),%eax
 804926a:    89 44 24 10              mov    %eax,0x10(%esp)
 804926e:    8d 44 24 28              lea    0x28(%esp),%eax
 8049272:    89 44 24 0c              mov    %eax,0xc(%esp)
 8049276:    8d 44 24 24              lea    0x24(%esp),%eax
 804927a:    89 44 24 08              mov    %eax,0x8(%esp)
 804927e:    c7 44 24 04 a9 a3 04     movl   $0x804a3a9,0x4(%esp)
 8049285:    08 
 8049286:    c7 04 24 d0 c4 04 08     movl   $0x804c4d0,(%esp)
 804928d:    e8 de f5 ff ff           call   8048870 <__isoc99_sscanf@plt>
 8049292:    83 f8 03                 cmp    $0x3,%eax                                                #判断返回值%eax是否等于3
 8049295:    75 35                    jne    80492cc <phase_defused+0x81> 

根据经验,esp+0x2c,esp+0x28,esp+0x24这三个地址下保存的是我们的三个输入,接着问查看地址0x804a3a9下的内容,得到以下信息:

(gdb) x/s 0x804a3a9
0x804a3a9:      "%d %d %s"

这说明我们进入隐藏关的输入应该是两个整数加一个字符串。我们接着查看下一个地址:

(gdb) print (char*) 0x804c4d0
$1 = 0x804c4d0 <input_strings+240> "10 5"

这个输出是我们第四关的答案,因此我们猜测,进入隐藏关是需要我们在第四关中输入两个整数和一个字符串,接下来我们要做的就是找到这个字符串。

接着代码调用了isoc99_sscanf@plt这个函数,如果返回值不等于3也会跳转到最下方。

part_3

 8049297:    c7 44 24 04 b2 a3 04     movl   $0x804a3b2,0x4(%esp)
 804929e:    08 
 804929f:    8d 44 24 2c              lea    0x2c(%esp),%eax
 80492a3:    89 04 24                 mov    %eax,(%esp)
 80492a6:    e8 09 fd ff ff           call   8048fb4 <strings_not_equal>
 80492ab:    85 c0                    test   %eax,%eax
 80492ad:    75 1d                    jne    80492cc <phase_defused+0x81>
 80492af:    c7 04 24 78 a2 04 08     movl   $0x804a278,(%esp)
 80492b6:    e8 45 f5 ff ff           call   8048800 <puts@plt>
 80492bb:    c7 04 24 a0 a2 04 08     movl   $0x804a2a0,(%esp)
 80492c2:    e8 39 f5 ff ff           call   8048800 <puts@plt>
 80492c7:    e8 de fb ff ff           call   8048eaa <secret_phase>
 80492cc:    c7 04 24 d8 a2 04 08     movl   $0x804a2d8,(%esp)
 80492d3:    e8 28 f5 ff ff           call   8048800 <puts@plt>
 80492d8:    8b 44 24 7c              mov    0x7c(%esp),%eax
 80492dc:    65 33 05 14 00 00 00     xor    %gs:0x14,%eax
 80492e3:    74 05                    je     80492ea <phase_defused+0x9f>
 80492e5:    e8 e6 f4 ff ff           call   80487d0 <__stack_chk_fail@plt>
 80492ea:    81 c4 8c 00 00 00        add    $0x8c,%esp
 80492f0:    c3                       ret    
 80492f1:    90                       nop
 80492f2:    90                       nop
 80492f3:    90                       nop
 80492f4:    90                       nop
 80492f5:    90                       nop
 80492f6:    90                       nop
 80492f7:    90                       nop
 80492f8:    90                       nop
 80492f9:    90                       nop
 80492fa:    90                       nop
 80492fb:    90                       nop
 80492fc:    90                       nop
 80492fd:    90                       nop
 80492fe:    90                       nop
 80492ff:    90                       nop

我们直接查看裸露在外的地址,得到以下信息:

(gdb) x/s 0x804a3b2
0x804a3b2:      "DrEvil"

一个字符串,我们要找的字符串很可能就是它。代码接着调用了字符串比较函数,判断输入的字符串和esp中存的字符串是否相等,若不相等,则跳转到最下方,正好错过进入隐藏关;若相等,就可以进入隐藏关了。

我们来验证一下,在第四关输入10 5 DrEvi,其他不变,出现了一些新的对话:

这证明我们已经成功进入隐藏关卡了,我们接下来回到中secret_phase来分析。

以下是secret_phase的具体汇编代码及分析:

08048eaa <secret_phase>:
 8048eaa:    53                       push   %ebx
 8048eab:    83 ec 18                 sub    $0x18,%esp
 8048eae:    e8 3a 02 00 00           call   80490ed <read_line>                    #读取字符串?
 8048eb3:    c7 44 24 08 0a 00 00     movl   $0xa,0x8(%esp)
 8048eba:    00 
 8048ebb:    c7 44 24 04 00 00 00     movl   $0x0,0x4(%esp)
 8048ec2:    00 
 8048ec3:    89 04 24                 mov    %eax,(%esp)
 8048ec6:    e8 15 fa ff ff           call   80488e0 <strtol@plt>                    #将字符串转换为整型num,存储在eax中
 8048ecb:    89 c3                    mov    %eax,%ebx
 8048ecd:    8d 40 ff                 lea    -0x1(%eax),%eax
 8048ed0:    3d e8 03 00 00           cmp    $0x3e8,%eax
 8048ed5:    76 05                    jbe    8048edc <secret_phase+0x32>  #num-1>1000爆炸,所以输入的数字必须小于等于1001
 8048ed7:    e8 ea 01 00 00           call   80490c6 <explode_bomb>
 8048edc:    89 5c 24 04              mov    %ebx,0x4(%esp)                                #参数-输入
 8048ee0:    c7 04 24 88 c0 04 08     movl   $0x804c088,(%esp)                        #参数-36
 8048ee7:    e8 6d ff ff ff           call   8048e59 <fun7>
 8048eec:    83 f8 03                 cmp    $0x3,%eax                                         #将fun7的返回值%eax与3比较
 8048eef:    74 05                    je     8048ef6 <secret_phase+0x4c>  #如果相等就跳转,否则爆炸
 8048ef1:    e8 d0 01 00 00           call   80490c6 <explode_bomb>                #即:返回值必须等于3
 8048ef6:    c7 04 24 b4 a1 04 08     movl   $0x804a1b4,(%esp)                        #"Wow! You've defused the secret stage!"
 8048efd:    e8 fe f8 ff ff           call   8048800 <puts@plt>
 8048f02:    e8 44 03 00 00           call   804924b <phase_defused>
 8048f07:    83 c4 18                 add    $0x18,%esp
 8048f0a:    5b                       pop    %ebx
 8048f0b:    c3                       ret    
 8048f0c:    90                       nop
 8048f0d:    90                       nop
 8048f0e:    90                       nop
 8048f0f:    90                       nop

这里先调用read_line函数,读取字符串。然后调用strtol函数,将字符串转换为整型num,并存储在eax中,之后比较num-1>1000(0x3e8),如果成立则会爆炸,所以输入的数字必须小于等于1001。接着把num存在esp+4中,把地址0x804c088中的值存在esp中。我们查看一下这个地址中的内容为:0x00000024。然后代码调用了func7这个函数,那么我们输入的字符串的整型值和36(0x00000024)均为func7的参数。

如果我们多查看一下地址0x804c088中的内容,会有如下发现:

(gdb) x/150 0x804c088
0x804c088 <n1>:         0x00000024      0x0804c094      0x0804c0a0      0x00000008
0x804c098 <n21+4>:      0x0804c0c4      0x0804c0ac      0x00000032      0x0804c0b8
0x804c0a8 <n22+8>:      0x0804c0d0      0x00000016      0x0804c118      0x0804c100
0x804c0b8 <n33>:        0x0000002d      0x0804c0dc      0x0804c124      0x00000006
0x804c0c8 <n31+4>:      0x0804c0e8      0x0804c10c      0x0000006b      0x0804c0f4
0x804c0d8 <n34+8>:      0x0804c130      0x00000028      0x00000000      0x00000000
0x804c0e8 <n41>:        0x00000001      0x00000000      0x00000000      0x00000063
0x804c0f8 <n47+4>:      0x00000000      0x00000000      0x00000023      0x00000000
0x804c108 <n44+8>:      0x00000000      0x00000007      0x00000000      0x00000000
0x804c118 <n43>:        0x00000014      0x00000000      0x00000000      0x0000002f
0x804c128 <n46+4>:      0x00000000      0x00000000      0x000003e9      0x00000000
0x804c138 <n48+8>:      0x00000000      0x000003db      0x00000001      0x0804c148
0x804c148 <node2>:      0x000002fc      0x00000002      0x0804c154      0x000000b8
0x804c158 <node3+4>:    0x00000003      0x0804c160      0x000003ba      0x00000004
0x804c168 <node4+8>:    0x0804c16c      0x000000dd      0x00000005      0x0804c178
0x804c178 <node6>:      0x000002c6      0x00000006      0x00000000      0x00000099

这很好,但我们先不管他,接着看代码。func7这个函数的返回值保存在eax中,返回值不等于3直接爆炸。好了,我们最后查看一下裸露在外的地址0x804a1b4,得到以下内容:

"Wow! You've defused the secret stage!"

很明显,这是成功拆完炸弹后的对话。总结一下,我们要输入一个字符串,这个字符串被转换成整型之后和36一起作为参数传入func7中,如果返回值是3,则成功。

那么我们接着来看func7中的内容,以下是func7的具体汇编代码及分析:

08048e59 <fun7>:
 8048e59:    53                       push   %ebx
 8048e5a:    83 ec 18                 sub    $0x18,%esp
 8048e5d:    8b 54 24 20              mov    0x20(%esp),%edx                    #设为x
 8048e61:    8b 4c 24 24              mov    0x24(%esp),%ecx                    #设为y
 8048e65:    85 d2                    test   %edx,%edx
 8048e67:    74 37                    je     8048ea0 <fun7+0x47>            #edx为0,eax返回0xffffffff
 8048e69:    8b 1a                    mov    (%edx),%ebx
 8048e6b:    39 cb                    cmp    %ecx,%ebx                                #两个数比较
 8048e6d:    7e 13                    jle    8048e82 <fun7+0x29>            #小于等于跳转
 8048e6f:    89 4c 24 04              mov    %ecx,0x4(%esp)
 8048e73:    8b 42 04                 mov    0x4(%edx),%eax
 8048e76:    89 04 24                 mov    %eax,(%esp)
 8048e79:    e8 db ff ff ff           call   8048e59 <fun7>                        #大于调用递归
 8048e7e:    01 c0                    add    %eax,%eax                                #eax=eax*2
 8048e80:    eb 23                    jmp    8048ea5 <fun7+0x4c>
 8048e82:    b8 00 00 00 00           mov    $0x0,%eax                                #eax=0
 8048e87:    39 cb                    cmp    %ecx,%ebx                                #两个数比较
 8048e89:    74 1a                    je     8048ea5 <fun7+0x4c>            #相等跳转
 8048e8b:    89 4c 24 04              mov    %ecx,0x4(%esp)
 8048e8f:    8b 42 08                 mov    0x8(%edx),%eax
 8048e92:    89 04 24                 mov    %eax,(%esp)
 8048e95:    e8 bf ff ff ff           call   8048e59 <fun7>                        #小于调用递归                
 8048e9a:    8d 44 00 01              lea    0x1(%eax,%eax,1),%eax        #eax=eax*2+1
 8048e9e:    eb 05                    jmp    8048ea5 <fun7+0x4c>
 8048ea0:    b8 ff ff ff ff           mov    $0xffffffff,%eax
 8048ea5:    83 c4 18                 add    $0x18,%esp                                #和第四题的逻辑很像
 8048ea8:    5b                       pop    %ebx
 8048ea9:    c3                       ret    

可以看到,这个函数的逻辑和第四题中func4的逻辑简直是一模一样,那么我们直接来猜,怎么才能使返回值为3。

其实不难猜出:

最底层:return 0
0*2+1=1
1*2+1=3

这样就得到了返回值。好,接下来我们回过去来看那个地址下的一大串东西

(gdb) x/150 0x804c088
0x804c088 <n1>:         0x00000024      0x0804c094      0x0804c0a0      0x00000008
0x804c098 <n21+4>:      0x0804c0c4      0x0804c0ac      0x00000032      0x0804c0b8
0x804c0a8 <n22+8>:      0x0804c0d0      0x00000016      0x0804c118      0x0804c100
0x804c0b8 <n33>:        0x0000002d      0x0804c0dc      0x0804c124      0x00000006
0x804c0c8 <n31+4>:      0x0804c0e8      0x0804c10c      0x0000006b      0x0804c0f4
0x804c0d8 <n34+8>:      0x0804c130      0x00000028      0x00000000      0x00000000
0x804c0e8 <n41>:        0x00000001      0x00000000      0x00000000      0x00000063
0x804c0f8 <n47+4>:      0x00000000      0x00000000      0x00000023      0x00000000
0x804c108 <n44+8>:      0x00000000      0x00000007      0x00000000      0x00000000
0x804c118 <n43>:        0x00000014      0x00000000      0x00000000      0x0000002f
0x804c128 <n46+4>:      0x00000000      0x00000000      0x000003e9      0x00000000
0x804c138 <n48+8>:      0x00000000      0x000003db      0x00000001      0x0804c148
0x804c148 <node2>:      0x000002fc      0x00000002      0x0804c154      0x000000b8
0x804c158 <node3+4>:    0x00000003      0x0804c160      0x000003ba      0x00000004
0x804c168 <node4+8>:    0x0804c16c      0x000000dd      0x00000005      0x0804c178
0x804c178 <node6>:      0x000002c6      0x00000006      0x00000000      0x00000099

这些数据以3个为一组,第一个为值,第二,第三个都是地址,这种结构很像二叉树。我们直接大胆假设这就是二叉树,那么0x00000024就是树的头节点的值,要使返回值是3,需要两次2*x+1,也就是两次的向右查找,也就是顺序遍历的第七个节点。我们找到第七组数据,它存储的值是0x0000006b,也就是107,我们来验证一下:

验证通过,我们的假设是正确的。

最后,我们来具体分析一下怎么得到107。

首先来到二叉树的首地址0x804c088对应的数据:0x00000024(36),因为36需要小于x,才能得到 eax = eax * 2+1,那么指针值应该为edx+8(加载右结点),指针值为0x0804c0a0,查看得到值为0x00000032(50)。 来到0x00000032对应的位置,我们想要数据eax = eax*2 + 1,则50需要小于等于x,那么指针值应该为0x0804c0a0+48(加载右结点),指针值为0x0804c0d0,查看得到的值为0x0000006b(107),最后我们得到了数据107,当我们输入107的时候,因为其对应的两个指针所处位置对应头部数据的值相等,所以eax=0。

所以107为答案。

四.实验总结

个人感觉前三关都挺简单,第四关和第六关最难,隐藏关由于有了第四关的经验减少了不少难度,这些关卡分别考察了汇编代码中的字符串存储,循环,跳转表,递归,字符的ascii码表示,链表,二叉树。最后,这p实验是真的耗时间。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值