csapp - bomb lab


环境

操作系统:ubuntu 12.04(32bit)
gcc版本:4.6.3
实验时间:2019/4/10 - 2019/4/11
bomb lab版本:csapp 原书第2版
@author:姬小野

使用方法

1、 使用命令 objdump -d bomb > bomb.s,获取可执行文件bomb的汇编源码,同时重定向输出到文件bomb.s中。
即可在文件中方便地查看汇编代码
在这里插入图片描述

2、 阅读官方文档,得知可以将输入写入到文件中,就不用每次拆炸弹都需要自己从头开始手动输入。

在这里插入图片描述


phase 1

汇编代码分析和注释如下

08048b50 <phase_1>:
 8048b50:   83 ec 1c                sub    $0x1c,%esp
 8048b53:   c7 44 24 04 bc a1 04    movl   $0x804a1bc,0x4(%esp) # 为什么这个奇怪的地址入栈?gdb打印,发现一个字符串
 8048b5a:   08 
 8048b5b:   8b 44 24 20             mov    0x20(%esp),%eax
 8048b5f:   89 04 24                mov    %eax,(%esp)
 8048b62:   e8 8d 04 00 00          call   8048ff4 <strings_not_equal> # 判断字符串是否相等
 8048b67:   85 c0                   test   %eax,%eax # 和jz配合检查%eax是否为0,为0跳。test,执行与运算。
 8048b69:   74 05                   je     8048b70 <phase_1+0x20> # je就是jz
 8048b6b:   e8 96 05 00 00          call   8049106 <explode_bomb>
 8048b70:   83 c4 1c                add    $0x1c,%esp # 返回了,栈指针指回帧指针处
 8048b73:   c3                      ret    

使用gdb工具打印地址0x804a1bc处的值。
得到如下字符串,猜测是phase1的密码。
(gdb) x/s 0x804a1bc
0x804a1bc: “We have to stand with our North Korean allies.”


phase 2

汇编代码分析和注释如下

08048b74 <phase_2>: 
 8048b74:   53                      push   %ebx
 8048b75:   83 ec 38                sub    $0x38,%esp # 为局部变量划分空间。栈向低地址方向增长,所以用sub
 8048b78:   8d 44 24 18             lea    0x18(%esp),%eax
 8048b7c:   89 44 24 04             mov    %eax,0x4(%esp)
 8048b80:   8b 44 24 40             mov    0x40(%esp),%eax
 8048b84:   89 04 24                mov    %eax,(%esp)
 8048b87:   e8 af 06 00 00          call   804923b <read_six_numbers> # 读取6个数字

 8048b8c:   83 7c 24 18 00          cmpl   $0x0,0x18(%esp) # 判断第一个数字是否为0,如果不是bomb爆炸。bingo
 8048b91:   79 05                   jns    8048b98 <phase_2+0x24>
 8048b93:   e8 6e 05 00 00          call   8049106 <explode_bomb>

 8048b98:   bb 01 00 00 00          mov    $0x1,%ebx # for循环的开始,设置i(%ebx)为1

 8048b9d:   89 d8                   mov    %ebx,%eax # %eax是一个临时值,对它进行操作后,和下一个数字进行比较。
 8048b9f:   03 44 9c 14             add    0x14(%esp,%ebx,4),%eax # 上一个数字加到%eax上
 8048ba3:   39 44 9c 18             cmp    %eax,0x18(%esp,%ebx,4) # 比较%eax和当前数字
 8048ba7:   74 05                   je     8048bae <phase_2+0x3a> # 判断与爆炸
 8048ba9:   e8 58 05 00 00          call   8049106 <explode_bomb>
 8048bae:   83 c3 01                add    $0x1,%ebx # 相当于i++
 8048bb1:   83 fb 06                cmp    $0x6,%ebx # 相当于i != 6
 8048bb4:   75 e7                   jne    8048b9d <phase_2+0x29>

 8048bb6:   83 c4 38                add    $0x38,%esp # 将栈指针移到帧指针处,意味着函数要返回了。这两句相当于leave
 8048bb9:   5b                      pop    %ebx
 8048bba:   c3                      ret    

关键代码(for循环部分)如下

int x = 0, i;
for (i = 1; i != 6; i++) {
    x += i;
    if (input[i] != x) bomb();
}

