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,后两个是链表、二叉树都不熟悉。也参考了不少其它大佬的解答。
自己的汇编阅读能力是提升了不少,后面看都是比较自然,但是联系到数据结构就比较不了解,还是得继续提高姿势水平。
如有谬误,敬请指正。
成果截图