CSAPP LAB2 bomb8

实验题目:

LAB2-炸弹实验(bomb8)

实验目的:

一共有七个关卡(一个隐藏关卡),我们需要通过汇编代码来找到七个对应的正确的密码(可能是数字,可能是字符串),并顺利通关。

实验环境:

Ubuntu12.04

实验内容及操作步骤:

一.第一关

08048b90 <phase_1>:

 8048b90:     83 ec 1c             sub    $0x1c,%esp

 8048b93:     c7 44 24 04 6c a1 04     movl   $0x804a16c,0x4(%esp)

 8048b9a:      08

 8048b9b:     8b 44 24 20          mov    0x20(%esp),%eax

 8048b9f:      89 04 24             mov    %eax,(%esp)

 8048ba2:      e8 a3 04 00 00         call   804904a <strings_not_equal>

 8048ba7:      85 c0                test   %eax,%eax

 8048ba9:      74 05                je     8048bb0 <phase_1+0x20>

 8048bab:      e8 a5 05 00 00         call   8049155 <explode_bomb>

 8048bb0:     83 c4 1c             add    $0x1c,%esp

 8048bb3:     c3                   ret   

分析:在观察汇编代码时,我们会看见有一串<explode_bomb>,代表发现炸弹。下面一行有$0x804a16c,是一个绝对地址,而在后面又有调用<strings_not_equal>,我们可以猜测正确答案就是$0x804a16c这个地址的内容。

实际操作时,我们可以在67处设置断点,之后运行进入第一关,通过x/s 0x804a16c来展示对应地址内容,最终可得到Wow!Brazil is big.

 

test命令可以将两个操作数进行逻辑与运算,并根据运算结果设置相关的标志位。在这里,test负责测试我们输入的内容是否为正确密码,如果是正确密码,下一行的 je可以跳转到8048bb0,即成功拆除炸弹;如果密码错误,那么炸弹就会爆炸。

 

二、第二关

08048bb4 <phase_2>:

 8048bb4:      56                  push   %esi

 8048bb5:      53                  push   %ebx

 8048bb6:      83 ec 34             sub    $0x34,%esp

 8048bb9:      8d 44 24 18           lea    0x18(%esp),%eax

 8048bbd:      89 44 24 04          mov    %eax,0x4(%esp)

 8048bc1:      8b 44 24 40          mov    0x40(%esp),%eax

 8048bc5:      89 04 24             mov    %eax,(%esp)

 8048bc8:      e8 af 05 00 00          call   804917c <read_six_numbers>

 8048bcd:      83 7c 24 18 01         cmpl   $0x1,0x18(%esp)

 8048bd2:      74 1e                je     8048bf2 <phase_2+0x3e>

 8048bd4:      e8 7c 05 00 00         call   8049155 <explode_bomb>

 8048bd9:      eb 17                jmp    8048bf2 <phase_2+0x3e>

 8048bdb:      8b 43 fc              mov    -0x4(%ebx),%eax

 8048bde:      01 c0                add    %eax,%eax

 8048be0:      39 03                cmp    %eax,(%ebx)

 8048be2:      74 05                je     8048be9 <phase_2+0x35>

 8048be4:      e8 6c 05 00 00         call   8049155 <explode_bomb>

 8048be9:      83 c3 04             add    $0x4,%ebx

 8048bec:      39 f3                cmp    %esi,%ebx

 8048bee:      75 eb                jne    8048bdb <phase_2+0x27>

 8048bf0:      eb 0a                jmp    8048bfc <phase_2+0x48>

 8048bf2:      8d 5c 24 1c           lea    0x1c(%esp),%ebx

 8048bf6:      8d 74 24 30          lea    0x30(%esp),%esi

 8048bfa:      eb df                jmp    8048bdb <phase_2+0x27>

 8048bfc:      83 c4 34             add    $0x34,%esp

 8048bff: 5b                        pop    %ebx

 8048c00:      5e                  pop    %esi

 8048c01:      c3                  ret   

分析:首先我们可以看到<read_six_numbers>,猜测密码是6个数。(这里有一个易错点,很容易会以为只需要6个数字,但是实际上却是6个数)。

紧接着有两条指令是cmpl $0x1,0x18(%esp)和je 8048bf2 <phase_2+0x3e>,通常%esp或其前面带数字,都是上一个函数调用所造成的,指上一个函数处理后输出的结果。而$0x1指的是它第一个数必须是1,那么我们可以推断出第一个数是1。

接下来的lea 0x1c(%esp),%ebx和mov -0x4(%ebx),%eax两条指令先把第二个数的地址存放进ebx,然后又把第一个数放进eax。

接着add %eax,%eax指令相当于把,eax中的数据乘2。cmp %eax,(%ebx),将eax和ebx的地址中的数据比较,也就是和输入的第2个数比较,如果不相等的话就会调用函数<explode_bomb>引爆炸弹;如果相等,那么ebx的地址加4,也就是指向下一个数,也就是第3个数。