第一个数字是0
第二个数字为0 + 1 = 1
第三个数字为1 + 2 = 3
第四个数字为3 + 3 = 6
第五个数字为6 + 4 = 10
第六个数字为10 + 5 = 15
结果为 0 1 3 6 10 15


phase 3

汇编代码分析与注释如下

08048bbb <phase_3>:
 8048bbb:   83 ec 2c                sub    $0x2c,%esp
 8048bbe:   8d 44 24 1c             lea    0x1c(%esp),%eax
 8048bc2:   89 44 24 0c             mov    %eax,0xc(%esp)
 8048bc6:   8d 44 24 18             lea    0x18(%esp),%eax
 8048bca:   89 44 24 08             mov    %eax,0x8(%esp)
 8048bce:   c7 44 24 04 af a3 04    movl   $0x804a3af,0x4(%esp) # "%d %d",两个数字?
 8048bd5:   08 
 8048bd6:   8b 44 24 30             mov    0x30(%esp),%eax
 8048bda:   89 04 24                mov    %eax,(%esp)
 8048bdd:   e8 8e fc ff ff          call   8048870 <__isoc99_sscanf@plt> # 应该是输入
 8048be2:   83 f8 01                cmp    $0x1,%eax # 判断scanf输入的值得数量是否大于1
 8048be5:   7f 05                   jg     8048bec <phase_3+0x31> # 大于,到8048bec,否则爆炸
 8048be7:   e8 1a 05 00 00          call   8049106 <explode_bomb>
 8048bec:   83 7c 24 18 07          cmpl   $0x7,0x18(%esp)
 8048bf1:   77 66                   ja     8048c59 <phase_3+0x9e> # 大于u,到8048c59,即爆炸

 8048bf3:   8b 44 24 18             mov    0x18(%esp),%eax # switch 的判断值
 8048bf7:   ff 24 85 1c a2 04 08    jmp    *0x804a21c(,%eax,4) # 跳转表

 8048bfe:   b8 00 00 00 00          mov    $0x0,%eax
 8048c03:   eb 05                   jmp    8048c0a <phase_3+0x4f> # 到8048c0a
 8048c05:   b8 0d 03 00 00          mov    $0x30d,%eax
 8048c0a:   2d 0a 01 00 00          sub    $0x10a,%eax
 8048c0f:   eb 05                   jmp    8048c16 <phase_3+0x5b> # 到8048c16
 8048c11:   b8 00 00 00 00          mov    $0x0,%eax
 8048c16:   05 4e 01 00 00          add    $0x14e,%eax
 8048c1b:   eb 05                   jmp    8048c22 <phase_3+0x67> # 到8048c22
 8048c1d:   b8 00 00 00 00          mov    $0x0,%eax
 8048c22:   2d 94 00 00 00          sub    $0x94,%eax
 8048c27:   eb 05                   jmp    8048c2e <phase_3+0x73> # 到8048c2e
 8048c29:   b8 00 00 00 00          mov    $0x0,%eax
 8048c2e:   05 94 00 00 00          add    $0x94,%eax
 8048c33:   eb 05                   jmp    8048c3a <phase_3+0x7f> # 到8048c3a
 8048c35:   b8 00 00 00 00          mov    $0x0,%eax
 8048c3a:   2d 94 00 00 00          sub    $0x94,%eax
 8048c3f:   eb 05                   jmp    8048c46 <phase_3+0x8b> # 到8048c46
 8048c41:   b8 00 00 00 00          mov    $0x0,%eax
 8048c46:   05 94 00 00 00          add    $0x94,%eax
 8048c4b:   eb 05                   jmp    8048c52 <phase_3+0x97> # 到8048c52
 8048c4d:   b8 00 00 00 00          mov    $0x0,%eax
 8048c52:   2d 94 00 00 00          sub    $0x94,%eax
 8048c57:   eb 0a                   jmp    8048c63 <phase_3+0xa8> # 到8048c63
 8048c59:   e8 a8 04 00 00          call   8049106 <explode_bomb>
 8048c5e:   b8 00 00 00 00          mov    $0x0,%eax
 8048c63:   83 7c 24 18 05          cmpl   $0x5,0x18(%esp)
 8048c68:   7f 06                   jg     8048c70 <phase_3+0xb5> # 到8048c70,即爆炸
 8048c6a:   3b 44 24 1c             cmp    0x1c(%esp),%eax # 输入的第二个数?
 8048c6e:   74 05                   je     8048c75 <phase_3+0xba> # 到8048c75,成功过关
 8048c70:   e8 91 04 00 00          call   8049106 <explode_bomb>
 8048c75:   83 c4 2c                add    $0x2c,%esp
 8048c78:   c3                      ret    

