《深入理解计算机系统》CSAPP_BombLab

Bomb Lab

操作系统:linux

调试工具:gdb

Link:CS:APP3e

Memory Dot 我的个人博客,欢迎来玩。

文章分为实验前准备复习拆炸弹三个模块。读者可以自取所需。

实验前准备

实验由phase_1 ~ phase_6组成,共6个阶段,
我们需要分别输入6个字符串密码,以便解除Dr. Evil的炸弹,
但只要有1个字符串输错,炸弹就会爆炸

那么如何知道6个正确的字符串呢?
我们需要分别查看phase_1 ~ phase_6的汇编代码,
而炸弹就存放在其中,我们要根据汇编代码小心地避开炸弹,分析出正确的字符串即可。
而分析需要用到学习的汇编知识常用的gdb指令以及linux终端指令
需要用到的linux终端指令
别忘了先进入文件地址中

  • objdump -d bomb > assembly.s
    反编译出bomb.c的汇编代码,并存放在assembly.s中(assembly.s是自己命名的)

  • gdb 文件名
    用gdb调试工具打开某个文件
    eg. gdb bomb 用gdb调试工具打开bomb.c文件
    我们就是要用gdb调试工具打开bomb.c文件后做实验

常用gdb指令:
gdb指令必须用gdb调试工具打开文件后才能调用

  • run (可简写为:r
    运行文件
  • quit (可简写为:q
    停止运行,跳出gdb调试工具
  • break 函数名 (可简写为:b 函数名
    在某个函数处打断点,以便之后用disas展示这个函数的汇编代码。
    eg. b phase_1 在phase_1函数打断点
  • disas 函数名
    展示这个函数的汇编代码(但必须先打断点)
    eg. disas phase_1 展示phase_1的汇编代码
  • x/NFU address or register
    用指定格式(FU)、指定数量(N)输出地址或者寄存器对应内容
    eg1. x/s 0x40139b 输出0x40139b对应内容
    eg2. x/s $rsi 输出%rsi对应内容
    eg3.x/4xw 0x6032d0 按24个十六进制4字节格式输出0x6032d0对应内容
  • p/F *address or *register
    用指定格式(F)打印地址或者寄存器对应内容(效果上,x/F = p/F
    eg1. p/x *(0x6032f0) 按十六进制打印0x6032f0对应内容
  • 格式F、单位U都与c语言类似,参考链接gdb:Format、Variables and Memory部分

复习

  • 测试和比较指令
    - ZF、OF、SF、CF条件码
    zero flag 零标志位;overflow flag 溢出标志位;sign flag 符号标志位;carry flag 进位标志位。
    - test S1,S2S1 & S2
    测试指令,进行位级与操作,若结果为0,返回ZF = 1;若结果非0,返回ZF = 0,但不会改变两个数的值。同时,需要特殊注意的是,可以对同一个寄存器使用test指令,例如test %eax,%eax
    - cmp S1,S2S2 - S1
    比较指令,进行减法操作,但不会改变两个数的值

  • 部分跳转指令

    • 不区分unsiged(无符号)、siged(有符号)跳转

      • jne
        根据ZF的值判断是否跳转,当ZF = 0才跳转。
      • je
        根据ZF的值判断是否跳转,当ZF = 1才跳转。
    • siged jump 指令

      • jlejump<=
        j:jump;l:less(小于);e:equal
      • jgejump<=
        j:jump;g:great(大于);e:equal
      • jgjump>
        j:jump;g:great(大于)
    • unsiged jump 指令

      • jbjump<
        j:jump;b:below(小于)
      • jbejump<=
        j:jump;b:below(小于);e:equal
      • jajump>
        j:jump;a:above(大于)
    • cmp与signed jump、unsigned jump的联系(原理与条件码有关,在此不解释了)

      • cmp S1,S2伴随signed jump指令使用时,S1置前,S2置后,例如:

           0x0000000000400f60 <+29>:	cmp    $0x1,%eax #若 1 > %eax,则不会爆炸
           0x0000000000400f63 <+32>:	jg     0x400f6a <phase_3+39> #大于跳转
        
      • cmp S1,S2伴随unsigned jump指令使用时,S2置前,S1置后,例如:

           0x0000000000400f6a <+39>:	cmpl   $0x7,0x8(%rsp) #若0x8(%rsp) > 7,则爆炸
           0x0000000000400f6f <+44>:	ja     0x400fad <phase_3+106> #大于跳转 
        
  • 部分二元操作

    • lea S,D (即D <-- S,等价于赋值算式D = S
      不是读取数据,是加载有效地址但和mov指令效果相同,常用于执行简洁的算术操作
    • sub S,D (即D <-- D - S,等价于赋值算式D -= S
      减法,D - S后赋值给D。
    • sar S,D (即D <-- D >> S,等价于赋值算式D >>= S
      算术右移,补符号位(sign),D >> S后赋值给D。注意:sar D,则默认是算术右移1
    • shr S,D (即D <-- D >> S,等价于赋值算式D >>= S
      算术右移,补0,D >> S后赋值给D。
  • 寄存器为空时,其值为0

  • %rdi
    系统往往要把一个值赋给%rdi后,才能当作入参(即输入的参数)

  • register
    注意:汇编代码为了优化常常使用同一个系列的register表示同一个变量
    例如:

    mov  $0x2,%rax
    sub  $0x1,%eax #此时,%eax = %rax = 2,该行执行%eax -= 1即%eax = 2 - 1
    
register(64bits) 作用
%rax 存放返回值
%rbx 被调用者保存(存放被调用者的地址,可以是函数地址,也可以是其它地址,callee save)
%rcx 第4个参数(存储参数,可以是数字字符参数,也可以是地址参数
%rdx 第3个参数
%rsi 第2个参数
%rdi 第1个参数(经常作入参使用)
%rbp 被调用者保存
%rsp 栈指针(用以开辟栈空间,注意入参的先进后出
%r8 第5个参数
%r9 第6个参数
%10 调用者保存(存放调用者函数的地址,可以是函数地址,也可以是其它地址,caller save)
%r11 调用者保存
%r12 被调用者保存
%r13 被调用者保存
%r14 被调用者保存
%r15 被调用者保存
  • 优化
    有些看似毫无作用的汇编代码,其实是在做优化

拆炸弹

正式拆炸弹之前需要注意到一点,我们输入的字符串都是放入到%rdi当中的。
(其实一般情况下,输入也是放到%rdi的)
也就是说,在每个phase调用时,默认%rdi这个寄存器存放着输入的字符串。
如何得到这点的呢?是通过分析main函数的汇编代码得出的。

  # main part 146 ~ 310
   0x0000000000400e32 <+146>:	callq  0x40149e <read_line> 
   0x0000000000400e37 <+151>:	mov    %rax,%rdi # input store %rdi
   0x0000000000400e3a <+154>:	callq  0x400ee0 <phase_1>
   0x0000000000400e3f <+159>:	callq  0x4015c4 <phase_defused>
   0x0000000000400e44 <+164>:	mov    $0x4023a8,%edi
   0x0000000000400e49 <+169>:	callq  0x400b10 <puts@plt>
   0x0000000000400e4e <+174>:	callq  0x40149e <read_line>
   0x0000000000400e53 <+179>:	mov    %rax,%rdi # input store %rdi
   0x0000000000400e56 <+182>:	callq  0x400efc <phase_2>
   0x0000000000400e5b <+187>:	callq  0x4015c4 <phase_defused>
   0x0000000000400e60 <+192>:	mov    $0x4022ed,%edi
   0x0000000000400e65 <+197>:	callq  0x400b10 <puts@plt>
   0x0000000000400e6a <+202>:	callq  0x40149e <read_line>
   0x0000000000400e6f <+207>:	mov    %rax,%rdi # input store %rdi
   0x0000000000400e72 <+210>:	callq  0x400f43 <phase_3>
   0x0000000000400e77 <+215>:	callq  0x4015c4 <phase_defused>
   0x0000000000400e7c <+220>:	mov    $0x40230b,%edi
   0x0000000000400e81 <+225>:	callq  0x400b10 <puts@plt>
   0x0000000000400e86 <+230>:	callq  0x40149e <read_line>
   0x0000000000400e8b <+235>:	mov    %rax,%rdi # input store %rdi
   0x0000000000400e8e <+238>:	callq  0x40100c <phase_4>
   0x0000000000400e93 <+243>:	callq  0x4015c4 <phase_defused>
   0x0000000000400e98 <+248>:	mov    $0x4023d8,%edi
   0x0000000000400e9d <+253>:	callq  0x400b10 <puts@plt>
   0x0000000000400ea2 <+258>:	callq  0x40149e <read_line>
   0x0000000000400ea7 <+263>:	mov    %rax,%rdi # input store %rdi
   0x0000000000400eaa <+266>:	callq  0x401062 <phase_5>
   0x0000000000400eaf <+271>:	callq  0x4015c4 <phase_defused>
   0x0000000000400eb4 <+276>:	mov    $0x40231a,%edi
   0x0000000000400eb9 <+281>:	callq  0x400b10 <puts@plt>
   0x0000000000400ebe <+286>:	callq  0x40149e <read_line>
   0x0000000000400ec3 <+291>:	mov    %rax,%rdi # input store %rdi
   0x0000000000400ec6 <+294>:	callq  0x4010f4 <phase_6>
   0x0000000000400ecb <+299>:	callq  0x4015c4 <phase_defused>
   0x0000000000400ed0 <+304>:	mov    $0x0,%eax 
   0x0000000000400ed5 <+309>:	pop    %rbx
   0x0000000000400ed6 <+310>:	retq   

分析:
我们可以看到,在每个phase调用前,都callq 0x40149e <read_line>,然后mov %rax,%rdi,这样子会将我们输入的字符串最终存到%rdi中。(我们是把字符串输入到read_line的,而read_line会返回到%rax中,mov操作将%rax存到%rdi注意%rax往往用来放返回值)

至于read_line的汇编代码我就不分析了,通过名字也可以看出:它是用来返回我们的输入值的

下面开始分析phase_1 ~ phase_6的汇编代码,
得出认为正确的字符串,并逐个解除炸弹

phase_1

查看phase_1的汇编代码:

   0x0000000000400ee0 <+0>:  sub    $0x8,%rsp #开辟8个字节的栈空间
   0x0000000000400ee4 <+4>:  mov    $0x402400,%esi #将立即数$0x402400赋值给%esi
   0x0000000000400ee9 <+9>:  callq  0x401338 <strings_not_equal> #调用函数strings_not_equal
   0x0000000000400eee <+14>: test   %eax,%eax #与操作(test),若为eax为0则跳转到phase_1+23;若为eax非0则顺序执行,调用explode_bomb,炸弹爆炸
   0x0000000000400ef0 <+16>: je     0x400ef7 <phase_1+23>
   0x0000000000400ef2 <+18>: callq  0x40143a <explode_bomb>
   0x0000000000400ef7 <+23>: add    $0x8,%rsp #释放8个字节的栈空间
   0x0000000000400efb <+27>: retq   #返回

分析:
重点就是<+14>的test指令,可以看出当%eax为0时才不会爆炸。也就是说函数strings_not_equal的返回值必须为0才行,根据名字我们可以猜测,字符串不相等时返回1,字符串相等时返回0。下面查看string_not_equal的汇编代码验证我们的猜测。

  #string_not_equal
   0x0000000000401338 <+0>:       push   %r12 #将%r12入栈
   0x000000000040133a <+2>:       push   %rbp #将%rbp入栈
   0x000000000040133b <+3>:       push   %rbx #将%rbx入栈
   0x000000000040133c <+4>:       mov    %rdi,%rbx #将%rdi赋给%rbx,不妨命名为第一参数指针,对应我们输入字符串的地址
   0x000000000040133f <+7>:       mov    %rsi,%rbp #将%rsi赋给%rbp,不妨命名为第二参数指针,对应地址是$0x402400
   0x0000000000401342 <+10>:	callq  0x40131b <string_length> #调用函数string_length,系统默认%rdi为入参,那么,就会计算第一参数的字符串的长度
   0x0000000000401347 <+15>:	mov    %eax,%r12d #将第一参数的字符串长度返回值保存到%r12d
   0x000000000040134a <+18>:	mov    %rbp,%rdi #把第二参数指针赋给%rdi,作为string_length的入参
   0x000000000040134d <+21>:	callq  0x40131b <string_length> #计算第二参数的字符串的长度
   0x0000000000401352 <+26>:	mov    $0x1,%edx #将1赋值给%edx,作为strings_not_equal的可能返回结果
   0x0000000000401357 <+31>:	cmp    %eax,%r12d #比较第一、第二参数的字符串的长度
   0x000000000040135a <+34>:	jne    0x40139b <strings_not_equal+99> #长度不相等时,跳转到<+99>,最终返回1,炸弹爆炸
   0x000000000040135c <+36>:	movzbl (%rbx),%eax #长度相等时,将第一参数的第一个字符串赋给%eax
   0x000000000040135f <+39>:	test   %al,%al #判断%al是否为空即0
   0x0000000000401361 <+41>:	je     0x401388 <strings_not_equal+80> #若%al为空,则跳转到<+80>,最终返回0,炸弹不会爆炸,这段我在优化,会顺序执行
   0x0000000000401363 <+43>:	cmp    0x0(%rbp),%al #取%rbp的第一个字符,和%al第一个字符做比较;此时(%rbp)对应$0x402400的第一个字符,%al是对应我们输入的字符串的第一个字符
   0x0000000000401366 <+46>:	je     0x401372 <strings_not_equal+58> #若相等跳转到<+58>
   0x0000000000401368 <+48>:	jmp    0x40138f <strings_not_equal+87> #若不相等跳转到<+87>,最终返回1,炸弹爆炸
   0x000000000040136a <+50>:	cmp    0x0(%rbp),%al #比较第一、第二参数的字符
   0x000000000040136d <+53>:	nopl   (%rax) #空操作,为了扩展%eax
   0x0000000000401370 <+56>:	jne    0x401396 <strings_not_equal+94> #不相等跳转到<+94>,最终返回1,炸弹爆炸
   0x0000000000401372 <+58>:	add    $0x1,%rbx #第一参数指针+1
   0x0000000000401376 <+62>:	add    $0x1,%rbp #第二参数指针+1
   0x000000000040137a <+66>:	movzbl (%rbx),%eax #零扩展%eax,并将第一参数的字符赋给%eax
   0x000000000040137d <+69>:	test   %al,%al #判断此时的%al是否为空,若空,则顺序执行,跳出循环,最终返回0,炸弹不会爆炸
   0x000000000040137f <+71>:	jne    0x40136a <strings_not_equal+50> #若不空,则继续比对字符
   0x0000000000401381 <+73>:	mov    $0x0,%edx
   0x0000000000401386 <+78>:	jmp    0x40139b <strings_not_equal+99>
   0x0000000000401388 <+80>:	mov    $0x0,%edx
   0x000000000040138d <+85>:	jmp    0x40139b <strings_not_equal+99>
   0x000000000040138f <+87>:	mov    $0x1,%edx
   0x0000000000401394 <+92>:	jmp    0x40139b <strings_not_equal+99> 
   0x0000000000401396 <+94>:	mov    $0x1,%edx
   0x000000000040139b <+99>:	mov    %edx,%eax #将%eax赋值为 0或 1,准备返回
   0x000000000040139d <+101>:	pop    %rbx #出栈
   0x000000000040139e <+102>:	pop    %rbp #出栈
   0x000000000040139f <+103>:	pop    %r12 #出栈
   0x00000000004013a1 <+105>:	retq   #返回

分析:
通过查看string_not_equal函数的汇编代码,可以找到,string_not_equal函数先是比较了输入字符串和地址0x402400对应的字符长度,再逐个比较字符,最终,字符相同返回0,字符不同返回1。我们可以看到,汇编进行不少优化。例如,如果第一个字符已经是不同的,就不用循环进行更多的操作。

key:由此可以知道,只要我们输入的字符和地址0x402400对应的字符相同即可。那么,我们通过gdb指令x/s 0x402400即可访问0x402400对应的字符。代码如下:

(gdb) x/s 0x402400
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!
//after input
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?

phase_2

我们先查看phase_2的汇编代码:

   0x0000000000400efc <+0>:	push   %rbp
   0x0000000000400efd <+1>:	push   %rbx
   0x0000000000400efe <+2>:	sub    $0x28,%rsp
   0x0000000000400f02 <+6>:	mov    %rsp,%rsi #将%rsp赋给%rsi
   0x0000000000400f05 <+9>:	callq  0x40145c <read_six_numbers>
   0x0000000000400f0a <+14>:	cmpl   $0x1,(%rsp) #(%rsp)必须是$0x1,否则爆炸
   0x0000000000400f0e <+18>:	je     0x400f30 <phase_2+52>
   0x0000000000400f10 <+20>:	callq  0x40143a <explode_bomb>
   0x0000000000400f15 <+25>:	jmp    0x400f30 <phase_2+52>
   0x0000000000400f17 <+27>:	mov    -0x4(%rbx),%eax
   0x0000000000400f1a <+30>:	add    %eax,%eax
   0x0000000000400f1c <+32>:	cmp    %eax,(%rbx)
   0x0000000000400f1e <+34>:	je     0x400f25 <phase_2+41>
   0x0000000000400f20 <+36>:	callq  0x40143a <explode_bomb>
   0x0000000000400f25 <+41>:	add    $0x4,%rbx
   0x0000000000400f29 <+45>:	cmp    %rbp,%rbx
   0x0000000000400f2c <+48>:	jne    0x400f17 <phase_2+27>
   0x0000000000400f2e <+50>:	jmp    0x400f3c <phase_2+64>
   0x0000000000400f30 <+52>:	lea    0x4(%rsp),%rbx
   0x0000000000400f35 <+57>:	lea    0x18(%rsp),%rbp
   0x0000000000400f3a <+62>:	jmp    0x400f17 <phase_2+27> #进入循环
   0x0000000000400f3c <+64>:	add    $0x28,%rsp
   0x0000000000400f40 <+68>:	pop    %rbx
   0x0000000000400f41 <+69>:	pop    %rbp
   0x0000000000400f42 <+70>:	retq   

callq 0x40145c <read_six_numbers>,我们查看这个函数的汇编代码:
(根据名字,我们猜测其作用是读取6个数字)

   0x000000000040145c <+0>:	sub    $0x18,%rsp
   0x0000000000401460 <+4>:	mov    %rsi,%rdx
   0x0000000000401463 <+7>:	lea    0x4(%rsi),%rcx
   0x0000000000401467 <+11>:	lea    0x14(%rsi),%rax
   0x000000000040146b <+15>:	mov    %rax,0x8(%rsp) #参数寄存器不够使用了,用栈帧
   0x0000000000401470 <+20>:	lea    0x10(%rsi),%rax
   0x0000000000401474 <+24>:	mov    %rax,(%rsp)
   0x0000000000401478 <+28>:	lea    0xc(%rsi),%r9
   0x000000000040147c <+32>:	lea    0x8(%rsi),%r8
   0x0000000000401480 <+36>:	mov    $0x4025c3,%esi
   0x0000000000401485 <+41>:	mov    $0x0,%eax
   0x000000000040148a <+46>:	callq  0x400bf0 <__isoc99_sscanf@plt>
   0x000000000040148f <+51>:	cmp    $0x5,%eax #即cmp  $0x5,$0x0,用以跳过炸弹
   0x0000000000401492 <+54>:	jg     0x401499 <read_six_numbers+61>
   0x0000000000401494 <+56>:	callq  0x40143a <explode_bomb>
   0x0000000000401499 <+61>:	add    $0x18,%rsp
   0x000000000040149d <+65>:	retq   

分析:
注意到phase_2<+6>已经将%rsp赋给%rsi,那么,对%rsi进行操作也就是之后对phase_2中的%rsp操作。
以下表格是对%rsi操作的展示:

%rsi 0x4(%rsi) 0x8(%rsi) 0xc(%rsi) 0x10(%rsi) 0x14(%rsi)
%rdx %rcx %r8 %r9 (%rsp) 0x8(%rsp)

注意,0x10 = 16D0x14 = 20D。十进制后缀D,Decimal.

可以看到,这样操作传递了6个地址参数,其地址间隔为4,最后两个因为参数寄存器不够用了,只能用栈帧%rsp。(注意,此处的%rsp是read_six_numbers自己开辟的,原先phase_2的%rsp已经中断
<+36>处,mov $0x4025c3,%esi,访问0x4025c3内容:

(gdb) x/s 0x4025c3
0x4025c3:	"%d %d %d %d %d %d"

可以知道%eax中存放着格式字符串
<+46>处,callq 0x400bf0 <__isoc99_sscanf@plt>,函数sscanf是c语言自带的函数,同scanf类似,作用是读取输入的字符串,并根据指定格式存储到指定地址。这告诉了我们的输入的字符串,应该是6个整型数字,而且之间用空格隔开。同时,它们存放在上述的6个参数地址中,也就是之后phase_2的%rsp按4递增地址的栈帧中。符合我们的猜测。

我们回到phase_2继续分析: 可以知道第一个参数数值(%rsp)必须是1,之后会进入一个循环。我们把这个循环单独拿出来看:

   0x0000000000400f17 <+27>:	mov    -0x4(%rbx),%eax
   0x0000000000400f1a <+30>:	add    %eax,%eax #此时的%eax是<+27>处的%eax的两倍
   0x0000000000400f1c <+32>:	cmp    %eax,(%rbx) #%eax与(%rbx)须相等,否则爆炸
   0x0000000000400f1e <+34>:	je     0x400f25 <phase_2+41>
   0x0000000000400f20 <+36>:	callq  0x40143a <explode_bomb>
   0x0000000000400f25 <+41>:	add    $0x4,%rbx #%rbx地址+4
   0x0000000000400f29 <+45>:	cmp    %rbp,%rbx #%rbp与%rbx相等时,即都为空时,跳出循环
   0x0000000000400f2c <+48>:	jne    0x400f17 <phase_2+27>
   0x0000000000400f2e <+50>:	jmp    0x400f3c <phase_2+64>
   0x0000000000400f30 <+52>:	lea    0x4(%rsp),%rbx #用%rbx替代0x4(%rsp)
   0x0000000000400f35 <+57>:	lea    0x18(%rsp),%rbp #将%rbp置空
   0x0000000000400f3a <+62>:	jmp    0x400f17 <phase_2+27> 

我们已经知道(%rsp) = 1,那么第一次循环中,<+32>处的%eax = 2,故只有(%rbx) = 0x4(%rsp) = 2,才不爆炸;第二次循环中,<+32>处的%eax = 4,故(%rbx) = 0x8(%rsp) = 4;第三次循环中,<+32>处的%eax = 8,故(%rbx) = 0xc(%rsp) = 8,可以看出是以1为首项,后一项是前一项两倍的递增数列,共6项,以此类推,有下表。

%rsi 0x4(%rsi) 0x8(%rsi) 0xc(%rsi) 0x10(%rsi) 0x14(%rsi)
%rdx %rcx %r8 %r9 (%rsp) 0x8(%rsp)
(%rsp) 0x4(%rsp) 0x8(%rsp) 0xc(%rsp) 0x10(%rsp) 0x14(%rsp)
1 2 4 8 16 32

key: 由此得知,phase_2该输入的字符串为1 2 4 8 16 32

//输入前
Phase 1 defused. How about the next one?
//after input
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2.  Keep going!

phase_3

phase_3的汇编代码如下:

   0x0000000000400f43 <+0>:	sub    $0x18,%rsp
   0x0000000000400f47 <+4>:	lea    0xc(%rsp),%rcx #第二个参数,栈帧先进后出
   0x0000000000400f4c <+9>:	lea    0x8(%rsp),%rdx #第一个参数
   0x0000000000400f51 <+14>:	mov    $0x4025cf,%esi #字符串格式"%d %d"
   0x0000000000400f56 <+19>:	mov    $0x0,%eax
   0x0000000000400f5b <+24>:	callq  0x400bf0 <__isoc99_sscanf@plt>
   0x0000000000400f60 <+29>:	cmp    $0x1,%eax #若 1 > %eax,则不会爆炸
   0x0000000000400f63 <+32>:	jg     0x400f6a <phase_3+39> #大于跳转
   0x0000000000400f65 <+34>:	callq  0x40143a <explode_bomb>
   0x0000000000400f6a <+39>:	cmpl   $0x7,0x8(%rsp) #若0x8(%rsp) > 7,则爆炸
   0x0000000000400f6f <+44>:	ja     0x400fad <phase_3+106> #大于跳转 
   0x0000000000400f71 <+46>:	mov    0x8(%rsp),%eax
   0x0000000000400f75 <+50>:	jmpq   *0x402470(,%rax,8) #*0x402470处地址 + 8 * %eax
   0x0000000000400f7c <+57>:	mov    $0xcf,%eax #将$0xcf赋给%eax,下面同理
   0x0000000000400f81 <+62>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f83 <+64>:	mov    $0x2c3,%eax
   0x0000000000400f88 <+69>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f8a <+71>:	mov    $0x100,%eax
   0x0000000000400f8f <+76>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f91 <+78>:	mov    $0x185,%eax
   0x0000000000400f96 <+83>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f98 <+85>:	mov    $0xce,%eax
   0x0000000000400f9d <+90>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400f9f <+92>:	mov    $0x2aa,%eax
   0x0000000000400fa4 <+97>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400fa6 <+99>:	mov    $0x147,%eax
   0x0000000000400fab <+104>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400fad <+106>:	callq  0x40143a <explode_bomb>
   0x0000000000400fb2 <+111>:	mov    $0x0,%eax
   0x0000000000400fb7 <+116>:	jmp    0x400fbe <phase_3+123>
   0x0000000000400fb9 <+118>:	mov    $0x137,%eax
   0x0000000000400fbe <+123>:	cmp    0xc(%rsp),%eax #第二个参数必须和%eax相等
   0x0000000000400fc2 <+127>:	je     0x400fc9 <phase_3+134>
   0x0000000000400fc4 <+129>:	callq  0x40143a <explode_bomb>
   0x0000000000400fc9 <+134>:	add    $0x18,%rsp
   0x0000000000400fcd <+138>:	retq   

分析:
访问0x4025cf,可知字符串格式为"%d %d",从而得知输入字符串是两个整数。根据<+39>: cmpl $0x7,0x8(%rsp)<+44>: ja 0x400fad <phase_3+106>可以知道0x8(%rsp) <= 7。那么,第一个参数%rdx取值要小于等于7
根据<+50>: jmpq *0x402470(,%rax,8)可以知道,跳转的地址是指针*0x402470的变换,增量为8,此时为0x8(%rsp)即为%eax。
下面我们用x/s指令逐个访问,指针地址是递增的,但对应存放的phase_3的地址并不是递增的。到这时,答案已经呼之欲出了。
(由此可以知道指针*0x402470指向一个存放phase_3部分地址的字符串。一开始我想的是对phase_3的部分地址直接进行增8操作,没有想到是对指针*0x402470进行增8操作,从而访问不同的phase_3中的地址,耽误了许久)

(gdb) x/s *0x402470
0x400f7c <phase_3+57>:	"\270", <incomplete sequence \317>
(gdb) x/s *(0x402470+8)
0x400fb9 <phase_3+118>:	"\270\067\001"
(gdb) x/s *(0x402470+16)
0x400f83 <phase_3+64>:	"\270\303\002"
(gdb) x/s *(0x402470+24)
0x400f8a <phase_3+71>:	"\270"
(gdb) x/s *(0x402470+32)
0x400f91 <phase_3+78>:	"\270\205\001"
(gdb) x/s *(0x402470+40)
0x400f98 <phase_3+85>:	"\270", <incomplete sequence \316>
(gdb) x/s *(0x402470+48)
0x400f9f <phase_3+92>:	"\270\252\002"
(gdb) x/s *(0x402470+56)
0x400fa6 <phase_3+99>:	"\270G\001"

phase_3继续分析:
根据<+123>: cmp 0xc(%rsp),%eax<+127>: je 0x400fc9 <phase_3+134>知道,第二个参数0xc(%rsp)必须和%eax相同,此时的%eax根据0x8(%rsp)不同而不同

key:
整理上述分析可以知道,共有8组答案如下:

0x8(%rsp) 0 1 2 3
x/s *0x402470 *(~ +8) *(~ +16) *(~ +24)
<phase_3+n> +57 +118 +64 +71
0xc(%rsp) H 0xcf 0x137 0x2c3 0x100
0xc(%rsp) D 207 311 707 256
0x8(%rsp) 4 5 6 7
x/s *(~ +32) *(~ +40) *(~ +48) *(~ +56)
<phase_3+n> +78 +85 +92 +99
0xc(%rsp) H 0x185 0xce 0x2aa 0x147
0xc(%rsp) D 389 206 682 327

(十六进制后缀,H,hexadecimal;十进制后缀,D,Decimal)

我们选择第8组答案7 327作为输入:

//输入前
That's number 2.  Keep going!
//after input
That's number 2.  Keep going!
7 327
Halfway there!

phase_4

phase_4的汇编代码如下:

   0x000000000040100c <+0>:	sub    $0x18,%rsp
   0x0000000000401010 <+4>:	lea    0xc(%rsp),%rcx #第二个参数,先进后出
   0x0000000000401015 <+9>:	lea    0x8(%rsp),%rdx #第一个参数,参一
   0x000000000040101a <+14>:	mov    $0x4025cf,%esi #格式字符串"%d %d"
   0x000000000040101f <+19>:	mov    $0x0,%eax
   0x0000000000401024 <+24>:	callq  0x400bf0 <__isoc99_sscanf@plt>
   0x0000000000401029 <+29>:	cmp    $0x2,%eax #返回值%eax必须为2,否则爆炸
   0x000000000040102c <+32>:	jne    0x401035 <phase_4+41>
   0x000000000040102e <+34>:	cmpl   $0xe,0x8(%rsp) #满足0x8(%rsp) <= 0xe,否则爆炸
   0x0000000000401033 <+39>:	jbe    0x40103a <phase_4+46> #小于等于
   0x0000000000401035 <+41>:	callq  0x40143a <explode_bomb>
   0x000000000040103a <+46>:	mov    $0xe,%edx #作为func4的入参
   0x000000000040103f <+51>:	mov    $0x0,%esi #作为func4的入参
   0x0000000000401044 <+56>:	mov    0x8(%rsp),%edi #将0x8(%rsp)赋给%edi,作为func4的入参
   0x0000000000401048 <+60>:	callq  0x400fce <func4>
   0x000000000040104d <+65>:	test   %eax,%eax #%eax必须为空即0,否则爆炸
   0x000000000040104f <+67>:	jne    0x401058 <phase_4+76>
   0x0000000000401051 <+69>:	cmpl   $0x0,0xc(%rsp) #0xc(%rsp)必须为0,否则爆炸
   0x0000000000401056 <+74>:	je     0x40105d <phase_4+81>
   0x0000000000401058 <+76>:	callq  0x40143a <explode_bomb>
   0x000000000040105d <+81>:	add    $0x18,%rsp
   0x0000000000401061 <+85>:	retq 

分析:仍旧是输入两个int参数,根据<+34>处可知,第一个参数0x8(%rsp)满足0xe > 0x8(%rsp),第二个参数0xc(%rsp)必须为0。但是第一个参数需要根据<func4>函数来确定。

<func4>的汇编代码如下:

第一次进入func4函数:

   0x0000000000400fce <+0>:	sub    $0x8,%rsp
   0x0000000000400fd2 <+4>:	mov    %edx,%eax # %eax=14
   0x0000000000400fd4 <+6>:	sub    %esi,%eax # %eax=14-0=14
   0x0000000000400fd6 <+8>:	mov    %eax,%ecx # %ecx=14
   0x0000000000400fd8 <+10>:	shr    $0x1f,%ecx #逻辑右移31位,%ecx=0
   0x0000000000400fdb <+13>:	add    %ecx,%eax # %eax=14
   0x0000000000400fdd <+15>:	sar    %eax #算术右移1位,补sign,%eax=7
   0x0000000000400fdf <+17>:	lea    (%rax,%rsi,1),%ecx %ecx=7+0=7
   0x0000000000400fe2 <+20>:	cmp    %edi,%ecx #第一个参数和%ecx=7比较
   0x0000000000400fe4 <+22>:	jle    0x400ff2 <func4+36> #第一个参数 <=7跳至<+36>
   0x0000000000400fe6 <+24>:	lea    -0x1(%rcx),%edx #否则%edx=7-1=6
   0x0000000000400fe9 <+27>:	callq  0x400fce <func4> #进入递归
   0x0000000000400fee <+32>:	add    %eax,%eax
   0x0000000000400ff0 <+34>:	jmp    0x401007 <func4+57>
   0x0000000000400ff2 <+36>:	mov    $0x0,%eax
   0x0000000000400ff7 <+41>:	cmp    %edi,%ecx #第一个参数和%ecx=7比较
   0x0000000000400ff9 <+43>:	jge    0x401007 <func4+57> #若第一个参数 >=7跳出func4
   0x0000000000400ffb <+45>:	lea    0x1(%rcx),%esi
   0x0000000000400ffe <+48>:	callq  0x400fce <func4>
   0x0000000000401003 <+53>:	lea    0x1(%rax,%rax,1),%eax
   0x0000000000401007 <+57>:	add    $0x8,%rsp
   0x000000000040100b <+61>:	retq   

分析:其实这个时候答案已经出来了,只要第一个参数为7%ecx=7相同即可。但显然func是一个递归函数,我们进入递归看看,还有什么答案。

第一次递归:(记为R1

   0x0000000000400fce <+0>:	sub    $0x8,%rsp
   0x0000000000400fd2 <+4>:	mov    %edx,%eax # %eax=6
   0x0000000000400fd4 <+6>:	sub    %esi,%eax # %eax=6-0=6
   0x0000000000400fd6 <+8>:	mov    %eax,%ecx # %ecx=6
   0x0000000000400fd8 <+10>:	shr    $0x1f,%ecx #逻辑右移31位,%ecx=0
   0x0000000000400fdb <+13>:	add    %ecx,%eax # %eax=6
   0x0000000000400fdd <+15>:	sar    %eax #算术右移1位,%eax=3
   0x0000000000400fdf <+17>:	lea    (%rax,%rsi,1),%ecx %ecx=3+0=3
   0x0000000000400fe2 <+20>:	cmp    %edi,%ecx #第一个参数和%ecx=3比较
   0x0000000000400fe4 <+22>:	jle    0x400ff2 <func4+36> #若第一个参数 <=3跳至<+36>
   0x0000000000400fe6 <+24>:	lea    -0x1(%rcx),%edx #否则,%edx=3-1=2,进入递归
   0x0000000000400fe9 <+27>:	callq  0x400fce <func4>
   0x0000000000400fee <+32>:	add    %eax,%eax
   0x0000000000400ff0 <+34>:	jmp    0x401007 <func4+57>
   0x0000000000400ff2 <+36>:	mov    $0x0,%eax
   0x0000000000400ff7 <+41>:	cmp    %edi,%ecx #第一个参数和%ecx=3比较
   0x0000000000400ff9 <+43>:	jge    0x401007 <func4+57> #若第一个参数 >=3跳出递归
   0x0000000000400ffb <+45>:	lea    0x1(%rcx),%esi
   0x0000000000400ffe <+48>:	callq  0x400fce <func4>
   0x0000000000401003 <+53>:	lea    0x1(%rax,%rax,1),%eax
   0x0000000000401007 <+57>:	add    $0x8,%rsp
   0x000000000040100b <+61>:	retq   

第一个参数为3%ecx=3相同即可。

第二次递归:(记为R2

   0x0000000000400fce <+0>:	sub    $0x8,%rsp
   0x0000000000400fd2 <+4>:	mov    %edx,%eax # %eax=2
   0x0000000000400fd4 <+6>:	sub    %esi,%eax # %eax=2-0=2
   0x0000000000400fd6 <+8>:	mov    %eax,%ecx # %ecx=2
   0x0000000000400fd8 <+10>:	shr    $0x1f,%ecx #逻辑右移31位,%ecx=0
   0x0000000000400fdb <+13>:	add    %ecx,%eax # %eax=2
   0x0000000000400fdd <+15>:	sar    %eax #算术右移1位,%eax=1
   0x0000000000400fdf <+17>:	lea    (%rax,%rsi,1),%ecx %ecx=1+0=1
   0x0000000000400fe2 <+20>:	cmp    %edi,%ecx #第一个参数和%ecx=1比较
   0x0000000000400fe4 <+22>:	jle    0x400ff2 <func4+36> #若第一个参数 <=1跳至<+36>
   0x0000000000400fe6 <+24>:	lea    -0x1(%rcx),%edx #否则,%edx=1-1=0,进入递归
   0x0000000000400fe9 <+27>:	callq  0x400fce <func4>
   0x0000000000400fee <+32>:	add    %eax,%eax
   0x0000000000400ff0 <+34>:	jmp    0x401007 <func4+57>
   0x0000000000400ff2 <+36>:	mov    $0x0,%eax
   0x0000000000400ff7 <+41>:	cmp    %edi,%ecx #第一个参数和%ecx=1比较
   0x0000000000400ff9 <+43>:	jge    0x401007 <func4+57> #若第一个参数 >=1跳出递归
   0x0000000000400ffb <+45>:	lea    0x1(%rcx),%esi
   0x0000000000400ffe <+48>:	callq  0x400fce <func4>
   0x0000000000401003 <+53>:	lea    0x1(%rax,%rax,1),%eax
   0x0000000000401007 <+57>:	add    $0x8,%rsp
   0x000000000040100b <+61>:	retq   

第一个参数为1%ecx=1相同即可。

第三次递归:(记为R3

   0x0000000000400fce <+0>:	sub    $0x8,%rsp
   0x0000000000400fd2 <+4>:	mov    %edx,%eax # %eax=0
   0x0000000000400fd4 <+6>:	sub    %esi,%eax # %eax=0-0=0
   0x0000000000400fd6 <+8>:	mov    %eax,%ecx # %ecx=0
   0x0000000000400fd8 <+10>:	shr    $0x1f,%ecx #逻辑右移31位,%ecx=0
   0x0000000000400fdb <+13>:	add    %ecx,%eax # %eax=0
   0x0000000000400fdd <+15>:	sar    %eax #算术右移1位,%eax=0
   0x0000000000400fdf <+17>:	lea    (%rax,%rsi,1),%ecx %ecx=0+0=0
   0x0000000000400fe2 <+20>:	cmp    %edi,%ecx #第一个参数和%ecx=0比较
   0x0000000000400fe4 <+22>:	jle    0x400ff2 <func4+36> #若第一个参数 <=0跳至<+36>
   0x0000000000400fe6 <+24>:	lea    -0x1(%rcx),%edx #否则,%edx=0-1=-1,进入递归
   0x0000000000400fe9 <+27>:	callq  0x400fce <func4>
   0x0000000000400fee <+32>:	add    %eax,%eax
   0x0000000000400ff0 <+34>:	jmp    0x401007 <func4+57>
   0x0000000000400ff2 <+36>:	mov    $0x0,%eax
   0x0000000000400ff7 <+41>:	cmp    %edi,%ecx #第一个参数和%ecx=0比较
   0x0000000000400ff9 <+43>:	jge    0x401007 <func4+57> #若第一个参数 >=0跳出递归
   0x0000000000400ffb <+45>:	lea    0x1(%rcx),%esi
   0x0000000000400ffe <+48>:	callq  0x400fce <func4>
   0x0000000000401003 <+53>:	lea    0x1(%rax,%rax,1),%eax
   0x0000000000401007 <+57>:	add    $0x8,%rsp
   0x000000000040100b <+61>:	retq   

第一个参数为0%ecx=0相同即可。

第四次递归:(记为R4

   0x0000000000400fce <+0>:	sub    $0x8,%rsp
   0x0000000000400fd2 <+4>:	mov    %edx,%eax # %eax=-1
   0x0000000000400fd4 <+6>:	sub    %esi,%eax # %eax=-1-0=1
   0x0000000000400fd6 <+8>:	mov    %eax,%ecx # %ecx=-1
   0x0000000000400fd8 <+10>:	shr    $0x1f,%ecx #逻辑右移31位,%ecx=1
   0x0000000000400fdb <+13>:	add    %ecx,%eax # %eax=2
   0x0000000000400fdd <+15>:	sar    %eax #算术右移1位,%eax=1
   0x0000000000400fdf <+17>:	lea    (%rax,%rsi,1),%ecx %ecx=1+0=1
   0x0000000000400fe2 <+20>:	cmp    %edi,%ecx #第一个参数和%ecx=1比较
   0x0000000000400fe4 <+22>:	jle    0x400ff2 <func4+36> #若第一个参数 <=1跳至<+36>
   0x0000000000400fe6 <+24>:	lea    -0x1(%rcx),%edx #否则,%edx=1-1=0,进入递归
   0x0000000000400fe9 <+27>:	callq  0x400fce <func4>
   0x0000000000400fee <+32>:	add    %eax,%eax
   0x0000000000400ff0 <+34>:	jmp    0x401007 <func4+57>
   0x0000000000400ff2 <+36>:	mov    $0x0,%eax
   0x0000000000400ff7 <+41>:	cmp    %edi,%ecx
   0x0000000000400ff9 <+43>:	jge    0x401007 <func4+57> #大于等于
   0x0000000000400ffb <+45>:	lea    0x1(%rcx),%esi
   0x0000000000400ffe <+48>:	callq  0x400fce <func4>
   0x0000000000401003 <+53>:	lea    0x1(%rax,%rax,1),%eax
   0x0000000000401007 <+57>:	add    $0x8,%rsp
   0x000000000040100b <+61>:	retq   

第一个参数为1%ecx=1相同即可。

第五次递归:(记为R5,此时R5 = R3

   0x0000000000400fce <+0>:	sub    $0x8,%rsp
   0x0000000000400fd2 <+4>:	mov    %edx,%eax # %eax=0
   0x0000000000400fd4 <+6>:	sub    %esi,%eax # %eax=0-0=0
   0x0000000000400fd6 <+8>:	mov    %eax,%ecx # %ecx=0
   0x0000000000400fd8 <+10>:	shr    $0x1f,%ecx #逻辑右移31位,%ecx=0
   0x0000000000400fdb <+13>:	add    %ecx,%eax # %eax=0
   0x0000000000400fdd <+15>:	sar    %eax #算术右移1位,%eax=0
   0x0000000000400fdf <+17>:	lea    (%rax,%rsi,1),%ecx %ecx=0+0=0
   0x0000000000400fe2 <+20>:	cmp    %edi,%ecx #第一个参数和%ecx=0比较
   0x0000000000400fe4 <+22>:	jle    0x400ff2 <func4+36> #若第一个参数 <=0跳至<+36>
   0x0000000000400fe6 <+24>:	lea    -0x1(%rcx),%edx #否则,%edx=0-1=-1,进入递归
   0x0000000000400fe9 <+27>:	callq  0x400fce <func4>
   0x0000000000400fee <+32>:	add    %eax,%eax
   0x0000000000400ff0 <+34>:	jmp    0x401007 <func4+57>
   0x0000000000400ff2 <+36>:	mov    $0x0,%eax
   0x0000000000400ff7 <+41>:	cmp    %edi,%ecx #第一个参数和%ecx=0比较
   0x0000000000400ff9 <+43>:	jge    0x401007 <func4+57> #若第一个参数 >=0跳出递归
   0x0000000000400ffb <+45>:	lea    0x1(%rcx),%esi
   0x0000000000400ffe <+48>:	callq  0x400fce <func4>
   0x0000000000401003 <+53>:	lea    0x1(%rax,%rax,1),%eax
   0x0000000000401007 <+57>:	add    $0x8,%rsp
   0x000000000040100b <+61>:	retq   

第一个参数为0%ecx=0相同即可。

由此可以知道,R4之后会一直在R3、R4之间无限循环,直到程序崩溃。那么,第一个参数的取值只能为:0 1 3 7

key:

得出4组答案为:

0x8(%rsp) 第一个输入参数 7 3 1 0
0xc(%rsp) 第二个输入参数 0 0 0 0

我们取0 0这组输入。

//输入前
Halfway there!
//after input
Halfway there!
0 0
So you got that one.  Try this one.

phase_5

phase_5的汇编代码如下:

   0x0000000000401062 <+0>:	push   %rbx
   0x0000000000401063 <+1>:	sub    $0x20,%rsp
   0x0000000000401067 <+5>:	mov    %rdi,%rbx #%rbx = 输入
   0x000000000040106a <+8>:	mov    %fs:0x28,%rax #%rax = 0x28 = 40D
   0x0000000000401073 <+17>:	mov    %rax,0x18(%rsp) #0x18(%rsp) = 0x28
   0x0000000000401078 <+22>:	xor    %eax,%eax #%eax = 0x28 ^ 0x28 = 0
   0x000000000040107a <+24>:	callq  0x40131b <string_length>
   0x000000000040107f <+29>:	cmp    $0x6,%eax #字符串长度是否为6
   0x0000000000401082 <+32>:	je     0x4010d2 <phase_5+112> 是,跳转至+112
   0x0000000000401084 <+34>:	callq  0x40143a <explode_bomb>否,爆炸
   0x0000000000401089 <+39>:	jmp    0x4010d2 <phase_5+112>
   0x000000000040108b <+41>:	movzbl (%rbx,%rax,1),%ecx #%ecx = %rbx + 0,第一次循环
   0x000000000040108f <+45>:	mov    %cl,(%rsp)
   0x0000000000401092 <+48>:	mov    (%rsp),%rdx #%rdx = (%rsp) = %cl
   0x0000000000401096 <+52>:	and    $0xf,%edx #%edx &= $0xf,%edx不变
   0x0000000000401099 <+55>:	movzbl 0x4024b0(%rdx),%edx #%edx = 0x4024b0+%rdx
   0x00000000004010a0 <+62>:	mov    %dl,0x10(%rsp,%rax,1) #%rsp+0x10(16D)+0 = %dl
   0x00000000004010a4 <+66>:	add    $0x1,%rax #%rax++
   0x00000000004010a8 <+70>:	cmp    $0x6,%rax #%rax = 6时,跳出循环
   0x00000000004010ac <+74>:	jne    0x40108b <phase_5+41> 否则,进入循环
   0x00000000004010ae <+76>:	movb   $0x0,0x16(%rsp) #0x16(%rsp) = 0
   0x00000000004010b3 <+81>:	mov    $0x40245e,%esi #%esi = 0x40245e
   0x00000000004010b8 <+86>:	lea    0x10(%rsp),%rdi #入参
   0x00000000004010bd <+91>:	callq  0x401338 <strings_not_equal>#字符相等返回值为0,否则为1
   0x00000000004010c2 <+96>:	test   %eax,%eax
   0x00000000004010c4 <+98>:	je     0x4010d9 <phase_5+119> #返回值为0,跳转至<+119>
   0x00000000004010c6 <+100>:	callq  0x40143a <explode_bomb> #返回值为1,爆炸
   0x00000000004010cb <+105>:	nopl   0x0(%rax,%rax,1)
   0x00000000004010d0 <+110>:	jmp    0x4010d9 <phase_5+119>
   0x00000000004010d2 <+112>:	mov    $0x0,%eax #%eax = 0
   0x00000000004010d7 <+117>:	jmp    0x40108b <phase_5+41>
   0x00000000004010d9 <+119>:	mov    0x18(%rsp),%rax
   0x00000000004010de <+124>:	xor    %fs:0x28,%rax
   0x00000000004010e7 <+133>:	je     0x4010ee <phase_5+140> #0x18(%rsp)=0x28,结束pahse_5
   0x00000000004010e9 <+135>:	callq  0x400b30 <__stack_chk_fail@plt> #否则进入fail函数
   0x00000000004010ee <+140>:	add    $0x20,%rsp
   0x00000000004010f2 <+144>:	pop    %rbx
   0x00000000004010f3 <+145>:	retq   

分析:
很显然,输入的字符串长度得为6。利用x/s指令,在<+55>处我们查看x/s 0x4024b0,给出一句嘲讽我们的话,"maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"很明显不是答案,也许是答案,也许不是答案,也许在暗示答案不能单单copy,我们无视他的嘲讽,对<+41> ~ <+74>处的循环进行专门分析。

第二次循环:

   0x000000000040108b <+41>:	movzbl (%rbx,%rax,1),%ecx #%ecx = %rbx + 1
   0x000000000040108f <+45>:	mov    %cl,(%rsp)
   0x0000000000401092 <+48>:	mov    (%rsp),%rdx #%rdx = (%rsp) = %cl
   0x0000000000401096 <+52>:	and    $0xf,%edx #%edx &= 0xf,位级与运算,取尾4位,范围为0 ~ 15
   0x0000000000401099 <+55>:	movzbl 0x4024b0(%rdx),%edx #%edx = 0x4024b0+%rdx
   0x00000000004010a0 <+62>:	mov    %dl,0x10(%rsp,%rax,1) #%rsp+0x10(16D)+1 = %dl
   0x00000000004010a4 <+66>:	add    $0x1,%rax #%rax++
   0x00000000004010a8 <+70>:	cmp    $0x6,%rax #%rax = 6时,跳出循环
   0x00000000004010ac <+74>:	jne    0x40108b <phase_5+41> 否则,进入循环

以此类推至第六次循环后,应当有<+41>: %ecx = %rbx+5<+62>: %rsp+0x10+5 = %dl。此时,%rbx输入,会有: %rsp+0x10+5 = %dl = %edx = 0x4024b0+(%rdx & 0xf)
上式等价于:= 0x4024b0+(%cl & 0xf) = 0x4024b0+((%rbx+5) & 0xf)
所有情况见下表。

%rsp+0x10+0 0x4024b0+%rbx+0(0x4024b0 + (第一个输入参数 & 0xf))
%rsp+0x10+1 0x4024b0+%rbx+1(0x4024b0 + (第二个输入参数 & 0xf))
%rsp+0x10+2 0x4024b0+%rbx+2(0x4024b0 + (第三个输入参数 & 0xf))
%rsp+0x10+3 0x4024b0+%rbx+3(0x4024b0 + (第四个输入参数 & 0xf))
%rsp+0x10+4 0x4024b0+%rbx+4(0x4024b0 + (第五个输入参数 & 0xf))
%rsp+0x10+5 0x4024b0+%rbx+5(0x4024b0 + (第六个输入参数 & 0xf))

可以看出%rsp+0x10的赋值是0x4024b0的偏移,偏移量范围为0 ~ 15,也就是嘲讽句的前16个字符,即maduiersnfotvbyl

我们回到phase_5函数继续分析,利用x/s指令,在<+81>处我们查看x/s 0x40245e,给出一个单词字符串,"flyers"(译为飞翔者们或者考熟的鸭子飞走了)。也就是说,<+86>处的入参必须与flyers相同。

根据maduiersnfotvbyl,取得flyers对应0x4024b0的偏移量是9、15、14、5、6、7,但是只能输入6个字符,如果直接输入91514567,超过6个字符会爆炸。

要解决这个问题,可以先把偏移量转换为二进制1001、1111、1110、0101、0110、0111。因为有%rbx & 0xf的位级操作,那么我们只要保证尾4位相同即可。这时可以联想到用来定义字符ASCII表,那么答案有许多种,我比较懒,就只找尾4位为11111110的字符。分别为/.,从而得到输入9/.567

key:

//输入前
So you got that one.  Try this one.
//after input
So you got that one.  Try this one.
9/.567
Good work!  On to the next...

phase_6

查看phase_6的汇编代码:
分析:
好家伙,一个电脑屏幕根本装不下全部代码。我们分成几个部分来看。

注意在循环开始前会有一些变量准备0x10 = 16D,0x14 = 20Dflag这个变量命名常用来当作循环是否结束的标志

Part1,读入了6个整数。

   0x00000000004010f4 <+0>:	push   %r14 #入栈
   0x00000000004010f6 <+2>:	push   %r13
   0x00000000004010f8 <+4>:	push   %r12
   0x00000000004010fa <+6>:	push   %rbp
   0x00000000004010fb <+7>:	push   %rbx
   0x00000000004010fc <+8>:	sub    $0x50,%rsp #开辟栈空间
   0x0000000000401100 <+12>:	mov    %rsp,%r13
   0x0000000000401103 <+15>:	mov    %rsp,%rsi
   0x0000000000401106 <+18>:	callq  0x40145c <read_six_numbers> #读入6个数字
   0x000000000040110b <+23>:	mov    %rsp,%r14
   0x000000000040110e <+26>:	mov    $0x0,%r12d #%r12d = 0

根据phase_2<read_six_numbers>分析可以知道,这段读入了6个整数,对栈帧%rsp偏移(偏移量为4)即是输入input[i],i取0 ~ 5。如下表所示:

(%rsp) 0x4(%rsp) 0x8(%rsp) 0xc(%rsp) 0x10(%rsp) 0x14(%rsp)
input[0] input[1] input[2] input[3] input[4] input[5]

Part2,保证6个输入整数(input[i],i取0 ~ 5)都不相同且范围为1 ~ 6

   #loop1,+32 ~ +93该循环说明:1<= input[i] <= 6,i取0 ~ 5
   0x0000000000401114 <+32>:	mov    %r13,%rbp #%rbp = %r13
   0x0000000000401117 <+35>:	mov    0x0(%r13),%eax #%eax = %r13 
   0x000000000040111b <+39>:	sub    $0x1,%eax #%eax = %eax - 1
   0x000000000040111e <+42>:	cmp    $0x5,%eax #比较5和%eax,
   0x0000000000401121 <+45>:	jbe    0x401128 <phase_6+52> #若%eax <= 5,跳转至<+52>
   0x0000000000401123 <+47>:	callq  0x40143a <explode_bomb> #否则,爆炸
   0x0000000000401128 <+52>:	add    $0x1,%r12d #%r12d++,flag值
   0x000000000040112c <+56>:	cmp    $0x6,%r12d #比较6和%r12d
   0x0000000000401130 <+60>:	je     0x401153 <phase_6+95> #若6 == %r12d,结束loop2
   0x0000000000401132 <+62>:	mov    %r12d,%ebx #否则,%ebx = %r12d
   #loop2,+65 ~ +87,该循环是loop1的子循环,用来是保证6个输入都不相等
   0x0000000000401135 <+65>:	movslq %ebx,%rax 
   0x0000000000401138 <+68>:	mov    (%rsp,%rax,4),%eax #%eax = %rsp+4*%rax
   0x000000000040113b <+71>:	cmp    %eax,0x0(%rbp) #比较%rsp+4*%rax和%rsp
   0x000000000040113e <+74>:	jne    0x401145 <phase_6+81> #若不相等,跳转至<+81>
   0x0000000000401140 <+76>:	callq  0x40143a <explode_bomb> #否则,爆炸
   0x0000000000401145 <+81>:	add    $0x1,%ebx #%ebx++,loop2取input[i]
   0x0000000000401148 <+84>:	cmp    $0x5,%ebx #比较5和%ebx
   0x000000000040114b <+87>:	jle    0x401135 <phase_6+65> #若%ebx <= 5,进入loop2
   0x000000000040114d <+89>:	add    $0x4,%r13 #%r13+4,loop1取input[i]
   0x0000000000401151 <+93>:	jmp    0x401114 <phase_6+32> #进入loop1

loop1确定了input[i]的取值范围为:1 ~ 6。loop2保证6个输入都不相等。
取值范围如何确定的?+39 ~ +45处得出,input[i] <= 6。同时,因为+45jbe 是对无符号数操作,若input[i]0 ,执行0 - 1 = -1在比较时会出错(溢出),所以不能取0。故只能取1 ~ 6

Part3,执行算式:input[i] = 7 - input[i]

   0x0000000000401153 <+95>:	lea    0x18(%rsp),%rsi #%rsi = 0x18(%rsp)
   0x0000000000401158 <+100>:	mov    %r14,%rax #%rax = %r14 = %rsp
   0x000000000040115b <+103>:	mov    $0x7,%ecx #%ecx = 7
   #loop3,+108 ~ +121,该循环执行算式:input[i] = 7 - input[i]
   0x0000000000401160 <+108>:	mov    %ecx,%edx
   0x0000000000401162 <+110>:	sub    (%rax),%edx #%edx = 7 - (%rax)
   0x0000000000401164 <+112>:	mov    %edx,(%rax) #(%rax) = %edx
   0x0000000000401166 <+114>:	add    $0x4,%rax #%rax+4,取input[i]
   0x000000000040116a <+118>:	cmp    %rsi,%rax #比较%rsi=0x18(%rsp)和%rax+4
   0x000000000040116d <+121>:	jne    0x401160 <phase_6+108>

因此为了方便,我们重新命名:sub_input[i] = 7 - input[i]

Part4,引入链表改变链表顺序

   ##注意,执行loop3后,sub_input[i] = 7 - input[i]
   0x000000000040116f <+123>:	mov    $0x0,%esi #%esi = 0
   #loop4,+128~+181,该循环执行赋值算式:%rsp + 8*i + 0x20 = $0x6032d0 + (8*(sub_input[i]-1))
   0x0000000000401174 <+128>:	jmp    0x401197 <phase_6+163>
   #loop5,+130~+139,该循环是loo4的子循环,执行0x6032d自加8,自加次数为sub_input[i]-1
   0x0000000000401176 <+130>:	mov    0x8(%rdx),%rdx #%rdx += 8
   0x000000000040117a <+134>:	add    $0x1,%eax #%eax++,flag值
   0x000000000040117d <+137>:	cmp    %ecx,%eax #比较input[i]和%eax
   0x000000000040117f <+139>:	jne    0x401176 <phase_6+130> #不相等,则继续loop5
   0x0000000000401181 <+141>:	jmp    0x401188 <phase_6+148> #相等,->+148,进入loop4
   0x0000000000401183 <+143>:	mov    $0x6032d0,%edx #%edx = 0x6032d0
   0x0000000000401188 <+148>:	mov    %rdx,0x20(%rsp,%rsi,2) #0x20+%rsp+2*%rsi = %rdx
   0x000000000040118d <+153>:	add    $0x4,%rsi #%rsi+4,flag值
   0x0000000000401191 <+157>:	cmp    $0x18,%rsi #比较0x18和flag值
   0x0000000000401195 <+161>:	je     0x4011ab <phase_6+183> #若相等,结束loop4
   0x0000000000401197 <+163>:	mov    (%rsp,%rsi,1),%ecx #取sub_input[i]
   0x000000000040119a <+166>:	cmp    $0x1,%ecx #比较1和sub_input[i]
   0x000000000040119d <+169>:	jle    0x401183 <phase_6+143> #若sub_input[i] <= 1,跳转至+143
   0x000000000040119f <+171>:	mov    $0x1,%eax #否则,%eax = 1
   0x00000000004011a4 <+176>:	mov    $0x6032d0,%edx #&edx = 0x6032d0
   0x00000000004011a9 <+181>:	jmp    0x401176 <phase_6+130> #进入loop5

首先我们查看0x6032d0,一开始用x/s指令都是乱码,如图所示。

(gdb) x/s 0x6032d0
0x6032d0 <node1>:	"L\001"
(gdb) x/100s 0x6032d0
0x6032d0 <node1>:	"L\001"
0x6032d3 <node1+3>:	""
0x6032d4 <node1+4>:	"\001"
0x6032d6 <node1+6>:	""
0x6032d7 <node1+7>:	""
0x6032d8 <node1+8>:	"\340\062`"
0x6032dc <node1+12>:	""
0x6032dd <node1+13>:	""
0x6032de <node1+14>:	""
0x6032df <node1+15>:	""
0x6032e0 <node2>:	"\250"
0x6032e2 <node2+2>:	""
...
--Type <RET> for more, q to quit, c to continue without paging--

但是注意到node1,说明其很有可能是链表,而不是字符串。
我们用x/24xw指令,按xw格式(十六进制4字节格式)输出24个数据。
如图所示,符合我们的猜测,就是链表,0x6032d0是head指针。
其中,node是节点地址,val是节点值,order是它的序列(一共6个),next是下个节点的地址(能够发现它刚好就是按照序列排列的,最后一个节点的next是0x0即NULL)。

(gdb) x/24xw 0x6032d0
0x6032d0 <node1>:	0x0000014c	0x00000001	0x006032e0	0x00000000
0x6032e0 <node2>:	0x000000a8	0x00000002	0x006032f0	0x00000000
0x6032f0 <node3>:	0x0000039c	0x00000003	0x00603300	0x00000000
0x603300 <node4>:	0x000002b3	0x00000004	0x00603310	0x00000000
0x603310 <node5>:	0x000001dd	0x00000005	0x00603320	0x00000000
0x603320 <node6>:	0x000001bb	0x00000006	0x00000000	0x00000000

##注释一下
node 		        val         order        next 
0x6032d0 <node1>:	0x0000014c	0x00000001	0x006032e0	0x00000000
0x6032e0 <node2>:	0x000000a8	0x00000002	0x006032f0	0x00000000
0x6032f0 <node3>:	0x0000039c	0x00000003	0x00603300	0x00000000
0x603300 <node4>:	0x000002b3	0x00000004	0x00603310	0x00000000
0x603310 <node5>:	0x000001dd	0x00000005	0x00603320	0x00000000
0x603320 <node6>:	0x000001bb	0x00000006	0x00000000	0x00000000
##注意到,node的间隔为0x10 = 16D
##我们还可以用c语言描述node:
struct node{
  int val;
  int order;
  node next;
}

那么,该段循环是怎么改变链表顺序的呢?首先引入栈帧%rsp + 8*i + 0x20,i取0 ~ 5,然后在+130 ~ +137 0x6032d0将随sub_input[i]的改变而改变,最后将改变后的链表逐个赋给栈帧。
即执行执行赋值算式:%rsp + 8*i + 0x20 = $0x6032d0 + (8*(sub_input[i]-1))
这样下来就能够重排链表了。但此处还没有给出改变链表顺序的标准是什么。
同时注意到一个重要的细节问题:改变时0x6032d0的间隔为8D,但我们需要的间隔为0x10 = 16D,如何解决?
事实上,间隔为8也没关系。只要部分+8的地址所存数值恰好为+16的地址即可。(不是所有地址都需要这样,另一部分地址执行两次+8就行了)
上述表述等价于三个等式
*(0x6032d0+8) = 0x6032e0*(0x6032f0+8) = 0x603200*(0x603310+8)= 0x603320 成立。
我们用p/x 指令打印数值看三个等式是否成立。(这个成立是phase_6已经设置好的,但不查看之前完全不知道)

(gdb) p/x *(0x6032d0+8)
$1 = 0x6032e0
(gdb) p/x *(0x6032f0+8)
$2 = 0x603300
(gdb) p/x *(0x603310+8)
$3 = 0x603320
#证明等式成立。

执行part4后,可以得出如下表格:

rec_node0 rec_node1 rec_node2 rec_node3 rec_node4 rec_node5
%rsp + 0x20 %rsp + 0x28 %rsp + 0x30 %rsp + 0x38 %rsp + 0x40 %rsp + 0x48

说明rec_node1 ~ rec_node6将逐个放入栈帧%rsp + 0x20 ~ %rsp + 0x48中。
注意:因为node0 ~ node5已经随sub_input而重新排列了,但我们还不知道是如何排列的。
所以,不妨命名为:rec_node0 ~ rec_node5,表明链表重组,也就是所谓的改变链表顺序
(rec:recombination)

同时也可以得出sub_input对应rec_node的表格:

sub_input[0] sub_input[1] sub_input[2] sub_input[3] sub_input[4] sub_input[5]
1 2 3 4 5 6
0x6032d0 0x6032e0 0x6032f0 0x603300 0x603310 0x603320

part5重新连接next

   0x00000000004011ab <+183>:	mov    0x20(%rsp),%rbx 
   0x00000000004011b0 <+188>:	lea    0x28(%rsp),%rax 
   0x00000000004011b5 <+193>:	lea    0x50(%rsp),%rsi #%rsi = 0x50(%rsp) = 0
   0x00000000004011ba <+198>:	mov    %rbx,%rcx
   #loop6,+201~+220,判断flag值的5次+初始1次,
   #执行6次赋值算式:0x8+(0x20(%rsp)+(j-1)*8) = (0x28(%rsp)+j*8),j取0 ~ 5
   #会将间隔为8的%0x28(%rsp) ~ 0x50(%rsp)的地址,逐个赋给间隔为8的%0x20(%rsp) ~ 0x48(%rsp)
   #loop6结束时,%rdx = 0x48(%rsp)
   0x00000000004011bd <+201>:	mov    (%rax),%rdx
   0x00000000004011c0 <+204>:	mov    %rdx,0x8(%rcx) #0x8(%rcx+(j-1)*8) = %rax+j*8
   0x00000000004011c4 <+208>:	add    $0x8,%rax #%rax += 8,记为j,每次增8,也是flag值
   0x00000000004011c8 <+212>:	cmp    %rsi,%rax #比较%rsi和%rax,执行5次
   0x00000000004011cb <+215>:	je     0x4011d2 <phase_6+222> #若相等,结束loop6
   0x00000000004011cd <+217>:	mov    %rdx,%rcx #否则,%rcx = %rdx
   0x00000000004011d0 <+220>:	jmp    0x4011bd <phase_6+201> #-> +201,继续loop6
   0x00000000004011d2 <+222>:	movq   $0x0,0x8(%rdx) #0x8(%rdx) = 0x50(%rsp) = 0

注意:

  • 0x50(%rsp) = 0 = NULL是next连接完才赋值的。
  • 这里利用栈帧%0x28(%rsp) ~ 0x50(%rsp)逐个赋给%0x20(%rsp) ~ 0x48(%rsp)
    改变的是rec_node ->next指向的地址,而不是rec_ndoe本身。也就是所谓的重新连接next
  • 之所以要重连next,也是因为node已经重排为rec_ndoe了。

part6,说明改变链表顺序的标准:val降序排列。(从大到小)

   0x00000000004011da <+230>:	mov    $0x5,%ebp #%ebp = 5
   #loop7,判断flag值的5次+初始1次,
   #共执行6次判断算式:(0x8+0x20+%rsp+k*8) >= (0x20+%rsp+k*8) ? 继续执行直到k=5结束:爆炸
   #即保证等式:node->val >= node->next->val成立。
   0x00000000004011df <+235>:	mov    0x8(%rbx),%rax 
   0x00000000004011e3 <+239>:	mov    (%rax),%eax 
   0x00000000004011e5 <+241>:	cmp    %eax,(%rbx) #比较(0x8+0x20+%rsp+k*8)和(0x20+%rsp+k*8)
   0x00000000004011e7 <+243>:	jge    0x4011ee <phase_6+250>  #若前者 >= 后者,-> +250
   0x00000000004011e9 <+245>:	callq  0x40143a <explode_bomb> #否则,爆炸
   0x00000000004011ee <+250>:	mov    0x8(%rbx),%rbx #取0x20+%rsp+k*8
   0x00000000004011f2 <+254>:	sub    $0x1,%ebp #%ebp--,flag值,执行5次
   0x00000000004011f5 <+257>:	jne    0x4011df <phase_6+235> #%ebp不为0时,继续loop7
   ##
   0x00000000004011f7 <+259>:	add    $0x50,%rsp #释放栈空间
   0x00000000004011fb <+263>:	pop    %rbx #出栈
   0x00000000004011fc <+264>:	pop    %rbp
   0x00000000004011fd <+265>:	pop    %r12
   0x00000000004011ff <+267>:	pop    %r13
   0x0000000000401201 <+269>:	pop    %r14
   0x0000000000401203 <+271>:	retq    

之所以按val降序排列,是因为必须满足(0x8+0x20+%rsp+k*8) >= (0x20+%rsp+k*8),k取0~5
node->val >= node->next->val成立,也就是链表中当前节点值大于下一个节点值

综上part1 ~ part6可以得出答案:(sub_input模7得到input

rec_node0 rec_node1 rec_node2 rec_node3 rec_node4 rec_node5
val 0x39c 0x2b3 0x1dd 0x1bb 0x14c 0x0a8
rec_node 0x6032f0 0x603300 0x603300 0x603300 0x6032d0 0x6032e0
sub_input 3 4 5 6 1 2
input 4 3 2 1 6 5

inuput为:4 3 2 1 6 5

ps:如果你仔细观察,你会发现,按val降序排列之后,order其实恰好对应着sub_input

key:

//before input
Good work!  On to the next...
//after input
Good work!  On to the next...
4 3 2 1 6 5
Congratulations! You've defused the bomb!
[Inferior 1 (process 46719) exited normally]

可恶啊!竟然还有个彩蛋关。什么?你说我怎么发现的,当然是“口口相传”啦。
(事实上可以在编译文件上查找ctrl+f到)

secret_phase

用到了二叉树。

查看phase_defused的汇编代码:(可以知道在phase_4中多输入个DrEvil即0 0 DrEvil才能进入彩蛋关)

   0x00000000004015c4 <+0>:	sub    $0x78,%rsp
   0x00000000004015c8 <+4>:	mov    %fs:0x28,%rax
   0x00000000004015d1 <+13>:	mov    %rax,0x68(%rsp)
   0x00000000004015d6 <+18>:	xor    %eax,%eax
   0x00000000004015d8 <+20>:	cmpl   $0x6,0x202181(%rip)   # 0x603760 <num_input_strings>
   0x00000000004015df <+27>:	jne    0x40163f <phase_defused+123>
   0x00000000004015e1 <+29>:	lea    0x10(%rsp),%r8
   0x00000000004015e6 <+34>:	lea    0xc(%rsp),%rcx
   0x00000000004015eb <+39>:	lea    0x8(%rsp),%rdx
   0x00000000004015f0 <+44>:	mov    $0x402619,%esi #输入格式%esi:"%d %d %s"
   0x00000000004015f5 <+49>:	mov    $0x603870,%edi #input
   0x00000000004015fa <+54>:	callq  0x400bf0 <__isoc99_sscanf@plt>
   0x00000000004015ff <+59>:	cmp    $0x3,%eax 
   0x0000000000401602 <+62>:	jne    0x401635 <phase_defused+113>
   0x0000000000401604 <+64>:	mov    $0x402622,%esi #%esi = DrEvil
   0x0000000000401609 <+69>:	lea    0x10(%rsp),%rdi
   0x000000000040160e <+74>:	callq  0x401338 <strings_not_equal>
   0x0000000000401613 <+79>:	test   %eax,%eax #字符串相同返回值为0,进入secret_phase
   0x0000000000401615 <+81>:	jne    0x401635 <phase_defused+113> 
   0x0000000000401617 <+83>:	mov    $0x4024f8,%edi
   0x000000000040161c <+88>:	callq  0x400b10 <puts@plt>
   0x0000000000401621 <+93>:	mov    $0x402520,%edi
   0x0000000000401626 <+98>:	callq  0x400b10 <puts@plt>
   0x000000000040162b <+103>:	mov    $0x0,%eax
   0x0000000000401630 <+108>:	callq  0x401242 <secret_phase>
   0x0000000000401635 <+113>:	mov    $0x402558,%edi
   0x000000000040163a <+118>:	callq  0x400b10 <puts@plt>
   0x000000000040163f <+123>:	mov    0x68(%rsp),%rax
   0x0000000000401644 <+128>:	xor    %fs:0x28,%rax
   0x000000000040164d <+137>:	je     0x401654 <phase_defused+144>
   0x000000000040164f <+139>:	callq  0x400b30 <__stack_chk_fail@plt>
   0x0000000000401654 <+144>:	add    $0x78,%rsp
   0x0000000000401658 <+148>:	retq 

分析:
我们对一些字符串进行访问,可以知道调用sscanf函数前得输入,其格式为"%d %d %s"但输入内容是空的,这说明我们还没输入。我们需要输入,但,是什么时候输入呢?

(gdb) x/s 0x402619
0x402619:	"%d %d %s"
(gdb) x/s 0x603870
0x603870 <input_strings+240>:	""
(gdb) x/s 0x402622
0x402622:	"DrEvil"

要确定输入是何时输入的,我们可以先在*0x4015f5处,即输入处打断点,然后run bomb文件,最后访问0x603870的内容。

Reading symbols from bomb...
(gdb) b *0x4015f5
Breakpoint 1 at 0x4015f5
(gdb) run
Starting program: /home/duile/Desktop/csapp_lab/bomb-handout/bomb 
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?
1 2 4 8 16 32
That's number 2.  Keep going!
7 327
Halfway there!
0 0 
So you got that one.  Try this one.
9/.567
Good work!  On to the next...
4 3 2 1 6 5

Breakpoint 1, 0x00000000004015f5 in phase_defused ()
(gdb) x/s 0x603870
0x603870 <input_strings+240>:	"0 0 "

此时有0x603870 <input_strings+240>: "0 0 ",这说明输入是在phase_4处进行的。其输入格式为"%d %d %s",同时有*(0x402622) == DrEvil,之后要进行字符比对且相同才进入secret_phase。那么我们大胆猜测,应该在phase_4处输入0 0 DrEvil才能进入彩蛋关。

#果然如此
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?
1 2 4 8 16 32
That's number 2.  Keep going!
7 327 
Halfway there!
0 0 DrEvil #将0 0替换为0 0 DrEvil
So you got that one.  Try this one.
9/.567
Good work!  On to the next...
4 3 2 1 6 5
Curses, you've found the secret phase!
But finding it and solving it are quite different...

开始正式分析,首先查看secret_phase的汇编代码:(说明调用fun7之后,返回值必须为2)

   0x0000000000401242 <+0>:	push   %rbx
   0x0000000000401243 <+1>:	callq  0x40149e <read_line>
   0x0000000000401248 <+6>:	mov    $0xa,%edx #base = 10D
   0x000000000040124d <+11>:	mov    $0x0,%esi
   0x0000000000401252 <+16>:	mov    %rax,%rdi #input
   0x0000000000401255 <+19>:	callq  0x400bd0 <strtol@plt>
   0x000000000040125a <+24>:	mov    %rax,%rbx #返回值rax赋给%rbx
   0x000000000040125d <+27>:	lea    -0x1(%rax),%eax
   0x0000000000401260 <+30>:	cmp    $0x3e8,%eax #满足-0x1(%rax) <= 0x3e8,跳转至+42,否则爆炸
   0x0000000000401265 <+35>:	jbe    0x40126c <secret_phase+42>
   0x0000000000401267 <+37>:	callq  0x40143a <explode_bomb> 
   0x000000000040126c <+42>:	mov    %ebx,%esi #esi是输入
   0x000000000040126e <+44>:	mov    $0x6030f0,%edi #rdi = 0x24
   0x0000000000401273 <+49>:	callq  0x401204 <fun7>
   0x0000000000401278 <+54>:	cmp    $0x2,%eax #返回值必须等于2,否则爆炸
   0x000000000040127b <+57>:	je     0x401282 <secret_phase+64>
   0x000000000040127d <+59>:	callq  0x40143a <explode_bomb>
   0x0000000000401282 <+64>:	mov    $0x402438,%edi
   0x0000000000401287 <+69>:	callq  0x400b10 <puts@plt>
   0x000000000040128c <+74>:	callq  0x4015c4 <phase_defused>
   0x0000000000401291 <+79>:	pop    %rbx
   0x0000000000401292 <+80>:	retq   

查看fun7的汇编代码:

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

分析:

这是一棵二叉树,思路如图所示:

方法一:实现eax = 2。(ps:递归调用顺序与返回顺序相反

eax = 0 --> eax = 2*eax+1 --> eax = 2*eax
0	    --> 1             --> 2
故返回顺序为:
D	    --> C             --> B
因而递归调用顺序为:(与返回顺序相反)
B	    --> C             --> D

下面描述调用过程:(ps:esi是input,每次调用完都会更新rid指向的地址

  • B:第一次调用fun7,(rdi) = (0x6030f0) = 0x24。同时输入要不满足(rdi) <= esi ,也就说输入小于0x24,才能走路径B。
    此次调用结束时更新rdi指向的地址,即:rdi+0x8 = 0x603110

  • C:第二次调用fun7,(rdi) = (0x603110) = 0x8。同时输入要满足(rdi) <= esi且不满足edx == esi,也就说输入大于0x8且不等于0x8,才能走路径C。
    此次调用结束时更新rdi指向的地址,即:rdi+0x10 = 0x603150

  • D:第三次调用fun7,(rdi) = (0x603150) = 0x16。同时输入要满足(rdi) <= esi且满足edx == esi,也就说输入等于0x16即20D,才能走路径D。
    三次调用结束,得出input为20

#这是gdb指令的访问过程:(使用的是打印指令,更清晰)
(gdb) p/a *(0x6030f0)
$1 = 0x24
(gdb) p/a *(0x6030f0+0x8)
$2 = 0x603110 <n21>
(gdb) p/a *(0x603110)
$3 = 0x8
(gdb) p/a *(0x603110+0x10)
$4 = 0x603150 <n32>
(gdb) p/a *(0x603150)
$5 = 0x16

方法二:实现eax = 2。

eax = 0	--> eax = 2*eax--> eax = 2*eax+1 --> eax = 2*eax
0	    --> 0          --> 1             --> 2
故返回顺序为:
D	    --> B          --> C             --> B
因而递归调用顺序为:(与返回顺序相反)
B	    --> C          --> B             --> D
我们按照递归调用访问(rdi)得出结果:0x14 = 20D(使用的是打印指令,更便捷)
(gdb) x/a 0x6030f0+0x8  #b
0x6030f8 <n1+8>:	0x603110 <n21>
(gdb) x/a 0x603110+0x10 #c
0x603120 <n21+16>:	0x603150 <n32>
(gdb) x/a 0x603150+0x8	#b
0x603158 <n32+8>:	0x603270 <n43>
(gdb) x/a 0x603270			#d
0x603270 <n43>:	0x14

key:我们取20这个答案。

//before input
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?
1 2 4 8 16 32
That's number 2.  Keep going!
7 327 
Halfway there!
0 0 DrEvil
So you got that one.  Try this one.
9/.567
Good work!  On to the next...
4 3 2 1 6 5
Curses, you've found the secret phase!
But finding it and solving it are quite different...
//after input
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?
1 2 4 8 16 32
That's number 2.  Keep going!
7 327 
Halfway there!
0 0 DrEvil
So you got that one.  Try this one.
9/.567
Good work!  On to the next...
4 3 2 1 6 5
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!
[Inferior 1 (process 48781) exited normally]

无聊地查看一下二叉树内容:(此处对解题不是很有必要)

(gdb) x/116wx 0x6030f0
0x6030f0 <n1>:	0x00000024	0x00000000	0x00603110	0x00000000
0x603100 <n1+16>:	0x00603130	0x00000000	0x00000000	0x00000000
0x603110 <n21>:	0x00000008	0x00000000	0x00603190	0x00000000
0x603120 <n21+16>:	0x00603150	0x00000000	0x00000000	0x00000000
0x603130 <n22>:	0x00000032	0x00000000	0x00603170	0x00000000
0x603140 <n22+16>:	0x006031b0	0x00000000	0x00000000	0x00000000
0x603150 <n32>:	0x00000016	0x00000000	0x00603270	0x00000000
0x603160 <n32+16>:	0x00603230	0x00000000	0x00000000	0x00000000
0x603170 <n33>:	0x0000002d	0x00000000	0x006031d0	0x00000000
0x603180 <n33+16>:	0x00603290	0x00000000	0x00000000	0x00000000
0x603190 <n31>:	0x00000006	0x00000000	0x006031f0	0x00000000
0x6031a0 <n31+16>:	0x00603250	0x00000000	0x00000000	0x00000000
0x6031b0 <n34>:	0x0000006b	0x00000000	0x00603210	0x00000000
0x6031c0 <n34+16>:	0x006032b0	0x00000000	0x00000000	0x00000000
0x6031d0 <n45>:	0x00000028	0x00000000	0x00000000	0x00000000
0x6031e0 <n45+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x6031f0 <n41>:	0x00000001	0x00000000	0x00000000	0x00000000
0x603200 <n41+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x603210 <n47>:	0x00000063	0x00000000	0x00000000	0x00000000
0x603220 <n47+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x603230 <n44>:	0x00000023	0x00000000	0x00000000	0x00000000
0x603240 <n44+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x603250 <n42>:	0x00000007	0x00000000	0x00000000	0x00000000
0x603260 <n42+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x603270 <n43>:	0x00000014	0x00000000	0x00000000	0x00000000
0x603280 <n43+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x603290 <n46>:	0x0000002f	0x00000000	0x00000000	0x00000000
0x6032a0 <n46+16>:	0x00000000	0x00000000	0x00000000	0x00000000
0x6032b0 <n48>:	0x000003e9	0x00000000	0x00000000	0x00000000

#c语言描述二叉树
typedef struct BiTNode{
    TElemType data;//数据
    struct BiTNode *light, *right;//左右孩子指针
} BiTNode, *BiTree;

整理后的图状展示:

└─ 36
   ├─ 8
   │  ├─ 6
   │  │  ├─ left: 1
   │  │  └─ right: 7
   │  └─ 22
   │     ├─ left: 20
   │     └─ right: 35
   └─ 50
      ├─ 45
      │  ├─ left: 40
      │  └─ right: 47
      └─ 107
         ├─ left: 99
         └─ right: 1001

结语

  • But you know, Alan, sometimes it's the very people who no one imagines anything of who do the things no one can imagine. ---The Imitation Game

  • 从9.27到10.11,总算完结了。期间有几天是没有写的。总共是8天,最后两关都用了2天。当然不是一整天,5、6个小时应该是有的。完全靠自己写的是phase_4,奈何没啥基础。前几个都在适应gdb是怎么调试的,后几个难度比较大,phase_5是没联系到ASCII,后两个是链表、二叉树都不熟悉。也参考了不少其它大佬的解答。

  • 自己的汇编阅读能力是提升了不少,后面看都是比较自然,但是联系到数据结构就比较不了解,还是得继续提高姿势水平。

  • 如有谬误,敬请指正。

posted @ 2021-09-28 23:41  duile  阅读(810)  评论(0编辑  收藏  举报