cmp %esi,%ebx这条指令比较当前的地址是不是已经超过了我们输入的6个数的存储地址,如果超过了那就停止循环,如果没有,就跳转到mov -0x4(%ebx),%eax指令继续进行循环。这个循环内进行的操作就是将前一个数乘2和当前的数比较。

 因此如果不想引爆炸弹,我们输入的6个数中,后一个数必须是前一个数的2倍,并且第一个数是1,那么很容易就能得到了6个数分别是:1 2 4 8 16 32

 

三、第三关

8048c02:      83 ec 2c             sub    $0x2c,%esp

 8048c05:      8d 44 24 1c          lea    0x1c(%esp),%eax

 8048c09:      89 44 24 0c          mov    %eax,0xc(%esp)

 8048c0d:      8d 44 24 18          lea    0x18(%esp),%eax

 8048c11:      89 44 24 08          mov    %eax,0x8(%esp)

 8048c15:      c7 44 24 04 ef a2 04      movl   $0x804a2ef,0x4(%esp)

 8048c1c:      08

 8048c1d:      8b 44 24 30          mov    0x30(%esp),%eax

 8048c21:      89 04 24             mov    %eax,(%esp)

 8048c24:      e8 37 fc ff ff            call   8048860 <__isoc99_sscanf@plt> 输入

 8048c29:      83 f8 01             cmp    $0x1,%eax 至少两个数

 8048c2c:      7f 05                jg     8048c33 <phase_3+0x31> 否则会爆炸

 8048c2e:      e8 22 05 00 00         call   8049155 <explode_bomb>

 8048c33:      83 7c 24 18 07         cmpl   $0x7,0x18(%esp)

 8048c38:      77 66                ja     8048ca0 <phase_3+0x9e>

 8048c3a:      8b 44 24 18          mov    0x18(%esp),%eax

 8048c3e:      ff 24 85 88 a1 04 08      jmp    *0x804a188(,%eax,4)

 8048c45:      b8 00 00 00 00         mov    $0x0,%eax

 8048c4a:      eb 05                jmp    8048c51 <phase_3+0x4f>

 8048c4c:      b8 4a 02 00 00         mov    $0x24a,%eax

 8048c51:      2d f1 02 00 00         sub    $0x2f1,%eax

 8048c56:      eb 05                jmp    8048c5d <phase_3+0x5b>

 8048c58:      b8 00 00 00 00         mov    $0x0,%eax

 8048c5d:      05 e9 02 00 00         add    $0x2e9,%eax

 8048c62:      eb 05                jmp    8048c69 <phase_3+0x67>

 8048c64:      b8 00 00 00 00         mov    $0x0,%eax

 8048c69:      2d 2c 03 00 00         sub    $0x32c,%eax

 8048c6e:      eb 05                jmp    8048c75 <phase_3+0x73>

 8048c70:      b8 00 00 00 00         mov    $0x0,%eax

 8048c75:      05 2c 03 00 00         add    $0x32c,%eax

 8048c7a:      eb 05                jmp    8048c81 <phase_3+0x7f>

 8048c7c:      b8 00 00 00 00         mov    $0x0,%eax

 8048c81:      2d 2c 03 00 00         sub    $0x32c,%eax

 8048c86:      eb 05                jmp    8048c8d <phase_3+0x8b>

 8048c88:      b8 00 00 00 00         mov    $0x0,%eax

 8048c8d:      05 2c 03 00 00         add    $0x32c,%eax

 8048c92:      eb 05                jmp    8048c99 <phase_3+0x97>

 8048c94:      b8 00 00 00 00         mov    $0x0,%eax

 8048c99:      2d 2c 03 00 00         sub    $0x32c,%eax

 8048c9e:      eb 0a                jmp    8048caa <phase_3+0xa8>

 8048ca0:      e8 b0 04 00 00         call   8049155 <explode_bomb>

 8048ca5:      b8 00 00 00 00         mov    $0x0,%eax

 8048caa:      83 7c 24 18 05         cmpl   $0x5,0x18(%esp)

 8048caf:       7f 06                jg     8048cb7 <phase_3+0xb5>

8048cb1:      3b 44 24 1c          cmp    0x1c(%esp),%eax

 8048cb5:      74 05                je     8048cbc <phase_3+0xba>

 8048cb7:      e8 99 04 00 00         call   8049155 <explode_bomb>

 8048cbc:      83 c4 2c             add    $0x2c,%esp

 8048cbf:      90                  nop

 8048cc0:      c3                  ret   

在最开始的绝对地址中,我们可以看到movl $0x804a2ef,0x4(%esp),这里有着绝对地址0x804a2ef,经查询后发现内容为“%d %d”我们可以猜测密码是两个数字。

开头的地址传送部分都是给函数<__isoc99_sscanf@plt>传送参数,把我们输入的数据写入栈帧,函数在调用<__isoc99_sscanf@plt>函数后,接着函数调用了而cmp $0x1,%eax表明输入参数多于1个。这里也验证了我们的想法。

 

在之后的cmpl $0x7,0x18(%esp),我们可以得知输入的第一个数字不能大于7,否则会爆炸。

继续往下看,mov 0x18(%esp),%eax这句话我们输入的第一个数传给eax。然后看到jmp    *0x804a188(,%eax,4),这是switch跳转语句,即跳转到以地址*0x804a188为基址的跳转表中。我们可以查看这个跳转表中的地址元素。

 

