CS:APP Lab2 BombLab

BombLab需要的前置知识为反汇编和阅读汇编指令的能力,使用的主要工具有二:
Objdump --- 反汇编二进制文件
gdb --- 调试程序

First of all

先用objdump反汇编bomb文件:

objdump -d bomb > bomb.s

汇总常用的gdb命令:

gdb命令 描述
layout asm 显示汇编代码窗口
layout regs 显示寄存器窗口
b *address 在指定地址上打断点
x/s 打印ASCII字符串
x/nfu addr 以f格式打印从addr开始的n个长度单元为u的内存值,常用x/nxb
display /ni $pc 显示当程序停止时,将要执行的n条汇编指令
si/ni 执行一行汇编指令,区别是若存在函数调用,si进入函数内部,ni直接执行

接下来就可以开始手撕汇编了。

phase1

0000000000400ee0 <phase_1>:
  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 <strings_not_equal>
  400eee:   85 c0                   test   %eax,%eax
  400ef0:   74 05                   je     400ef7 <phase_1+0x17>
  400ef2:   e8 43 05 00 00          callq  40143a <explode_bomb>
  400ef7:   48 83 c4 08             add    $0x8,%rsp
  400efb:   c3                      retq   

可以看到调用<strings_not_equal>函数后执行了test指令,这条指令的结果直接影响了bomb是否爆炸,成功拆解phase1的条件为eax=0。看名字虽然能猜到这一函数的作用,但还是需要进一步分析<strings_not_equal>的功能以及对栈与寄存器的影响。

000000000040131b <string_length>:
  40131b:   80 3f 00                cmpb   $0x0,(%rdi)
  40131e:   74 12                   je     401332 <string_length+0x17>
  401320:   48 89 fa                mov    %rdi,%rdx
  401323:   48 83 c2 01             add    $0x1,%rdx
  401327:   89 d0                   mov    %edx,%eax
  401329:   29 f8                   sub    %edi,%eax
  40132b:   80 3a 00                cmpb   $0x0,(%rdx)
  40132e:   75 f3                   jne    401323 <string_length+0x8>
  401330:   f3 c3                   repz retq 
  401332:   b8 00 00 00 00          mov    $0x0,%eax
  401337:   c3                      retq   ;计算从(%rdi)开始的字符串长度

0000000000401338 <strings_not_equal>:
  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
  401342:   e8 d4 ff ff ff          callq  40131b <string_length>
  401347:   41 89 c4                mov    %eax,%r12d ;计算(%rdi)开始的字符串长度并保存到%12d
  40134a:   48 89 ef                mov    %rbp,%rdi
  40134d:   e8 c9 ff ff ff          callq  40131b <string_length>
  401352:   ba 01 00 00 00          mov    $0x1,%edx
  401357:   41 39 c4                cmp    %eax,%r12d ;比较(%rdi)和(%rsi)开始的字符串长度大小
  40135a:   75 3f                   jne    40139b <strings_not_equal+0x63>
  40135c:   0f b6 03                movzbl (%rbx),%eax
  40135f:   84 c0                   test   %al,%al ;若字符串大小都为0,则返回0
  401361:   74 25                   je     401388 <strings_not_equal+0x50>
  401363:   3a 45 00                cmp    0x0(%rbp),%al ;比较两个字符串中第一个字符
  401366:   74 0a                   je     401372 <strings_not_equal+0x3a>
  401368:   eb 25                   jmp    40138f <strings_not_equal+0x57>
  40136a:   3a 45 00                cmp    0x0(%rbp),%al ;比较两个字符串中下一个字符,不相等则返回1
  40136d:   0f 1f 00                nopl   (%rax)
  401370:   75 24                   jne    401396 <strings_not_equal+0x5e>
  401372:   48 83 c3 01             add    $0x1,%rbx
  401376:   48 83 c5 01             add    $0x1,%rbp 
  40137a:   0f b6 03                movzbl (%rbx),%eax 
  40137d:   84 c0                   test   %al,%al ;若遍历完字符串则返回0
  40137f:   75 e9                   jne    40136a <strings_not_equal+0x32>
  401381:   ba 00 00 00 00          mov    $0x0,%edx
  401386:   eb 13                   jmp    40139b <strings_not_equal+0x63>
  401388:   ba 00 00 00 00          mov    $0x0,%edx
  40138d:   eb 0c                   jmp    40139b <strings_not_equal+0x63>
  40138f:   ba 01 00 00 00          mov    $0x1,%edx
  401394:   eb 05                   jmp    40139b <strings_not_equal+0x63>
  401396:   ba 01 00 00 00          mov    $0x1,%edx
  40139b:   89 d0                   mov    %edx,%eax
  40139d:   5b                      pop    %rbx
  40139e:   5d                      pop    %rbp
  40139f:   41 5c                   pop    %r12
  4013a1:   c3                      retq   