分析如下:

看这两行,发现地址0x804a3af有点怪,在gdb中打印,发现它的值是"%d %d",说明输入是两个数字

8048bce:    c7 44 24 04 af a3 04    movl   $0x804a3af,0x4(%esp) # "%d %d",两个数字?
8048bdd:    e8 8e fc ff ff          call   8048870 <__isoc99_sscanf@plt> # 调用的函数为scanf

判断scanf输入的值的数量,如果小于等于1,则爆炸

8048be2:    83 f8 01                cmp    $0x1,%eax # case 值从2开始
8048be5:    7f 05                   jg     8048bec <phase_3+0x31> # 大于,到8048bec, 否则爆炸

发现如果输入的第一个参数大于7,那么也爆炸

8048bec:    83 7c 24 18 07          cmpl   $0x7,0x18(%esp)
8048bf1:    77 66                   ja     8048c59 <phase_3+0x9e> # 大于u,到8048c59,即爆炸

参数1范围在 0 <= arg1 <= 7 内。

接下来就是执行switch的跳转表

8048bf3:    8b 44 24 18             mov    0x18(%esp),%eax # switch 的判断值
8048bf7:    ff 24 85 1c a2 04 08    jmp    *0x804a21c(,%eax,4) # 跳转表

下面的就是几条case语句了。
分析switch结束之后的语句,发现arg1 > 5的话,爆炸

8048c63:    83 7c 24 18 05          cmpl   $0x5,0x18(%esp)
8048c68:    7f 06                   jg     8048c70 <phase_3+0xb5> # 到8048c70,即爆炸

所以arg1的范围为[0, 5]。

case一共有8组,case 0 ~ case 7

case 0: a = 0;
case 1: a -= 10a;
case 2: a += 14e;
case 3: a -= 94;
case 4: a += 94;
case 5: a -= 94;
case 6: a += 94;
case 7: a -= 94;

在执行之前,a = 0

经测试,有六组可行的数据。分别是:

0 701
1 -80
2 186
3 -148
4 0
5 -148

phase 4

汇编代码分析与注释如下

08048c79 <func4>:
 8048c79:   83 ec 1c                sub    $0x1c,%esp
 8048c7c:   89 5c 24 10             mov    %ebx,0x10(%esp)
 8048c80:   89 74 24 14             mov    %esi,0x14(%esp)
 8048c84:   89 7c 24 18             mov    %edi,0x18(%esp)
 8048c88:   8b 74 24 20             mov    0x20(%esp),%esi # 6
 8048c8c:   8b 5c 24 24             mov    0x24(%esp),%ebx # 2
 8048c90:   85 f6                   test   %esi,%esi # 判断是否为0!也是基例!!!
 8048c92:   7e 2b                   jle    8048cbf <func4+0x46> # 结果小于等于0则跳转!
 8048c94:   83 fe 01                cmp    $0x1,%esi
 8048c97:   74 2b                   je     8048cc4 <func4+0x4b> # 递归基例,等于1!!!
 8048c99:   89 5c 24 04             mov    %ebx,0x4(%esp)
 8048c9d:   8d 46 ff                lea    -0x1(%esi),%eax
 8048ca0:   89 04 24                mov    %eax,(%esp)
 8048ca3:   e8 d1 ff ff ff          call   8048c79 <func4> # 第一个递归
 8048ca8:   8d 3c 18                lea    (%eax,%ebx,1),%edi
 8048cab:   89 5c 24 04             mov    %ebx,0x4(%esp)
 8048caf:   83 ee 02                sub    $0x2,%esi
 8048cb2:   89 34 24                mov    %esi,(%esp)
 8048cb5:   e8 bf ff ff ff          call   8048c79 <func4> # 第二个递归,返回值是%eax?
 8048cba:   8d 1c 07                lea    (%edi,%eax,1),%ebx
 8048cbd:   eb 05                   jmp    8048cc4 <func4+0x4b>
 8048cbf:   bb 00 00 00 00          mov    $0x0,%ebx
 8048cc4:   89 d8                   mov    %ebx,%eax
 8048cc6:   8b 5c 24 10             mov    0x10(%esp),%ebx
 8048cca:   8b 74 24 14             mov    0x14(%esp),%esi
 8048cce:   8b 7c 24 18             mov    0x18(%esp),%edi
 8048cd2:   83 c4 1c                add    $0x1c,%esp
 8048cd5:   c3                      ret    