我们可以看到一共有8个地址,对应我们第一个数字输入0-7。

比如第一个数字输入0,我们跳转到0x08048c4c,对应mov $0x24a,%eax、sub $0x2f1,%eax和jmp 8048c5d <phase_3+0x5b>。以及最后跳转到的代码指令cmp 0x1c(%esp),%eax也就是将我们运算得到的eax和我们输入的第2个数作比较,相等时才算成功。

因此第一个数字为0时,输入的第二个数也就是对应的运算后的-234

 

剩下7个也是ok的,这里不做赘述。

四、第四关

08048cc1 <func4>:

 8048cc1:      56                  push   %esi

 8048cc2:      53                  push   %ebx

 8048cc3:      83 ec 14             sub    $0x14,%esp

 8048cc6:      8b 54 24 20          mov    0x20(%esp),%edx

 8048cca:      8b 44 24 24          mov    0x24(%esp),%eax

 8048cce:      8b 5c 24 28          mov    0x28(%esp),%ebx

 8048cd2:      89 d9                mov    %ebx,%ecx

 8048cd4:      29 c1                sub    %eax,%ecx

 8048cd6:      89 ce                mov    %ecx,%esi

 8048cd8:      c1 ee 1f             shr    $0x1f,%esi  逻辑右移(不考虑符号位,全补0

 8048cdb:      01 f1                add    %esi,%ecx

 8048cdd:      d1 f9                sar    %ecx 算术右移(考虑符号位置)

 8048cdf:      01 c1                add    %eax,%ecx

 8048ce1:      39 d1                cmp    %edx,%ecx

 8048ce3:      7e 17                jle    8048cfc <func4+0x3b> 分界点,小于等于则跳

 8048ce5:      83 e9 01             sub    $0x1,%ecx

 8048ce8:      89 4c 24 08          mov    %ecx,0x8(%esp)

 8048cec:      89 44 24 04          mov    %eax,0x4(%esp)

 8048cf0:      89 14 24             mov    %edx,(%esp)

 8048cf3:      e8 c9 ff ff ff             call   8048cc1 <func4>

 8048cf8:      01 c0                add    %eax,%eax

 8048cfa:       eb 20                jmp    8048d1c <func4+0x5b>

 8048cfc:       b8 00 00 00 00         mov    $0x0,%eax

 8048d01:      39 d1                cmp    %edx,%ecx

 8048d03:      7d 17                jge    8048d1c <func4+0x5b>

 8048d05:      89 5c 24 08          mov    %ebx,0x8(%esp)

 8048d09:      83 c1 01             add    $0x1,%ecx

 8048d0c:      89 4c 24 04          mov    %ecx,0x4(%esp)

 8048d10:      89 14 24             mov    %edx,(%esp)

 8048d13:      e8 a9 ff ff ff             call   8048cc1 <func4> 自己调用自己,推测出递归

 8048d18:      8d 44 00 01          lea    0x1(%eax,%eax,1),%eax

 8048d1c:      83 c4 14             add    $0x14,%esp

 8048d1f:      5b                  pop    %ebx

 8048d20:      5e                  pop    %esi

 8048d21:      c3                  ret   

08048d22 <phase_4>:

 8048d22:      83 ec 2c             sub    $0x2c,%esp

 8048d25:      8d 44 24 1c          lea    0x1c(%esp),%eax

 8048d29:      89 44 24 0c          mov    %eax,0xc(%esp)

 8048d2d:      8d 44 24 18          lea    0x18(%esp),%eax

 8048d31:      89 44 24 08          mov    %eax,0x8(%esp)

 8048d35:      c7 44 24 04 ef a2 04      movl   $0x804a2ef,0x4(%esp)

 8048d3c:      08

 8048d3d:      8b 44 24 30          mov    0x30(%esp),%eax

 8048d41:      89 04 24             mov    %eax,(%esp)

 8048d44:      e8 17 fb ff ff            call   8048860 <__isoc99_sscanf@plt> 调用函数输入

 8048d49:      83 f8 02             cmp    $0x2,%eax 由此可知返回值要为2

 8048d4c:      75 07                jne    8048d55 <phase_4+0x33>

 8048d4e:      83 7c 24 18 0e         cmpl   $0xe,0x18(%esp) 比较是否小于等于14

 8048d53:      76 05                jbe    8048d5a <phase_4+0x38>

 8048d55:      e8 fb 03 00 00         call   8049155 <explode_bomb>

 8048d5a:      c7 44 24 08 0e 00 00     movl   $0xe,0x8(%esp)

 8048d61:      00

 8048d62:      c7 44 24 04 00 00 00     movl   $0x0,0x4(%esp)

 8048d69:      00

 8048d6a:      8b 44 24 18           mov    0x18(%esp),%eax

 8048d6e:      89 04 24             mov    %eax,(%esp) 到这里为止,设置参数14,0

 8048d71:      e8 4b ff ff ff             call   8048cc1 <func4> 按顺序输入 (:,014

 8048d76:      83 f8 06             cmp    $0x6,%eax 判断返回值是否为6

 8048d79:      75 07                jne    8048d82 <phase_4+0x60>

 8048d7b:      83 7c 24 1c 06         cmpl   $0x6,0x1c(%esp) 判断输入的第二个参数是否为6,不是则爆炸,所以可以确定第二个参数值为6

 8048d80:      74 05                je     8048d87 <phase_4+0x65>

 8048d82:      e8 ce 03 00 00         call   8049155 <explode_bomb>

 8048d87:      83 c4 2c             add    $0x2c,%esp

 8048d8a:      c3                  ret   

首先我们分析fun4,通过call 8048cc1 <func4> 自己调用自己,推测出递归。那么我们需要推测出递归函数。

 

在分析过程中,我们能发现对于func4(a1,a2,a3)a1在(%esp),a20x4%esp, a30x8%esp)。

回到主函数,我们发现$0x804a2ef赋值给了0x4(%esp),查询后发现显示“%d %d”,我们猜测答案是两个数字。

 

再通过cmpl $0xe,0x18(%esp),比较是否小于等于14,我们知道了输入的第一个数小于等于14。

movl   $0xe,0x8(%esp)movl $0x0,0x4(%esp)两句话,让我们知道了a2=0,a3=14。再根据主函数,写得:

 

再继续往下分析,cmp $0x6,%eax 判断返回值是否为6cmpl $0x6,0x1c(%esp)判断输入的第二个参数是否为6,不是则爆炸,所以可以确定第二个参数值需要为6。通过程序一个一个实验:

 

我们得到结果,第一个数为6时,第二个数也为6。

 

如上图,验证正确。

五、第五关

08048d8b <phase_5>:

 8048d8b:      53                  push   %ebx

 8048d8c:      83 ec 28             sub    $0x28,%esp

 8048d8f:      8b 5c 24 30          mov    0x30(%esp),%ebx

 8048d93:      65 a1 14 00 00 00       mov    %gs:0x14,%eax

 8048d99:      89 44 24 1c          mov    %eax,0x1c(%esp)

 8048d9d:      31 c0              xor    %eax,%eax 相当于把eax置0,这个比mov $0x0 %eax效率高。

 8048d9f:      89 1c 24             mov    %ebx,(%esp)

 8048da2:      e8 84 02 00 00         call   804902b <string_length>

 8048da7:      83 f8 06             cmp    $0x6,%eax

 8048daa:      74 46                je     8048df2 <phase_5+0x67>

 8048dac:      e8 a4 03 00 00         call   8049155 <explode_bomb>

 8048db1:      eb 3f                jmp    8048df2 <phase_5+0x67>

 8048db3:      0f b6 14 03          movzbl (%ebx,%eax,1),%edx

 8048db7:      83 e2 0f             and    $0xf,%edx

 8048dba:      0f b6 92 a8 a1 04 08     movzbl 0x804a1a8(%edx),%edx

 8048dc1:      88 54 04 15          mov    %dl,0x15(%esp,%eax,1)

 8048dc5:      83 c0 01             add    $0x1,%eax

 8048dc8:      83 f8 06             cmp    $0x6,%eax

 8048dcb:      75 e6                jne    8048db3 <phase_5+0x28>

 8048dcd:      c6 44 24 1b 00         movb   $0x0,0x1b(%esp)

 8048dd2:      c7 44 24 04 80 a1 04     movl   $0x804a180,0x4(%esp)

 8048dd9:      08

 8048dda:      8d 44 24 15          lea    0x15(%esp),%eax

 8048dde:      89 04 24             mov    %eax,(%esp)

 8048de1:      e8 64 02 00 00         call   804904a <strings_not_equal>

 8048de6:      85 c0                test   %eax,%eax

 8048de8:      74 0f                je     8048df9 <phase_5+0x6e>

 8048dea:      e8 66 03 00 00         call   8049155 <explode_bomb>

 8048def:      90                  nop

 8048df0:      eb 07                jmp    8048df9 <phase_5+0x6e>

 8048df2:      b8 00 00 00 00         mov    $0x0,%eax

 8048df7:      eb ba                jmp    8048db3 <phase_5+0x28>

 8048df9:      8b 44 24 1c          mov    0x1c(%esp),%eax

 8048dfd:      65 33 05 14 00 00 00    xor    %gs:0x14,%eax 这里的xor把eax置0了,因为最开始还有一句mov %gs:0x14,%eax

 8048e04:      74 05               je     8048e0b <phase_5+0x80>

 8048e06:      e8 b5 f9 ff ff            call   80487c0 <__stack_chk_fail@plt>

 8048e0b:      83 c4 28             add    $0x28,%esp

 8048e0e:      5b                  pop    %ebx

 8048e0f:      90                  nop

 8048e10:      c3                  ret   

首先,从call 804902b <string_length>和cmp $0x6,%eax可以看出我们需要输入一个长度为6的字符串。一直到第二个cmp $0x6,%eax,如果字符串长度不为6,会一直循环直到为6才继续。

接下来我们看到movzbl (%ebx,%eax,1),%edx和and $0xf,%edx,就是把输入的字符串中的每个字符依次拿出来,保留低四位,高四位任意。然后用基址变址寻址从0x804a1a8(%edx),%edx中找到相应的字符传到edx,在取低8位也就是正好一个char类型的字符传到0x15(%esp,%eax,1)这样的一个地址中去。然后循环对6个字符都进行这个操作。至此我们可能还看不出什么头绪,但是得到了一个字符串。往下继续看。

 

再接下来,我们看到了movl $0x804a180,0x4(%esp)有确定地址,习惯性看看里面写了什么,发现是一个长度为6的字符串。再后面有call 804904a <strings_not_equal>,我们可以得知这里就需要判断答案是否正确了。

 

结合这两个地址,说明前面那一大串字符串经过变换后会得到最后的“sabres”,因此我们需要倒推,看看我们最开始输入的是什么。

因为sabres是通过基址变址寻址得到的,因此通过这个字符串我们就能得到他们对应的偏移分别是7 1 13 6 5 7,而这些偏移正是通过我们输入的字符的ASCII码的低四位得到的,因此他们对应的二进制分别是0111  0001  1101  0110  0101  0111,然后从ASCII码表中找出对应的低位能对应上的字母即可。同样这一题的答案也是不唯一的。

 

对应得其中一个答案是gamfeg,验证正确。(不止一个答案,wqmvuw验证有多个答案)

   

 

 

六、第六关

08048e11 <phase_6>:

 8048e11:      56                  push   %esi

 8048e12:      53                  push   %ebx

 8048e13:      83 ec 44             sub    $0x44,%esp

 8048e16:      8d 44 24 10           lea     0x10(%esp),%eax

 8048e1a:      89 44 24 04          mov    %eax,0x4(%esp)

 8048e1e:      8b 44 24 50          mov    0x50(%esp),%eax

 8048e22:      89 04 24             mov    %eax,(%esp)

 8048e25:      e8 52 03 00 00         call    804917c <read_six_numbers>

 8048e2a:      be 00 00 00 00         mov    $0x0,%esi


//这是第一个循环,第一个循环里面还嵌入着一个小循环。这里的条件是数组中所有的元素都小于等于6,且不能存在两个数相同。所以输入的数字是1-6的排列组合之一

 8048e2f:      8b 44 b4 10          mov    0x10(%esp,%esi,4),%eax

 8048e33:      83 e8 01             sub    $0x1,%eax

 8048e36:      83 f8 05             cmp    $0x5,%eax  eax-1<=5,否则爆炸

 8048e39:      76 05                jbe    8048e40 <phase_6+0x2f>

 8048e3b:      e8 15 03 00 00         call   8049155 <explode_bomb>

 8048e40:      83 c6 01             add    $0x1,%esi

 8048e43:      83 fe 06             cmp    $0x6,%esi esi是计数器

 8048e46:      75 07                jne    8048e4f <phase_6+0x3e>

 8048e48:      bb 00 00 00 00         mov    $0x0,%ebx

 8048e4d:      eb 38                jmp    8048e87 <phase_6+0x76> 第一个大循环的出口

 8048e4f:      89 f3                mov    %esi,%ebx

 8048e51:      8b 44 9c 10          mov    0x10(%esp,%ebx,4),%eax 数组中的下一个值,开始小循环

 8048e55:      39 44 b4 0c          cmp    %eax,0xc(%esp,%esi,4)

 8048e59:      75 05                jne    8048e60 <phase_6+0x4f>

 8048e5b:      e8 f5 02 00 00         call   8049155 <explode_bomb>

 8048e60:      83 c3 01             add    $0x1,%ebx

 8048e63:      83 fb 05             cmp    $0x5,%ebx小于等于就跳出循环

 8048e66:      7e e9                jle    8048e51 <phase_6+0x40> 小循环出口

 8048e68:      eb c5                jmp    8048e2f <phase_6+0x1e> 跳回去了,是个循环

//第二个循环

8048e6a:      8b 52 08             mov    0x8(%edx),%edx

 8048e6d:      83 c0 01             add    $0x1,%eax

 8048e70:      39 c8                cmp    %ecx,%eax

 8048e72:      75 f6                jne    8048e6a <phase_6+0x59>

 8048e74:      eb 05                jmp    8048e7b <phase_6+0x6a>

 8048e76:      ba 3c c1 04 08         mov    $0x804c13c,%edx 这里存了一个地址值

 8048e7b:      89 54 b4 28          mov    %edx,0x28(%esp,%esi,4) 把地址传给了一个点

 8048e7f:      83 c3 01              add    $0x1,%ebx

 8048e82:      83 fb 06             cmp    $0x6,%ebx

 8048e85:      74 17                je     8048e9e <phase_6+0x8d>

 8048e87:      89 de                mov    %ebx,%esi

 8048e89:      8b 4c 9c 10          mov    0x10(%esp,%ebx,4),%ecx

 8048e8d:      83 f9 01             cmp    $0x1,%ecx

 8048e90:      7e e4                jle    8048e76 <phase_6+0x65> 小于等于1则跳转

 8048e92:      b8 01 00 00 00         mov    $0x1,%eax

 8048e97:      ba 3c c1 04 08         mov    $0x804c13c,%edx

 8048e9c:      eb cc                jmp    8048e6a <phase_6+0x59>

 8048e9e:      8b 5c 24 28          mov    0x28(%esp),%ebx

 8048ea2:      8d 44 24 2c          lea     0x2c(%esp),%eax

 8048ea6:      8d 74 24 40          lea     0x40(%esp),%esi

 8048eaa:      89 d9                mov    %ebx,%ecx

 8048eac:      8b 10                mov    (%eax),%edx

 8048eae:      89 51 08             mov    %edx,0x8(%ecx)

 8048eb1:      83 c0 04             add    $0x4,%eax

 8048eb4:      39 f0                cmp    %esi,%eax

 8048eb6:      74 04                je     8048ebc <phase_6+0xab>

 8048eb8:      89 d1                mov    %edx,%ecx

 8048eba:      eb f0                jmp    8048eac <phase_6+0x9b>

 8048ebc:      c7 42 08 00 00 00 00     movl   $0x0,0x8(%edx)

 8048ec3:      be 05 00 00 00         mov    $0x5,%esi

 8048ec8:      8b 43 08             mov    0x8(%ebx),%eax

 8048ecb:      8b 00                mov    (%eax),%eax

 8048ecd:      39 03                cmp    %eax,(%ebx)  ebx<=eax就不爆炸,从大到小排

 8048ecf: 7e 05                      jle     8048ed6 <phase_6+0xc5>

 8048ed1:      e8 7f 02 00 00         call    8049155 <explode_bomb>

 8048ed6:      8b 5b 08             mov    0x8(%ebx),%ebx

 8048ed9:      83 ee 01             sub    $0x1,%esi

 8048edc:      75 ea                jne    8048ec8 <phase_6+0xb7>

 8048ede:      83 c4 44             add    $0x44,%esp

 8048ee1:      5b                  pop    %ebx

 8048ee2:      5e                   pop    %esi

 8048ee3:      c3                   ret   

通过call 804917c <read_six_numbers>这句话,猜测我们的答案是6个数字

接下来看到sub $0x1,%eaxcmp $0x5,%eax,得知我们输入的数字必须要小于等于6。

在开始小循环时,我们能发现,mov 0x10(%esp,%ebx,4),%eax和cmp %eax,0xc(%esp,%esi,4)把后面的数和x进行比较,不相等才不会引发爆炸。这一层循环比较x和它后面的所有数。

这两层循环就能确定输入的所有数都不相等。

接着我们能够看到mov $0x804c13c,%edx,遇到固定地址,习惯性查看一下,发现这里存储的是一个链表的地址数据。

 

由这些内容还可以看出应该输入的数是16这六个数。

1

2

3

4

5

6

0x063

0x2a9

0x389

0x0f4

0x3cd

0x2f7

再按升序排列:

1

4

2

6

3

5

0x063

0x0f4

0x2a9

0x2f7

0x389

0x3cd

  那么得到答案:1 4 2 6 3 5

 

 

  验证正确。

七、如何进入隐藏关卡

  我们发现汇编代码中存在secret_phase,但是之前的过程并没有用到,猜测它是一个隐藏关卡。首先,我们要找到调用了secret phase的地方。我们发现只有phase_defused调用了它。

  

080492c6 <phase_defused>:

 80492c6:   81 ec 8c 00 00 00       sub    $0x8c,%esp

 80492cc:   65 a1 14 00 00 00       mov    %gs:0x14,%eax

 80492d2:   89 44 24 7c             mov    %eax,0x7c(%esp)

 80492d6:   31 c0                   xor    %eax,%eax

 80492d8:   83 3d c8 c3 04 08 06    cmpl   $0x6,0x804c3c8

 80492df:   75 72                   jne    8049353 <phase_defused+0x8d>

phase_defused在每一关成功后都被调用,我们发现cmpl $0x6,0x804c3c8这句话可能与通过前六关有关,没通过的话就不能进入隐藏关卡。

80492e1:   8d 44 24 2c             lea    0x2c(%esp),%eax

 80492e5:   89 44 24 10             mov    %eax,0x10(%esp)

 80492e9:   8d 44 24 28             lea    0x28(%esp),%eax

 80492ed:   89 44 24 0c             mov    %eax,0xc(%esp)

 80492f1:   8d 44 24 24             lea    0x24(%esp),%eax

 80492f5:   89 44 24 08             mov    %eax,0x8(%esp)

 80492f9:   c7 44 24 04 49 a3 04    movl   $0x804a349,0x4(%esp)

 8049300:   08

 8049301:   c7 04 24 d0 c4 04 08    movl   $0x804c4d0,(%esp)

 8049308:   e8 53 f5 ff ff          call   8048860 <__isoc99_sscanf@plt>

  传入了$0x804a349$0x804c4d0两个立即数地址进去,我们看看传了什么。

 

 

此时,我们推测后一个地址可能是输入时的地址。查看每一个关卡输入时的地址,发现第四关输入时的地址正好是0x804c4d0

 804930d:   83 f8 03                cmp    $0x3,%eax

 8049310:   75 35                   jne    8049347 <phase_defused+0x81>

 8049312:   c7 44 24 04 52 a3 04    movl   $0x804a352,0x4(%esp)

 8049319:   08

 804931a:   8d 44 24 2c             lea    0x2c(%esp),%eax

 804931e:   89 04 24                mov    %eax,(%esp)

 8049321:   e8 24 fd ff ff          call   804904a <strings_not_equal>

 8049326:   85 c0                   test   %eax,%eax

 8049328:   75 1d                   jne    8049347 <phase_defused+0x81>

 804932a:   c7 04 24 18 a2 04 08    movl   $0x804a218,(%esp)

 8049331:   e8 ba f4 ff ff          call   80487f0 <puts@plt>

 8049336:   c7 04 24 40 a2 04 08    movl   $0x804a240,(%esp)

 804933d:   e8 ae f4 ff ff          call   80487f0 <puts@plt>

 8049342:   e8 ee fb ff ff          call   8048f35 <secret_phase>

 8049347:   c7 04 24 78 a2 04 08    movl   $0x804a278,(%esp)

 804934e:   e8 9d f4 ff ff          call   80487f0 <puts@plt>

 8049353:   8b 44 24 7c             mov    0x7c(%esp),%eax

 8049357:   65 33 05 14 00 00 00    xor    %gs:0x14,%eax

 804935e:   74 05                   je     8049365 <phase_defused+0x9f>

 8049360:   e8 5b f4 ff ff          call   80487c0 <__stack_chk_fail@plt>

 8049365:   81 c4 8c 00 00 00       add    $0x8c,%esp

 804936b:   c3                      ret   

 804936c:   66 90                   xchg   %ax,%ax

 804936e:   66 90                   xchg   %ax,%ax

  将<__isoc99_sscanf@plt>的返回值与3比较,猜测该返回值应该是参数的数量,等于3才可能解锁隐藏关。然后又传递了一个地址0x804a352,将该地址处的字符串与输入的字符串比较,相等才能解锁隐藏关。,我们看看0x804a352处是什么:得到该处的字符串是DrEvil。

 

所以在进行第四关时,输入两个数后再输入DrEvil,通过第六关之后即可进入secret_phase。

 

(第四关里面获取输入元素数量也使用了<__isoc99_sscanf@plt>,在那里给的参数是"%d %d",应该是只能接受两个输入,即使输入了三个,返回值也会是2,第四关的通关不受影响。)

八、隐藏关卡

08048f35 <secret_phase>:

 8048f35:      53                  push   %ebx

 8048f36:      83 ec 18             sub    $0x18,%esp

 8048f39:      e8 8e 02 00 00         call   80491cc <read_line>

 8048f3e:      c7 44 24 08 0a 00 00     movl   $0xa,0x8(%esp)

 8048f45:      00

 8048f46:      c7 44 24 04 00 00 00     movl   $0x0,0x4(%esp)

 8048f4d:      00

 8048f4e:      89 04 24             mov    %eax,(%esp)

 8048f51:      e8 7a f9 ff ff            call   80488d0 <strtol@plt>

从call 80491cc <read_line>我们可以推测,答案是一行输入。然后输入的内容和$0xa、$0x0一起作为<strtol@plt>的参数。

<strtol@plt>的作用是把参数按照其base(即进制)转换成长整型数,由此可知应该输入的是一个数。

 8048f56:      89 c3                mov    %eax,%ebx

 8048f58:      8d 40 ff             lea    -0x1(%eax),%eax

 8048f5b:      3d e8 03 00 00         cmp    $0x3e8,%eax

 8048f60:      76 05                jbe    8048f67 <secret_phase+0x32>

 8048f62:      e8 ee 01 00 00         call   8049155 <explode_bomb>

$0x3e8是一个确定的数字,换成十进制是1000。但是前面还有lea -0x1(%eax),%eax,即eax-1。则我们知道了输入的数字需要小于等于1001。

 8048f67:      89 5c 24 04          mov    %ebx,0x4(%esp)

 8048f6b:      c7 04 24 88 c0 04 08     movl   $0x804c088,(%esp)

 8048f72:      e8 6d ff ff ff             call   8048ee4 <fun7>

 8048f77:      85 c0                test   %eax,%eax  fun7返回值要为0

 8048f79:      74 05                je     8048f80 <secret_phase+0x4b>

 8048f7b:      e8 d5 01 00 00         call   8049155 <explode_bomb>

 8048f80:      c7 04 24 b8 a1 04 08     movl   $0x804a1b8,(%esp)

(剩下的汇编代码略)

  在这里, $0x804c088x与输入的数%ebx(y)作为参数调用<fun7>test %eax,%eax相当于cmp $0x0,%eax。我们知道了<fun7>的返回值一定得是0。接下来我们来看<fun7>。

08048ee4 <fun7>:

 8048ee4:      53                  push   %ebx

 8048ee5:      83 ec 18             sub    $0x18,%esp

 8048ee8:      8b 54 24 20          mov    0x20(%esp),%edx

 8048eec:      8b 4c 24 24          mov    0x24(%esp),%ecx

(1)判断x是否为0

8048ef0:      85 d2                test   %edx,%edx

 8048ef2:      74 37                je     8048f2b <fun7+0x47>

(2)x不为0时的三种情况:

 8048ef4:      8b 1a                mov    (%edx),%ebx

 8048ef6:      39 cb                cmp    %ecx,%ebx

 8048ef8:      7e 13                jle    8048f0d <fun7+0x29>

 8048efa: 89 4c 24 04              mov    %ecx,0x4(%esp)

 8048efe: 8b 42 04                    mov    0x4(%edx),%eax

 8048f01:      89 04 24             mov    %eax,(%esp)

 8048f04:      e8 db ff ff ff             call   8048ee4 <fun7>

 8048f09:      01 c0                add    %eax,%eax

 8048f0b:      eb 23                jmp    8048f30 <fun7+0x4c>

 8048f0d:      b8 00 00 00 00         mov    $0x0,%eax

 8048f12:      39 cb                cmp    %ecx,%ebx

 8048f14:      74 1a                je     8048f30 <fun7+0x4c>

 8048f16:      89 4c 24 04          mov    %ecx,0x4(%esp)

 8048f1a:      8b 42 08             mov    0x8(%edx),%eax

 8048f1d:      89 04 24             mov    %eax,(%esp)

 8048f20:      e8 bf ff ff ff             call   8048ee4 <fun7>

 8048f25:      8d 44 00 01          lea    0x1(%eax,%eax,1),%eax

(3)结尾部分

 8048f29:      eb 05                jmp    8048f30 <fun7+0x4c>

 8048f2b:      b8 ff ff ff ff              mov    $0xffffffff,%eax

 8048f30:      83 c4 18             add    $0x18,%esp

 8048f33:      5b                  pop    %ebx

 8048f34:      c3                  ret

  通过分析fun7,我们可以得到:

(1)若x为0,函数返回$0xffffffff

(2)若x不为0:

1、当x指向的值大于y,将地址x+4,和y一起作为参数调用fun7,返回其返回值的2倍。

2、当x指向的值等于y,返回0。

3、当x指向的值小于y,将地址x+8,和y一起作为参数调用fun7,返回其返回值的2倍加1。

  而我们需要的返回值是0,即x指向的值等于y。我们来看看x$0x804c088)指向的值:

   

 

  0x24转换为十进制,为36。

 

