Bomb Lab 操作系统:linux 
调试工具:gdb 
Link:CS:APP3e 
文章分为实验前准备 、复习 、拆炸弹 三个模块。读者可以自取所需。
  
实验前准备 实验由phase_1 ~ phase_6组成,共6个阶段, 我们需要分别输入6个字符串密码 ,以便解除Dr. Evil 的炸弹,但只要有1个字符串输错,炸弹就会爆炸 。
那么如何知道6个正确的字符串呢? 我们需要分别查看phase_1 ~ phase_6的汇编代码, 而炸弹就存放在其中,我们要根据汇编代码小心地避开炸弹,分析出正确的字符串即可。 而分析需要用到学习的汇编知识 、常用的gdb指令 以及linux终端指令 。 需要用到的linux终端指令 :别忘了先进入文件地址中 
常用gdb指令:gdb指令必须用gdb调试工具打开文件后才能调用 
run (可简写为:r)运行文件 。 
quit (可简写为:q)停止运行,跳出gdb调试工具  
break 函数名 (可简写为:b 函数名) 在某个函数处打断点,以便之后用disas展示这个函数的汇编代码。 eg. b phase_1    在phase_1函数打断点  
disas 函数名  展示这个函数的汇编代码(但必须先打断点,因为bomb文件已经运行了) 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 部分 
 
复习  
拆炸弹 在正式拆炸弹之前 需要注意到一点,我们输入的字符串都是放入到%rdi当中的。 (其实一般情况下,输入也是放到%rdi的) 也就是说,在每个phase调用时,默认%rdi这个寄存器存放着输入的字符串。 如何得到这点的呢?是通过分析main函数的汇编代码得出的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 # 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的汇编代码:
1 2 3 4 5 6 7 8 0x0000000000400ee0  <+0 >:  sub    $0x8 ,%rsp #开辟8 个字节的栈空间0x0000000000400ee4  <+4 >:  mov    $0x402400 ,%esi #将立即数$0x402400 赋值给%esi0x0000000000400ee9  <+9 >:  callq  0x401338  <strings_not_equal> #调用函数strings_not_equal0x0000000000400eee  <+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的汇编代码验证我们的猜测。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #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对应的字符。代码如下:
1 2 (gdb) x/s 0x402400  0x402400 :	"Border relations with Canada have never been better." 
 
那么,只要在运行界面输入Border relations with Canada have never been better.即可。(译为:与加拿大的边境关系从来没有这么好过。 )
1 2 3 Welcome to my fiendish little bomb. You have 6  phases with which to blow yourself up. Have a nice day! 
 
1 2 3 4 5 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的汇编代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0x0000000000400efc  <+0 >:	push   %rbp0x0000000000400efd  <+1 >:	push   %rbx0x0000000000400efe  <+2 >:	sub    $0x28 ,%rsp0x0000000000400f02  <+6 >:	mov    %rsp,%rsi #将%rsp赋给%rsi0x0000000000400f05  <+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),%eax0x0000000000400f1a  <+30 >:	add    %eax,%eax0x0000000000400f1c  <+32 >:	cmp    %eax,(%rbx)0x0000000000400f1e  <+34 >:	je     0x400f25  <phase_2+41 >0x0000000000400f20  <+36 >:	callq  0x40143a  <explode_bomb>0x0000000000400f25  <+41 >:	add    $0x4 ,%rbx0x0000000000400f29  <+45 >:	cmp    %rbp,%rbx0x0000000000400f2c  <+48 >:	jne    0x400f17  <phase_2+27 >0x0000000000400f2e  <+50 >:	jmp    0x400f3c  <phase_2+64 >0x0000000000400f30  <+52 >:	lea    0x4 (%rsp),%rbx0x0000000000400f35  <+57 >:	lea    0x18 (%rsp),%rbp0x0000000000400f3a  <+62 >:	jmp    0x400f17  <phase_2+27 > #进入循环0x0000000000400f3c  <+64 >:	add    $0x28 ,%rsp0x0000000000400f40  <+68 >:	pop    %rbx0x0000000000400f41  <+69 >:	pop    %rbp0x0000000000400f42  <+70 >:	retq   
 