08048cd6 <phase_4>:
 8048cd6:   83 ec 2c                sub    $0x2c,%esp
 8048cd9:   8d 44 24 18             lea    0x18(%esp),%eax
 8048cdd:   89 44 24 0c             mov    %eax,0xc(%esp)
 8048ce1:   8d 44 24 1c             lea    0x1c(%esp),%eax
 8048ce5:   89 44 24 08             mov    %eax,0x8(%esp)
 8048ce9:   c7 44 24 04 af a3 04    movl   $0x804a3af,0x4(%esp) # 同样,是"%d %d"
 8048cf0:   08 
 8048cf1:   8b 44 24 30             mov    0x30(%esp),%eax
 8048cf5:   89 04 24                mov    %eax,(%esp)
 8048cf8:   e8 73 fb ff ff          call   8048870 <__isoc99_sscanf@plt> # scanf,截止目前,和上面完全一样
 8048cfd:   83 f8 02                cmp    $0x2,%eax
 8048d00:   75 0e                   jne    8048d10 <phase_4+0x3a> # 判断是否输入两个参数
 8048d02:   8b 44 24 18             mov    0x18(%esp),%eax # 第一个参数
 8048d06:   83 f8 01                cmp    $0x1,%eax
 8048d09:   7e 05                   jle    8048d10 <phase_4+0x3a> # arg2 >= 2
 8048d0b:   83 f8 04                cmp    $0x4,%eax
 8048d0e:   7e 05                   jle    8048d15 <phase_4+0x3f> # arg2 <= 4
 8048d10:   e8 f1 03 00 00          call   8049106 <explode_bomb>
 8048d15:   8b 44 24 18             mov    0x18(%esp),%eax # arg1赋值给%eax
 8048d19:   89 44 24 04             mov    %eax,0x4(%esp) # func的参数2
 8048d1d:   c7 04 24 06 00 00 00    movl   $0x6,(%esp) # func的参数1,越早的参数越小!!!
 8048d24:   e8 50 ff ff ff          call   8048c79 <func4> # 调用func4了
 8048d29:   3b 44 24 1c             cmp    0x1c(%esp),%eax # func4返回的结果(%eax是返回值),判断是否和参数2相等
 8048d2d:   74 05                   je     8048d34 <phase_4+0x5e> # 判断结果,相等就OK,否则爆炸
 8048d2f:   e8 d2 03 00 00          call   8049106 <explode_bomb>
 8048d34:   83 c4 2c                add    $0x2c,%esp
 8048d37:   c3                      ret    

phase 4的过程是输入两个数(y, x),然后调用一个函数func4(x, 6),判断y 是否等于这个函数的返回值。如果不是,则爆炸。
根据下面四行代码可知,输入x 的取值范围为[2, 4]。

8048d06:   83 f8 01                cmp    $0x1,%eax
 8048d09:   7e 05                   jle    8048d10 <phase_4+0x3a> # arg2 >= 2
 8048d0b:   83 f8 04                cmp    $0x4,%eax
 8048d0e:   7e 05                   jle    8048d15 <phase_4+0x3f> # arg2 <= 4

以x = 2做讨论,下面的语句是执行func(6, 2)。结果是多少呢?

8048d15:   8b 44 24 18             mov    0x18(%esp),%eax # arg1赋值给%eax
 8048d19:   89 44 24 04             mov    %eax,0x4(%esp) # func的参数2
 8048d1d:   c7 04 24 06 00 00 00    movl   $0x6,(%esp) # func的参数1,越早的参数越小!!!
 8048d24:   e8 50 ff ff ff          call   8048c79 <func4> # 调用func4了

分析func函数,发现它是一个递归函数。它有两个递归基例,和两个可能的自身调用。

int func4(int b, int a) {
    if (b <= 0) return 0;
    if (b == 1) return a;
    return func4(a, b - 1) + a + func4(a, b - 2);
}

递归基例:

8048c90:   85 f6                   test   %esi,%esi # 判断是否为0!也是基例!!!
 8048c92:   7e 2b                   jle    8048cbf <func4+0x46> # 结果小于等于0则跳转!
 8048c94:   83 fe 01                cmp    $0x1,%esi
 8048c97:   74 2b                   je     8048cc4 <func4+0x4b> # 递归基例,等于1!!!

递归调用

8048ca3:   e8 d1 ff ff ff          call   8048c79 <func4> # 第一个递归调用
8048cb5:   e8 bf ff ff ff          call   8048c79 <func4> # 第二个递归调用,返回值是%eax

根据上面的分析不难得出三种解,他们都可以通过关卡4.

40 2
60 3
80 4

phase 5

汇编代码分析与注释如下:

08048d38 <phase_5>:
 8048d38:   53                      push   %ebx
 8048d39:   83 ec 28                sub    $0x28,%esp
 8048d3c:   8b 5c 24 30             mov    0x30(%esp),%ebx
 8048d40:   65 a1 14 00 00 00       mov    %gs:0x14,%eax
 8048d46:   89 44 24 1c             mov    %eax,0x1c(%esp)
 8048d4a:   31 c0                   xor    %eax,%eax
 8048d4c:   89 1c 24                mov    %ebx,(%esp) # 数组开始?
 8048d4f:   e8 87 02 00 00          call   8048fdb <string_length> # 计算长度,写入到%eax里面了
 8048d54:   83 f8 06                cmp    $0x6,%eax
 8048d57:   74 05                   je     8048d5e <phase_5+0x26> # 字符串长度为6
 8048d59:   e8 a8 03 00 00          call   8049106 <explode_bomb>

 8048d5e:   b8 00 00 00 00          mov    $0x0,%eax # i = 0
 8048d63:   0f be 14 03             movsbl (%ebx,%eax,1),%edx # 符号扩展双字。%ebx的值是什么?
 8048d67:   83 e2 0f                and    $0xf,%edx
 8048d6a:   0f b6 92 3c a2 04 08    movzbl 0x804a23c(%edx),%edx
 8048d71:   88 54 04 15             mov    %dl,0x15(%esp,%eax,1) # 替换了读入的字符?
 8048d75:   83 c0 01                add    $0x1,%eax # i++
 8048d78:   83 f8 06                cmp    $0x6,%eax
 8048d7b:   75 e6                   jne    8048d63 <phase_5+0x2b> # 循环,0 <= i < 6

 8048d7d:   c6 44 24 1b 00          movb   $0x0,0x1b(%esp)
 8048d82:   c7 44 24 04 12 a2 04    movl   $0x804a212,0x4(%esp) # oilers,刚好6个!
 8048d89:   08 
 8048d8a:   8d 44 24 15             lea    0x15(%esp),%eax
 8048d8e:   89 04 24                mov    %eax,(%esp) # 传递字符数组!数组,所以是地址?!
 8048d91:   e8 5e 02 00 00          call   8048ff4 <strings_not_equal>
 8048d96:   85 c0                   test   %eax,%eax
 8048d98:   74 05                   je     8048d9f <phase_5+0x67> # 判断字符是否相等,不相等则爆炸
 8048d9a:   e8 67 03 00 00          call   8049106 <explode_bomb>
 8048d9f:   8b 44 24 1c             mov    0x1c(%esp),%eax
 8048da3:   65 33 05 14 00 00 00    xor    %gs:0x14,%eax
 8048daa:   74 09                   je     8048db5 <phase_5+0x7d>
 8048dac:   8d 74 26 00             lea    0x0(%esi,%eiz,1),%esi
 8048db0:   e8 1b fa ff ff          call   80487d0 <__stack_chk_fail@plt> # 栈检查
 8048db5:   83 c4 28                add    $0x28,%esp
 8048db8:   5b                      pop    %ebx
 8048db9:   c3                      ret   

这是一道字符串的题目,提交答案正确的时候还有点小惊讶,没想到自己的第一个猜测就正确了!

首先是判断输入的字符串的长度,限制在6.

8048d4f:    e8 87 02 00 00          call   8048fdb <string_length> # 计算长度,写入到%eax里面了
8048d54:    83 f8 06                cmp    $0x6,%eax
8048d57:    74 05                   je     8048d5e <phase_5+0x26> # 字符串长度为6