验证一下:正确。

九、至此,所有炸弹都拆除了。

  

 

总结一下各个关卡:

phase_1:字符串比较

phase_2:循环

phase_3:switch

phase_4:递归

phase_5:指针

phase_6:链表/指针/结构

secret_phase:隐藏关、条件判断(如果要求的是其他非0数字的话就需要二叉树遍历)

十、收获

一些自认为有必要记录的tips:

1、ja和jg同是大于时跳转,它们的差别:

 

2、mov和lea区别:mov传地址,lea传值。mov也能传值。

 

3、在第五关中有这样一句话:mov %gs:0x14,%eax

gs是段寄存器,这句话意味着从地址 gs:0x14 的内存中将 4 个字节读入 eax。

4、关于movzbl指令

 

在函数中“unsigned int a = c”语句完成的是一个从unsigned char到unsigned int的赋值操作,由于int的类型长度大于char类型长度,所以实际是将一个字节的内容拷贝到一个可以容纳4个字节的地方,这样的话需要对源数据进行一下扩展,即填充高位的3个字节。

如何填充呢?由于变量a和c都为无符号整型,所以只需要填充0即可。而movzbl就是干这个活的。movzbl指令负责拷贝一个字节,并用0填充其目的操作数中的其余各位,这种扩展方式叫“零扩展”。

