本篇博客是《深入理解计算机系统》实验记录的第二篇,也是非常有名的炸弹实验。首先吐槽一下学校的这个实验代码不更新的问题,教材和上课讲授的均是x86-64位汇编语言,结果实验给的还是第二版配套的32位汇编语言,这两者之间有不少的差别,为此还查阅了一些关于32位汇编的资料自己学习(例如32位的参数传递规则和64位不同),这无疑增加了学习成本。
接下来进入正题,我领取的代码包是第19号,即bomb19。后来发现不同人领取的代码包实验题目虽不同,但解决思路大体相同,答案也是不一样的。不知为何,我感觉我的实验包好像难度可能还大一些,这可能是错觉。

无论如何,这几个phase全靠自己拿头磕出来的,花了两天多才拆完6个炸弹,第一天拆了5个,第二天边上课边拆剩下的1个(最后一个有点难哦)。现在把整个过程写出来,作为对这段经历的记录。

Ahead Of Work

这些事情是完成这个实验后总结的心得体会:
1.使用

objdump -d bomb > objdump.s

将可执行文件bomb反汇编出来,我们的工作需要阅读这些汇编代码,使用一个好用的阅读器阅读会事半功倍。

2.了解32位汇编语言传参规则,与64位不同,32位传参全部使用栈来完成,同时还需要了解enter和leave指令。

3.关于GDB的基本用法,这里有一个课程官网上提供的关于GDB的reference,建议配套使用。

4.拆炸弹的最好办法是画出栈帧图,求解过程要大胆假设,小心求证

5.阅读bomb.c可知,所有phase的调用全部都是先由read_line读入你的input,然后作为唯一参数将input传送入phase。

6.下面分析中涉及大量栈帧结构,第N号栈字表示从栈帧基址指针开始计数(向地址递减方向)的第N个字,简记为#N。

Phase1

第一个phase比较简单,阅读代码后,这里给出栈帧图示:

如果阅读phase_1的汇编代码,可以很容易画出上面的栈帧图。注意phase_1的参数构造区,这两个参数将被传递进一个名为strings_not_equal的函数,从名字上可以看出这个函数用于判断两个字符串是否相等。你可以去再阅读一下它的细节,但是从这里我们已经可以大胆地作出假设,那个magic number(0x8049958)中存放着要对比的字符串。在GDB下打印出来:

(gdb) break phase_1
Breakpoint 1 at 0x8048b86
(gdb) x/s 0x8049958
0x8049958:      "For NASA, space is still a high priority."

这句For NASA, space is still a high priority.,就是最终第一题的解。实验时的截图如下:

phase2

第二个炸弹稍微复杂一些,阅读反汇编可以知道函数中调用了一个名为read_six_numbers的函数使用sscanf从输入的字符串input中解析出6个数字。画出栈帧示意图如下:

图中的蓝线表示了将对应单元的地址放置到对应的栈帧。看图中右边关于read_six_numbers函数的栈帧,从名字可以猜测出来这个函数期待从我们输入的字符串中读取6个整数。根据IA32的传参规则,我们可以知道读取的6个整型数字将会被放置回phase_2的栈帧中,作为局部变量参与后面的运算。为什么说是6个整型数字呢,因为read_six_numbers向sscanf传递的第一个参数,也就是0x8049c7d这个东西(看上去很奇怪是吧),其实这指向了一个模式字符串。如果打印出来,可以看到:

好了,知道了这个函数的作用,所以我们知道我们一定要输入6个整型数字,少于6个就会引爆炸弹,这是对sscanf返回值的检测得出的。现在返回phase_2,看看它要对这六个数字做什么:

 8048bbc:    8b 45 e4                mov    -0x1c(%ebp),%eax         ;第7号栈字的内容送入%eax8048bbf: 83 f8 01                cmp    $0x1,%eax                ;cmp %eax : 18048bc2:   74 05                   je     8048bc9 <phase_2+0x25>    ;相等则跳转,否则引爆炸弹8048bc4:    e8 99 0a 00 00          call   8049662 <explode_bomb>8048bc9: c7 45 fc 01 00 00 00    movl   $0x1,-0x4(%ebp)          ;1送入第1号栈字8048bd0:   eb 22                   jmp    8048bf4 <phase_2+0x50>    8048bd2:    8b 45 fc                mov    -0x4(%ebp),%eax          ;%eax = 第1号栈字中的内容8048bd5:  8b 4c 85 e4             mov    -0x1c(%ebp,%eax,4),%ecx  ;%ecx = *(%ebp + 4 * %eax - 28)8048bd9:   8b 45 fc                mov    -0x4(%ebp),%eax          ;%eax = 1号栈字中的内容8048bdc:   48                      dec    %eax                     ;%eax = %eax - 18048bdd:   8b 54 85 e4             mov    -0x1c(%ebp,%eax,4),%edx  ;%edx = *(%ebp + 4 * %eax - 28)8048be1:   8b 45 fc                mov    -0x4(%ebp),%eax          ;%eax = 1号栈字中的内容8048be4:   40                      inc    %eax                     ;%eax = %eax + 18048be5:  0f af c2                imul   %edx,%eax                ;%eax = %eax * %edx8048be8:    39 c1                   cmp    %eax,%ecx                ;cmp %ecx : %eax8048bea:    74 05                   je     8048bf1 <phase_2+0x4d>    ;相等跳至8048bf1,否则引爆炸弹8048bec:  e8 71 0a 00 00          call   8049662 <explode_bomb>8048bf1: ff 45 fc                incl   -0x4(%ebp)               ;递增1号栈字8048bf4: 83 7d fc 05             cmpl   $0x5,-0x4(%ebp)          ;cmp 第1号栈字:58048bf8: 7e d8                   jle    8048bd2 <phase_2+0x2e>    ;相等或小于,jmp 8048bd2