接下来的那一大段就是一个i = 0 到 i < 6 的for循环

在循环中使用了我们读取的字符串。这个循环做了什么工作呢?先看看循环体之后的一段,这是一个字符串比较过程。比较的两个字符串是0x15(%esp)和地址0x804a212中的字符串。使用gdb工具,查看0x804a212处的值,发现是oilers。0x15(%esp)是我们输入的字符串,那么这个判断是不是说明我们输入的字符串应该是oilers呢?没这么简单!

 8048d8a:   8d 44 24 15             lea    0x15(%esp),%eax
 8048d8e:   89 04 24                mov    %eax,(%esp) # 传递字符数组!数组,所以是地址?!
 8048d91:   e8 5e 02 00 00          call   8048ff4 <strings_not_equal>
 8048d96:   85 c0                   test   %eax,%eax
 8048d98:   74 05                   je     8048d9f <phase_5+0x67> # 判断字符是否相等,不相等则爆炸

观察for循环,我发现,0x15(%esp)处的值不再是原生的输入数据了,而是经过了更改的。如何更改?这就是解密的关键过程。

 8048d5e:   b8 00 00 00 00          mov    $0x0,%eax # i = 0
 8048d63:   0f be 14 03             movsbl (%ebx,%eax,1),%edx # 符号扩展双字。%ebx的值是什么?
 8048d67:   83 e2 0f                and    $0xf,%edx
 8048d6a:   0f b6 92 3c a2 04 08    movzbl 0x804a23c(%edx),%edx
 8048d71:   88 54 04 15             mov    %dl,0x15(%esp,%eax,1) # 替换了读入的字符?
 8048d75:   83 c0 01                add    $0x1,%eax # i++
 8048d78:   83 f8 06                cmp    $0x6,%eax
 8048d7b:   75 e6                   jne    8048d63 <phase_5+0x2b> # 循环,0 <= i < 6

假设读入a[6].i = 0 时,对a[0],首先将它与0xf相与,得到的值作为偏移量,和0x804a23c地址相加得到一个新的地址,传递给%edx,然后再把低八位传递给a[0]。也就是说,原先的a[0]是用来生成地址的,生成了地址,这个地址的值在哪里?我们使用gdb查看0x804a23c。

(gdb) x/s 0x804a23c
0x804a23c <array.2954>:  "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"

发现它是一个字符串(字符数组),它里面0-15位置的字符,就是我们解密的素材了。逆向的过程是,我们要找到oilers,找到每个字符的下表,如o的下标10,i的下标4,l(15),e(5),r(6),s(7)。
这串密码10-4-15-5-6-7是原生字符串和0xf相与之后得到的,那么什么字符串可以得到这串密码呢?一种可能的解是ASCII值为10-4-15-5-6-7的字符串。但是它是多解的,因为与0xf相与截取的是低四位,所以高四位无论是什么都没关系。为了输入方便,我把它们调整到了a-z区间,每个字符加(01100000)2,也就是96. 得到的这组可行解为 jdoefg 。第五关也顺利通过了。


phase 6

汇编代码分析与注释如下