分析<strings_not_equal>函数可知,这一函数比较了(%rdi)和(%rsi)开始的字符串,相等返回0,否则返回1。其中这一函数调用了<string_length>,它的作用为计算从(%rdi)开始的字符串长度。

回到phase1的汇编代码,可以看到(%rsi)为指定的0x402400,(%rdi)的值在main函数中由stdin输入给出(main函数中<read_line>使用了glibc相关的函数调用,且bomb.c也给出了源码,因此这里不再做分析)。所以phase1的答案为0x402400开始的字符串,用 gdb的x/s指令输出,结果为:

Border relations with Canada have never been better.

phase2

0000000000400efc <phase_2>:
  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 <read_six_numbers>
  400f0a:   83 3c 24 01             cmpl   $0x1,(%rsp) ;比较(%rsp)和1,不相等则bomb!
  400f0e:   74 20                   je     400f30 <phase_2+0x34>
  400f10:   e8 25 05 00 00          callq  40143a <explode_bomb>
  400f15:   eb 19                   jmp    400f30 <phase_2+0x34>
  400f17:   8b 43 fc                mov    -0x4(%rbx),%eax
  400f1a:   01 c0                   add    %eax,%eax
  400f1c:   39 03                   cmp    %eax,(%rbx) ;若(%rbx)不等于-0x4(%rbx)的两倍,则bomb!
  400f1e:   74 05                   je     400f25 <phase_2+0x29>
  400f20:   e8 15 05 00 00          callq  40143a <explode_bomb>
  400f25:   48 83 c3 04             add    $0x4,%rbx ;%rbx+=4
  400f29:   48 39 eb                cmp    %rbp,%rbx ;比较%rbx和上界%rbp,不相等则继续遍历
  400f2c:   75 e9                   jne    400f17 <phase_2+0x1b>
  400f2e:   eb 0c                   jmp    400f3c <phase_2+0x40>
  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 <phase_2+0x1b>
  400f3c:   48 83 c4 28             add    $0x28,%rsp
  400f40:   5b                      pop    %rbx
  400f41:   5d                      pop    %rbp
  400f42:   c3                      retq   

先不看<read_six_numbers>的函数调用(虽然看名字也知道作用),分析一下phase_2的汇编代码,可以看到该函数对(%rsp)的开始到0x18(%rsp)的长度为4byte的共6个数据做了比较,且要求每个数据都应当为前一个的2倍。初始的(%rsp)被赋值为1,所以6个数依次为:

1 2 4 8 16 32

<read_six_numbers>可能是为(%rsp)开始的地址填入6个int型的数据,而对应的汇编代码涉及到系统调用。所以在400f0a处打上断点,程序中断时查看(%rsp)开始的数据来验证我们的猜想。


可以看到,(%rsp)开始6个int型数据正是我们所输入的6个数,输入之前推断出的6个数就能完成对phase2的defuse。

phase3