上来就进行了一次判定,要判定#7是否为1,否则引爆炸弹。所以这使得我们输入的第一个数一定要是1,接下来的动作可以列个表格(汇编代码注释已给出,不再逐行解读):
这里%eax是作为循环计数变量的,循环次数为5.

%eax %ecx %edx cmp
1 #6 #7 #6:2#7
2 #5 #6 #5:3#6
3 #4 #5 #4:4#5
4 #3 #4 #3:5#4
5 #2 #3 #2:6#3

上面执行的比较操作要求两个比较数字完全相等,否则就会引爆炸弹。
因为#7确定是1,所以可以推出来:
#6 = 2 (#7 * 2)
#5 = 6 (#6 * 3)
#4 = 24 (#5 * 4)
#3 = 120 (#4 * 5)
#2 = 720 (#3 * 6)

所以要输入的序列就是

1 2 6 24 120 720

phase3

首先给出汇编代码:

08048bfc <phase_3>:8048bfc:    55                      push   %ebp8048bfd: 89 e5                   mov    %esp,%ebp8048bff:    83 ec 28                sub    $0x28,%esp8048c02:   c7 45 f8 00 00 00 00    movl   $0x0,-0x8(%ebp)      ;赋值8048c09: c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%ebp)      ;赋值 x28048c10:  8d 45 f0                lea    -0x10(%ebp),%eax     ;第4个栈字的地址交给%eax8048c13: 89 44 24 0c             mov    %eax,0xc(%esp)       ;第4个栈字的地址交给#78048c17:   8d 45 f4                lea    -0xc(%ebp),%eax      ;第3个栈字的地址交给%eax8048c1a: 89 44 24 08             mov    %eax,0x8(%esp)       ;第3个栈字的地址交给#88048c1e:   c7 44 24 04 82 99 04    movl   $0x8049982,0x4(%esp) ;magic number->#98048c25:    08 8048c26: 8b 45 08                mov    0x8(%ebp),%eax       8048c29:    89 04 24                mov    %eax,(%esp)          ;input放到栈顶8048c2c:  e8 37 fc ff ff          call   8048868 <sscanf@plt>  ;读取一些东东8048c31: 89 45 fc                mov    %eax,-0x4(%ebp)      ;返回值放在#18048c34:    83 7d fc 01             cmpl   $0x1,-0x4(%ebp)      ;cmp ret:18048c38:  7f 05                   jg     8048c3f <phase_3+0x43>    ;大于1跳转,否则引爆炸弹8048c3a:    e8 23 0a 00 00          call   8049662 <explode_bomb>8048c3f: 8b 45 f4                mov    -0xc(%ebp),%eax      ;将#3中的东东放到%eax8048c42:  89 45 ec                mov    %eax,-0x14(%ebp)     ;将%eax放到#58048c45:  83 7d ec 07             cmpl   $0x7,-0x14(%ebp)     ;cmp #5:78048c49:   77 54                   ja     8048c9f <phase_3+0xa3>    ;#5 > 7, jmp 0x8048c9f, 引爆炸弹8048c4b: 8b 55 ec                mov    -0x14(%ebp),%edx         ;#5放到%edx8048c4e:   8b 04 95 88 99 04 08    mov    0x8049988(,%edx,4),%eax  ;(0x8049988 + 4 * #5)放到%eax8048c55:    ff e0                   jmp    *%eax                    ;间接跳转8048c57:   c7 45 f8 52 02 00 00    movl   $0x252,-0x8(%ebp)        ;给#2赋值为0x2528048c5e:    eb 44                   jmp    8048ca4 <phase_3+0xa8>8048c60:    c7 45 f8 0e 02 00 00    movl   $0x20e,-0x8(%ebp)        ;给#2赋值为0x20e8048c67:    eb 3b                   jmp    8048ca4 <phase_3+0xa8>8048c69:    c7 45 f8 5a 01 00 00    movl   $0x15a,-0x8(%ebp)        ;给#2赋值为0x15a8048c70:    eb 32                   jmp    8048ca4 <phase_3+0xa8>8048c72:    c7 45 f8 b9 01 00 00    movl   $0x1b9,-0x8(%ebp)        ;给#2赋值为0x1b98048c79:    eb 29                   jmp    8048ca4 <phase_3+0xa8>8048c7b:    c7 45 f8 6b 01 00 00    movl   $0x16b,-0x8(%ebp)        ;给#2赋值为0x16b8048c82:    eb 20                   jmp    8048ca4 <phase_3+0xa8>8048c84:    c7 45 f8 2a 01 00 00    movl   $0x12a,-0x8(%ebp)        ;给#2赋值为0x12a8048c8b:    eb 17                   jmp    8048ca4 <phase_3+0xa8>8048c8d:    c7 45 f8 28 03 00 00    movl   $0x328,-0x8(%ebp)        ;给#2赋值为0x3288048c94:    eb 0e                   jmp    8048ca4 <phase_3+0xa8>8048c96:    c7 45 f8 e8 00 00 00    movl   $0xe8,-0x8(%ebp)         ;给#2赋值为0xe88048c9d: eb 05                   jmp    8048ca4 <phase_3+0xa8>8048c9f:    e8 be 09 00 00          call   8049662 <explode_bomb> ;引爆炸弹8048ca4:   8b 45 f0                mov    -0x10(%ebp),%eax         ;#4的值放到%eax8048ca7: 39 45 f8                cmp    %eax,-0x8(%ebp)          ;cmp #2:#4,相等,安全退出,否则引爆炸弹8048caa:  74 05                   je     8048cb1 <phase_3+0xb5>8048cac:    e8 b1 09 00 00          call   8049662 <explode_bomb>8048cb1: c9                      leave  8048cb2: c3                      ret

