《深入理解计算机系统/CSAPP》Bomb Lab

原文链接

任务目标

“拆除炸弹”:通过反汇编,找到要输入的信息,输入到程序中达到“拆弹”的目的。

文件说明:

  • bomb :二进制文件,要拆除的“炸弹”
  • bomb.c :程序主函数源代码

可能使用到的工具:

  • gdb :用于程序调试
  • objdump :用于反汇编
  • 流程图软件(帮助简化思考笔者未在文章给出流程图,请读者自行绘制

可能使用到的命令(详细用法未给出):

  • objdump -d :返回二进制文件反汇编码
  • (gdb)break ... :设置断点
  • (gdb) next :单步调试,不进入
  • (gdb) step :单步调试,进入函数
  • (gdb)info r :查看寄存器
  • (gdb)info r … :查看某寄存器
  • (gdb)p ... :输出变量
  • (gdb)x ... :查看内容
  • (gdb)set args … :设置函数运行参数

第一关

阅读bomb.c,了解炸弹工作流程。

initialize_bomb();     // 初始化
input = read_line();   // 读取输入
phase_1(input);        // “拆弹”
phase_defused();       

phase_1函数便是要拆除的“炸弹”了,需要了解“炸弹”内部是如何工作的,我们来反汇编它。

使用objdump -d bomb > bomb.as将汇编代码保存到bomb.as文件当中。

查找到phase_1反汇编后的代码。

0000000000400ee0 :
  400ee0:       48 83 ec 08             sub    $0x8,%rsp
  400ee4:       be 00 24 40 00          mov    $0x402400,%esi
  400ee9:       e8 4a 04 00 00          callq  401338 
  400eee:       85 c0                   test   %eax,%eax
  400ef0:       74 05                   je     400ef7 
  400ef2:       e8 43 05 00 00          callq  40143a 
  400ef7:       48 83 c4 08             add    $0x8,%rsp
  400efb:       c3                      retq  

首先看到explode_bomb函数,猜测调用这个函数会引爆炸弹,所以需要执行之前的je指令跳过它。

je指令能跳转所满足的条件为%eax = 0,猜测strings_not_equal函数的功能用于比较字符串是否相同。

0000000000401338 :
  401338:       41 54                   push   %r12 
  40133a:       55                      push   %rbp 
  40133b:       53                      push   %rbx 
  40133c:       48 89 fb                mov    %rdi,%rbx
  40133f:       48 89 f5                mov    %rsi,%rbp
  ----------------------------------------------> 参数为%rdi和%rsi
  ............................................... 
  ----------------------------------------------> 返回寄存器%eax
  40139d:       5b                      pop    %rbx
  40139e:       5d                      pop    %rbp 
  40139f:       41 5c                   pop    %r12 
  4013a1:       c3                      retq

strings_not_equal函数可以初略看出,若字符串不相等会返回1,反之。

该函数所需要的参数为两个字符串首地址,用%rsi%rdi寄存器存放。

使用gdb调试,查看这两个字符串存放的数据是什么。

$gdb bomb
(gdb) break strings_not_equal
(gdb) r
(gdb) p/s (char*)$rdi
$5 = 0x603780  "111111"
(gdb) p/s (char*)$rsi
$6 = 0x402400 "Border relations with Canada have never been better."

第一关密码便得到了Border relations with Canada have never been better.

Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?

第二关

0000000000400efc :
  400efc:       55                      push   %rbp 
  400efd:       53                      push   %rbx 
  400efe:       48 83 ec 28             sub    $0x28,%rsp
  400f02:       48 89 e6                mov    %rsp,%rsi
  400f05:       e8 52 05 00 00          callq  40145c 
  400f0a:       83 3c 24 01             cmpl   $0x1,(%rsp)
  400f0e:       74 20                   je     400f30 
  400f10:       e8 25 05 00 00          callq  40143a 
  400f15:       eb 19                   jmp    400f30 
  400f17:       8b 43 fc                mov    -0x4(%rbx),%eax
  400f1a:       01 c0                   add    %eax,%eax
  400f1c:       39 03                   cmp    %eax,(%rbx)
  400f1e:       74 05                   je     400f25 
  400f20:       e8 15 05 00 00          callq  40143a 
  400f25:       48 83 c3 04             add    $0x4,%rbx
  400f29:       48 39 eb                cmp    %rbp,%rbx
  400f2c:       75 e9                   jne    400f17 
  400f2e:       eb 0c                   jmp    400f3c 
  400f30:       48 8d 5c 24 04          lea    0x4(%rsp),%rbx
  400f35:       48 8d 6c 24 18          lea    0x18(%rsp),%rbp
  400f3a:       eb db                   jmp    400f17 
  400f3c:       48 83 c4 28             add    $0x28,%rsp
  400f40:       5b                      pop    %rbx 
  400f41:       5d                      pop    %rbp 
  400f42:       c3                      retq  

猜测read_six_numbers会读入六个数字,我们将断点设置在0x400f0a处,在文件psol.txt中写入测试数据。

Border relations with Canada have never been better.
1 2 3 4 5 6 
(gdb) set args psol.txt
(gdb) break *0x400f0a
(gdb) r
(gdb) x $rsp
0x7fffffffea30: 0x00000001

推测输入的六个数存放在首地址为rsp的数组中。去除无用汇编代码,修改整理下逻辑,转化为伪代码。

cmpl $0x1,(%rsp)        -> arr[0] == 1
jne explode_bomb
L1:
lea 0x4(%rsp),%rbx      -> rbx = arr[1]
lea 0x18(%rsp),%rbp     -> rbp = arr[6]
L2:
mov -0x4(%rbx),%eax     -> eax = arr[?-1] 
add %eax,%eax           -> eax += eax
cmp %eax,(%rbx)         -> cmp arr[?],arr[?-1]
jne explode_bomb
add $0x4,%rbx           -> arr[?+1]
cmp %rbp,%rbx
jne L2 
----------------------------------------------
流程简化如下
if(arr[0]!=0) bomb()
for(int i=1;i<6;i++){
    if(arr[i-1]*2 != arr[i]) bomb()
}
----------------------------------------------

推导出序列为1 2 4 8 16 32,成功拆除。

Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2.  Keep going!

第三关

0000000000400f43 :
  400f43:       48 83 ec 18             sub    $0x18,%rsp
  400f47:       48 8d 4c 24 0c          lea    0xc(%rsp),%rcx
  400f4c:       48 8d 54 24 08          lea    0x8(%rsp),%rdx
  400f51:       be cf 25 40 00          mov    $0x4025cf,%esi
  400f56:       b8 00 00 00 00          mov    $0x0,%eax
  400f5b:       e8 90 fc ff ff          callq  400bf0 <__isoc99_sscanf@plt>
  400f60:       83 f8 01                cmp    $0x1,%eax
  400f63:       7f 05                   jg     400f6a 
  400f65:       e8 d0 04 00 00          callq  40143a 
  400f6a:       83 7c 24 08 07          cmpl   $0x7,0x8(%rsp)
  400f6f:       77 3c                   ja     400fad 
  400f71:       8b 44 24 08             mov    0x8(%rsp),%eax
  400f75:       ff 24 c5 70 24 40 00    jmpq   *0x402470(,%rax,8)
  400f7c:       b8 cf 00 00 00          mov    $0xcf,%eax
  400f81:       eb 3b                   jmp    400fbe 
  400f83:       b8 c3 02 00 00          mov    $0x2c3,%eax
  400f88:       eb 34                   jmp    400fbe 
  400f8a:       b8 00 01 00 00          mov    $0x100,%eax
  400f8f:       eb 2d                   jmp    400fbe 
  400f91:       b8 85 01 00 00          mov    $0x185,%eax
  400f96:       eb 26                   jmp    400fbe 
  400f98:       b8 ce 00 00 00          mov    $0xce,%eax
  400f9d:       eb 1f                   jmp    400fbe 
  400f9f:       b8 aa 02 00 00          mov    $0x2aa,%eax
  400fa4:       eb 18                   jmp    400fbe 
  400fa6:       b8 47 01 00 00          mov    $0x147,%eax
  400fab:       eb 11                   jmp    400fbe 
  400fad:       e8 88 04 00 00          callq  40143a 
  400fb2:       b8 00 00 00 00          mov    $0x0,%eax
  400fb7:       eb 05                   jmp    400fbe 
  400fb9:       b8 37 01 00 00          mov    $0x137,%eax
  400fbe:       3b 44 24 0c             cmp    0xc(%rsp),%eax
  400fc2:       74 05                   je     400fc9 
  400fc4:       e8 71 04 00 00          callq  40143a 
  400fc9:       48 83 c4 18             add    $0x18,%rsp
  400fcd:       c3                      retq

代码变长了,我们分段来分析。

  400f5b:       e8 90 fc ff ff          callq  400bf0 <__isoc99_sscanf@plt>
  400f60:       83 f8 01                cmp    $0x1,%eax
  400f63:       7f 05                   jg     400f6a 
  400f65:       e8 d0 04 00 00          callq  40143a 

sscanf函数是库函数,返回值eax表示正确格式化的数据个数,在测试文件中随便写入几个数0 1 2 3 4 5,断点在0x400f60处查看下eax中的值。

(gdb) set args psol.txt
(gdb) break *0x400f60
(gdb) run
(gdb) info r eax
eax            0x2                 2

eax=0x2,而我输入参数是多于两个的,所有第三关要求输入两个参数。并且通过调试(第二关)我们可以知道输入的两个参数存放的位置:

  0x0000000000400f6a <+39>: cmpl   $0x7,0x8(%rsp)
  0x0000000000400f6f <+44>: ja     0x400fad 
  -----------------------------------------------------------
   读出0x8(%rsp)
   (gdb) x $rsp+0x8
   0x7fffffffea58:  0x00000000
   (gdb) x $rsp+0x8+0x4
   0x7fffffffea5c:  0x00000001
   0x8(%rsp)为输入的第一个参数a
   0xc(%rsp)为输入的第二个参数b

通过上面的代码我们可以获取到第一个条件a<=7

0x0000000000400f71 <+46>:   mov    0x8(%rsp),%eax
0x0000000000400f75 <+50>:   jmpq   *0x402470(,%rax,8)
--------------------------------------------------------------
*0x402470(,%rax,8) = *(0x402470+8*a) = 0x402470[8*a]
读出从0x402470[0..7]的所有数据
(gdb) x/8xg 0x402470
0x402470:   0x0000000000400f7c  0x0000000000400fb9
0x402480:   0x0000000000400f83  0x0000000000400f8a
0x402490:   0x0000000000400f91  0x0000000000400f98
0x4024a0:   0x0000000000400f9f  0x0000000000400fa6
以上便是跳转的地址范围

分析下面关键代码,来决定跳转的地址。

   0x0000000000400f7c <+57>:    mov    $0xcf,%eax
   0x0000000000400f81 <+62>:    jmp    0x400fbe 
   0x0000000000400f83 <+64>:    mov    $0x2c3,%eax
   0x0000000000400f88 <+69>:    jmp    0x400fbe 
   0x0000000000400f8a <+71>:    mov    $0x100,%eax
   0x0000000000400f8f <+76>:    jmp    0x400fbe 
   0x0000000000400f91 <+78>:    mov    $0x185,%eax
   0x0000000000400f96 <+83>:    jmp    0x400fbe 
   0x0000000000400f98 <+85>:    mov    $0xce,%eax
   0x0000000000400f9d <+90>:    jmp    0x400fbe 
   0x0000000000400f9f <+92>:    mov    $0x2aa,%eax
   0x0000000000400fa4 <+97>:    jmp    0x400fbe 
   0x0000000000400fa6 <+99>:    mov    $0x147,%eax
   0x0000000000400fab <+104>:   jmp    0x400fbe 
   --------------------------------------------------------- 都会跳转到
   0x0000000000400fad <+106>:   callq  0x40143a 
   --------------------------------------------------------- 
   0x0000000000400fb2 <+111>:   mov    $0x0,%eax
   0x0000000000400fb7 <+116>:   jmp    0x400fbe 
   0x0000000000400fb9 <+118>:   mov    $0x137,%eax
   0x0000000000400fbe <+123>:   cmp    0xc(%rsp),%eax        
   --------------------------------------------------------- 
   0x0000000000400fc2 <+127>:   je     0x400fc9 
   0x0000000000400fc4 <+129>:   callq  0x40143a 

他们都会跳转到0x400fbe,最后会比较0xc(%rsp)eax的大小。

通过前面的分析知道0xc(%rsp)便是输入的参数b,我们再讨论输入不同的a,最终得到的eax值是多少。

a 跳转地址 最终eax/b的值
0 0x0000000000400f7c 0xcf
1 0x0000000000400fb9 0x137
2 0x0000000000400f83 0x2c3
3 0x0000000000400f8a 0x100
4 0x0000000000400f91 0x185
5 0x0000000000400f98 0xce
6 0x0000000000400f9f 0x2aa
7 0x0000000000400fa6 0x147

随意选择一组a,b的值填入即可。

Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2.  Keep going!
Halfway there!

第四关

000000000040100c :
  40100c:       48 83 ec 18             sub    $0x18,%rsp
  401010:       48 8d 4c 24 0c          lea    0xc(%rsp),%rcx
  401015:       48 8d 54 24 08          lea    0x8(%rsp),%rdx
  40101a:       be cf 25 40 00          mov    $0x4025cf,%esi
  40101f:       b8 00 00 00 00          mov    $0x0,%eax
  401024:       e8 c7 fb ff ff          callq  400bf0 <__isoc99_sscanf@plt>
  401029:       83 f8 02                cmp    $0x2,%eax
  40102c:       75 07                   jne    401035 
  40102e:       83 7c 24 08 0e          cmpl   $0xe,0x8(%rsp)
  401033:       76 05                   jbe    40103a 
  401035:       e8 00 04 00 00          callq  40143a 
  40103a:       ba 0e 00 00 00          mov    $0xe,%edx
  40103f:       be 00 00 00 00          mov    $0x0,%esi
  401044:       8b 7c 24 08             mov    0x8(%rsp),%edi
  401048:       e8 81 ff ff ff          callq  400fce 
  40104d:       85 c0                   test   %eax,%eax
  40104f:       75 07                   jne    401058 
  401051:       83 7c 24 0c 00          cmpl   $0x0,0xc(%rsp)
  401056:       74 05                   je     40105d 
  401058:       e8 dd 03 00 00          callq  40143a 
  40105d:       48 83 c4 18             add    $0x18,%rsp
  401061:       c3                      retq 

和第三关同理,库函数sscanf需要输入的参数个数为2个,代码中已有,所有这次不用断点测试。

40102e:       83 7c 24 08 0e          cmpl   $0xe,0x8(%rsp)         
401033:       76 05                   jbe    40103a 

获取第一个情报a<=0xe,继续分析代码。

   0x000000000040103a <+46>:    mov    $0xe,%edx
   0x000000000040103f <+51>:    mov    $0x0,%esi
   0x0000000000401044 <+56>:    mov    0x8(%rsp),%edi
   0x0000000000401048 <+60>:    callq  0x400fce 
   0x000000000040104d <+65>:    test   %eax,%eax
   0x000000000040104f <+67>:    jne    0x401058 
   0x0000000000401051 <+69>:    cmpl   $0x0,0xc(%rsp)
   0x0000000000401056 <+74>:    je     0x40105d 
   0x0000000000401058 <+76>:    callq  0x40143a 

需要再访问函数func4之后,eax=0才不会触发“爆炸”函数。

获取第二个情报b=0x0,在func4函数中要让eax=0,我们继续看func4函数。

  400fd2:       89 d0                   mov    %edx,%eax  
  400fd4:       29 f0                   sub    %esi,%eax 
  400fd6:       89 c1                   mov    %eax,%ecx 
  400fd8:       c1 e9 1f                shr    $0x1f,%ecx 
  400fdb:       01 c8                   add    %ecx,%eax 
  400fdd:       d1 f8                   sar    %eax 
  400fdf:       8d 0c 30                lea    (%rax,%rsi,1),%ecx
  400fe2:       39 f9                   cmp    %edi,%ecx
  400fe4:       7e 0c                   jle    400ff2 
  400fe6:       8d 51 ff                lea    -0x1(%rcx),%edx
  400fe9:       e8 e0 ff ff ff          callq  400fce 
  400fee:       01 c0                   add    %eax,%eax
  400ff0:       eb 15                   jmp    401007 
  400ff2:       b8 00 00 00 00          mov    $0x0,%eax
  400ff7:       39 f9                   cmp    %edi,%ecx
  400ff9:       7d 0c                   jge    401007 
  400ffb:       8d 71 01                lea    0x1(%rcx),%esi
  400ffe:       e8 cb ff ff ff          callq  400fce 
  401003:       8d 44 00 01             lea    0x1(%rax,%rax,1),%eax

现在获得的情报有:

  • a<=0xeb=0,这两个参数范围确定。

  • func4函数需要三个参数,%edx=0xe%esi=0x0%edi=a

  • func4函数反汇编的倒数第二行,可能是一个递归程序,最终发现其实没有调用

  • func4函数并没有改变%edi寄存器的值。

整理一下代码,可以明显看到这里有一条指令mov $0x0,%eax

  400fe2:       39 f9                   cmp    %edi,%ecx
  400fe4:       7e 0c                   jle    400ff2 
  400fe6:       8d 51 ff                lea    -0x1(%rcx),%edx
  400fe9:       e8 e0 ff ff ff          callq  400fce 
  400fee:       01 c0                   add    %eax,%eax
  400ff0:       eb 15                   jmp    401007 
  400ff2:       b8 00 00 00 00          mov    $0x0,%eax
  400ff7:       39 f9                   cmp    %edi,%ecx
  400ff9:       7d 0c                   jge    401007 

能够跳转到这里的条件是%ecx <= %edi(第一行),能够结束函数的条件是%ecx >= %edi(倒数第二行)。所以使%ecx = %edi即可,而%edi便是输入的参数a。计算或者断点调试一下得到%ecx=7。填入参数7 0即可过关。

Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2.  Keep going!
Halfway there!
So you got that one.  Try this one.

第五关

0000000000401062 :
  401062:       53                      push   %rbx 
  401063:       48 83 ec 20             sub    $0x20,%rsp
  401067:       48 89 fb                mov    %rdi,%rbx
  40106a:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
  401071:       00 00 
  401073:       48 89 44 24 18          mov    %rax,0x18(%rsp)
  401078:       31 c0                   xor    %eax,%eax
  40107a:       e8 9c 02 00 00          callq  40131b 
  40107f:       83 f8 06                cmp    $0x6,%eax
  401082:       74 4e                   je     4010d2 
  401084:       e8 b1 03 00 00          callq  40143a 
  401089:       eb 47                   jmp    4010d2 
  40108b:       0f b6 0c 03             movzbl (%rbx,%rax,1),%ecx
  40108f:       88 0c 24                mov    %cl,(%rsp)
  401092:       48 8b 14 24             mov    (%rsp),%rdx
  401096:       83 e2 0f                and    $0xf,%edx
  401099:       0f b6 92 b0 24 40 00    movzbl 0x4024b0(%rdx),%edx
  4010a0:       88 54 04 10             mov    %dl,0x10(%rsp,%rax,1)
  4010a4:       48 83 c0 01             add    $0x1,%rax
  4010a8:       48 83 f8 06             cmp    $0x6,%rax
  4010ac:       75 dd                   jne    40108b 
  4010ae:       c6 44 24 16 00          movb   $0x0,0x16(%rsp)
  4010b3:       be 5e 24 40 00          mov    $0x40245e,%esi
  4010b8:       48 8d 7c 24 10          lea    0x10(%rsp),%rdi
  4010bd:       e8 76 02 00 00          callq  401338 
  4010c2:       85 c0                   test   %eax,%eax
  4010c4:       74 13                   je     4010d9 
  4010c6:       e8 6f 03 00 00          callq  40143a 
  4010cb:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
  4010d0:       eb 07                   jmp    4010d9 
  4010d2:       b8 00 00 00 00          mov    $0x0,%eax
  4010d7:       eb b2                   jmp    40108b 
  4010d9:       48 8b 44 24 18          mov    0x18(%rsp),%rax
  4010de:       64 48 33 04 25 28 00    xor    %fs:0x28,%rax
  4010e5:       00 00 
  4010e7:       74 05                   je     4010ee 
  4010e9:       e8 42 fa ff ff          callq  400b30 <__stack_chk_fail@plt>
  4010ee:       48 83 c4 20             add    $0x20,%rsp
  4010f2:       5b                      pop    %rbx 
  4010f3:       c3                      retq 
  40107a:       e8 9c 02 00 00          callq  40131b 
  40107f:       83 f8 06                cmp    $0x6,%eax
  401082:       74 4e                   je     4010d2 

又相同的string_length函数,通关第一关的经验,我们知道肯定要输入一个字符串并且长度为6,在测试文件中写入6个字符,调试程序。

   0x0000000000401089 <+39>:    jmp    0x4010d2 
   0x000000000040108b <+41>:    movzbl (%rbx,%rax,1),%ecx
   0x000000000040108f <+45>:    mov    %cl,(%rsp)
   0x0000000000401092 <+48>:    mov    (%rsp),%rdx
   0x0000000000401096 <+52>:    and    $0xf,%edx
   0x0000000000401099 <+55>:    movzbl 0x4024b0(%rdx),%edx
   0x00000000004010a0 <+62>:    mov    %dl,0x10(%rsp,%rax,1)
   0x00000000004010a4 <+66>:    add    $0x1,%rax
   0x00000000004010a8 <+70>:    cmp    $0x6,%rax
   0x00000000004010ac <+74>:    jne    0x40108b 
   0x00000000004010ae <+76>:    movb   $0x0,0x16(%rsp)
   0x00000000004010b3 <+81>:    mov    $0x40245e,%esi
   0x00000000004010b8 <+86>:    lea    0x10(%rsp),%rdi
   0x00000000004010bd <+91>:    callq  0x401338 
   0x00000000004010c2 <+96>:    test   %eax,%eax
   0x00000000004010c4 <+98>:    je     0x4010d9 
   0x00000000004010c6 <+100>:   callq  0x40143a 
   0x00000000004010cb <+105>:   nopl   0x0(%rax,%rax,1)
   0x00000000004010d0 <+110>:   jmp    0x4010d9 
   0x00000000004010d2 <+112>:   mov    $0x0,%eax
   0x00000000004010d7 <+117>:   jmp    0x40108b 

设置断点到0x40108b<+41>处,得到%rbx寄存器中是我们输入的字符串首地址。

(gdb) x/6c $rbx
0x6038c0 :   97 'a'  98 'b'  99 'c'  100 'd' 101 'e' 102 'f'

看一下这一段代码。

   0x000000000040108b <+41>:    movzbl (%rbx,%rax,1),%ecx
   0x000000000040108f <+45>:    mov    %cl,(%rsp)
   0x0000000000401092 <+48>:    mov    (%rsp),%rdx
   0x0000000000401096 <+52>:    and    $0xf,%edx
   0x0000000000401099 <+55>:    movzbl 0x4024b0(%rdx),%edx
   0x00000000004010a0 <+62>:    mov    %dl,0x10(%rsp,%rax,1)
   0x00000000004010a4 <+66>:    add    $0x1,%rax
   0x00000000004010a8 <+70>:    cmp    $0x6,%rax
   0x00000000004010ac <+74>:    jne    0x40108b 
   -------------------------------------------------------------
   narr首地址为rsp
   array首地址为0x4024b0
   for(int i=0;i<6;i++){
       narr[i] = array[input[i]&0xf]
   }
   -------------------------------------------------------------
   因为条件input[i]&0xf <= 0xf
   读取出0x4024b0开始的16个数据
   (gdb) x/16c 0x4024b0
   0x4024b0 :   109 'm' 97 'a'  100 'd' 117 'u' 105 'i' 101 'e' 114 'r'115 's'
   0x4024b8 : 110 'n' 102 'f' 111 'o' 116 't' 118 'v' 98 'b'  121 'y' 108 'l'

继续阅读接下来的代码。

   0x00000000004010ae <+76>:    movb   $0x0,0x16(%rsp)
   0x00000000004010b3 <+81>:    mov    $0x40245e,%esi
   0x00000000004010b8 <+86>:    lea    0x10(%rsp),%rdi
   0x00000000004010bd <+91>:    callq  0x401338 
   0x00000000004010c2 <+96>:    test   %eax,%eax
   0x00000000004010c4 <+98>:    je     0x4010d9 

通过第一关知道strings_not_equal函数的功能:比较首地址为esirdi的字符串是否相等。查看下比较的字符串。

(gdb) x/s 0x40245e
0x40245e:   "flyers"

获得对比的字符串为flyers。通过上面的array数组得到下表。

array[i] char input[c]&0xf == i array[i] char input[c]&0xf == i
array[0] m 0,@,P,` array[8] n 8,H,X,h
array[1] a 1,A,Q,a array[9] f 9,I,Y,i
array[2] d 2,B,R,b array[0xa] o :,J,Z,j
array[3] u 3,C,S,c array[0xb] t ;,K,[,k
array[4] i 4,D,T,d array[0xc] v <,L,'\',l
array[5] e 5,E,U,e array[0xd] b =,M,],m
array[6] r 6,F,V,f array[0xe] y >,N,^,n
array[7] s 7,G,W,g array[0xf] l ?,O,_,o

随意选取上表"flyers"对应的input[]&0xfionefgYONUFG,任意组合都能过关。

Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2.  Keep going!
Halfway there!
So you got that one.  Try this one.
Good work!  On to the next...

第六关

  401106:       e8 51 03 00 00          callq  40145c 
  40110b:       49 89 e6                mov    %rsp,%r14
  40110e:       41 bc 00 00 00 00       mov    $0x0,%r12d
  401114:       4c 89 ed                mov    %r13,%rbp
  401117:       41 8b 45 00             mov    0x0(%r13),%eax
  40111b:       83 e8 01                sub    $0x1,%eax
  40111e:       83 f8 05                cmp    $0x5,%eax
  401121:       76 05                   jbe    401128 

需要读入六个数字,随意写1 2 3 4 5 6测试,断点在0x401117处调试。

(gdb) x/6u $rbp  
0x7fffffffe9f0: 1   2   3   4
0x7fffffffea00: 5   6

rbpr13寄存器是存放输入数字的数组首地址。

上面的代码可以得到情报arr[0]-1 <= 5,然后看代码跳转到的位置。

这段代码需要看到0x401153,因为0x401130处的指令会跳转到这里。

  40110e:       41 bc 00 00 00 00       mov    $0x0,%r12d
  
  401114:       4c 89 ed                mov    %r13,%rbp
  401117:       41 8b 45 00             mov    0x0(%r13),%eax
  40111b:       83 e8 01                sub    $0x1,%eax
  40111e:       83 f8 05                cmp    $0x5,%eax
  401121:       76 05                   jbe    401128 
  401123:       e8 12 03 00 00          callq  40143a 
  
  401128:       41 83 c4 01             add    $0x1,%r12d               
  40112c:       41 83 fc 06             cmp    $0x6,%r12d
  401130:       74 21                   je     401153 
  401132:       44 89 e3                mov    %r12d,%ebx
  
  401135:       48 63 c3                movslq %ebx,%rax
  401138:       8b 04 84                mov    (%rsp,%rax,4),%eax       
  40113b:       39 45 00                cmp    %eax,0x0(%rbp)
  40113e:       75 05                   jne    401145 
  401140:       e8 f5 02 00 00          callq  40143a 
  401145:       83 c3 01                add    $0x1,%ebx
  401148:       83 fb 05                cmp    $0x5,%ebx
  40114b:       7e e8                   jle    401135 
  40114d:       49 83 c5 04             add    $0x4,%r13
  401151:       eb c1                   jmp    401114 
  
  401153:       48 8d 74 24 18          lea    0x18(%rsp),%rsi
  ---------------------------------------------------------------------
  循环分析比较麻烦,401151会跳转到401114这个是最外层,完成了%r13 + 0x4。即arr++
  %13初始的时候是数组首地址。
   按照思路逻辑如下
   for i=0;i<6;i++{
       if(arr[i] - 1 > 5) bomb()
       for j=i+1;j<=5;j++{
           if(arr[j] == arr[i]) bomb()
       }
   }

可以得到情报:六个数不相同,且需要<=6,接着看下一段代码。

  401153:       48 8d 74 24 18          lea    0x18(%rsp),%rsi
  401158:       4c 89 f0                mov    %r14,%rax
  
  40115b:       b9 07 00 00 00          mov    $0x7,%ecx
  401160:       89 ca                   mov    %ecx,%edx
  401162:       2b 10                   sub    (%rax),%edx
  401164:       89 10                   mov    %edx,(%rax)         
  
  401166:       48 83 c0 04             add    $0x4,%rax
  40116a:       48 39 f0                cmp    %rsi,%rax
  40116d:       75 f1                   jne    401160 

rsi的值为rsp+0x18 = rsp+0x4*6即数组的结束位置。

%rax = %r14往前看,是输入的数组首地址。

这段代码一个循环很简单看出,其逻辑如下:

for(int i=0;i<6;i++){
    arr[i] = 7 - arr[i];
}

继续看下一段代码。

  40116f:       be 00 00 00 00          mov    $0x0,%esi
  401174:       eb 21                   jmp    401197 
  
  401176:       48 8b 52 08             mov    0x8(%rdx),%rdx
  40117a:       83 c0 01                add    $0x1,%eax
  40117d:       39 c8                   cmp    %ecx,%eax
  40117f:       75 f5                   jne    401176 
  401181:       eb 05                   jmp    401188 
  
  401183:       ba d0 32 60 00          mov    $0x6032d0,%edx
  
  401188:       48 89 54 74 20          mov    %rdx,0x20(%rsp,%rsi,2)
  40118d:       48 83 c6 04             add    $0x4,%rsi
  401191:       48 83 fe 18             cmp    $0x18,%rsi
  401195:       74 14                   je     4011ab 
  
  401197:       8b 0c 34                mov    (%rsp,%rsi,1),%ecx
  40119a:       83 f9 01                cmp    $0x1,%ecx
  40119d:       7e e4                   jle    401183 
  40119f:       b8 01 00 00 00          mov    $0x1,%eax
  4011a4:       ba d0 32 60 00          mov    $0x6032d0,%edx
  4011a9:       eb cb                   jmp    401176 
  
  4011ab:       48 8b 5c 24 20          mov    0x20(%rsp),%rbx
  --------------------------------------------------------------------------
  借助流程图分析我们得到如下逻辑伪代码
  for(int i=0;i<6;i++){
      int cnt = arr[i];
      int add = 0x6032d0;
      for(int j=1;j:   0x006032e0
  (gdb) x 0x6032e0+0x8 
  0x6032e8 :   0x006032f0
  (gdb) x 0x6032f0+0x8
  0x6032f8 :   0x00603300
  (gdb) x 0x603300+0x8
  0x603308 :   0x00603310
  (gdb) x 0x603310+0x8
  0x603318 :   0x00603320

通过上述处理后得到的新数组narry其首地址为rsp+0x20,结束地址为rsp+0x50,其中的数据可选范围为0x6032d0 - 0x603320,继续往下看。

  4011ab:       48 8b 5c 24 20          mov    0x20(%rsp),%rbx
  4011b0:       48 8d 44 24 28          lea    0x28(%rsp),%rax
  4011b5:       48 8d 74 24 50          lea    0x50(%rsp),%rsi
  4011ba:       48 89 d9                mov    %rbx,%rcx
  
  4011bd:       48 8b 10                mov    (%rax),%rdx
  4011c0:       48 89 51 08             mov    %rdx,0x8(%rcx)
  4011c4:       48 83 c0 08             add    $0x8,%rax
  4011c8:       48 39 f0                cmp    %rsi,%rax
  4011cb:       74 05                   je     4011d2 
  4011cd:       48 89 d1                mov    %rdx,%rcx
  4011d0:       eb eb                   jmp    4011bd 
  
  4011d2:       48 c7 42 08 00 00 00    movq   $0x0,0x8(%rdx)
  --------------------------------------------------------------- 
  narr 数组存放数据范围上面分析为 0x6032d0 - 0x603320 
  int *narr = {};
  for(int i=1;i<6;i++){
      *(narr[i-1]+0x8) = narr[i]
  }
  将narr[i-1]+0x8内存地址数据存放为narr[i]

再来看最后一段代码的功能:

  4011d2:       48 c7 42 08 00 00 00    movq   $0x0,0x8(%rdx)
  4011d9:       00

  4011da:       bd 05 00 00 00          mov    $0x5,%ebp

  4011df:       48 8b 43 08             mov    0x8(%rbx),%rax
  4011e3:       8b 00                   mov    (%rax),%eax
  4011e5:       39 03                   cmp    %eax,(%rbx)
  4011e7:       7d 05                   jge    4011ee 
  4011e9:       e8 4c 02 00 00          callq  40143a 
  4011ee:       48 8b 5b 08             mov    0x8(%rbx),%rbx
  4011f2:       83 ed 01                sub    $0x1,%ebp
  4011f5:       75 e8                   jne    4011df 

%rbx寄存器的值没有变化,仍然为narr数组的首元素即narr[0]

结合上一段代码,这段代码的逻辑也能够知道了。

eax    :  *(narr[0]+0x8) => narr[1] => *(narr[1])
(%rbx) :  *(narr[0])
for(int i=1;i<=5;i++){
    if( *(narr[i-1]) < *(narr[i]) ) bomb()        
}

获取narr,以及*(narr[i])的取值如下:

(gdb) x/d 0x6032d0+0x10*0
0x6032d0 :   332
(gdb) x/d 0x6032d0+0x10*1
0x6032e0 :   168
(gdb) x/d 0x6032d0+0x10*2
0x6032f0 :   924
(gdb) x/d 0x6032d0+0x10*3
0x603300 :   691
(gdb) x/d 0x6032d0+0x10*4
0x603310 :   477
(gdb) x/d 0x6032d0+0x10*5
0x603320 :   443
---------------------------------
int narr[1..6] = {
    0x6032d0,0x6032e0,0x6032f0,
    0x603300,0x603310,0x603320
};
int m_narr[1..6] = {
    332,168,924,691,477,443
};
*(narr[i])需要从大到小排序,其位置对应如下:
924 691 477 443 332 168
3   4   5   6   1   2

因为原数据被7减之后才得到该序列,逆运算得到4 3 2 1 6 5,拆弹成功!

Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2.  Keep going!
Halfway there!
So you got that one.  Try this one.
Good work!  On to the next...
Congratulations! You've defused the bomb!

彩蛋关

bomb.c注释说明了存在彩蛋关,查看汇编代码,发现phase_defused函数会调用函数secret_phase

  4015fa:       e8 f1 f5 ff ff          callq  400bf0 <__isoc99_sscanf@plt>
  4015ff:       83 f8 03                cmp    $0x3,%eax
  401602:       75 31                   jne    401635 
  4015f0:       be 19 26 40 00          mov    $0x402619,%esi
  4015f5:       bf 70 38 60 00          mov    $0x603870,%edi
  4015fa:       e8 f1 f5 ff ff          callq  400bf0 <__isoc99_sscanf@plt>
  4015ff:       83 f8 03                cmp    $0x3,%eax
  401602:       75 31                   jne    401635 
  401604:       be 22 26 40 00          mov    $0x402622,%esi
  401609:       48 8d 7c 24 10          lea    0x10(%rsp),%rdi
  40160e:       e8 25 fd ff ff          callq  401338 
  401613:       85 c0                   test   %eax,%eax
  401615:       75 1e                   jne    401635 
  (gdb) x/s 0x402619
  0x402619: "%d %d %s"
  (gdb) x/s 0x402622
  0x402622: "DrEvil"

这两段代码可以看到,只有输入的参数前两个为整数,第三个为字符串DrEvil时才不会跳过隐藏关。我们再看看运行结束时0x603870里面的字符串是什么。

(gdb) x/s 0x603870
0x603870 :   "7 0"

是我们第四关选择的答案。那么在第四关后面加上字符串DrEvil开启隐藏关卡,查看secret_phase函数代码。

  401255:       e8 76 f9 ff ff          callq  400bd0 
  40125a:       48 89 c3                mov    %rax,%rbx
  40125d:       8d 40 ff                lea    -0x1(%rax),%eax
  401260:       3d e8 03 00 00          cmp    $0x3e8,%eax
  401265:       76 05                   jbe    40126c 
  401267:       e8 ce 01 00 00          callq  40143a 

断点在0x40125d看看寄存器里面的值是什么,猜测是我们输入的数字,因为之前有一个函数strtol

(gdb) set args psol.txt
(gdb) break *0x40125d
Breakpoint 1 at 0x40125d
(gdb) r
Starting program: /home/pipapa/bomb/bomb psol.txt
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2.  Keep going!
Halfway there!
So you got that one.  Try this one.
Good work!  On to the next...
Curses, you've found the secret phase!
But finding it and solving it are quite different...
1
Breakpoint 1, 0x000000000040125d in secret_phase ()
(gdb) info r eax
eax            0x1                 1

果然是我们输入的数字,结合上面跳转代码知道a - 1 <= 0x3e8,继续往下看。

   0x000000000040126c <+42>:    mov    %ebx,%esi
   0x000000000040126e <+44>:    mov    $0x6030f0,%edi
   0x0000000000401273 <+49>:    callq  0x401204 
   0x0000000000401278 <+54>:    cmp    $0x2,%eax
   0x000000000040127b <+57>:    je     0x401282 
   0x000000000040127d <+59>:    callq  0x40143a 

这时候%esi是我们输入的数字,%edi0x6030f0,然后调用fun7函数之后,要让返回值为2,我们搞明白fun7函数所完成的功能。

   0x0000000000401204 <+0>: sub    $0x8,%rsp
   0x0000000000401208 <+4>: test   %rdi,%rdi
   0x000000000040120b <+7>: je     0x401238 
   0x000000000040120d <+9>: mov    (%rdi),%edx
   0x000000000040120f <+11>:    cmp    %esi,%edx
   0x0000000000401211 <+13>:    jle    0x401220 
   0x0000000000401213 <+15>:    mov    0x8(%rdi),%rdi
   0x0000000000401217 <+19>:    callq  0x401204 
   0x000000000040121c <+24>:    add    %eax,%eax
   0x000000000040121e <+26>:    jmp    0x40123d 
   
   0x0000000000401220 <+28>:    mov    $0x0,%eax
   0x0000000000401225 <+33>:    cmp    %esi,%edx
   0x0000000000401227 <+35>:    je     0x40123d 
   0x0000000000401229 <+37>:    mov    0x10(%rdi),%rdi
   0x000000000040122d <+41>:    callq  0x401204 
   0x0000000000401232 <+46>:    lea    0x1(%rax,%rax,1),%eax
   0x0000000000401236 <+50>:    jmp    0x40123d 
   0x0000000000401238 <+52>:    mov    $0xffffffff,%eax
   0x000000000040123d <+57>:    add    $0x8,%rsp
   0x0000000000401241 <+61>:    retq 

%esi%edifun7函数的两个参数,%eaxfun7函数的返回值,可以看到可能是一个递归函数。

int fun7(di,si){
    if(di == 0) return 0xffffffff;
    if(*di <= si){
        a = 0;
        if(si == *di) return;
        di = *(di+0x10)
        a = fun7(di,si)
        return 2*a + 1;
    }else{
        di = *(di+0x8)
        a = fun7(di,si)
        return 2*a
    }
}

分析这个函数,fun7要么让di=*(di+0x10)要么让di=*(di+0x8),那么如果把它看成一个节点,则含有指向两个节点的指针,和一个值*di,它的结构可能如下(可能是颗树):

struct node{
    long val;           // 8个字节?
    struct node *l,*r;  // l和r都是4个字节
}

那么它的第一个节点的地址是0x6030f0,我们来画一下这颗树:

[图片上传失败...(image-47b81-1578127466578)]

若访问到空节点,则返回值会出现0xffffffff,所以我们只能在树上(软件原因树的最右分枝未补全)的节点选择。通过代码我们知道,若从节点x出发a初始值为0,若改节点为右儿子则a=2*a+1、为左儿子则a=a*2。所以2022节点都可以在到达根节点时返回a=2,输入任意一值即可通关。

Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2.  Keep going!
Halfway there!
So you got that one.  Try this one.
Good work!  On to the next...
Curses, you've found the secret phase!
But finding it and solving it are quite different...
20
Wow! You've defused the secret stage!
Congratulations! You've defused the bomb!

你可能感兴趣的:(《深入理解计算机系统/CSAPP》Bomb Lab)