0000000000400f43 <phase_3>:
  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 <phase_3+0x27>
  400f65:   e8 d0 04 00 00          callq  40143a <explode_bomb>
  400f6a:   83 7c 24 08 07          cmpl   $0x7,0x8(%rsp)
  400f6f:   77 3c                   ja     400fad <phase_3+0x6a>
  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 <phase_3+0x7b>
  400f83:   b8 c3 02 00 00          mov    $0x2c3,%eax
  400f88:   eb 34                   jmp    400fbe <phase_3+0x7b>
  400f8a:   b8 00 01 00 00          mov    $0x100,%eax
  400f8f:   eb 2d                   jmp    400fbe <phase_3+0x7b>
  400f91:   b8 85 01 00 00          mov    $0x185,%eax
  400f96:   eb 26                   jmp    400fbe <phase_3+0x7b>
  400f98:   b8 ce 00 00 00          mov    $0xce,%eax
  400f9d:   eb 1f                   jmp    400fbe <phase_3+0x7b>
  400f9f:   b8 aa 02 00 00          mov    $0x2aa,%eax
  400fa4:   eb 18                   jmp    400fbe <phase_3+0x7b>
  400fa6:   b8 47 01 00 00          mov    $0x147,%eax
  400fab:   eb 11                   jmp    400fbe <phase_3+0x7b>
  400fad:   e8 88 04 00 00          callq  40143a <explode_bomb>
  400fb2:   b8 00 00 00 00          mov    $0x0,%eax
  400fb7:   eb 05                   jmp    400fbe <phase_3+0x7b>
  400fb9:   b8 37 01 00 00          mov    $0x137,%eax
  400fbe:   3b 44 24 0c             cmp    0xc(%rsp),%eax
  400fc2:   74 05                   je     400fc9 <phase_3+0x86>
  400fc4:   e8 71 04 00 00          callq  40143a <explode_bomb>
  400fc9:   48 83 c4 18             add    $0x18,%rsp
  400fcd:   c3                      retq   

先看<sscanf>函数调用,首先scanf函数接受的参数为一个格式字符串和对应个数的数据指针,调用<sscanf>前赋了一个与地址很相似的值给%rsi并且清零了%rax,调用后将返回值与1作比较,若小于等于1则引爆bomb。<sscanf>函数的返回值为成功读入数据的个数,因此这里至少需要输入2个以上的数据。而格式字符串还不为我们所知,猜测%rsi中存放的地址与格式字符串相关,尝试用x/s输出这一位置的字符串:


结果验证了我们的猜想,但输入数据存放的位置还未确定。接下来phase3对0x8(%rsp)和7作了比较,且一开始使用了lea 0xc(%rsp),%rcxlea 0x8(%rsp),%rdx存放了这两个地址,很有可能是存放我们输入数据的位置。照之前的过程验证一下:

继续向下分析,0x8(%rsp)若大于7就会引爆bomb。jmpq *0x402470(,%rax,8)指令作了间接跳转,这是switch语句的特征。虽然汇编指令中没有跳转表可供参考,但可以通过逐步调试查看之后跳转的指令。第一个输入为0-7闭区间任意值共8种情况,第二个输入为跳转后%eax被赋予的值。这里选择了0作为第一个输入,跳转后%eax的值为0xcf,因此其中合法的一组输入为:

0 207

phase4

000000000040100c <phase_4>:
  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 <phase_4+0x29>
  40102e:   83 7c 24 08 0e          cmpl   $0xe,0x8(%rsp)
  401033:   76 05                   jbe    40103a <phase_4+0x2e>
  401035:   e8 00 04 00 00          callq  40143a <explode_bomb>
  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 <func4>
  40104d:   85 c0                   test   %eax,%eax
  40104f:   75 07                   jne    401058 <phase_4+0x4c>
  401051:   83 7c 24 0c 00          cmpl   $0x0,0xc(%rsp)
  401056:   74 05                   je     40105d <phase_4+0x51>
  401058:   e8 dd 03 00 00          callq  40143a <explode_bomb>
  40105d:   48 83 c4 18             add    $0x18,%rsp
  401061:   c3                      retq   