callq  0x40145c <read_six_numbers>,我们查看这个函数的汇编代码: (根据名字,我们猜测其作用是读取6个数字)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 0x000000000040145c  <+0 >:	sub    $0x18 ,%rsp0x0000000000401460  <+4 >:	mov    %rsi,%rdx0x0000000000401463  <+7 >:	lea    0x4 (%rsi),%rcx0x0000000000401467  <+11 >:	lea    0x14 (%rsi),%rax0x000000000040146b  <+15 >:	mov    %rax,0x8 (%rsp) #参数寄存器不够使用了,用栈帧0x0000000000401470  <+20 >:	lea    0x10 (%rsi),%rax0x0000000000401474  <+24 >:	mov    %rax,(%rsp)0x0000000000401478  <+28 >:	lea    0xc (%rsi),%r90x000000000040147c  <+32 >:	lea    0x8 (%rsi),%r80x0000000000401480  <+36 >:	mov    $0x4025c3 ,%esi0x0000000000401485  <+41 >:	mov    $0x0 ,%eax0x000000000040148a  <+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 ,%rsp0x000000000040149d  <+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 = 16D,0x14 = 20D。十进制后缀D,Decimal.
可以看到,这样操作传递了6个地址参数,其地址间隔为4,最后两个因为参数寄存器不够用了,只能用栈帧%rsp。(注意,此处的%rsp是read_six_numbers自己开辟的,原先phase_2的%rsp已经中断 )<+36>处,mov    $0x4025c3,%esi,访问0x4025c3内容:
1 2 (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,之后会进入一个循环。我们把这个循环单独拿出来看:
1 2 3 4 5 6 7 8 9 10 11 12 0x0000000000400f17  <+27 >:	mov    -0x4 (%rbx),%eax0x0000000000400f1a  <+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
1 2 Phase 1  defused. How about the next one? 
 
1 2 3 4 Phase 1  defused. How about the next one? 1  2  4  8  16  32 That' s number 2.   Keep going! 
 
phase_3 phase_3的汇编代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 0x0000000000400f43  <+0 >:	sub    $0x18 ,%rsp0x0000000000400f47  <+4 >:	lea    0xc (%rsp),%rcx #第二个参数,栈帧先进后出0x0000000000400f4c  <+9 >:	lea    0x8 (%rsp),%rdx #第一个参数0x0000000000400f51  <+14 >:	mov    $0x4025cf ,%esi #字符串格式"%d %d" 0x0000000000400f56  <+19 >:	mov    $0x0 ,%eax0x0000000000400f5b  <+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),%eax0x0000000000400f75  <+50 >:	jmpq   *0x402470 (,%rax,8 ) #*0x402470 处地址 + 8  * %eax0x0000000000400f7c  <+57 >:	mov    $0xcf ,%eax #将$0xcf 赋给%eax,下面同理0x0000000000400f81  <+62 >:	jmp    0x400fbe  <phase_3+123 >0x0000000000400f83  <+64 >:	mov    $0x2c3 ,%eax0x0000000000400f88  <+69 >:	jmp    0x400fbe  <phase_3+123 >0x0000000000400f8a  <+71 >:	mov    $0x100 ,%eax0x0000000000400f8f  <+76 >:	jmp    0x400fbe  <phase_3+123 >0x0000000000400f91  <+78 >:	mov    $0x185 ,%eax0x0000000000400f96  <+83 >:	jmp    0x400fbe  <phase_3+123 >0x0000000000400f98  <+85 >:	mov    $0xce ,%eax0x0000000000400f9d  <+90 >:	jmp    0x400fbe  <phase_3+123 >0x0000000000400f9f  <+92 >:	mov    $0x2aa ,%eax0x0000000000400fa4  <+97 >:	jmp    0x400fbe  <phase_3+123 >0x0000000000400fa6  <+99 >:	mov    $0x147 ,%eax0x0000000000400fab  <+104 >:	jmp    0x400fbe  <phase_3+123 >0x0000000000400fad  <+106 >:	callq  0x40143a  <explode_bomb>0x0000000000400fb2  <+111 >:	mov    $0x0 ,%eax0x0000000000400fb7  <+116 >:	jmp    0x400fbe  <phase_3+123 >0x0000000000400fb9  <+118 >:	mov    $0x137 ,%eax0x0000000000400fbe  <+123 >:	cmp    0xc (%rsp),%eax #第二个参数必须和%eax相等0x0000000000400fc2  <+127 >:	je     0x400fc9  <phase_3+134 >0x0000000000400fc4  <+129 >:	callq  0x40143a  <explode_bomb>0x0000000000400fc9  <+134 >:	add    $0x18 ,%rsp0x0000000000400fcd  <+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中的地址,耽误了许久)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 (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作为输入:
1 2 That' s number 2.   Keep going! 
 
1 2 3 4 That' s number 2.   Keep going! 7  327 Halfway there! 
 
phase_4 phase_4的汇编代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0x000000000040100c  <+0 >:	sub    $0x18 ,%rsp0x0000000000401010  <+4 >:	lea    0xc (%rsp),%rcx #第二个参数,先进后出0x0000000000401015  <+9 >:	lea    0x8 (%rsp),%rdx #第一个参数,参一0x000000000040101a  <+14 >:	mov    $0x4025cf ,%esi #格式字符串"%d %d" 0x000000000040101f  <+19 >:	mov    $0x0 ,%eax0x0000000000401024  <+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 ,%rsp0x0000000000401061  <+85 >:	retq 
 
分析: 仍旧是输入两个int参数 ,根据<+34>处可知,第一个参数0x8(%rsp)满足0xe > 0x8(%rsp),第二个参数0xc(%rsp)必须为0。但是第一个参数需要根据<func4>函数来确定。
<func4>的汇编代码如下:
第一次进入func4函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0x0000000000400fce  <+0 >:	sub    $0x8 ,%rsp0x0000000000400fd2  <+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,%eax0x0000000000400ff0  <+34 >:	jmp    0x401007  <func4+57 >0x0000000000400ff2  <+36 >:	mov    $0x0 ,%eax0x0000000000400ff7  <+41 >:	cmp    %edi,%ecx #第一个参数和%ecx=7 比较0x0000000000400ff9  <+43 >:	jge    0x401007  <func4+57 > #若第一个参数 >=7 跳出func40x0000000000400ffb  <+45 >:	lea    0x1 (%rcx),%esi0x0000000000400ffe  <+48 >:	callq  0x400fce  <func4>0x0000000000401003  <+53 >:	lea    0x1 (%rax,%rax,1 ),%eax0x0000000000401007  <+57 >:	add    $0x8 ,%rsp0x000000000040100b  <+61 >:	retq   
 
分析: 其实这个时候答案已经出来了,只要第一个参数为7和%ecx=7相同即可 。但显然func是一个递归函数,我们进入递归看看,还有什么答案。
第一次递归:(记为R1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0x0000000000400fce  <+0 >:	sub    $0x8 ,%rsp0x0000000000400fd2  <+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,%eax0x0000000000400ff0  <+34 >:	jmp    0x401007  <func4+57 >0x0000000000400ff2  <+36 >:	mov    $0x0 ,%eax0x0000000000400ff7  <+41 >:	cmp    %edi,%ecx #第一个参数和%ecx=3 比较0x0000000000400ff9  <+43 >:	jge    0x401007  <func4+57 > #若第一个参数 >=3 跳出递归0x0000000000400ffb  <+45 >:	lea    0x1 (%rcx),%esi0x0000000000400ffe  <+48 >:	callq  0x400fce  <func4>0x0000000000401003  <+53 >:	lea    0x1 (%rax,%rax,1 ),%eax0x0000000000401007  <+57 >:	add    $0x8 ,%rsp0x000000000040100b  <+61 >:	retq   
 
第一个参数为3和%ecx=3相同即可。 
第二次递归:(记为R2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0x0000000000400fce  <+0 >:	sub    $0x8 ,%rsp0x0000000000400fd2  <+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,%eax0x0000000000400ff0  <+34 >:	jmp    0x401007  <func4+57 >0x0000000000400ff2  <+36 >:	mov    $0x0 ,%eax0x0000000000400ff7  <+41 >:	cmp    %edi,%ecx #第一个参数和%ecx=1 比较0x0000000000400ff9  <+43 >:	jge    0x401007  <func4+57 > #若第一个参数 >=1 跳出递归0x0000000000400ffb  <+45 >:	lea    0x1 (%rcx),%esi0x0000000000400ffe  <+48 >:	callq  0x400fce  <func4>0x0000000000401003  <+53 >:	lea    0x1 (%rax,%rax,1 ),%eax0x0000000000401007  <+57 >:	add    $0x8 ,%rsp0x000000000040100b  <+61 >:	retq   
 
第一个参数为1和%ecx=1相同即可。 
第三次递归:(记为R3)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0x0000000000400fce  <+0 >:	sub    $0x8 ,%rsp0x0000000000400fd2  <+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,%eax0x0000000000400ff0  <+34 >:	jmp    0x401007  <func4+57 >0x0000000000400ff2  <+36 >:	mov    $0x0 ,%eax0x0000000000400ff7  <+41 >:	cmp    %edi,%ecx #第一个参数和%ecx=0 比较0x0000000000400ff9  <+43 >:	jge    0x401007  <func4+57 > #若第一个参数 >=0 跳出递归0x0000000000400ffb  <+45 >:	lea    0x1 (%rcx),%esi0x0000000000400ffe  <+48 >:	callq  0x400fce  <func4>0x0000000000401003  <+53 >:	lea    0x1 (%rax,%rax,1 ),%eax0x0000000000401007  <+57 >:	add    $0x8 ,%rsp0x000000000040100b  <+61 >:	retq   
 
第一个参数为0和%ecx=0相同即可。 
第四次递归:(记为R4)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0x0000000000400fce  <+0 >:	sub    $0x8 ,%rsp0x0000000000400fd2  <+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,%eax0x0000000000400ff0  <+34 >:	jmp    0x401007  <func4+57 >0x0000000000400ff2  <+36 >:	mov    $0x0 ,%eax0x0000000000400ff7  <+41 >:	cmp    %edi,%ecx0x0000000000400ff9  <+43 >:	jge    0x401007  <func4+57 > #大于等于0x0000000000400ffb  <+45 >:	lea    0x1 (%rcx),%esi0x0000000000400ffe  <+48 >:	callq  0x400fce  <func4>0x0000000000401003  <+53 >:	lea    0x1 (%rax,%rax,1 ),%eax0x0000000000401007  <+57 >:	add    $0x8 ,%rsp0x000000000040100b  <+61 >:	retq   
 
第一个参数为1和%ecx=1相同即可。 
第五次递归:(记为R5,此时R5 = R3)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0x0000000000400fce  <+0 >:	sub    $0x8 ,%rsp0x0000000000400fd2  <+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,%eax0x0000000000400ff0  <+34 >:	jmp    0x401007  <func4+57 >0x0000000000400ff2  <+36 >:	mov    $0x0 ,%eax0x0000000000400ff7  <+41 >:	cmp    %edi,%ecx #第一个参数和%ecx=0 比较0x0000000000400ff9  <+43 >:	jge    0x401007  <func4+57 > #若第一个参数 >=0 跳出递归0x0000000000400ffb  <+45 >:	lea    0x1 (%rcx),%esi0x0000000000400ffe  <+48 >:	callq  0x400fce  <func4>0x0000000000401003  <+53 >:	lea    0x1 (%rax,%rax,1 ),%eax0x0000000000401007  <+57 >:	add    $0x8 ,%rsp0x000000000040100b  <+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这组输入。
 
1 2 3 4 Halfway there! 0  0 So you got that one.  Try this  one. 
 
phase_5 phase_5的汇编代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 0x0000000000401062  <+0 >:	push   %rbx0x0000000000401063  <+1 >:	sub    $0x20 ,%rsp0x0000000000401067  <+5 >:	mov    %rdi,%rbx #%rbx = 输入0x000000000040106a  <+8 >:	mov    %fs:0x28 ,%rax #%rax = 0x28  = 40 D0x0000000000401073  <+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) = %cl0x0000000000401096  <+52 >:	and     $0xf ,%edx #%edx &= $0xf ,%edx不变0x0000000000401099  <+55 >:	movzbl 0x4024b0 (%rdx),%edx #%edx = 0x4024b0 +%rdx0x00000000004010a0  <+62 >:	mov    %dl,0x10 (%rsp,%rax,1 ) #%rsp+0x10 (16 D)+0  = %dl0x00000000004010a4  <+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,%eax0x00000000004010c4  <+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),%rax0x00000000004010de  <+124 >:	xor     %fs:0x28 ,%rax0x00000000004010e7  <+133 >:	je     0x4010ee  <phase_5+140 > #0x18 (%rsp)=0x28 ,结束pahse_50x00000000004010e9  <+135 >:	callq  0x400b30  <__stack_chk_fail@plt> #否则进入fail函数0x00000000004010ee  <+140 >:	add    $0x20 ,%rsp0x00000000004010f2  <+144 >:	pop    %rbx0x00000000004010f3  <+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>处的循环进行专门分析。
第二次循环:
1 2 3 4 5 6 7 8 9 0x000000000040108b  <+41 >:	movzbl (%rbx,%rax,1 ),%ecx #%ecx = %rbx + 1 0x000000000040108f  <+45 >:	mov    %cl,(%rsp)0x0000000000401092  <+48 >:	mov    (%rsp),%rdx #%rdx = (%rsp) = %cl0x0000000000401096  <+52 >:	and     $0xf ,%edx #%edx &= 0xf ,位级与运算,取尾4 位,范围为0  ~ 15 0x0000000000401099  <+55 >:	movzbl 0x4024b0 (%rdx),%edx #%edx = 0x4024b0 +%rdx0x00000000004010a0  <+62 >:	mov    %dl,0x10 (%rsp,%rax,1 ) #%rsp+0x10 (16 D)+1  = %dl0x00000000004010a4  <+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位为1111和1110的字符。分别为/和.,从而得到输入9/.567。
key: 
1 2 So you got that one.  Try this  one. 
 
1 2 3 4 So you got that one.  Try this  one. 9 /.567 Good work!  On to the next... 
 
phase_6 查看phase_6的汇编代码:分析:  好家伙,一个电脑屏幕根本装不下全部代码。我们分成几个部分来看。
注意 :在循环开始前会有一些变量准备 ;0x10 = 16D,0x14 = 20D;flag这个变量命名 常用来当作循环是否结束的标志 。
Part1 ,读入了6个整数。
1 2 3 4 5 6 7 8 9 10 11 0x00000000004010f4  <+0 >:	push   %r14 #入栈0x00000000004010f6  <+2 >:	push   %r130x00000000004010f8  <+4 >:	push   %r120x00000000004010fa  <+6 >:	push   %rbp0x00000000004010fb  <+7 >:	push   %rbx0x00000000004010fc  <+8 >:	sub    $0x50 ,%rsp #开辟栈空间0x0000000000401100  <+12 >:	mov    %rsp,%r130x0000000000401103  <+15 >:	mov    %rsp,%rsi0x0000000000401106  <+18 >:	callq  0x40145c  <read_six_numbers> #读入6 个数字0x000000000040110b  <+23 >:	mov    %rsp,%r140x000000000040110e  <+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 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #loop1,+32  ~ +93 该循环说明:1 <= input[i] <= 6 ,i取0  ~ 5  0x0000000000401114  <+32 >:	mov    %r13,%rbp #%rbp = %r130x0000000000401117  <+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 和%r12d0x0000000000401130  <+60 >:	je     0x401153  <phase_6+95 > #若6  == %r12d,结束loop20x0000000000401132  <+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 *%rax0x000000000040113b  <+71 >:	cmp    %eax,0x0 (%rbp) #比较%rsp+4 *%rax和%rsp0x000000000040113e  <+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 和%ebx0x000000000040114b  <+87 >:	jle    0x401135  <phase_6+65 > #若%ebx <= 5 ,进入loop20x000000000040114d  <+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。同时,因为+45的 jbe 是对无符号数 操作,若input[i]取 0 ,执行0 - 1 = -1在比较时会出错(溢出) ,所以不能取0。故只能取1 ~ 6。
Part3 ,执行算式:input[i] = 7 - input[i]
1 2 3 4 5 6 7 8 9 10 0x0000000000401153  <+95 >:	lea    0x18 (%rsp),%rsi #%rsi = 0x18 (%rsp)0x0000000000401158  <+100 >:	mov    %r14,%rax #%rax = %r14 = %rsp0x000000000040115b  <+103 >:	mov    $0x7 ,%ecx #%ecx = 7 #loop3,+108  ~ +121 ,该循环执行算式:input[i] = 7  - input[i] 0x0000000000401160  <+108 >:	mov    %ecx,%edx0x0000000000401162  <+110 >:	sub    (%rax),%edx #%edx = 7  - (%rax)0x0000000000401164  <+112 >:	mov    %edx,(%rax) #(%rax) = %edx0x0000000000401166  <+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 ,引入链表 并改变链表顺序 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ##注意,执行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]和%eax0x000000000040117f  <+139 >:	jne    0x401176  <phase_6+130 > #不相等,则继续loop50x0000000000401181  <+141 >:	jmp    0x401188  <phase_6+148 > #相等,->+148 ,进入loop40x0000000000401183  <+143 >:	mov    $0x6032d0 ,%edx #%edx = 0x6032d0 0x0000000000401188  <+148 >:	mov    %rdx,0x20 (%rsp,%rsi,2 ) #0x20 +%rsp+2 *%rsi = %rdx0x000000000040118d  <+153 >:	add    $0x4 ,%rsi #%rsi+4 ,flag值0x0000000000401191  <+157 >:	cmp    $0x18 ,%rsi #比较0x18 和flag值0x0000000000401195  <+161 >:	je     0x4011ab  <phase_6+183 > #若相等,结束loop40x0000000000401197  <+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指令都是乱码,如图所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 (gdb) x/s 0x6032d0  0x6032d0  <node1>:	"L\001" (gdb) x/100 s 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)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 (gdb) x/24 xw 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  = 16 D ##我们还可以用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已经设置好的,但不查看之前完全不知道)
1 2 3 4 5 6 7 (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 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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),%rdx0x00000000004011c0  <+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 > #若相等,结束loop60x00000000004011cd  <+217 >:	mov    %rdx,%rcx #否则,%rcx = %rdx0x00000000004011d0  <+220 >:	jmp    0x4011bd  <phase_6+201 > #-> +201 ,继续loop60x00000000004011d2  <+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降序排列 。(从大到小)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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    %rbp0x00000000004011fd  <+265 >:	pop    %r120x00000000004011ff  <+267 >:	pop    %r130x0000000000401201  <+269 >:	pop    %r140x0000000000401203  <+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: 
1 2 Good work!  On to the next... 
 
1 2 3 4 5 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才能进入彩蛋关)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 0x00000000004015c4  <+0 >:	sub    $0x78 ,%rsp0x00000000004015c8  <+4 >:	mov    %fs:0x28 ,%rax0x00000000004015d1  <+13 >:	mov    %rax,0x68 (%rsp)0x00000000004015d6  <+18 >:	xor     %eax,%eax0x00000000004015d8  <+20 >:	cmpl   $0x6 ,0x202181 (%rip)   # 0x603760  <num_input_strings>0x00000000004015df  <+27 >:	jne    0x40163f  <phase_defused+123 >0x00000000004015e1  <+29 >:	lea    0x10 (%rsp),%r80x00000000004015e6  <+34 >:	lea    0xc (%rsp),%rcx0x00000000004015eb  <+39 >:	lea    0x8 (%rsp),%rdx0x00000000004015f0  <+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 = DrEvil0x0000000000401609  <+69 >:	lea    0x10 (%rsp),%rdi0x000000000040160e  <+74 >:	callq  0x401338  <strings_not_equal>0x0000000000401613  <+79 >:	test   %eax,%eax #字符串相同返回值为0 ,进入secret_phase0x0000000000401615  <+81 >:	jne    0x401635  <phase_defused+113 > 0x0000000000401617  <+83 >:	mov    $0x4024f8 ,%edi0x000000000040161c  <+88 >:	callq  0x400b10  <puts @plt>0x0000000000401621  <+93 >:	mov    $0x402520 ,%edi0x0000000000401626  <+98 >:	callq  0x400b10  <puts @plt>0x000000000040162b  <+103 >:	mov    $0x0 ,%eax0x0000000000401630  <+108 >:	callq  0x401242  <secret_phase>0x0000000000401635  <+113 >:	mov    $0x402558 ,%edi0x000000000040163a  <+118 >:	callq  0x400b10  <puts @plt>0x000000000040163f  <+123 >:	mov    0x68 (%rsp),%rax0x0000000000401644  <+128 >:	xor     %fs:0x28 ,%rax0x000000000040164d  <+137 >:	je     0x401654  <phase_defused+144 >0x000000000040164f  <+139 >:	callq  0x400b30  <__stack_chk_fail@plt>0x0000000000401654  <+144 >:	add    $0x78 ,%rsp0x0000000000401658  <+148 >:	retq 
 
分析:  我们对一些字符串进行访问,可以知道调用sscanf函数前得输入,其格式为"%d %d %s"但输入内容是空的,这说明我们还没输入。我们需要输入,但,是什么时候输入呢? 
1 2 3 4 5 6 (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的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 0x6038700x603870 <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才能进入彩蛋关。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #果然如此 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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0x0000000000401242  <+0 >:	push   %rbx0x0000000000401243  <+1 >:	callq  0x40149e  <read_line>0x0000000000401248  <+6 >:	mov    $0xa ,%edx #base = 10D 0x000000000040124d  <+11 >:	mov    $0x0 ,%esi0x0000000000401252  <+16 >:	mov    %rax,%rdi #input 0x0000000000401255  <+19 >:	callq  0x400bd0  <strtol@plt>0x000000000040125a  <+24 >:	mov    %rax,%rbx #返回值rax赋给%rbx0x000000000040125d  <+27 >:	lea    -0x1 (%rax),%eax0x0000000000401260  <+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 ,%edi0x0000000000401287  <+69 >:	callq  0x400b10  <puts @plt>0x000000000040128c  <+74 >:	callq  0x4015c4  <phase_defused>0x0000000000401291  <+79 >:	pop    %rbx0x0000000000401292  <+80 >:	retq   
 
查看fun7的汇编代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 0x0000000000401204  <+0 >:	sub    $0x8 ,%rsp0x0000000000401208  <+4 >:	test   %rdi,%rdi #rdi == 0时,返回-1 0x000000000040120b  <+7 >:	je     0x401238  <fun7+52 >0x000000000040120d  <+9 >:	mov    (%rdi),%edx0x000000000040120f  <+11 >:	cmp    %esi,%edx #(%rdi) <= %esi0x0000000000401211  <+13 >:	jle    0x401220  <fun7+28 >0x0000000000401213  <+15 >:	mov    0x8 (%rdi),%rdi #rdi += 0x8 0x0000000000401217  <+19 >:	callq  0x401204  <fun7>0x000000000040121c  <+24 >:	add    %eax,%eax0x000000000040121e  <+26 >:	jmp    0x40123d  <fun7+57 >0x0000000000401220  <+28 >:	mov    $0x0 ,%eax0x0000000000401225  <+33 >:	cmp    %esi,%edx0x0000000000401227  <+35 >:	je     0x40123d  <fun7+57 > #%esi == %edx0x0000000000401229  <+37 >:	mov    0x10 (%rdi),%rdi #rdi += 0x10  0x000000000040122d  <+41 >:	callq  0x401204  <fun7>0x0000000000401232  <+46 >:	lea    0x1 (%rax,%rax,1 ),%eax0x0000000000401236  <+50 >:	jmp    0x40123d  <fun7+57 >0x0000000000401238  <+52 >:	mov    $0xffffffff ,%eax0x000000000040123d  <+57 >:	add    $0x8 ,%rsp0x0000000000401241  <+61 >:	retq   
 
分析: 
这是一棵二叉树,思路如图所示:
方法一:实现eax = 2 。(ps:递归调用顺序与返回顺序相反 )
1 2 3 4 5 6 eax = 0  --> eax = 2 *eax+1  --> eax = 2 *eax 0 	--> 1              --> 2 故返回顺序为: D	--> C             --> B 因而递归调用顺序为:(与返回顺序相反) B	--> B             --> 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。
 
 
1 2 3 4 5 6 7 8 9 10 11 #这是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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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  = 20 D(使用的是打印指令,更便捷) (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这个答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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  DrEvilSo 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... 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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  DrEvilSo 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] 
 
无聊地查看一下二叉树内容:(此处对解题不是很有必要)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 (gdb) x/116 wx 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; 
 
整理后的图状展示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 └─ 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,后两个是链表、二叉树都不熟悉。也参考了不少其它大佬的解答。 
自己的汇编阅读能力是提升了不少,后面看都是比较自然,但是联系到数据结构就比较不了解,还是得继续提高姿势水平。 
如有谬误,敬请指正。 
成果截图