08048dba <phase_6>:
 8048dba:   56                      push   %esi
 8048dbb:   53                      push   %ebx
 8048dbc:   83 ec 44                sub    $0x44,%esp
 8048dbf:   8d 44 24 10             lea    0x10(%esp),%eax
 8048dc3:   89 44 24 04             mov    %eax,0x4(%esp)
 8048dc7:   8b 44 24 50             mov    0x50(%esp),%eax
 8048dcb:   89 04 24                mov    %eax,(%esp)
 8048dce:   e8 68 04 00 00          call   804923b <read_six_numbers> # 同样是六个数字
 8048dd3:   be 00 00 00 00          mov    $0x0,%esi # int i = 0。恐怕是while循环
 8048dd8:   8b 44 b4 10             mov    0x10(%esp,%esi,4),%eax # 第一个数值!
 8048ddc:   83 e8 01                sub    $0x1,%eax # 如果%eax初始值为7,那么减1后会爆炸.应<=6且>=1
 8048ddf:   83 f8 05                cmp    $0x5,%eax # 需要小于等于5
 8048de2:   76 05                   jbe    8048de9 <phase_6+0x2f> # 小于等于跳转,无符号!
 8048de4:   e8 1d 03 00 00          call   8049106 <explode_bomb>
 8048de9:   83 c6 01                add    $0x1,%esi # i++
 8048dec:   83 fe 06                cmp    $0x6,%esi # i < 6
 8048def:   74 33                   je     8048e24 <phase_6+0x6a> # 跳转到很下面,下面循环继续?while循环吧,break
 8048df1:   89 f3                   mov    %esi,%ebx
 8048df3:   8b 44 9c 10             mov    0x10(%esp,%ebx,4),%eax
 8048df7:   39 44 b4 0c             cmp    %eax,0xc(%esp,%esi,4) # 上一个数字!因为i++了,然后0xc比0x10少4
 8048dfb:   75 05                   jne    8048e02 <phase_6+0x48> # 如果上一个数字和这个数字不相等,跳转,否则爆炸
 8048dfd:   e8 04 03 00 00          call   8049106 <explode_bomb>
 8048e02:   83 c3 01                add    $0x1,%ebx # ++
 8048e05:   83 fb 05                cmp    $0x5,%ebx # 和5比较
 8048e08:   7e e9                   jle    8048df3 <phase_6+0x39> # 小于就进入循环。双重循环do while
 8048e0a:   eb cc                   jmp    8048dd8 <phase_6+0x1e> # soga,大循环跳转

 8048e0c:   8b 52 08                mov    0x8(%edx),%edx # 段一,这是一个结构体的指针。数值、编号、下一地址(+8)。 
 8048e0f:   83 c0 01                add    $0x1,%eax # 2,
 8048e12:   39 c8                   cmp    %ecx,%eax
 8048e14:   75 f6                   jne    8048e0c <phase_6+0x52>
 8048e16:   89 54 b4 28             mov    %edx,0x28(%esp,%esi,4) # 段二。读入的6个数之后的存储区,存结构体的地址。
 8048e1a:   83 c3 01                add    $0x1,%ebx
 8048e1d:   83 fb 06                cmp    $0x6,%ebx
 8048e20:   75 07                   jne    8048e29 <phase_6+0x6f>
 8048e22:   eb 1c                   jmp    8048e40 <phase_6+0x86>

 8048e24:   bb 00 00 00 00          mov    $0x0,%ebx # 0
 8048e29:   89 de                   mov    %ebx,%esi # 0
 8048e2b:   8b 4c 9c 10             mov    0x10(%esp,%ebx,4),%ecx # a[0]
 8048e2f:   b8 01 00 00 00          mov    $0x1,%eax # eax = 1
 8048e34:   ba 3c c1 04 08          mov    $0x804c13c,%edx 
 8048e39:   83 f9 01                cmp    $0x1,%ecx # 判断a[0] > 1?
 8048e3c:   7f ce                   jg     8048e0c <phase_6+0x52> # 如果大于跳转到段一,上面
 8048e3e:   eb d6                   jmp    8048e16 <phase_6+0x5c> # 否则跳转到段二,上面

 8048e40:   8b 5c 24 28             mov    0x28(%esp),%ebx # 第一个数据的地址
 8048e44:   8b 44 24 2c             mov    0x2c(%esp),%eax # 第二个数据的地址
 8048e48:   89 43 08                mov    %eax,0x8(%ebx)
 8048e4b:   8b 54 24 30             mov    0x30(%esp),%edx
 8048e4f:   89 50 08                mov    %edx,0x8(%eax)
 8048e52:   8b 44 24 34             mov    0x34(%esp),%eax
 8048e56:   89 42 08                mov    %eax,0x8(%edx)
 8048e59:   8b 54 24 38             mov    0x38(%esp),%edx
 8048e5d:   89 50 08                mov    %edx,0x8(%eax)
 8048e60:   8b 44 24 3c             mov    0x3c(%esp),%eax
 8048e64:   89 42 08                mov    %eax,0x8(%edx)
 8048e67:   c7 40 08 00 00 00 00    movl   $0x0,0x8(%eax)

 8048e6e:   be 05 00 00 00          mov    $0x5,%esi # i = 5,循环从5开始到1. # 这个循环判断p->val大于p->next->val
 8048e73:   8b 43 08                mov    0x8(%ebx),%eax # 下面三条,这时相互影响,类似递归调用啊。
 8048e76:   8b 10                   mov    (%eax),%edx
 8048e78:   39 13                   cmp    %edx,(%ebx)
 8048e7a:   7d 05                   jge    8048e81 <phase_6+0xc7> # 这是关键了
 8048e7c:   e8 85 02 00 00          call   8049106 <explode_bomb>
 8048e81:   8b 5b 08                mov    0x8(%ebx),%ebx
 8048e84:   83 ee 01                sub    $0x1,%esi
 8048e87:   75 ea                   jne    8048e73 <phase_6+0xb9> # ZF为0时跳转,ZF是最近操作得出的结果为0,ZF=1。也就是esi = 1 - 1 = 0时,ZF = 1,不跳转。所以可以作为一个循环,当 i >= 1时,循环继续。
 8048e89:   83 c4 44                add    $0x44,%esp
 8048e8c:   5b                      pop    %ebx
 8048e8d:   5e                      pop    %esi
 8048e8e:   c3                      ret    