5、关于movsbl指令

与movzbl类似,只是它用符号位填充。

6、test %eax %eax,也相当于 cmp $0x0,%eax

test指令操作是目的操作数和源操作数按位逻辑“与”操作。
运算结果不送回目的操作数(基本上和 and eax,eax 是一样的,不同的是test不改变eax的结果)。

十一、心得体会:

最开始做第一关的时候,就像无头苍蝇一样,不知道哪些语句通常是比较关键的,弯弯绕绕了不少时间。后来在看思路分享的PDF文件时逐渐明白了这个实验的正确打开方式。

第二关是循环类型的,比较简单。

第三关其实也不难,只要跟着汇编代码一步一步走就好。但是我前面算错了好几次,所以也卡了不少时间。后来换了一天去算就一次过了。(第一个数不同,第二个数也随之不同,多个答案)

第四关比较特殊,它有<fun_4>这个递归函数还需要分析。因为手算太难了,所以采用转换成类C代码,用数字一个一个尝试的方式把答案测出来了。

第五关的难点在于对ASCII码不熟悉,没想到还有高四位低四位这种。后来通过转换表可以得到答案。(答案不唯一)

第六关也卡了好一会儿,后来半想半猜写出来的。

隐藏关卡是听别人说有,所以多留意了一下,就发现了<secret_phase>。这里实际上相当于做了两道题,一是要找出进入隐藏关卡的入口以及条件,二是破解隐藏关卡的密码。

总而言之,这次炸弹实验让我对汇编语言的阅读更加熟练了,也对于汇编语言中哪一些是传参、哪一些是调用函数有了一个习惯上的认知。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值