phase4的开始部分和phase3很接近,甚至使用了同一个格式字符串,由phase3可知我们读入的两个int型数据存放在了0x8(%rsp)和0x8(%rsp)的位置。紧接着将%rdx赋值为0xe,%rsi赋值为0x0后调用了函数func4。

0000000000400fce <func4>:           ;%rdx=0xe,%rsi=0x0,%rdi=0x8(%rsp)
  400fce:   48 83 ec 08             sub    $0x8,%rsp
  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 <func4+0x24>
  400fe6:   8d 51 ff                lea    -0x1(%rcx),%edx
  400fe9:   e8 e0 ff ff ff          callq  400fce <func4>
  400fee:   01 c0                   add    %eax,%eax
  400ff0:   eb 15                   jmp    401007 <func4+0x39>
  400ff2:   b8 00 00 00 00          mov    $0x0,%eax
  400ff7:   39 f9                   cmp    %edi,%ecx
  400ff9:   7d 0c                   jge    401007 <func4+0x39>
  400ffb:   8d 71 01                lea    0x1(%rcx),%esi
  400ffe:   e8 cb ff ff ff          callq  400fce <func4>
  401003:   8d 44 00 01             lea    0x1(%rax,%rax,1),%eax
  401007:   48 83 c4 08             add    $0x8,%rsp
  40100b:   c3                      retq   

分析一下func4的逻辑,我们的目的是让%eax为0从而返回后的test指令能够将ZF置为0。可以看到cmp %edi,%ecx前的指令都与我们的输入无关,打上断点让gdb调试到这里,%ecx的值为7,且%ecx<=%edi时跳转到mov $0x0,%eax,这正是我们所想要的。接着执行下面的指令,若满足%ecx>=%edi则直接跳转出func4,且%eax也为0。为了同时满足这两个指令的跳转条件,只需将%rdi置为7即可,而%rdi被赋值为我们输入的第一个数据,因此第一个输入可置为7。

func4结束后继续看phase4中的指令,接下来的逻辑就很容易了,只需要比较第二个参数与0的大小,不相等则引爆bomb,相等则成功defuse。因此phase4的一组合法输入为:

7 0

phase5

0000000000401062 <phase_5>:
  401062:   53                      push   %rbx
  401063:   48 83 ec 20             sub    $0x20,%rsp
  401067:   48 89 fb                mov    %rdi,%rbx ;%rbx=%rdi
  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 <string_length>
  40107f:   83 f8 06                cmp    $0x6,%eax
  401082:   74 4e                   je     4010d2 <phase_5+0x70>
  401084:   e8 b1 03 00 00          callq  40143a <explode_bomb>
  401089:   eb 47                   jmp    4010d2 <phase_5+0x70>
  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 ;所有1byte字符只保留最后四位
  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 <phase_5+0x29>
  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 <strings_not_equal> ;与0x40245e开始的字符串比较
  4010c2:   85 c0                   test   %eax,%eax
  4010c4:   74 13                   je     4010d9 <phase_5+0x77>
  4010c6:   e8 6f 03 00 00          callq  40143a <explode_bomb>
  4010cb:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
  4010d0:   eb 07                   jmp    4010d9 <phase_5+0x77>
  4010d2:   b8 00 00 00 00          mov    $0x0,%eax
  4010d7:   eb b2                   jmp    40108b <phase_5+0x29>
  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 <phase_5+0x8c>
  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   

可以看到0x18(%rsp)开始的8个byte用于作栈保护,这8个byte在执行过程中不能改动,否则会调用<__stack_chk_fail@plt>函数产生报错,最多能改动的内存为(%rsp)开始到0x18(%rsp)的共24的字节。接下来调用了<string_length>函数,若返回值不等于6则引爆bomb,所以我们的输入应当为一个长度为6的字符串。