这道题栈帧结构不难,但是它有些信息是比较隐秘的,需要用gdb在执行中去获取,以下是phase_3的栈帧结构。

栈帧示意图中的蓝线表示将对应栈帧的地址放置到对应的单元,phase_3会调用sscanf函数从你的输入中读取两个整数,魔数0x8049982指向sscanf从你输入的字符串中解析数据时用到的格式字符串。

解析之后的返回值根据汇编代码可知会放置到#1,汇编代码会测试是否读取了大于一个值,如果你输入的数字只有一个,那么会引爆炸弹,所以需要输入两个数字,这和之前的分析是相吻合的。随后汇编代码会检查输入的第一个数字是否大于7,大于7则引爆炸弹,所以我们第一个输入的数字不可以大于7。

接下来是重点,我们输入的数字会作为一个偏移量去索引一个跳表(jump table)结构,因为这里使用了间接跳转指令。为了知道这个跳表的具体内容,我们必须用gdb去打印并调试,0x8049988这个显得很突兀的地址就是跳表的基址。

8048c4e: 8b 04 95 88 99 04 08    mov    0x8049988(,%edx,4),%eax  ;(0x8049988 + 4 * #5)放到%eax8048c55:    ff e0                   jmp    *%eax                    ;间接跳转

使用gdb打印0x8049988,可见跳表的内容:

这8个地址分别对应到phase_3下面的一系列赋值语句(0-7共8种情况,正好一一对应),随后汇编代码会对比赋值语句赋值给#2的数字和你输入的第二个数字是否相等,不等则引爆炸弹。
所以这题已经豁然开朗了,你必须根据自己的第一个输入来确定跳转地址,从而得到第二个输入。
所以本题解不唯一,我使用的是:

4 363

这组解。

phase4

这道题涉及到了递归函数,这道题的汇编代码对应的栈帧结构如下所示,为了更好地理解递归函数的作用,我们假设输入的数字是3,这样可以在比较好的了解递归函数的作用同时不至于使栈帧结构过长。
首先给出添加注释的phase_4汇编代码:

8048ce2: 55                      push   %ebp8048ce3: 89 e5                   mov    %esp,%ebp8048ce5:    83 ec 28                sub    $0x28,%esp8048ce8:   8d 45 f4                lea    -0xc(%ebp),%eax          ;#3的地址放在%eax    8048ceb:    89 44 24 08             mov    %eax,0x8(%esp)           ;%eax放在#88048cef:   c7 44 24 04 a8 99 04    movl   $0x80499a8,0x4(%esp)     ;magic number->#98048cf6:    08 8048cf7: 8b 45 08                mov    0x8(%ebp),%eax           ;input放到%eax8048cfa:    89 04 24                mov    %eax,(%esp)              ;input放到栈顶8048cfd:  e8 66 fb ff ff          call   8048868 <sscanf@plt>      ;读取一些东东8048d02: 89 45 fc                mov    %eax,-0x4(%ebp)          ;sscanf返回值放在#18048d05:  83 7d fc 01             cmpl   $0x1,-0x4(%ebp)          ;cmp #1:18048d09:   75 07                   jne    8048d12 <phase_4+0x30>    ;#1不等于1,引爆炸弹8048d0b:    8b 45 f4                mov    -0xc(%ebp),%eax          ;#3中的值(输入值)放入%eax8048d0e:   85 c0                   test   %eax,%eax                ;测试一下输入的数据8048d10:  7f 05                   jg     8048d17 <phase_4+0x35>    ;大于0,jmp 8048d17,否则引爆炸弹8048d12:   e8 4b 09 00 00          call   8049662 <explode_bomb>     8048d17:    8b 45 f4                mov    -0xc(%ebp),%eax          ;#3中的值放入%eax8048d1a:    89 04 24                mov    %eax,(%esp)              ;解析后的输入值放入栈顶8048d1d:    e8 91 ff ff ff          call   8048cb3 <func4>8048d22:    89 45 f8                mov    %eax,-0x8(%ebp)          ;func4的返回值放回#28048d25:  81 7d f8 d0 02 00 00    cmpl   $0x2d0,-0x8(%ebp)        ;cmp #2:7208048d2c: 74 05                   je     8048d33 <phase_4+0x51>    ;相等安全退出,否则引爆炸弹8048d2e:   e8 2f 09 00 00          call   8049662 <explode_bomb>8048d33: c9                      leave  8048d34: c3                      ret

简而言之,phase_4首先使用sscanf函数从输入的字符串中解析出一个数字,随后判断这个数字是否大于0。如果输入的字符串中包含了除了一个数字之外的其他内容,或者输入的是一个负数,那么都会引爆炸弹。随后,phase_4以输入的数值为参数调用func4。整体的栈帧结构如下(假设输入的是3,蓝线表示的是将对应单元的地址送入另一单元):

func4的汇编代码含注释如下:

8048cb3: 55                      push   %ebp8048cb4: 89 e5                   mov    %esp,%ebp8048cb6:    83 ec 08                sub    $0x8,%esp8048cb9:    83 7d 08 01             cmpl   $0x1,0x8(%ebp)       ;cmp input:18048cbd:    7f 09                   jg     8048cc8 <func4+0x15>  ;如果输入大于1,jmp 8048cc88048cbf: c7 45 fc 01 00 00 00    movl   $0x1,-0x4(%ebp)      ;输入小于等于,#1放18048cc6: eb 15                   jmp    8048cdd <func4+0x2a>  ;8048cc8:   8b 45 08                mov    0x8(%ebp),%eax       ;input放到%eax中8048ccb:   48                      dec    %eax                 ;递减%eax8048ccc: 89 04 24                mov    %eax,(%esp)          ;%eax放到栈顶8048ccf:   e8 df ff ff ff          call   8048cb3 <func4>        ;递归调用func48048cd4:  89 c2                   mov    %eax,%edx            ;递归调用的返回值放回%edx8048cd6: 0f af 55 08             imul   0x8(%ebp),%edx       ;%edx = %edx * 当前函数的input8048cda:  89 55 fc                mov    %edx,-0x4(%ebp)      ;%edx放到#18048cdd:   8b 45 fc                mov    -0x4(%ebp),%eax      ;#1放到%eax,返回8048ce0: c9                      leave  8048ce1: c3                      ret    

参照栈帧结构逐行跟踪func4的执行过程,可以发现这个递归函数func4在计算输入参数的阶乘
现在回到phase_4,可以看到phase_4将func4的返回结果与720进行比较,相等则正常退出。
那么事实上这个程序就是想让我们输入一个数,它的阶乘等于720,很容易得到这个数字是6。
所以这道题输入的是

6

phase5

这个题有一定的难度,首先给出这道题的汇编代码:

 8048d35:    55                      push   %ebp8048d36: 89 e5                   mov    %esp,%ebp8048d38:    83 ec 38                sub    $0x38,%esp8048d3b:   8d 45 e8                lea    -0x18(%ebp),%eax     ;#6的地址放在%eax8048d3e:    89 44 24 0c             mov    %eax,0xc(%esp)       ;%eax放置在#118048d42: 8d 45 ec                lea    -0x14(%ebp),%eax     ;#5的地址放在%eax8048d45:    89 44 24 08             mov    %eax,0x8(%esp)       ;%eax放置在#128048d49: c7 44 24 04 82 99 04    movl   $0x8049982,0x4(%esp) ;magic number->#13.8048d50:  08 8048d51: 8b 45 08                mov    0x8(%ebp),%eax           ;input放置到%eax8048d54:   89 04 24                mov    %eax,(%esp)              ;%eax放置到栈顶8048d57:  e8 0c fb ff ff          call   8048868 <sscanf@plt>      ;读取一些东东8048d5c: 89 45 fc                mov    %eax,-0x4(%ebp)          ;sscanf返回值放置到#18048d5f: 83 7d fc 01             cmpl   $0x1,-0x4(%ebp)          ;cmp #1:18048d63:   7f 05                   jg     8048d6a <phase_5+0x35>    ;如果#1大于1,跳转到8048d6a,否则引爆炸弹8048d65:    e8 f8 08 00 00          call   8049662 <explode_bomb> 8048d6a:    8b 45 ec                mov    -0x14(%ebp),%eax         ;#5(input1)的值放置到%eax8048d6d:    83 e0 0f                and    $0xf,%eax                ;只保留input1的低四位8048d70:  89 45 ec                mov    %eax,-0x14(%ebp)         ;把低四位放回到#58048d73:  8b 45 ec                mov    -0x14(%ebp),%eax         ;#5的值放置到%eax8048d76:    89 45 f8                mov    %eax,-0x8(%ebp)          ;%eax的值放置到#28048d79:    c7 45 f0 00 00 00 00    movl   $0x0,-0x10(%ebp)         ;0放置到#48048d80: c7 45 f4 00 00 00 00    movl   $0x0,-0xc(%ebp)          ;0放置在#38048d87: eb 16                   jmp    8048d9f <phase_5+0x6a>    ;跳转到8048d9f8048d89: ff 45 f0                incl   -0x10(%ebp)              ;递增#48048d8c:   8b 45 ec                mov    -0x14(%ebp),%eax         ;#5的值放置到%eax8048d8f:    8b 04 85 c0 a5 04 08    mov    0x804a5c0(,%eax,4),%eax  ;%eax = *(0x804a5c0 + 4 * %eax)8048d96:   89 45 ec                mov    %eax,-0x14(%ebp)         ;%eax放置到#58048d99:  8b 45 ec                mov    -0x14(%ebp),%eax         ;#5放置到%eax8048d9c:  01 45 f4                add    %eax,-0xc(%ebp)          ;#3 = #3 + %eax8048d9f:   8b 45 ec                mov    -0x14(%ebp),%eax         ;#5的值放置到%eax8048da2:    83 f8 0f                cmp    $0xf,%eax                ;cmp %eax:158048da5:    75 e2                   jne    8048d89 <phase_5+0x54>    ;%eax不等于15,跳转至8048d898048da7:    83 7d f0 0b             cmpl   $0xb,-0x10(%ebp)         ;cmp #4:118048dab:  75 08                   jne    8048db5 <phase_5+0x80>    ;如果#4不等于11,引爆炸弹8048dad:  8b 45 e8                mov    -0x18(%ebp),%eax         ;#6的值放置在%eax8048db0:    39 45 f4                cmp    %eax,-0xc(%ebp)          ;cmp #3:%eax8048db3:    74 05                   je     8048dba <phase_5+0x85>    ;如果%eax等于#3,正常离开否则引爆炸弹8048db5:   e8 a8 08 00 00          call   8049662 <explode_bomb>8048dba: c9                      leave  8048dbb: c3                      ret

对应的栈帧结构为:

大致描述一下这段代码做的事情,首先这段代码调用sscanf从输入的字符串中解析出2个整数,如果输入的整数个数不满足要求就会引爆炸弹。然后这段代码用输入的第一个数字对15取余,随后就是一个非常tricky的过程:
这段代码在索引一个数组,这个数组开始于内存地址0x804a5c0,用gdb去打印一下:

这个数组非常重要,下面我换一种表现方式,左上角是偏移量:

后面的代码实际上完成的事情是这样的,它把你输入的第一个数取低四位,也就是对15取余(这里我们可以看出来此题的答案不唯一)。我们开始使用这个数来索引数组中的元素,之后再以索引到的数字作为偏移量再次索引,这样重复11次之后要求恰好索引到15这个数字,然后输入的第二个数字对应的是这个索引过程中所经历节点数值之和,这两个条件有一个不满足就会引爆炸弹。这两个条件是:

1.从(输入的第一个数字mod15)开始索引数组,索引11次后到达15
2.路径中通过的数字加和等于第二个输入数字

现在就用到了上述的数组表格,我们采用逆推法,可以在格子中模拟索引过程如下:

15 -> 6 -> 14 -> 2 -> 1 -> 10 -> 0 -> 8 -> 4 -> 9 -> 13 -> 11

上面恰好是从15这个数字逆向推11次得到的结果,可以发现最终停留在了11,那么11就是我们输入的第一个数字
这个过程中的路径加和为:

13 + 9 + 4 + 8 + 0 + 10 + 1 + 2 + 14 + 6 + 15 = 82

所以这道题的最终答案是:

11 82

phase6

最后一个题相对较难,我在拆这个炸弹的过程中耗费了不少时间,而且在最终解决的过程做了合理的联想。
这道题不准备给出栈帧结构,因为实际在解题的过程中发现,栈帧结构对于求解本题的作用不大。
求解本题的关键是搞懂这个fun6是在做什么,这个函数的反汇编如下:

08048dbc <fun6>:8048dbc:   55                      push   %ebp                     8048dbd:    89 e5                   mov    %esp,%ebp8048dbf:    83 ec 10                sub    $0x10,%esp               8048dc2:    8b 45 08                mov    0x8(%ebp),%eax       ;地址放置到%eax8048dc5:  89 45 f0                mov    %eax,-0x10(%ebp)     ;%eax放置到#48048dc8:  8b 45 08                mov    0x8(%ebp),%eax       ;地址放置到%eax8048dcb:  89 45 f0                mov    %eax,-0x10(%ebp)     ;%eax放置到#48048dce:  8b 45 08                mov    0x8(%ebp),%eax       ;地址放置到%eax  8048dd1:    8b 40 08                mov    0x8(%eax),%eax       ;%eax = *(地址+8)8048dd4:   89 45 f4                mov    %eax,-0xc(%ebp)      ;%eax放置到#3 如果*(地址+8)是0,那么直接返回#4,否则8048dd7:   8b 45 f0                mov    -0x10(%ebp),%eax     ;#4(地址)放置到%eax8048dda:  c7 40 08 00 00 00 00    movl   $0x0,0x8(%eax)       ;*(地址+8) = 08048de1:  eb 62                   jmp    8048e45 <fun6+0x89>   ;jmp 8048e458048de3:    8b 45 f0                mov    -0x10(%ebp),%eax     ;#4放置到%eax8048de6:  89 45 fc                mov    %eax,-0x4(%ebp)      ;%eax放置到#18048de9:  8b 45 f0                mov    -0x10(%ebp),%eax     ;#4放置到%eax8048dec:  89 45 f8                mov    %eax,-0x8(%ebp)      ;%eax放置到#28048def:  eb 0f                   jmp    8048e00 <fun6+0x44>   ;jmp 8048e008048df1:    8b 45 fc                mov    -0x4(%ebp),%eax      ;#1放置到%eax8048df4:  89 45 f8                mov    %eax,-0x8(%ebp)      ;%eax放置到#28048df7:  8b 45 fc                mov    -0x4(%ebp),%eax      ;#1放置到%eax8048dfa:  8b 40 08                mov    0x8(%eax),%eax       ;*(%eax + 8)放置到%eax8048dfd:    89 45 fc                mov    %eax,-0x4(%ebp)      ;%eax放置到#18048e00:  83 7d fc 00             cmpl   $0x0,-0x4(%ebp)      ;cmp #1:0 其实就是判断指针是否非空8048e04:  74 0e                   je     8048e14 <fun6+0x58>   ;相等则jmp 8048e148048e06: 8b 45 fc                mov    -0x4(%ebp),%eax      ;#1放置到%eax8048e09:  8b 10                   mov    (%eax),%edx          ;*(%eax)放置到%edx8048e0b: 8b 45 f4                mov    -0xc(%ebp),%eax      ;#3放置到%eax8048e0e:  8b 00                   mov    (%eax),%eax          ;*(%eax)放置到%eax8048e10: 39 c2                   cmp    %eax,%edx            ;cmp %edx:%eax8048e12:  7f dd                   jg     8048df1 <fun6+0x35>   ;if %edx > %eax, jmp 8048df18048e14: 8b 45 f8                mov    -0x8(%ebp),%eax      ;#2放置到%eax8048e17:  3b 45 fc                cmp    -0x4(%ebp),%eax      ;cmp %eax:#18048e1a: 74 0b                   je     8048e27 <fun6+0x6b>   ;相等则jmp 8048e278048e1c: 8b 55 f8                mov    -0x8(%ebp),%edx      ;#2放置到%edx8048e1f:  8b 45 f4                mov    -0xc(%ebp),%eax      ;#3放置到%eax8048e22:  89 42 08                mov    %eax,0x8(%edx)       ;%eax放置到*(%edx+8)8048e25:  eb 06                   jmp    8048e2d <fun6+0x71>   ;jmp 8048e2d8048e27:    8b 45 f4                mov    -0xc(%ebp),%eax      ;#3放置到%eax8048e2a:  89 45 f0                mov    %eax,-0x10(%ebp)     ;%eax放置到#4,那么这时候#4放置的是*(地址+8)8048e2d:   8b 45 f4                mov    -0xc(%ebp),%eax      ;#3放置到%eax8048e30:  8b 40 08                mov    0x8(%eax),%eax       ;*(%eax + 8)放置到%eax8048e33:    89 45 f8                mov    %eax,-0x8(%ebp)      ;%eax放置到#28048e36:  8b 55 f4                mov    -0xc(%ebp),%edx      ;#3放置到%edx8048e39:  8b 45 fc                mov    -0x4(%ebp),%eax      ;#1放置到%eax8048e3c:  89 42 08                mov    %eax,0x8(%edx)       ;%eax放置到*(%edx + 8)8048e3f:    8b 45 f8                mov    -0x8(%ebp),%eax      ;#2放置到%eax8048e42:  89 45 f4                mov    %eax,-0xc(%ebp)      ;%eax放置到#38048e45:  83 7d f4 00             cmpl   $0x0,-0xc(%ebp)      ;cmp #3:08048e49:   75 98                   jne    8048de3 <fun6+0x27>   ;#3不是0则跳到8048de38048e4b:    8b 45 f0                mov    -0x10(%ebp),%eax     ;#3是0,#4放置到%eax,返回8048e4e:    c9                      leave  8048e4f: c3                      ret

很长而且也不容易理顺,我在求解时给出了类C的伪代码形式,如下所示(和前面的约定相同,这里的#N表示的是对应函数栈帧的第N个栈字):

 start:#1 = #4;#2 = #4;while(#1 && *(#1) > *(#3))#2 = #1;#1 = *(#1 + 8);if(#1 != #2)*(#2 + 8) = #3;else#4 = #3;#2 = *(#3 + 8);*(#3 + 8) = #1;#3 = #2;if(#3) goto start;

这就是上述汇编代码对应的类C语言形式,乍一看还是难以理解。这时候需要加一些联想,这里面反复出现的*(#N + 8)让我联想到了链表结构连续向后索引的时候的动作,因此这道题很有可能和链表有关。一旦想到这道题可能和链表有关之后,就立即去着手解读和模拟上面这份代码,你会发现它执行的实际上是一个链表的插入排序算法(从大到小),这个算法以你输入的数字构造一个节点,随后遍历一个在内存中已经存在的一个链表,以插入排序的形式将其与你输入的链表节点完全合并。

当函数从fun6返回至phase6时,返回值为指向链表头部的指针,随后phase6的代码会进行5次索引,它要求你输入的数值在链表中排序后正好处在第5位,phase6的汇编代码注释如下所示:

08048e50 <phase_6>:8048e50:    55                      push   %ebp                     8048e51:    89 e5                   mov    %esp,%ebp8048e53:    83 ec 18                sub    $0x18,%esp8048e56:   c7 45 f8 6c a6 04 08    movl   $0x804a66c,-0x8(%ebp)    ;magic number->#28048e5d:    8b 45 08                mov    0x8(%ebp),%eax           8048e60:    89 04 24                mov    %eax,(%esp)              ;input放置到栈顶8048e63: e8 f0 f9 ff ff          call   8048858 <atoi@plt>        ;字符串转化为整数8048e68:   89 c2                   mov    %eax,%edx                ;返回值放置到%edx8048e6a: 8b 45 f8                mov    -0x8(%ebp),%eax          ;#2->%eax8048e6d:    89 10                   mov    %edx,(%eax)              ;返回值放置到*(#2)8048e6f:    8b 45 f8                mov    -0x8(%ebp),%eax          ;#2->%eax8048e72:    89 04 24                mov    %eax,(%esp)              ;%eax放置到栈顶8048e75:  e8 42 ff ff ff          call   8048dbc <fun6>         8048e7a:    89 45 f8                mov    %eax,-0x8(%ebp)          ;fun6返回值放置到#28048e7d:   8b 45 f8                mov    -0x8(%ebp),%eax          ;#2放置到%eax8048e80:  89 45 fc                mov    %eax,-0x4(%ebp)          ;%eax放置到#18048e83:  c7 45 f4 01 00 00 00    movl   $0x1,-0xc(%ebp)          ;1放置到#38048e8a: eb 0c                   jmp    8048e98 <phase_6+0x48>    ;jmp 8048e988048e8c:    8b 45 fc                mov    -0x4(%ebp),%eax          ;#1放置到%eax8048e8f:  8b 40 08                mov    0x8(%eax),%eax           ;%eax = *(%eax + 8)8048e92:   89 45 fc                mov    %eax,-0x4(%ebp)          ;%eax放置到#18048e95:  ff 45 f4                incl   -0xc(%ebp)               ;递增#38048e98:   83 7d f4 05             cmpl   $0x5,-0xc(%ebp)          ;cmp #3:58048e9c:   7e ee                   jle    8048e8c <phase_6+0x3c>    ;if #3<= 5, jmp 8048e8c8048e9e: 8b 45 fc                mov    -0x4(%ebp),%eax          ;否则#1放置到%eax8048ea1:    8b 10                   mov    (%eax),%edx              ;%edx = *(%eax)8048ea3:    a1 6c a6 04 08          mov    0x804a66c,%eax           ;*(0x804a66c) -> %eax,注意这不是立即数!!!8048ea8:    39 c2                   cmp    %eax,%edx                ;cmp %edx:%eax8048eaa:  74 05                   je     8048eb1 <phase_6+0x61>    ;相等则正常离开,否则引爆炸弹8048eac:  e8 b1 07 00 00          call   8049662 <explode_bomb>8048eb1: c9                      leave  8048eb2: c3                      ret

所以当务之急是找出这个原本在内存中的链表的详细信息,我使用gdb一个个遍历了这个链表的所有结点,汇总如下:

地址 val(decimal) next
0x804a660 652 0x804a654
0x804a654 557 0x804a648
0x804a648 802 0x804a63c
0x804a63c 134 0x804a630
0x804a630 826 0x804a624
0x804a624 895 0x804a618
0x804a618 438 0x804a60c
0x804a60c 309 0x804a600
0x804a600 693 0(end of list)

所以简单排一下序,这时候我们得到的链表是这样的:

895 -> 826 -> 802 ->693 -> [652 -> 557] -> 438 -> 309 -> 308

我们要想让我们输入的节点排序后位于此链表的第六个节点,那么需要让它的值介于[557, 652]之间,因此此题答案不唯一,我选择的值是:

558

感想

bomb lab在完成6个炸弹的拆除之后来到了终点,其实这还不是真正的终点,因为你可以看到在完成的反汇编代码文件中还有(secret_phase)这个隐藏的秘密阶段。考虑到时间问题,我没有继续完成和考虑这个秘密阶段。总的来说,做这个实验还是非常的有收获,它给了所有计算机专业的从业者和学生一个接触、阅读和调试机器级代码的机会,如果可以认真做这个实验,将会对计算机偏底层的逻辑表示方式有一个更加深入的理解。

CSAPP——Lab2——Bomb Lab相关推荐

  1. CSAPP Lab2 实验记录 ---- Bomb Lab(Phase 1 - Phase 6详细解答 + Secret Phase彩蛋解析)

    文章目录 Lab 总结博客链接 实验前提引子 实验需要指令及准备 Phase 1 Phase 2 Phase 3 Phase 4 Phase 5 Phase 6 Phase Secret(彩蛋Phas ...

  2. [精品]CSAPP Bomb Lab 解题报告(七)——隐藏关卡

    接上篇[精品]CSAPP Bomb Lab 解题报告(六) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  3. [精品]CSAPP Bomb Lab 解题报告(六)

    接上篇[精品]CSAPP Bomb Lab 解题报告(五) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  4. [精品]CSAPP Bomb Lab 解题报告(五)

    接上篇[精品]CSAPP Bomb Lab 解题报告(四) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  5. [精品]CSAPP Bomb Lab 解题报告(四)

    接上篇[精品]CSAPP Bomb Lab 解题报告(三) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  6. [精品]CSAPP Bomb Lab 解题报告(三)

    接上篇[精品]CSAPP Bomb Lab 解题报告(二) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  7. [精品]CSAPP Bomb Lab 解题报告(二)

    接上篇[精品]CSAPP Bomb Lab 解题报告(一) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  8. [精品]CSAPP Bomb Lab 解题报告(一)

    接上篇堆栈图解CSAPP Bomb Lab实验解析 gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 查看字 ...

  9. 堆栈图解CSAPP Bomb Lab实验解析

    CSAPP Bomb Lab 实验解析 Bomblab是csapp的第二个配套实验,该实验提供了一个bomb二进制文件和一个bomb.c源文件,我们的目标是运行bomb并按照提示一步步输入字符串,直到 ...

最新文章

  1. xshell连接不了服务器显示22端口,win10安装redis,xshell无法连接22端口
  2. (NO.00002)iOS游戏精灵战争雏形(六)
  3. linux内核dma内存分配,Linux 4.x 内核空间 DMA 虚拟内存地址
  4. (第2部分,共3部分):有关性能调优,Java中的JVM,GC,Mechanical Sympathy等的文章和视频的摘要...
  5. spring框架(六)之拦截器
  6. PAT学习资料汇总(PAT甲级、PAT顶级、PAT考试经验)
  7. 【Mac】Mac下安装MySQL优化工具 percona-toolkit 报错 Error: Failed to download resource openssl@1.1
  8. 人工智能专业就业有哪些岗位方向
  9. 一种雷达和电子海图图像叠加方法
  10. charles破解版_Charles抓包工具_charles mac\win7版
  11. PointNet解读
  12. 2021迅雷web实习生面试经验
  13. 百度脑图中如果想输入换行符本身(\n)怎么输入
  14. 华硕电脑锁定计算机,华硕笔记本电脑的BIOS怎么设置
  15. ubuntu mate 18.04官网下载,烧录及安装 SSH VNC ROS MAVROS librealsense realsense-ros vision_to_mavros(我自己亲自弄的)
  16. 网络带宽单位转换 — MB/s、Mb/s、Mbps、Mbit/s、Kbps
  17. 电脑上录屏的软件有哪些,屏幕录制软件哪个好用
  18. 蚂蚁金服开源背后的“有意思”工程师 | 1024快乐
  19. java 自己实现 解析处理user-agent 获取设备信息 ip-ua转化归因
  20. 狗看了都流泪的Mask-RCNN

热门文章

  1. 翌加:抖音账号想要更多关注和粉丝要做好背景图
  2. jquery validation engine ajax,validationEngine ajax验证 java
  3. 一个外贸经理的分享:7个找客户的方法和思路
  4. 视频教程-桫哥-GOlang-09反射-Go语言
  5. 信息论(熵、条件熵、联合熵、互信息)
  6. dnn降噪_万魔降噪双旗舰耳机分享:顺带聊聊如何挑选一款无线耳机
  7. css3动画案例—太阳大海跳动
  8. Excel - VBA基础应用
  9. OSChina 周四乱弹 —— 二次元世界很不安全
  10. MatLab中画树状图方法treeplot(nodes)中描述树结构的矢量nodes的构造