第一大段是一个双重循环用来判断数组a中是否有重复的数据,并且a中元素的值限制在 1 <= a[i] <= 6,因为做了无符号跳转,所以负数不行!那么这些数据就是[1, 6]的一个排列,刚刚好!

第二大段是根据读入的六个数字,将结构体的地址按顺序添加到栈中,根据后面的分析我们可以知道我们的目的是根据输入数据将链表元素重新排列,使得它从大到小排列。
结构体包含三个数据:val、id、下一元素地址。因此它是一个链表。

用GDB分析地址0x804c13c

(gdb) x/18x 0x804c13c
0x804c13c <node1>:  0x00000148  0x00000001  0x0804c148  0x00000218
0x804c14c <node2+4>:    0x00000002  0x0804c154  0x00000259  0x00000003
0x804c15c <node3+8>:    0x0804c160  0x0000012f  0x00000004  0x0804c16c
0x804c16c <node5>:  0x00000152  0x00000005  0x0804c178  0x000002ab
0x804c17c <node6+4>:    0x00000006  0x00000000

第三大段是根据我们添加的结构体的地址的顺序,将链表重新映射。与第二段结合就是,将链表元素按照输入的数据的顺序排列好。比如我们输入6 3 2 5 1 4,它的意思就是将原先排在第6的元素排在第一个,将原先排在第1的元素排在第5个。第二大段是整理元素地址顺序,第三大段是通过这个顺序修改元素地址。从而实现链表的排序。

第四大段的功能是,从排列后的第一个元素开始,以指向下一元素地址的方式遍历整个链表,同时比较当前元素和下一元素的值。因此它是一个循环量为5的一重循环。且,如果出现当前元素小于下一元素的情况,将会爆照。这里要求排列好的顺序是非递增序的。

因此,在了解了第4大段的功能之后,我们可以逆向地推理出应该输入的六个数据。也就是 6 3 2 5 1 4


secret phase

从函数phase_defused的汇编代码中可以发现隐藏关卡的入口。
打印地址0x804a3be的值,发现它是DrEvil(邪恶博士?哈哈)

在这里插入图片描述
在关卡四的密码后面加上DrEvil就可以在最后(六关通关后)进入隐藏关卡了。
在这里插入图片描述
在这里插入图片描述
从下面的条件判断,可初步看到输入数据的范围
在这里插入图片描述
接下来调用函数fun7,发现它有两个参数,第一个参数是一个地址(指针),第二个参数是我们输入的数据。
最后执行fun7返回的结果需要是4 。否则就会爆炸。
在这里插入图片描述
那么进入fun7里面分析,它有三种情况fun7(int a, int b):
1、a == b 时,return 0
2、a < b 时,return 2fun7(
(a+8), b) + 1;
3、a > b 时,return 2fun7(
(a+4), b);
那么它的函数大概是这样的

int fun7(int *a, int b) {
    if (*a == 0) return -1;
    if (*a == b) return 0;
    if (*a < b) {
        int c = fun7(*(a + 8), b);
        return 2*c + 1;
    } else {
        int c = fun7(*(a + 4), b);
        return 2*c;
    }
}

那么,要得到4

4 = 2*2
2 = 1*2
1 = 0*2 + 1
0 = 0

在这里插入图片描述
根据那样的结构,最后会得到输入的数据等于0x7,也就是7.

那么secret phase的结果就是7了


结果展示

an
swer 为输入文件。
在这里插入图片描述
程序结果
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值