继续向下分析,我们可以看到输入的6个1byte长的字符只保留了最后4bit,将剩下的4bit数与0x4024b0相加得到新的地址,把映射得到的新字符串存放在0x10(%rsp)中。然后将新字符串与0x40245e开始的字符串作比较,若不相等则引爆bomb。我们打印出这两个位置的字符串:


可以看到0x4024b0开始的16个字符虽然没有遍历整个字母表,但也足以表示我们需要的字符串'flyers'。我们先计算出6个符合要求的4bit串,然后查询ASCII码的字符表就能得到需要输入的字符串:

1001 1111 1110 0101 0110 0111

查询字母表后得到的一组输入为:

9?>%&'

phase6

00000000004010f4 <phase_6>:
  4010f4:   41 56                   push   %r14
  4010f6:   41 55                   push   %r13
  4010f8:   41 54                   push   %r12
  4010fa:   55                      push   %rbp
  4010fb:   53                      push   %rbx
  4010fc:   48 83 ec 50             sub    $0x50,%rsp
  401100:   49 89 e5                mov    %rsp,%r13
  401103:   48 89 e6                mov    %rsp,%rsi
  401106:   e8 51 03 00 00          callq  40145c <read_six_numbers>
  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 <phase_6+0x34>
  401123:   e8 12 03 00 00          callq  40143a <explode_bomb>
  401128:   41 83 c4 01             add    $0x1,%r12d
  40112c:   41 83 fc 06             cmp    $0x6,%r12d
  401130:   74 21                   je     401153 <phase_6+0x5f>
  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 <phase_6+0x51>
  401140:   e8 f5 02 00 00          callq  40143a <explode_bomb>
  401145:   83 c3 01                add    $0x1,%ebx
  401148:   83 fb 05                cmp    $0x5,%ebx
  40114b:   7e e8                   jle    401135 <phase_6+0x41>
  40114d:   49 83 c5 04             add    $0x4,%r13
  401151:   eb c1                   jmp    401114 <phase_6+0x20>
  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 <phase_6+0x6c>
  40116f:   be 00 00 00 00          mov    $0x0,%esi
  401174:   eb 21                   jmp    401197 <phase_6+0xa3>
  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 <phase_6+0x82>
  401181:   eb 05                   jmp    401188 <phase_6+0x94>
  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 <phase_6+0xb7>
  401197:   8b 0c 34                mov    (%rsp,%rsi,1),%ecx
  40119a:   83 f9 01                cmp    $0x1,%ecx
  40119d:   7e e4                   jle    401183 <phase_6+0x8f>
  40119f:   b8 01 00 00 00          mov    $0x1,%eax
  4011a4:   ba d0 32 60 00          mov    $0x6032d0,%edx
  4011a9:   eb cb                   jmp    401176 <phase_6+0x82>
  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 <phase_6+0xde>
  4011cd:   48 89 d1                mov    %rdx,%rcx
  4011d0:   eb eb                   jmp    4011bd <phase_6+0xc9>
  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 <phase_6+0xfa>
  4011e9:   e8 4c 02 00 00          callq  40143a <explode_bomb>
  4011ee:   48 8b 5b 08             mov    0x8(%rbx),%rbx
  4011f2:   83 ed 01                sub    $0x1,%ebp
  4011f5:   75 e8                   jne    4011df <phase_6+0xeb>
  4011f7:   48 83 c4 50             add    $0x50,%rsp
  4011fb:   5b                      pop    %rbx
  4011fc:   5d                      pop    %rbp
  4011fd:   41 5c                   pop    %r12
  4011ff:   41 5d                   pop    %r13
  401201:   41 5e                   pop    %r14
  401203:   c3                      retq   

蚌埠住了人肉逆不下去,直接上ida:

__int64 __fastcall phase_6(__int64 a1)
{
  int *v1; // r13
  int v2; // er12
  int v3; // ebx
  char *v4; // rax
  unsigned __int64 i; // rsi
  _QWORD *v6; // rdx
  int v7; // eax
  int v8; // ecx
  __int64 v9; // rbx
  char *v10; // rax
  __int64 j; // rcx
  __int64 v12; // rdx
  int v13; // ebp
  __int64 result; // rax
  int v15[6]; // [rsp+0h] [rbp-78h] BYREF
  char v16; // [rsp+18h] [rbp-60h] BYREF
  __int64 v17; // [rsp+20h] [rbp-58h]
  char v18; // [rsp+28h] [rbp-50h] BYREF
  char v19[40]; // [rsp+50h] [rbp-28h] BYREF

  v1 = v15;
  read_six_numbers(a1, v15);
  v2 = 0;
  while ( 1 )
  {
    if ( (unsigned int)(*v1 - 1) > 5 )
      explode_bomb(a1, v15);
    if ( ++v2 == 6 )
      break;
    v3 = v2;
    do
    {
      if ( *v1 == v15[v3] )
        explode_bomb(a1, v15);
      ++v3;
    }
    while ( v3 <= 5 );
    ++v1;
  }
  v4 = (char *)v15;
  do
  {
    *(_DWORD *)v4 = 7 - *(_DWORD *)v4;
    v4 += 4;
  }
  while ( v4 != &v16 );
  for ( i = 0LL; i != 24; i += 4LL )
  {
    v8 = v15[i / 4];
    if ( v8 <= 1 )
    {
      v6 = &node1;
    }
    else
    {
      v7 = 1;
      v6 = &node1;
      do
      {
        v6 = (_QWORD *)v6[1];
        ++v7;
      }
      while ( v7 != v8 );
    }
    *(__int64 *)((char *)&v17 + 2 * i) = (__int64)v6;
  }
  v9 = v17;
  v10 = &v18;
  for ( j = v17; ; j = v12 )
  {
    v12 = *(_QWORD *)v10;
    *(_QWORD *)(j + 8) = *(_QWORD *)v10;
    v10 += 8;
    if ( v10 == v19 )
      break;
  }
  *(_QWORD *)(v12 + 8) = 0LL;
  v13 = 5;
  do
  {
    result = **(unsigned int **)(v9 + 8);
    if ( *(_DWORD *)v9 < (int)result )
      explode_bomb(a1, v19);
    v9 = *(_QWORD *)(v9 + 8);
    --v13;
  }
  while ( v13 );
  return result;
}

输入的6个数据存放在(%rsp)开始的4*6=24个字节中,即v15[6]中,v16的地址为%rsp+0x18,v17的地址为%rsp+0x20,v18的地址为%rsp+0x28。
接下来从头到尾捋一捋逻辑,首先输入的6个数据都应该小于7且互不相等,否则在第一个循环中会引爆bomb。然后对v15[6]中的六个数据做了x=7-x的操作。关键的在下一个循环体中,v17开始的48个byte被赋了与v15[6]存放数据相关的值。为了理解这一循环体中的逻辑,我们打印从&node1开始的部分内存:


可以看到每个node i开始的第8个byte存放了node i+1的地址,node6的这一位置为0,这和我们熟知的单链表结构相似。再回到函数的逻辑,可以发现该循环体根据v15[6]的大小将v17开始的内存每一项赋值为单链表中node的地址,v15[6]中的数越大单链表遍历的越深。
下一个循环体的分析是最困难的,这个循环体将v17开始的6个node指针按次序拼接成一个单链表。结束后将尾结点的指针域置为null,所以我们整个过程实际上是将v17开始的结点按照输入给定的次序重新排列成单链表。
最后一个循环体中result为下一个结点的地址,只要当前结点的数据域值小于result中的数据就会引爆bomb,由此可以推断我们需要将1-6的整数根据单链表中数据域的数值大小重新排列:

3 4 5 6 1 2

经过x=7-x处理后为:

4 3 2 1 6 5

secret_phase

年后再补。
phase_6的汇编代码后紧接着就是secret _phase,但我们还不清楚secret_phase的入口位置,全局查找后得知,每个phase后调用的phase_defused函数中调用了secret_phase,ida中phase_defused函数如下:

unsigned __int64 phase_defused()
{
  char v1; // [rsp+8h] [rbp-70h] BYREF
  char v2; // [rsp+Ch] [rbp-6Ch] BYREF
  char v3[88]; // [rsp+10h] [rbp-68h] BYREF
  unsigned __int64 v4; // [rsp+68h] [rbp-10h]

  v4 = __readfsqword(0x28u);
  if ( num_input_strings == 6 )
  {
    if ( (unsigned int)__isoc99_sscanf(&unk_603870, "%d %d %s", &v1, &v2, v3) == 3
      && !(unsigned int)strings_not_equal(v3, "DrEvil") )
    {
      puts("Curses, you've found the secret phase!");
      puts("But finding it and solving it are quite different...");
      secret_phase();
    }
    puts("Congratulations! You've defused the bomb!");
  }
  return __readfsqword(0x28u) ^ v4;
}

这里的num_input_strings变量在read_line函数中被增加,而read_line函数在每个phase执行前被调用作为输入,这点在bomb.c文件中也有体现,read_line函数如下:

const char *read_line()
{
  int v0; // edx
  const char *v1; // rsi
  unsigned __int64 v2; // kr08_8
  int v3; // eax
  __int64 v4; // rax

  if ( !skip() )
  {
    if ( infile == (FILE *)stdin )
    {
      puts("Error: Premature EOF on stdin");
      exit(8);
    }
    if ( getenv("GRADE_BOMB") )
      exit(0);
    infile = (FILE *)stdin;
    if ( !skip() )
    {
      puts("Error: Premature EOF on stdin");
      exit(0);
    }
  }
  v0 = num_input_strings;
  v1 = (const char *)(80LL * num_input_strings + 6305664);
  v2 = strlen(v1) + 1;
  if ( (int)v2 - 1 > 78 )
  {
    puts("Error: Input line too long");
    v3 = num_input_strings++;
    v4 = 10LL * v3;
    input_strings[v4] = 0x636E7572742A2A2ALL;
    qword_603788[v4] = 0x2A2A2A64657461LL;
    explode_bomb();
  }
  *((_BYTE *)&input_strings[10 * num_input_strings - 1] + (int)v2 + 6) = 0;
  num_input_strings = v0 + 1;
  return v1;
}

可以看到若输入符合给定的规范,num_input_strings变量就会+1,也就是输入的字符串总数,所以phase_defused的逻辑为,在6个phase执行结束后,检查0x603870地址开始的三个参数是否与格式串"%d %d %s"匹配,且v3的值为DrEvil时进入secret_phase。我们在这个验证逻辑处打上断点并打印0x603870开始的字符串:


这和我们phase_4中的输入一致,因此phase_4的输入后添加字符串DrEvil才能进入secret_phase。secret_phase汇编代码如下:

0000000000401242 <secret_phase>:
  401242:   53                      push   %rbx
  401243:   e8 56 02 00 00          callq  40149e <read_line>
  401248:   ba 0a 00 00 00          mov    $0xa,%edx
  40124d:   be 00 00 00 00          mov    $0x0,%esi
  401252:   48 89 c7                mov    %rax,%rdi
  401255:   e8 76 f9 ff ff          callq  400bd0 <strtol@plt>
  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 <secret_phase+0x2a>
  401267:   e8 ce 01 00 00          callq  40143a <explode_bomb>
  40126c:   89 de                   mov    %ebx,%esi
  40126e:   bf f0 30 60 00          mov    $0x6030f0,%edi
  401273:   e8 8c ff ff ff          callq  401204 <fun7>
  401278:   83 f8 02                cmp    $0x2,%eax
  40127b:   74 05                   je     401282 <secret_phase+0x40>
  40127d:   e8 b8 01 00 00          callq  40143a <explode_bomb>
  401282:   bf 38 24 40 00          mov    $0x402438,%edi
  401287:   e8 84 f8 ff ff          callq  400b10 <puts@plt>
  40128c:   e8 33 03 00 00          callq  4015c4 <phase_defused>
  401291:   5b                      pop    %rbx
  401292:   c3                      retq   
  401293:   90                      nop
  401294:   90                      nop
  401295:   90                      nop
  401296:   90                      nop
  401297:   90                      nop
  401298:   90                      nop
  401299:   90                      nop
  40129a:   90                      nop
  40129b:   90                      nop
  40129c:   90                      nop
  40129d:   90                      nop
  40129e:   90                      nop
  40129f:   90                      nop

read_line函数调用后进入函数strtol,这个glibc库函数将字符串转化为长整型数据,三个参数依次为%rax,0,10,其中寄存器%rax保存着read_line得到的字符串的地址。接着对转化后产生的整数上界作了限制,然后调用函数fun7:

__int64 __fastcall fun7(__int64 a1, __int64 a2)
{
  __int64 result; // rax

  if ( !a1 )
    return 0xFFFFFFFFLL;
  if ( *(_DWORD *)a1 > (int)a2 )
    return 2 * (unsigned int)fun7(*(_QWORD *)(a1 + 8), a2);
  result = 0LL;
  if ( *(_DWORD *)a1 != (_DWORD)a2 )
    return 2 * (unsigned int)fun7(*(_QWORD *)(a1 + 16), a2) + 1;
  return result;
}

变量a1为secret_phase中%rdi存放的值0x6030f0,a2则为我们输入的字符串转化的整数。乍一看这一段代码只是作递归调用,但我们先将0x6030f0开始的64个Q_WORD打印出来:


每个地址的8byte和16byte偏移量的位置存放的值是内存地址,32个byte组成一个结点,存放着一个数据和两个结点地址,其结构为(盗了别人的图):

也就是二叉树的结构,所以fun7函数的逻辑可概括为:

  1. 如果结点地址为空,返回0xFFFFFFFF
  2. 如果数据值大于a2,返回2 * fun7(左结点,a2)
  3. 如果数据值小于a2,返回2 * fun7(右结点,a2) + 1
  4. 如果数据值等于a2,返回0

而我们secret_phase中最后需要的返回值为2,这个偶数应当由2 * (2 * 0 + 1)构造出来,拆解开就是左结点一次,右结点一次,最后相等,也就是0x16所存在的结点,换算成十进制即22。所以我们最终的input为:

Border relations with Canada have never been better.
1 2 4 8 16 32
0 207
7 0 DrEvil
9?>%&'
4 3 2 1 6 5
22

给一张bomb拆除截图:


Last

DrEvil还给我们留下了sig_handler以让我们一键通关,sig_handler函数逻辑如下:

void __noreturn sig_handler()
{
  puts("So you think you can stop the bomb with ctrl-c, do you?");
  sleep(3u);
  __printf_chk(1LL, "Well...");
  fflush(stdout);
  sleep(1u);
  puts("OK. :-)");
  exit(16);
}

运行bomb后直接ctrl_c,也可以看到sig_handler的作用:



那sig_handler是如何生效的呢,全局查找sig_handler后在initialize_bomb函数中发现了蛛丝马迹:

__sighandler_t initialize_bomb()
{
  return signal(2, (__sighandler_t)sig_handler);
}

这里使用了signal库函数将编号为2的信号与sig_handler绑定在一起,当触发这个信号时就调用sig_handler。查阅Linux信号列表后得知ctrl_c的编号为2:

SIGINT
产生方式: 键盘Ctrl+C
产生结果: 只对当前前台进程,和他的所在的进程组的每个进程都发送SIGINT信号,之后这些进程会执行信号处理程序再终止.

kill -l命令可查看Linux支持的信号列表:


SIGINT即为触发sig_handler的信号。

Conclusion

本来想用markdown语法完成二叉树的绘制,但简书对markdown的作图支持不是很友善,之后可能会熟悉之后选择线上工具绘图。但总之,BombLab堂堂结束!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容