《深入理解计算机系统》CSAPP_BombLab
Bomb Lab
操作系统:linux
调试工具:gdb
Link:CS:APP3e
Memory Dot 我的个人博客,欢迎来玩。
文章分为实验前准备、复习、拆炸弹三个模块。读者可以自取所需。
实验前准备
实验由phase_1 ~ phase_6
组成,共6个阶段,
我们需要分别输入6个字符串密码,以便解除Dr. Evil的炸弹,
但只要有1个字符串输错,炸弹就会爆炸。
那么如何知道6个正确的字符串呢?
我们需要分别查看phase_1 ~ phase_6
的汇编代码,
而炸弹就存放在其中,我们要根据汇编代码小心地避开炸弹,分析出正确的字符串即可。
而分析需要用到学习的汇编知识、常用的gdb指令以及linux终端指令。
需要用到的linux终端指令:
别忘了先进入文件地址中
-
objdump -d bomb > assembly.s
反编译出bomb.c
的汇编代码,并存放在assembly.s
中(assembly.s是自己命名的) -
gdb 文件名
用gdb调试工具打开某个文件
eg.gdb bomb
用gdb调试工具打开bomb.c文件
而我们就是要用gdb调试工具打开bomb.c文件后做实验
常用gdb指令:
gdb指令必须用gdb调试工具打开文件后才能调用
run
(可简写为:r
)
运行文件。quit
(可简写为:q
)
停止运行,跳出gdb调试工具break 函数名
(可简写为:b 函数名
)
在某个函数处打断点,以便之后用disas展示这个函数的汇编代码。
eg.b phase_1
在phase_1函数打断点disas 函数名
展示这个函数的汇编代码(但必须先打断点)
eg.disas phase_1
展示phase_1的汇编代码x/NFU address or register
用指定格式(FU)、指定数量(N)输出地址或者寄存器对应内容
eg1.x/s 0x40139b
输出0x40139b对应内容
eg2.x/s $rsi
输出%rsi对应内容
eg3.x/4xw 0x6032d0
按24个十六进制4字节格式输出0x6032d0对应内容p/F *address or *register
用指定格式(F)打印地址或者寄存器对应内容(效果上,x/F = p/F
)
eg1.p/x *(0x6032f0)
按十六进制打印0x6032f0对应内容- 格式F、单位U都与c语言类似,参考链接gdb:Format、Variables and Memory部分
复习
-
测试和比较指令
-ZF、OF、SF、CF
(条件码)
zero flag 零标志位;overflow flag 溢出标志位;sign flag 符号标志位;carry flag 进位标志位。
-test S1,S2
即S1 & S2
测试指令,进行位级与操作,若结果为0,返回ZF = 1
;若结果非0,返回ZF = 0
,但不会改变两个数的值。同时,需要特殊注意的是,可以对同一个寄存器使用test指令,例如test %eax,%eax
。
-cmp S1,S2
即S2 - S1
比较指令,进行减法操作,但不会改变两个数的值。 -
部分跳转指令
-
不区分unsiged(无符号)、siged(有符号)跳转
jne
根据ZF的值判断是否跳转,当ZF = 0
才跳转。je
根据ZF的值判断是否跳转,当ZF = 1
才跳转。
-
siged jump 指令
jle
(jump<=
)
j:jump;l:less(小于);e:equaljge
(jump<=
)
j:jump;g:great(大于);e:equaljg
(jump>
)
j:jump;g:great(大于)
-
unsiged jump 指令
jb
(jump<
)
j:jump;b:below(小于)jbe
(jump<=
)
j:jump;b:below(小于);e:equalja
(jump>
)
j:jump;a:above(大于)
-
cmp
与signed jump、unsigned jump的联系(原理与条件码有关,在此不解释了)-
当
cmp S1,S2
伴随signed jump指令使用时,S1置前,S2置后,例如:0x0000000000400f60 <+29>: cmp $0x1,%eax #若 1 > %eax,则不会爆炸 0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39> #大于跳转
-
当
cmp S1,S2
伴随unsigned jump指令使用时,S2置前,S1置后,例如:0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp) #若0x8(%rsp) > 7,则爆炸 0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106> #大于跳转
-
-
-
部分二元操作
lea S,D
(即D <-- S
,等价于赋值算式D = S
)
不是读取数据,是加载有效地址,但和mov
指令效果相同,常用于执行简洁的算术操作。sub S,D
(即D <-- D - S
,等价于赋值算式D -= S
)
减法,D - S后赋值给D。sar S,D
(即D <-- D >> S
,等价于赋值算式D >>= S
)
算术右移,补符号位(sign),D >> S后赋值给D。注意:若sar D
,则默认是算术右移1
位。shr S,D
(即D <-- D >> S
,等价于赋值算式D >>= S
)
算术右移,补0
,D >> S后赋值给D。
-
当寄存器为空时,其值为
0
-
%rdi
系统往往要把一个值赋给%rdi后,才能当作入参(即输入的参数) -
register
注意:汇编代码为了优化常常使用同一个系列的register
表示同一个变量
例如:mov $0x2,%rax sub $0x1,%eax #此时,%eax = %rax = 2,该行执行%eax -= 1即%eax = 2 - 1
register(64bits) | 作用 |
---|---|
%rax | 存放返回值 |
%rbx | 被调用者保存(存放被调用者的地址,可以是函数地址,也可以是其它地址,callee save) |
%rcx | 第4个参数(存储参数,可以是数字字符参数,也可以是地址参数) |
%rdx | 第3个参数 |
%rsi | 第2个参数 |
%rdi | 第1个参数(经常作入参使用) |
%rbp | 被调用者保存 |
%rsp | 栈指针(用以开辟栈空间,注意入参的先进后出) |
%r8 | 第5个参数 |
%r9 | 第6个参数 |
%10 | 调用者保存(存放调用者函数的地址,可以是函数地址,也可以是其它地址,caller save) |
%r11 | 调用者保存 |
%r12 | 被调用者保存 |
%r13 | 被调用者保存 |
%r14 | 被调用者保存 |
%r15 | 被调用者保存 |
- 优化
有些看似毫无作用的汇编代码,其实是在做优化。
拆炸弹
在正式拆炸弹之前需要注意到一点,我们输入的字符串都是放入到%rdi
当中的。
(其实一般情况下,输入也是放到%rdi
的)
也就是说,在每个phase
调用时,默认%rdi
这个寄存器存放着输入的字符串。
如何得到这点的呢?是通过分析main
函数的汇编代码得出的。
# main part 146 ~ 310
0x0000000000400e32 <+146>: callq 0x40149e <read_line>
0x0000000000400e37 <+151>: mov %rax,%rdi # input store %rdi
0x0000000000400e3a <+154>: callq 0x400ee0 <phase_1>
0x0000000000400e3f <+159>: callq 0x4015c4 <phase_defused>
0x0000000000400e44 <+164>: mov $0x4023a8,%edi
0x0000000000400e49 <+169>: callq 0x400b10 <puts@plt>
0x0000000000400e4e <+174>: callq 0x40149e <read_line>
0x0000000000400e53 <+179>: mov %rax,%rdi # input store %rdi
0x0000000000400e56 <+182>: callq 0x400efc <phase_2>
0x0000000000400e5b <+187>: callq 0x4015c4 <phase_defused>
0x0000000000400e60 <+192>: mov $0x4022ed,%edi
0x0000000000400e65 <+197>: callq 0x400b10 <puts@plt>
0x0000000000400e6a <+202>: callq 0x40149e <read_line>
0x0000000000400e6f <+207>: mov %rax,%rdi # input store %rdi
0x0000000000400e72 <+210>: callq 0x400f43 <phase_3>
0x0000000000400e77 <+215>: callq 0x4015c4 <phase_defused>
0x0000000000400e7c <+220>: mov $0x40230b,%edi
0x0000000000400e81 <+225>: callq 0x400b10 <puts@plt>
0x0000000000400e86 <+230>: callq 0x40149e <read_line>
0x0000000000400e8b <+235>: mov %rax,%rdi # input store %rdi
0x0000000000400e8e <+238>: callq 0x40100c <phase_4>
0x0000000000400e93 <+243>: callq 0x4015c4 <phase_defused>
0x0000000000400e98 <+248>: mov $0x4023d8,%edi
0x0000000000400e9d <+253>: callq 0x400b10 <puts@plt>
0x0000000000400ea2 <+258>: callq 0x40149e <read_line>
0x0000000000400ea7 <+263>: mov %rax,%rdi # input store %rdi
0x0000000000400eaa <+266>: callq 0x401062 <phase_5>
0x0000000000400eaf <+271>: callq 0x4015c4 <phase_defused>
0x0000000000400eb4 <+276>: mov $0x40231a,%edi
0x0000000000400eb9 <+281>: callq 0x400b10 <puts@plt>
0x0000000000400ebe <+286>: callq 0x40149e <read_line>
0x0000000000400ec3 <+291>: mov %rax,%rdi # input store %rdi
0x0000000000400ec6 <+294>: callq 0x4010f4 <phase_6>
0x0000000000400ecb <+299>: callq 0x4015c4 <phase_defused>
0x0000000000400ed0 <+304>: mov $0x0,%eax
0x0000000000400ed5 <+309>: pop %rbx
0x0000000000400ed6 <+310>: retq
分析:
我们可以看到,在每个phase
调用前,都callq 0x40149e <read_line>
,然后mov %rax,%rdi
,这样子会将我们输入的字符串最终存到%rdi
中。(我们是把字符串输入到read_line
的,而read_line
会返回到%rax
中,mov
操作将%rax
存到%rdi
。注意,%rax
往往用来放返回值)
至于read_line
的汇编代码我就不分析了,通过名字也可以看出:它是用来返回我们的输入值的
下面开始分析phase_1 ~ phase_6
的汇编代码,
得出认为正确的字符串,并逐个解除炸弹
phase_1
查看phase_1
的汇编代码:
0x0000000000400ee0 <+0>: sub $0x8,%rsp #开辟8个字节的栈空间
0x0000000000400ee4 <+4>: mov $0x402400,%esi #将立即数$0x402400赋值给%esi
0x0000000000400ee9 <+9>: callq 0x401338 <strings_not_equal> #调用函数strings_not_equal
0x0000000000400eee <+14>: test %eax,%eax #与操作(test),若为eax为0则跳转到phase_1+23;若为eax非0则顺序执行,调用explode_bomb,炸弹爆炸
0x0000000000400ef0 <+16>: je 0x400ef7 <phase_1+23>
0x0000000000400ef2 <+18>: callq 0x40143a <explode_bomb>
0x0000000000400ef7 <+23>: add $0x8,%rsp #释放8个字节的栈空间
0x0000000000400efb <+27>: retq #返回
分析:
重点就是<+14>
的test指令,可以看出当%eax
为0时才不会爆炸。也就是说函数strings_not_equal
的返回值必须为0才行,根据名字我们可以猜测,字符串不相等时返回1,字符串相等时返回0。下面查看string_not_equal
的汇编代码验证我们的猜测。
#string_not_equal
0x0000000000401338 <+0>: push %r12 #将%r12入栈
0x000000000040133a <+2>: push %rbp #将%rbp入栈
0x000000000040133b <+3>: push %rbx #将%rbx入栈
0x000000000040133c <+4>: mov %rdi,%rbx #将%rdi赋给%rbx,不妨命名为第一参数指针,对应我们输入字符串的地址
0x000000000040133f <+7>: mov %rsi,%rbp #将%rsi赋给%rbp,不妨命名为第二参数指针,对应地址是$0x402400
0x0000000000401342 <+10>: callq 0x40131b <string_length> #调用函数string_length,系统默认%rdi为入参,那么,就会计算第一参数的字符串的长度
0x0000000000401347 <+15>: mov %eax,%r12d #将第一参数的字符串长度返回值保存到%r12d
0x000000000040134a <+18>: mov %rbp,%rdi #把第二参数指针赋给%rdi,作为string_length的入参
0x000000000040134d <+21>: callq 0x40131b <string_length> #计算第二参数的字符串的长度
0x0000000000401352 <+26>: mov $0x1,%edx #将1赋值给%edx,作为strings_not_equal的可能返回结果
0x0000000000401357 <+31>: cmp %eax,%r12d #比较第一、第二参数的字符串的长度
0x000000000040135a <+34>: jne 0x40139b <strings_not_equal+99> #长度不相等时,跳转到<+99>,最终返回1,炸弹爆炸
0x000000000040135c <+36>: movzbl (%rbx),%eax #长度相等时,将第一参数的第一个字符串赋给%eax
0x000000000040135f <+39>: test %al,%al #判断%al是否为空即0
0x0000000000401361 <+41>: je 0x401388 <strings_not_equal+80> #若%al为空,则跳转到<+80>,最终返回0,炸弹不会爆炸,这段我在优化,会顺序执行
0x0000000000401363 <+43>: cmp 0x0(%rbp),%al #取%rbp的第一个字符,和%al第一个字符做比较;此时(%rbp)对应$0x402400的第一个字符,%al是对应我们输入的字符串的第一个字符
0x0000000000401366 <+46>: je 0x401372 <strings_not_equal+58> #若相等跳转到<+58>
0x0000000000401368 <+48>: jmp 0x40138f <strings_not_equal+87> #若不相等跳转到<+87>,最终返回1,炸弹爆炸
0x000000000040136a <+50>: cmp 0x0(%rbp),%al #比较第一、第二参数的字符
0x000000000040136d <+53>: nopl (%rax) #空操作,为了扩展%eax
0x0000000000401370 <+56>: jne 0x401396 <strings_not_equal+94> #不相等跳转到<+94>,最终返回1,炸弹爆炸
0x0000000000401372 <+58>: add $0x1,%rbx #第一参数指针+1
0x0000000000401376 <+62>: add $0x1,%rbp #第二参数指针+1
0x000000000040137a <+66>: movzbl (%rbx),%eax #零扩展%eax,并将第一参数的字符赋给%eax
0x000000000040137d <+69>: test %al,%al #判断此时的%al是否为空,若空,则顺序执行,跳出循环,最终返回0,炸弹不会爆炸
0x000000000040137f <+71>: jne 0x40136a <strings_not_equal+50> #若不空,则继续比对字符
0x0000000000401381 <+73>: mov $0x0,%edx
0x0000000000401386 <+78>: jmp 0x40139b <strings_not_equal+99>
0x0000000000401388 <+80>: mov $0x0,%edx
0x000000000040138d <+85>: jmp 0x40139b <strings_not_equal+99>
0x000000000040138f <+87>: mov $0x1,%edx
0x0000000000401394 <+92>: jmp 0x40139b <strings_not_equal+99>
0x0000000000401396 <+94>: mov $0x1,%edx
0x000000000040139b <+99>: mov %edx,%eax #将%eax赋值为 0或 1,准备返回
0x000000000040139d <+101>: pop %rbx #出栈
0x000000000040139e <+102>: pop %rbp #出栈
0x000000000040139f <+103>: pop %r12 #出栈
0x00000000004013a1 <+105>: retq #返回
分析:
通过查看string_not_equal
函数的汇编代码,可以找到,string_not_equal
函数先是比较了输入字符串和地址0x402400
对应的字符长度,再逐个比较字符,最终,字符相同返回0
,字符不同返回1
。我们可以看到,汇编进行不少优化。例如,如果第一个字符已经是不同的,就不用循环进行更多的操作。
key:由此可以知道,只要我们输入的字符和地址0x402400
对应的字符相同即可。那么,我们通过gdb指令x/s 0x402400
即可访问0x402400
对应的字符。代码如下:
(gdb) x/s 0x402400
0x402400: "Border relations with Canada have never been better."
那么,只要在运行界面输入Border relations with Canada have never been better.
即可。(译为:与加拿大的边境关系从来没有这么好过。)
//输入前
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
//after input
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
phase_2
我们先查看phase_2
的汇编代码:
0x0000000000400efc <+0>: push %rbp
0x0000000000400efd <+1>: push %rbx
0x0000000000400efe <+2>: sub $0x28,%rsp
0x0000000000400f02 <+6>: mov %rsp,%rsi #将%rsp赋给%rsi
0x0000000000400f05 <+9>: callq 0x40145c <read_six_numbers>
0x0000000000400f0a <+14>: cmpl $0x1,(%rsp) #(%rsp)必须是$0x1,否则爆炸
0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
0x0000000000400f10 <+20>: callq 0x40143a <explode_bomb>
0x0000000000400f15 <+25>: jmp 0x400f30 <phase_2+52>
0x0000000000400f17 <+27>: mov -0x4(%rbx),%eax
0x0000000000400f1a <+30>: add %eax,%eax
0x0000000000400f1c <+32>: cmp %eax,(%rbx)
0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
0x0000000000400f20 <+36>: callq 0x40143a <explode_bomb>
0x0000000000400f25 <+41>: add $0x4,%rbx
0x0000000000400f29 <+45>: cmp %rbp,%rbx
0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
0x0000000000400f2e <+50>: jmp 0x400f3c <phase_2+64>
0x0000000000400f30 <+52>: lea 0x4(%rsp),%rbx
0x0000000000400f35 <+57>: lea 0x18(%rsp),%rbp
0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27> #进入循环
0x0000000000400f3c <+64>: add $0x28,%rsp
0x0000000000400f40 <+68>: pop %rbx
0x0000000000400f41 <+69>: pop %rbp
0x0000000000400f42 <+70>: retq
callq 0x40145c <read_six_numbers>
,我们查看这个函数的汇编代码:
(根据名字,我们猜测其作用是读取6个数字)
0x000000000040145c <+0>: sub $0x18,%rsp
0x0000000000401460 <+4>: mov %rsi,%rdx
0x0000000000401463 <+7>: lea 0x4(%rsi),%rcx
0x0000000000401467 <+11>: lea 0x14(%rsi),%rax
0x000000000040146b <+15>: mov %rax,0x8(%rsp) #参数寄存器不够使用了,用栈帧
0x0000000000401470 <+20>: lea 0x10(%rsi),%rax
0x0000000000401474 <+24>: mov %rax,(%rsp)
0x0000000000401478 <+28>: lea 0xc(%rsi),%r9
0x000000000040147c <+32>: lea 0x8(%rsi),%r8
0x0000000000401480 <+36>: mov $0x4025c3,%esi
0x0000000000401485 <+41>: mov $0x0,%eax
0x000000000040148a <+46>: callq 0x400bf0 <__isoc99_sscanf@plt>
0x000000000040148f <+51>: cmp $0x5,%eax #即cmp $0x5,$0x0,用以跳过炸弹
0x0000000000401492 <+54>: jg 0x401499 <read_six_numbers+61>
0x0000000000401494 <+56>: callq 0x40143a <explode_bomb>
0x0000000000401499 <+61>: add $0x18,%rsp
0x000000000040149d <+65>: retq
分析:
注意到phase_2
中<+6>
已经将%rsp赋给%rsi,那么,对%rsi进行操作也就是之后对phase_2
中的%rsp操作。
以下表格是对%rsi操作的展示:
%rsi | 0x4(%rsi) | 0x8(%rsi) | 0xc(%rsi) | 0x10(%rsi) | 0x14(%rsi) |
---|---|---|---|---|---|
%rdx | %rcx | %r8 | %r9 | (%rsp) | 0x8(%rsp) |
注意,0x10 = 16D
,0x14 = 20D
。十进制后缀D
,Decimal.
可以看到,这样操作传递了6个地址参数,其地址间隔为4
,最后两个因为参数寄存器不够用了,只能用栈帧%rsp。(注意,此处的%rsp是read_six_numbers
自己开辟的,原先phase_2
的%rsp已经中断)
<+36>
处,mov $0x4025c3,%esi
,访问0x4025c3内容:
(gdb) x/s 0x4025c3
0x4025c3: "%d %d %d %d %d %d"
可以知道%eax中存放着格式字符串
<+46>
处,callq 0x400bf0 <__isoc99_sscanf@plt>
,函数sscanf
是c语言自带的函数,同scanf
类似,作用是读取输入的字符串,并根据指定格式存储到指定地址。这告诉了我们的输入的字符串,应该是6个整型数字,而且之间用空格隔开。同时,它们存放在上述的6个参数地址中,也就是之后phase_2
的%rsp按4
递增地址的栈帧中。符合我们的猜测。
我们回到phase_2
继续分析: 可以知道第一个参数数值(%rsp)
必须是1
,之后会进入一个循环。我们把这个循环单独拿出来看:
0x0000000000400f17 <+27>: mov -0x4(%rbx),%eax
0x0000000000400f1a <+30>: add %eax,%eax #此时的%eax是<+27>处的%eax的两倍
0x0000000000400f1c <+32>: cmp %eax,(%rbx) #%eax与(%rbx)须相等,否则爆炸
0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
0x0000000000400f20 <+36>: callq 0x40143a <explode_bomb>
0x0000000000400f25 <+41>: add $0x4,%rbx #%rbx地址+4
0x0000000000400f29 <+45>: cmp %rbp,%rbx #%rbp与%rbx相等时,即都为空时,跳出循环
0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
0x0000000000400f2e <+50>: jmp 0x400f3c <phase_2+64>
0x0000000000400f30 <+52>: lea 0x4(%rsp),%rbx #用%rbx替代0x4(%rsp)
0x0000000000400f35 <+57>: lea 0x18(%rsp),%rbp #将%rbp置空
0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27>
我们已经知道(%rsp) = 1
,那么第一次循环中,<+32>
处的%eax = 2
,故只有(%rbx) = 0x4(%rsp) = 2
,才不爆炸;第二次循环中,<+32>
处的%eax = 4
,故(%rbx) = 0x8(%rsp) = 4
;第三次循环中,<+32>
处的%eax = 8
,故(%rbx) = 0xc(%rsp) = 8
,可以看出是以1为首项,后一项是前一项两倍的递增数列,共6项,以此类推,有下表。
%rsi | 0x4(%rsi) | 0x8(%rsi) | 0xc(%rsi) | 0x10(%rsi) | 0x14(%rsi) |
---|---|---|---|---|---|
%rdx | %rcx | %r8 | %r9 | (%rsp) | 0x8(%rsp) |
(%rsp) | 0x4(%rsp) | 0x8(%rsp) | 0xc(%rsp) | 0x10(%rsp) | 0x14(%rsp) |
1 | 2 | 4 | 8 | 16 | 32 |
key: 由此得知,phase_2该输入的字符串为1 2 4 8 16 32
//输入前
Phase 1 defused. How about the next one?
//after input
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2. Keep going!
phase_3
phase_3
的汇编代码如下:
0x0000000000400f43 <+0>: sub $0x18,%rsp
0x0000000000400f47 <+4>: lea 0xc(%rsp),%rcx #第二个参数,栈帧先进后出
0x0000000000400f4c <+9>: lea 0x8(%rsp),%rdx #第一个参数
0x0000000000400f51 <+14>: mov $0x4025cf,%esi #字符串格式"%d %d"
0x0000000000400f56 <+19>: mov $0x0,%eax
0x0000000000400f5b <+24>: callq 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000400f60 <+29>: cmp $0x1,%eax #若 1 > %eax,则不会爆炸
0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39> #大于跳转
0x0000000000400f65 <+34>: callq 0x40143a <explode_bomb>
0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp) #若0x8(%rsp) > 7,则爆炸
0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106> #大于跳转
0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax
0x0000000000400f75 <+50>: jmpq *0x402470(,%rax,8) #*0x402470处地址 + 8 * %eax
0x0000000000400f7c <+57>: mov $0xcf,%eax #将$0xcf赋给%eax,下面同理
0x0000000000400f81 <+62>: jmp 0x400fbe <phase_3+123>
0x0000000000400f83 <+64>: mov $0x2c3,%eax
0x0000000000400f88 <+69>: jmp 0x400fbe <phase_3+123>
0x0000000000400f8a <+71>: mov $0x100,%eax
0x0000000000400f8f <+76>: jmp 0x400fbe <phase_3+123>
0x0000000000400f91 <+78>: mov $0x185,%eax
0x0000000000400f96 <+83>: jmp 0x400fbe <phase_3+123>
0x0000000000400f98 <+85>: mov $0xce,%eax
0x0000000000400f9d <+90>: jmp 0x400fbe <phase_3+123>
0x0000000000400f9f <+92>: mov $0x2aa,%eax
0x0000000000400fa4 <+97>: jmp 0x400fbe <phase_3+123>
0x0000000000400fa6 <+99>: mov $0x147,%eax
0x0000000000400fab <+104>: jmp 0x400fbe <phase_3+123>
0x0000000000400fad <+106>: callq 0x40143a <explode_bomb>
0x0000000000400fb2 <+111>: mov $0x0,%eax
0x0000000000400fb7 <+116>: jmp 0x400fbe <phase_3+123>
0x0000000000400fb9 <+118>: mov $0x137,%eax
0x0000000000400fbe <+123>: cmp 0xc(%rsp),%eax #第二个参数必须和%eax相等
0x0000000000400fc2 <+127>: je 0x400fc9 <phase_3+134>
0x0000000000400fc4 <+129>: callq 0x40143a <explode_bomb>
0x0000000000400fc9 <+134>: add $0x18,%rsp
0x0000000000400fcd <+138>: retq
分析:
访问0x4025cf
,可知字符串格式为"%d %d"
,从而得知输入字符串是两个整数。根据<+39>: cmpl $0x7,0x8(%rsp)
和<+44>: ja 0x400fad <phase_3+106>
可以知道0x8(%rsp) <= 7
。那么,第一个参数%rdx取值要小于等于7。
根据<+50>: jmpq *0x402470(,%rax,8)
可以知道,跳转的地址是指针*0x402470
的变换,增量为8
,此时为0x8(%rsp)
即为%eax。
下面我们用x/s
指令逐个访问,指针地址是递增的,但对应存放的phase_3
的地址并不是递增的。到这时,答案已经呼之欲出了。
(由此可以知道指针*0x402470
指向一个存放phase_3
部分地址的字符串。一开始我想的是对phase_3
的部分地址直接进行增8
操作,没有想到是对指针*0x402470
进行增8
操作,从而访问不同的phase_3
中的地址,耽误了许久)
(gdb) x/s *0x402470
0x400f7c <phase_3+57>: "\270", <incomplete sequence \317>
(gdb) x/s *(0x402470+8)
0x400fb9 <phase_3+118>: "\270\067\001"
(gdb) x/s *(0x402470+16)
0x400f83 <phase_3+64>: "\270\303\002"
(gdb) x/s *(0x402470+24)
0x400f8a <phase_3+71>: "\270"
(gdb) x/s *(0x402470+32)
0x400f91 <phase_3+78>: "\270\205\001"
(gdb) x/s *(0x402470+40)
0x400f98 <phase_3+85>: "\270", <incomplete sequence \316>
(gdb) x/s *(0x402470+48)
0x400f9f <phase_3+92>: "\270\252\002"
(gdb) x/s *(0x402470+56)
0x400fa6 <phase_3+99>: "\270G\001"
对phase_3
继续分析:
根据<+123>: cmp 0xc(%rsp),%eax
和<+127>: je 0x400fc9 <phase_3+134>
知道,第二个参数0xc(%rsp)
必须和%eax相同,此时的%eax根据0x8(%rsp)
不同而不同。
key:
整理上述分析可以知道,共有8
组答案如下:
0x8(%rsp) | 0 | 1 | 2 | 3 |
---|---|---|---|---|
x/s | *0x402470 | *(~ +8) | *(~ +16) | *(~ +24) |
<phase_3+n> | +57 | +118 | +64 | +71 |
0xc(%rsp) H | 0xcf | 0x137 | 0x2c3 | 0x100 |
0xc(%rsp) D | 207 | 311 | 707 | 256 |
0x8(%rsp) | 4 | 5 | 6 | 7 |
---|---|---|---|---|
x/s | *(~ +32) | *(~ +40) | *(~ +48) | *(~ +56) |
<phase_3+n> | +78 | +85 | +92 | +99 |
0xc(%rsp) H | 0x185 | 0xce | 0x2aa | 0x147 |
0xc(%rsp) D | 389 | 206 | 682 | 327 |
(十六进制后缀,H,hexadecimal;十进制后缀,D,Decimal)
我们选择第8组答案7 327
作为输入:
//输入前
That's number 2. Keep going!
//after input
That's number 2. Keep going!
7 327
Halfway there!
phase_4
phase_4
的汇编代码如下:
0x000000000040100c <+0>: sub $0x18,%rsp
0x0000000000401010 <+4>: lea 0xc(%rsp),%rcx #第二个参数,先进后出
0x0000000000401015 <+9>: lea 0x8(%rsp),%rdx #第一个参数,参一
0x000000000040101a <+14>: mov $0x4025cf,%esi #格式字符串"%d %d"
0x000000000040101f <+19>: mov $0x0,%eax
0x0000000000401024 <+24>: callq 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000401029 <+29>: cmp $0x2,%eax #返回值%eax必须为2,否则爆炸
0x000000000040102c <+32>: jne 0x401035 <phase_4+41>
0x000000000040102e <+34>: cmpl $0xe,0x8(%rsp) #满足0x8(%rsp) <= 0xe,否则爆炸
0x0000000000401033 <+39>: jbe 0x40103a <phase_4+46> #小于等于
0x0000000000401035 <+41>: callq 0x40143a <explode_bomb>
0x000000000040103a <+46>: mov $0xe,%edx #作为func4的入参
0x000000000040103f <+51>: mov $0x0,%esi #作为func4的入参
0x0000000000401044 <+56>: mov 0x8(%rsp),%edi #将0x8(%rsp)赋给%edi,作为func4的入参
0x0000000000401048 <+60>: callq 0x400fce <func4>
0x000000000040104d <+65>: test %eax,%eax #%eax必须为空即0,否则爆炸
0x000000000040104f <+67>: jne 0x401058 <phase_4+76>
0x0000000000401051 <+69>: cmpl $0x0,0xc(%rsp) #0xc(%rsp)必须为0,否则爆炸
0x0000000000401056 <+74>: je 0x40105d <phase_4+81>
0x0000000000401058 <+76>: callq 0x40143a <explode_bomb>
0x000000000040105d <+81>: add $0x18,%rsp
0x0000000000401061 <+85>: retq
分析:仍旧是输入两个int
参数,根据<+34>
处可知,第一个参数0x8(%rsp)
满足0xe > 0x8(%rsp)
,第二个参数0xc(%rsp)
必须为0。但是第一个参数需要根据<func4>
函数来确定。
<func4>
的汇编代码如下:
第一次进入func4函数:
0x0000000000400fce <+0>: sub $0x8,%rsp
0x0000000000400fd2 <+4>: mov %edx,%eax # %eax=14
0x0000000000400fd4 <+6>: sub %esi,%eax # %eax=14-0=14
0x0000000000400fd6 <+8>: mov %eax,%ecx # %ecx=14
0x0000000000400fd8 <+10>: shr $0x1f,%ecx #逻辑右移31位,%ecx=0
0x0000000000400fdb <+13>: add %ecx,%eax # %eax=14
0x0000000000400fdd <+15>: sar %eax #算术右移1位,补sign,%eax=7
0x0000000000400fdf <+17>: lea (%rax,%rsi,1),%ecx %ecx=7+0=7
0x0000000000400fe2 <+20>: cmp %edi,%ecx #第一个参数和%ecx=7比较
0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36> #第一个参数 <=7跳至<+36>
0x0000000000400fe6 <+24>: lea -0x1(%rcx),%edx #否则%edx=7-1=6
0x0000000000400fe9 <+27>: callq 0x400fce <func4> #进入递归
0x0000000000400fee <+32>: add %eax,%eax
0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
0x0000000000400ff2 <+36>: mov $0x0,%eax
0x0000000000400ff7 <+41>: cmp %edi,%ecx #第一个参数和%ecx=7比较
0x0000000000400ff9 <+43>: jge 0x401007 <func4+57> #若第一个参数 >=7跳出func4
0x0000000000400ffb <+45>: lea 0x1(%rcx),%esi
0x0000000000400ffe <+48>: callq 0x400fce <func4>
0x0000000000401003 <+53>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401007 <+57>: add $0x8,%rsp
0x000000000040100b <+61>: retq
分析:其实这个时候答案已经出来了,只要第一个参数为7
和%ecx=7
相同即可。但显然func
是一个递归函数,我们进入递归看看,还有什么答案。
第一次递归:(记为R1
)
0x0000000000400fce <+0>: sub $0x8,%rsp
0x0000000000400fd2 <+4>: mov %edx,%eax # %eax=6
0x0000000000400fd4 <+6>: sub %esi,%eax # %eax=6-0=6
0x0000000000400fd6 <+8>: mov %eax,%ecx # %ecx=6
0x0000000000400fd8 <+10>: shr $0x1f,%ecx #逻辑右移31位,%ecx=0
0x0000000000400fdb <+13>: add %ecx,%eax # %eax=6
0x0000000000400fdd <+15>: sar %eax #算术右移1位,%eax=3
0x0000000000400fdf <+17>: lea (%rax,%rsi,1),%ecx %ecx=3+0=3
0x0000000000400fe2 <+20>: cmp %edi,%ecx #第一个参数和%ecx=3比较
0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36> #若第一个参数 <=3跳至<+36>
0x0000000000400fe6 <+24>: lea -0x1(%rcx),%edx #否则,%edx=3-1=2,进入递归
0x0000000000400fe9 <+27>: callq 0x400fce <func4>
0x0000000000400fee <+32>: add %eax,%eax
0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
0x0000000000400ff2 <+36>: mov $0x0,%eax
0x0000000000400ff7 <+41>: cmp %edi,%ecx #第一个参数和%ecx=3比较
0x0000000000400ff9 <+43>: jge 0x401007 <func4+57> #若第一个参数 >=3跳出递归
0x0000000000400ffb <+45>: lea 0x1(%rcx),%esi
0x0000000000400ffe <+48>: callq 0x400fce <func4>
0x0000000000401003 <+53>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401007 <+57>: add $0x8,%rsp
0x000000000040100b <+61>: retq
第一个参数为3
和%ecx=3
相同即可。
第二次递归:(记为R2
)
0x0000000000400fce <+0>: sub $0x8,%rsp
0x0000000000400fd2 <+4>: mov %edx,%eax # %eax=2
0x0000000000400fd4 <+6>: sub %esi,%eax # %eax=2-0=2
0x0000000000400fd6 <+8>: mov %eax,%ecx # %ecx=2
0x0000000000400fd8 <+10>: shr $0x1f,%ecx #逻辑右移31位,%ecx=0
0x0000000000400fdb <+13>: add %ecx,%eax # %eax=2
0x0000000000400fdd <+15>: sar %eax #算术右移1位,%eax=1
0x0000000000400fdf <+17>: lea (%rax,%rsi,1),%ecx %ecx=1+0=1
0x0000000000400fe2 <+20>: cmp %edi,%ecx #第一个参数和%ecx=1比较
0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36> #若第一个参数 <=1跳至<+36>
0x0000000000400fe6 <+24>: lea -0x1(%rcx),%edx #否则,%edx=1-1=0,进入递归
0x0000000000400fe9 <+27>: callq 0x400fce <func4>
0x0000000000400fee <+32>: add %eax,%eax
0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
0x0000000000400ff2 <+36>: mov $0x0,%eax
0x0000000000400ff7 <+41>: cmp %edi,%ecx #第一个参数和%ecx=1比较
0x0000000000400ff9 <+43>: jge 0x401007 <func4+57> #若第一个参数 >=1跳出递归
0x0000000000400ffb <+45>: lea 0x1(%rcx),%esi
0x0000000000400ffe <+48>: callq 0x400fce <func4>
0x0000000000401003 <+53>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401007 <+57>: add $0x8,%rsp
0x000000000040100b <+61>: retq
第一个参数为1
和%ecx=1
相同即可。
第三次递归:(记为R3
)
0x0000000000400fce <+0>: sub $0x8,%rsp
0x0000000000400fd2 <+4>: mov %edx,%eax # %eax=0
0x0000000000400fd4 <+6>: sub %esi,%eax # %eax=0-0=0
0x0000000000400fd6 <+8>: mov %eax,%ecx # %ecx=0
0x0000000000400fd8 <+10>: shr $0x1f,%ecx #逻辑右移31位,%ecx=0
0x0000000000400fdb <+13>: add %ecx,%eax # %eax=0
0x0000000000400fdd <+15>: sar %eax #算术右移1位,%eax=0
0x0000000000400fdf <+17>: lea (%rax,%rsi,1),%ecx %ecx=0+0=0
0x0000000000400fe2 <+20>: cmp %edi,%ecx #第一个参数和%ecx=0比较
0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36> #若第一个参数 <=0跳至<+36>
0x0000000000400fe6 <+24>: lea -0x1(%rcx),%edx #否则,%edx=0-1=-1,进入递归
0x0000000000400fe9 <+27>: callq 0x400fce <func4>
0x0000000000400fee <+32>: add %eax,%eax
0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
0x0000000000400ff2 <+36>: mov $0x0,%eax
0x0000000000400ff7 <+41>: cmp %edi,%ecx #第一个参数和%ecx=0比较
0x0000000000400ff9 <+43>: jge 0x401007 <func4+57> #若第一个参数 >=0跳出递归
0x0000000000400ffb <+45>: lea 0x1(%rcx),%esi
0x0000000000400ffe <+48>: callq 0x400fce <func4>
0x0000000000401003 <+53>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401007 <+57>: add $0x8,%rsp
0x000000000040100b <+61>: retq
第一个参数为0
和%ecx=0
相同即可。
第四次递归:(记为R4
)
0x0000000000400fce <+0>: sub $0x8,%rsp
0x0000000000400fd2 <+4>: mov %edx,%eax # %eax=-1
0x0000000000400fd4 <+6>: sub %esi,%eax # %eax=-1-0=1
0x0000000000400fd6 <+8>: mov %eax,%ecx # %ecx=-1
0x0000000000400fd8 <+10>: shr $0x1f,%ecx #逻辑右移31位,%ecx=1
0x0000000000400fdb <+13>: add %ecx,%eax # %eax=2
0x0000000000400fdd <+15>: sar %eax #算术右移1位,%eax=1
0x0000000000400fdf <+17>: lea (%rax,%rsi,1),%ecx %ecx=1+0=1
0x0000000000400fe2 <+20>: cmp %edi,%ecx #第一个参数和%ecx=1比较
0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36> #若第一个参数 <=1跳至<+36>
0x0000000000400fe6 <+24>: lea -0x1(%rcx),%edx #否则,%edx=1-1=0,进入递归
0x0000000000400fe9 <+27>: callq 0x400fce <func4>
0x0000000000400fee <+32>: add %eax,%eax
0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
0x0000000000400ff2 <+36>: mov $0x0,%eax
0x0000000000400ff7 <+41>: cmp %edi,%ecx
0x0000000000400ff9 <+43>: jge 0x401007 <func4+57> #大于等于
0x0000000000400ffb <+45>: lea 0x1(%rcx),%esi
0x0000000000400ffe <+48>: callq 0x400fce <func4>
0x0000000000401003 <+53>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401007 <+57>: add $0x8,%rsp
0x000000000040100b <+61>: retq
第一个参数为1
和%ecx=1
相同即可。
第五次递归:(记为R5
,此时R5 = R3
)
0x0000000000400fce <+0>: sub $0x8,%rsp
0x0000000000400fd2 <+4>: mov %edx,%eax # %eax=0
0x0000000000400fd4 <+6>: sub %esi,%eax # %eax=0-0=0
0x0000000000400fd6 <+8>: mov %eax,%ecx # %ecx=0
0x0000000000400fd8 <+10>: shr $0x1f,%ecx #逻辑右移31位,%ecx=0
0x0000000000400fdb <+13>: add %ecx,%eax # %eax=0
0x0000000000400fdd <+15>: sar %eax #算术右移1位,%eax=0
0x0000000000400fdf <+17>: lea (%rax,%rsi,1),%ecx %ecx=0+0=0
0x0000000000400fe2 <+20>: cmp %edi,%ecx #第一个参数和%ecx=0比较
0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36> #若第一个参数 <=0跳至<+36>
0x0000000000400fe6 <+24>: lea -0x1(%rcx),%edx #否则,%edx=0-1=-1,进入递归
0x0000000000400fe9 <+27>: callq 0x400fce <func4>
0x0000000000400fee <+32>: add %eax,%eax
0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
0x0000000000400ff2 <+36>: mov $0x0,%eax
0x0000000000400ff7 <+41>: cmp %edi,%ecx #第一个参数和%ecx=0比较
0x0000000000400ff9 <+43>: jge 0x401007 <func4+57> #若第一个参数 >=0跳出递归
0x0000000000400ffb <+45>: lea 0x1(%rcx),%esi
0x0000000000400ffe <+48>: callq 0x400fce <func4>
0x0000000000401003 <+53>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401007 <+57>: add $0x8,%rsp
0x000000000040100b <+61>: retq
第一个参数为0
和%ecx=0
相同即可。
由此可以知道,R4
之后会一直在R3、R4
之间无限循环,直到程序崩溃。那么,第一个参数的取值只能为:0 1 3 7
key:
得出4
组答案为:
0x8(%rsp) 第一个输入参数 | 7 | 3 | 1 | 0 |
---|---|---|---|---|
0xc(%rsp) 第二个输入参数 | 0 | 0 | 0 | 0 |
我们取0 0
这组输入。
//输入前
Halfway there!
//after input
Halfway there!
0 0
So you got that one. Try this one.
phase_5
phase_5
的汇编代码如下:
0x0000000000401062 <+0>: push %rbx
0x0000000000401063 <+1>: sub $0x20,%rsp
0x0000000000401067 <+5>: mov %rdi,%rbx #%rbx = 输入
0x000000000040106a <+8>: mov %fs:0x28,%rax #%rax = 0x28 = 40D
0x0000000000401073 <+17>: mov %rax,0x18(%rsp) #0x18(%rsp) = 0x28
0x0000000000401078 <+22>: xor %eax,%eax #%eax = 0x28 ^ 0x28 = 0
0x000000000040107a <+24>: callq 0x40131b <string_length>
0x000000000040107f <+29>: cmp $0x6,%eax #字符串长度是否为6
0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112> 是,跳转至+112
0x0000000000401084 <+34>: callq 0x40143a <explode_bomb>否,爆炸
0x0000000000401089 <+39>: jmp 0x4010d2 <phase_5+112>
0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx #%ecx = %rbx + 0,第一次循环
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx #%rdx = (%rsp) = %cl
0x0000000000401096 <+52>: and $0xf,%edx #%edx &= $0xf,%edx不变
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx #%edx = 0x4024b0+%rdx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1) #%rsp+0x10(16D)+0 = %dl
0x00000000004010a4 <+66>: add $0x1,%rax #%rax++
0x00000000004010a8 <+70>: cmp $0x6,%rax #%rax = 6时,跳出循环
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41> 否则,进入循环
0x00000000004010ae <+76>: movb $0x0,0x16(%rsp) #0x16(%rsp) = 0
0x00000000004010b3 <+81>: mov $0x40245e,%esi #%esi = 0x40245e
0x00000000004010b8 <+86>: lea 0x10(%rsp),%rdi #入参
0x00000000004010bd <+91>: callq 0x401338 <strings_not_equal>#字符相等返回值为0,否则为1
0x00000000004010c2 <+96>: test %eax,%eax
0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119> #返回值为0,跳转至<+119>
0x00000000004010c6 <+100>: callq 0x40143a <explode_bomb> #返回值为1,爆炸
0x00000000004010cb <+105>: nopl 0x0(%rax,%rax,1)
0x00000000004010d0 <+110>: jmp 0x4010d9 <phase_5+119>
0x00000000004010d2 <+112>: mov $0x0,%eax #%eax = 0
0x00000000004010d7 <+117>: jmp 0x40108b <phase_5+41>
0x00000000004010d9 <+119>: mov 0x18(%rsp),%rax
0x00000000004010de <+124>: xor %fs:0x28,%rax
0x00000000004010e7 <+133>: je 0x4010ee <phase_5+140> #0x18(%rsp)=0x28,结束pahse_5
0x00000000004010e9 <+135>: callq 0x400b30 <__stack_chk_fail@plt> #否则进入fail函数
0x00000000004010ee <+140>: add $0x20,%rsp
0x00000000004010f2 <+144>: pop %rbx
0x00000000004010f3 <+145>: retq
分析:
很显然,输入的字符串长度得为6
。利用x/s指令,在<+55>
处我们查看x/s 0x4024b0
,给出一句嘲讽我们的话,"maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
,很明显不是答案,也许是答案,也许不是答案,也许在暗示答案不能单单copy,我们无视他的嘲讽,对<+41> ~ <+74>
处的循环进行专门分析。
第二次循环:
0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx #%ecx = %rbx + 1
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx #%rdx = (%rsp) = %cl
0x0000000000401096 <+52>: and $0xf,%edx #%edx &= 0xf,位级与运算,取尾4位,范围为0 ~ 15
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx #%edx = 0x4024b0+%rdx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1) #%rsp+0x10(16D)+1 = %dl
0x00000000004010a4 <+66>: add $0x1,%rax #%rax++
0x00000000004010a8 <+70>: cmp $0x6,%rax #%rax = 6时,跳出循环
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41> 否则,进入循环
以此类推至第六次循环后,应当有<+41>: %ecx = %rbx+5
和<+62>: %rsp+0x10+5 = %dl
。此时,%rbx
是输入,会有: %rsp+0x10+5 = %dl = %edx = 0x4024b0+(%rdx & 0xf)
上式等价于:= 0x4024b0+(%cl & 0xf) = 0x4024b0+((%rbx+5) & 0xf)
所有情况见下表。
%rsp+0x10+0 | 0x4024b0+%rbx+0(0x4024b0 + (第一个输入参数 & 0xf)) |
---|---|
%rsp+0x10+1 | 0x4024b0+%rbx+1(0x4024b0 + (第二个输入参数 & 0xf)) |
%rsp+0x10+2 | 0x4024b0+%rbx+2(0x4024b0 + (第三个输入参数 & 0xf)) |
%rsp+0x10+3 | 0x4024b0+%rbx+3(0x4024b0 + (第四个输入参数 & 0xf)) |
%rsp+0x10+4 | 0x4024b0+%rbx+4(0x4024b0 + (第五个输入参数 & 0xf)) |
%rsp+0x10+5 | 0x4024b0+%rbx+5(0x4024b0 + (第六个输入参数 & 0xf)) |
可以看出%rsp+0x10
的赋值是0x4024b0
的偏移,偏移量范围为0 ~ 15
,也就是嘲讽句的前16个字符,即maduiersnfotvbyl
。
我们回到phase_5
函数继续分析,利用x/s指令,在<+81>
处我们查看x/s 0x40245e
,给出一个单词字符串,"flyers"
(译为飞翔者们或者考熟的鸭子飞走了)。也就是说,<+86>
处的入参必须与flyers
相同。
根据maduiersnfotvbyl
,取得flyers
对应0x4024b0
的偏移量是9、15、14、5、6、7
,但是只能输入6
个字符,如果直接输入91514567
,超过6
个字符会爆炸。
要解决这个问题,可以先把偏移量转换为二进制1001、1111、1110、0101、0110、0111
。因为有%rbx & 0xf
的位级操作,那么我们只要保证尾4位相同即可。这时可以联想到用来定义字符的ASCII表,那么答案有许多种,我比较懒,就只找尾4位为1111
和1110
的字符。分别为/
和.
,从而得到输入9/.567
。
key:
//输入前
So you got that one. Try this one.
//after input
So you got that one. Try this one.
9/.567
Good work! On to the next...
phase_6
查看phase_6
的汇编代码:
分析:
好家伙,一个电脑屏幕根本装不下全部代码。我们分成几个部分来看。
注意:在循环开始前会有一些变量准备;0x10 = 16D,0x14 = 20D
;flag
这个变量命名常用来当作循环是否结束的标志。
Part1,读入了6个整数。
0x00000000004010f4 <+0>: push %r14 #入栈
0x00000000004010f6 <+2>: push %r13
0x00000000004010f8 <+4>: push %r12
0x00000000004010fa <+6>: push %rbp
0x00000000004010fb <+7>: push %rbx
0x00000000004010fc <+8>: sub $0x50,%rsp #开辟栈空间
0x0000000000401100 <+12>: mov %rsp,%r13
0x0000000000401103 <+15>: mov %rsp,%rsi
0x0000000000401106 <+18>: callq 0x40145c <read_six_numbers> #读入6个数字
0x000000000040110b <+23>: mov %rsp,%r14
0x000000000040110e <+26>: mov $0x0,%r12d #%r12d = 0
根据phase_2
的<read_six_numbers>
分析可以知道,这段读入了6个整数,对栈帧%rsp
偏移(偏移量为4
)即是输入input[i],i取0 ~ 5
。如下表所示:
(%rsp) | 0x4(%rsp) | 0x8(%rsp) | 0xc(%rsp) | 0x10(%rsp) | 0x14(%rsp) |
---|---|---|---|---|---|
input[0] | input[1] | input[2] | input[3] | input[4] | input[5] |
Part2,保证6个输入整数(input[i],i取0 ~ 5
)都不相同且范围为1 ~ 6。
#loop1,+32 ~ +93该循环说明:1<= input[i] <= 6,i取0 ~ 5
0x0000000000401114 <+32>: mov %r13,%rbp #%rbp = %r13
0x0000000000401117 <+35>: mov 0x0(%r13),%eax #%eax = %r13
0x000000000040111b <+39>: sub $0x1,%eax #%eax = %eax - 1
0x000000000040111e <+42>: cmp $0x5,%eax #比较5和%eax,
0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52> #若%eax <= 5,跳转至<+52>
0x0000000000401123 <+47>: callq 0x40143a <explode_bomb> #否则,爆炸
0x0000000000401128 <+52>: add $0x1,%r12d #%r12d++,flag值
0x000000000040112c <+56>: cmp $0x6,%r12d #比较6和%r12d
0x0000000000401130 <+60>: je 0x401153 <phase_6+95> #若6 == %r12d,结束loop2
0x0000000000401132 <+62>: mov %r12d,%ebx #否则,%ebx = %r12d
#loop2,+65 ~ +87,该循环是loop1的子循环,用来是保证6个输入都不相等
0x0000000000401135 <+65>: movslq %ebx,%rax
0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax #%eax = %rsp+4*%rax
0x000000000040113b <+71>: cmp %eax,0x0(%rbp) #比较%rsp+4*%rax和%rsp
0x000000000040113e <+74>: jne 0x401145 <phase_6+81> #若不相等,跳转至<+81>
0x0000000000401140 <+76>: callq 0x40143a <explode_bomb> #否则,爆炸
0x0000000000401145 <+81>: add $0x1,%ebx #%ebx++,loop2取input[i]
0x0000000000401148 <+84>: cmp $0x5,%ebx #比较5和%ebx
0x000000000040114b <+87>: jle 0x401135 <phase_6+65> #若%ebx <= 5,进入loop2
0x000000000040114d <+89>: add $0x4,%r13 #%r13+4,loop1取input[i]
0x0000000000401151 <+93>: jmp 0x401114 <phase_6+32> #进入loop1
loop1确定了input[i]
的取值范围为:1 ~ 6
。loop2保证6个输入都不相等。
取值范围如何确定的?由+39 ~ +45
处得出,input[i] <= 6
。同时,因为+45
的 jbe
是对无符号数操作,若input[i]
取 0
,执行0 - 1 = -1
在比较时会出错(溢出),所以不能取0
。故只能取1 ~ 6
。
Part3,执行算式:input[i] = 7 - input[i]
0x0000000000401153 <+95>: lea 0x18(%rsp),%rsi #%rsi = 0x18(%rsp)
0x0000000000401158 <+100>: mov %r14,%rax #%rax = %r14 = %rsp
0x000000000040115b <+103>: mov $0x7,%ecx #%ecx = 7
#loop3,+108 ~ +121,该循环执行算式:input[i] = 7 - input[i]
0x0000000000401160 <+108>: mov %ecx,%edx
0x0000000000401162 <+110>: sub (%rax),%edx #%edx = 7 - (%rax)
0x0000000000401164 <+112>: mov %edx,(%rax) #(%rax) = %edx
0x0000000000401166 <+114>: add $0x4,%rax #%rax+4,取input[i]
0x000000000040116a <+118>: cmp %rsi,%rax #比较%rsi=0x18(%rsp)和%rax+4
0x000000000040116d <+121>: jne 0x401160 <phase_6+108>
因此为了方便,我们重新命名:sub_input[i] = 7 - input[i]
Part4,引入链表并改变链表顺序。
##注意,执行loop3后,sub_input[i] = 7 - input[i]
0x000000000040116f <+123>: mov $0x0,%esi #%esi = 0
#loop4,+128~+181,该循环执行赋值算式:%rsp + 8*i + 0x20 = $0x6032d0 + (8*(sub_input[i]-1))
0x0000000000401174 <+128>: jmp 0x401197 <phase_6+163>
#loop5,+130~+139,该循环是loo4的子循环,执行0x6032d自加8,自加次数为sub_input[i]-1
0x0000000000401176 <+130>: mov 0x8(%rdx),%rdx #%rdx += 8
0x000000000040117a <+134>: add $0x1,%eax #%eax++,flag值
0x000000000040117d <+137>: cmp %ecx,%eax #比较input[i]和%eax
0x000000000040117f <+139>: jne 0x401176 <phase_6+130> #不相等,则继续loop5
0x0000000000401181 <+141>: jmp 0x401188 <phase_6+148> #相等,->+148,进入loop4
0x0000000000401183 <+143>: mov $0x6032d0,%edx #%edx = 0x6032d0
0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2) #0x20+%rsp+2*%rsi = %rdx
0x000000000040118d <+153>: add $0x4,%rsi #%rsi+4,flag值
0x0000000000401191 <+157>: cmp $0x18,%rsi #比较0x18和flag值
0x0000000000401195 <+161>: je 0x4011ab <phase_6+183> #若相等,结束loop4
0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx #取sub_input[i]
0x000000000040119a <+166>: cmp $0x1,%ecx #比较1和sub_input[i]
0x000000000040119d <+169>: jle 0x401183 <phase_6+143> #若sub_input[i] <= 1,跳转至+143
0x000000000040119f <+171>: mov $0x1,%eax #否则,%eax = 1
0x00000000004011a4 <+176>: mov $0x6032d0,%edx #&edx = 0x6032d0
0x00000000004011a9 <+181>: jmp 0x401176 <phase_6+130> #进入loop5
首先我们查看0x6032d0
,一开始用x/s
指令都是乱码,如图所示。
(gdb) x/s 0x6032d0
0x6032d0 <node1>: "L\001"
(gdb) x/100s 0x6032d0
0x6032d0 <node1>: "L\001"
0x6032d3 <node1+3>: ""
0x6032d4 <node1+4>: "\001"
0x6032d6 <node1+6>: ""
0x6032d7 <node1+7>: ""
0x6032d8 <node1+8>: "\340\062`"
0x6032dc <node1+12>: ""
0x6032dd <node1+13>: ""
0x6032de <node1+14>: ""
0x6032df <node1+15>: ""
0x6032e0 <node2>: "\250"
0x6032e2 <node2+2>: ""
...
--Type <RET> for more, q to quit, c to continue without paging--
但是注意到node1
,说明其很有可能是链表,而不是字符串。
我们用x/24xw
指令,按xw格式(十六进制4字节格式)输出24个数据。
如图所示,符合我们的猜测,就是链表,0x6032d0
是head指针。
其中,node是节点地址,val是节点值,order是它的序列(一共6个),next是下个节点的地址(能够发现它刚好就是按照序列排列的,最后一个节点的next是0x0即NULL
)。
(gdb) x/24xw 0x6032d0
0x6032d0 <node1>: 0x0000014c 0x00000001 0x006032e0 0x00000000
0x6032e0 <node2>: 0x000000a8 0x00000002 0x006032f0 0x00000000
0x6032f0 <node3>: 0x0000039c 0x00000003 0x00603300 0x00000000
0x603300 <node4>: 0x000002b3 0x00000004 0x00603310 0x00000000
0x603310 <node5>: 0x000001dd 0x00000005 0x00603320 0x00000000
0x603320 <node6>: 0x000001bb 0x00000006 0x00000000 0x00000000
##注释一下
node val order next
0x6032d0 <node1>: 0x0000014c 0x00000001 0x006032e0 0x00000000
0x6032e0 <node2>: 0x000000a8 0x00000002 0x006032f0 0x00000000
0x6032f0 <node3>: 0x0000039c 0x00000003 0x00603300 0x00000000
0x603300 <node4>: 0x000002b3 0x00000004 0x00603310 0x00000000
0x603310 <node5>: 0x000001dd 0x00000005 0x00603320 0x00000000
0x603320 <node6>: 0x000001bb 0x00000006 0x00000000 0x00000000
##注意到,node的间隔为0x10 = 16D
##我们还可以用c语言描述node:
struct node{
int val;
int order;
node next;
}
那么,该段循环是怎么改变链表顺序的呢?首先引入栈帧%rsp + 8*i + 0x20,i取0 ~ 5
,然后在+130 ~ +137
,0x6032d0
将随sub_input[i]
的改变而改变,最后将改变后的链表逐个赋给栈帧。
即执行执行赋值算式:%rsp + 8*i + 0x20 = $0x6032d0 + (8*(sub_input[i]-1))
这样下来就能够重排链表了。但此处还没有给出改变链表顺序的标准是什么。
同时注意到一个重要的细节问题:改变时0x6032d0的间隔为8D,但我们需要的间隔为0x10 = 16D,如何解决?
事实上,间隔为8也没关系。只要部分+8的地址所存数值恰好为+16的地址即可。(不是所有地址都需要这样,另一部分地址执行两次+8就行了)
上述表述等价于三个等式
*(0x6032d0+8) = 0x6032e0
、*(0x6032f0+8) = 0x603200
、*(0x603310+8)= 0x603320
成立。
我们用p/x 指令打印数值看三个等式是否成立。(这个成立是phase_6
已经设置好的,但不查看之前完全不知道)
(gdb) p/x *(0x6032d0+8)
$1 = 0x6032e0
(gdb) p/x *(0x6032f0+8)
$2 = 0x603300
(gdb) p/x *(0x603310+8)
$3 = 0x603320
#证明等式成立。
执行part4后,可以得出如下表格:
rec_node0 | rec_node1 | rec_node2 | rec_node3 | rec_node4 | rec_node5 |
---|---|---|---|---|---|
%rsp + 0x20 | %rsp + 0x28 | %rsp + 0x30 | %rsp + 0x38 | %rsp + 0x40 | %rsp + 0x48 |
说明rec_node1 ~ rec_node6
将逐个放入栈帧%rsp + 0x20 ~ %rsp + 0x48
中。
注意:因为node0 ~ node5已经随sub_input而重新排列了,但我们还不知道是如何排列的。
所以,不妨命名为:rec_node0 ~ rec_node5,表明链表重组,也就是所谓的改变链表顺序。
(rec:recombination)
同时也可以得出sub_input
对应rec_node
的表格:
sub_input[0] | sub_input[1] | sub_input[2] | sub_input[3] | sub_input[4] | sub_input[5] |
---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 |
0x6032d0 | 0x6032e0 | 0x6032f0 | 0x603300 | 0x603310 | 0x603320 |
part5,重新连接next。
0x00000000004011ab <+183>: mov 0x20(%rsp),%rbx
0x00000000004011b0 <+188>: lea 0x28(%rsp),%rax
0x00000000004011b5 <+193>: lea 0x50(%rsp),%rsi #%rsi = 0x50(%rsp) = 0
0x00000000004011ba <+198>: mov %rbx,%rcx
#loop6,+201~+220,判断flag值的5次+初始1次,
#执行6次赋值算式:0x8+(0x20(%rsp)+(j-1)*8) = (0x28(%rsp)+j*8),j取0 ~ 5
#会将间隔为8的%0x28(%rsp) ~ 0x50(%rsp)的地址,逐个赋给间隔为8的%0x20(%rsp) ~ 0x48(%rsp)
#loop6结束时,%rdx = 0x48(%rsp)
0x00000000004011bd <+201>: mov (%rax),%rdx
0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx) #0x8(%rcx+(j-1)*8) = %rax+j*8
0x00000000004011c4 <+208>: add $0x8,%rax #%rax += 8,记为j,每次增8,也是flag值
0x00000000004011c8 <+212>: cmp %rsi,%rax #比较%rsi和%rax,执行5次
0x00000000004011cb <+215>: je 0x4011d2 <phase_6+222> #若相等,结束loop6
0x00000000004011cd <+217>: mov %rdx,%rcx #否则,%rcx = %rdx
0x00000000004011d0 <+220>: jmp 0x4011bd <phase_6+201> #-> +201,继续loop6
0x00000000004011d2 <+222>: movq $0x0,0x8(%rdx) #0x8(%rdx) = 0x50(%rsp) = 0
注意:
0x50(%rsp) = 0 = NULL
是next连接完才赋值的。- 这里利用栈帧
%0x28(%rsp) ~ 0x50(%rsp)
逐个赋给%0x20(%rsp) ~ 0x48(%rsp)
改变的是rec_node ->next指向的地址,而不是rec_ndoe本身。也就是所谓的重新连接next。 - 之所以要重连next,也是因为node已经重排为rec_ndoe了。
part6,说明改变链表顺序的标准:按val
降序排列。(从大到小)
0x00000000004011da <+230>: mov $0x5,%ebp #%ebp = 5
#loop7,判断flag值的5次+初始1次,
#共执行6次判断算式:(0x8+0x20+%rsp+k*8) >= (0x20+%rsp+k*8) ? 继续执行直到k=5结束:爆炸
#即保证等式:node->val >= node->next->val成立。
0x00000000004011df <+235>: mov 0x8(%rbx),%rax
0x00000000004011e3 <+239>: mov (%rax),%eax
0x00000000004011e5 <+241>: cmp %eax,(%rbx) #比较(0x8+0x20+%rsp+k*8)和(0x20+%rsp+k*8)
0x00000000004011e7 <+243>: jge 0x4011ee <phase_6+250> #若前者 >= 后者,-> +250
0x00000000004011e9 <+245>: callq 0x40143a <explode_bomb> #否则,爆炸
0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx #取0x20+%rsp+k*8
0x00000000004011f2 <+254>: sub $0x1,%ebp #%ebp--,flag值,执行5次
0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235> #%ebp不为0时,继续loop7
##
0x00000000004011f7 <+259>: add $0x50,%rsp #释放栈空间
0x00000000004011fb <+263>: pop %rbx #出栈
0x00000000004011fc <+264>: pop %rbp
0x00000000004011fd <+265>: pop %r12
0x00000000004011ff <+267>: pop %r13
0x0000000000401201 <+269>: pop %r14
0x0000000000401203 <+271>: retq
之所以按val
降序排列,是因为必须满足(0x8+0x20+%rsp+k*8) >= (0x20+%rsp+k*8),k取0~5
即node->val >= node->next->val
成立,也就是链表中当前节点值大于下一个节点值。
综上part1 ~ part6可以得出答案:(sub_input
模7得到input
)
rec_node0 | rec_node1 | rec_node2 | rec_node3 | rec_node4 | rec_node5 | |
---|---|---|---|---|---|---|
val | 0x39c | 0x2b3 | 0x1dd | 0x1bb | 0x14c | 0x0a8 |
rec_node | 0x6032f0 | 0x603300 | 0x603300 | 0x603300 | 0x6032d0 | 0x6032e0 |
sub_input | 3 | 4 | 5 | 6 | 1 | 2 |
input | 4 | 3 | 2 | 1 | 6 | 5 |
inuput为:4 3 2 1 6 5
ps:如果你仔细观察,你会发现,按val降序排列之后,order其实恰好对应着sub_input。
key:
//before input
Good work! On to the next...
//after input
Good work! On to the next...
4 3 2 1 6 5
Congratulations! You've defused the bomb!
[Inferior 1 (process 46719) exited normally]
可恶啊!竟然还有个彩蛋关。什么?你说我怎么发现的,当然是“口口相传”啦。
(事实上可以在编译文件上查找ctrl+f
到)
secret_phase
用到了二叉树。
查看phase_defused
的汇编代码:(可以知道在phase_4中多输入个DrEvil即0 0 DrEvil
才能进入彩蛋关)
0x00000000004015c4 <+0>: sub $0x78,%rsp
0x00000000004015c8 <+4>: mov %fs:0x28,%rax
0x00000000004015d1 <+13>: mov %rax,0x68(%rsp)
0x00000000004015d6 <+18>: xor %eax,%eax
0x00000000004015d8 <+20>: cmpl $0x6,0x202181(%rip) # 0x603760 <num_input_strings>
0x00000000004015df <+27>: jne 0x40163f <phase_defused+123>
0x00000000004015e1 <+29>: lea 0x10(%rsp),%r8
0x00000000004015e6 <+34>: lea 0xc(%rsp),%rcx
0x00000000004015eb <+39>: lea 0x8(%rsp),%rdx
0x00000000004015f0 <+44>: mov $0x402619,%esi #输入格式%esi:"%d %d %s"
0x00000000004015f5 <+49>: mov $0x603870,%edi #input
0x00000000004015fa <+54>: callq 0x400bf0 <__isoc99_sscanf@plt>
0x00000000004015ff <+59>: cmp $0x3,%eax
0x0000000000401602 <+62>: jne 0x401635 <phase_defused+113>
0x0000000000401604 <+64>: mov $0x402622,%esi #%esi = DrEvil
0x0000000000401609 <+69>: lea 0x10(%rsp),%rdi
0x000000000040160e <+74>: callq 0x401338 <strings_not_equal>
0x0000000000401613 <+79>: test %eax,%eax #字符串相同返回值为0,进入secret_phase
0x0000000000401615 <+81>: jne 0x401635 <phase_defused+113>
0x0000000000401617 <+83>: mov $0x4024f8,%edi
0x000000000040161c <+88>: callq 0x400b10 <puts@plt>
0x0000000000401621 <+93>: mov $0x402520,%edi
0x0000000000401626 <+98>: callq 0x400b10 <puts@plt>
0x000000000040162b <+103>: mov $0x0,%eax
0x0000000000401630 <+108>: callq 0x401242 <secret_phase>
0x0000000000401635 <+113>: mov $0x402558,%edi
0x000000000040163a <+118>: callq 0x400b10 <puts@plt>
0x000000000040163f <+123>: mov 0x68(%rsp),%rax
0x0000000000401644 <+128>: xor %fs:0x28,%rax
0x000000000040164d <+137>: je 0x401654 <phase_defused+144>
0x000000000040164f <+139>: callq 0x400b30 <__stack_chk_fail@plt>
0x0000000000401654 <+144>: add $0x78,%rsp
0x0000000000401658 <+148>: retq
分析:
我们对一些字符串进行访问,可以知道调用sscanf
函数前得输入,其格式为"%d %d %s"
但输入内容是空的,这说明我们还没输入。我们需要输入,但,是什么时候输入呢?
(gdb) x/s 0x402619
0x402619: "%d %d %s"
(gdb) x/s 0x603870
0x603870 <input_strings+240>: ""
(gdb) x/s 0x402622
0x402622: "DrEvil"
要确定输入是何时输入的,我们可以先在*0x4015f5
处,即输入处打断点,然后run bomb文件,最后访问0x603870
的内容。
Reading symbols from bomb...
(gdb) b *0x4015f5
Breakpoint 1 at 0x4015f5
(gdb) run
Starting program: /home/duile/Desktop/csapp_lab/bomb-handout/bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2. Keep going!
7 327
Halfway there!
0 0
So you got that one. Try this one.
9/.567
Good work! On to the next...
4 3 2 1 6 5
Breakpoint 1, 0x00000000004015f5 in phase_defused ()
(gdb) x/s 0x603870
0x603870 <input_strings+240>: "0 0 "
此时有0x603870 <input_strings+240>: "0 0 "
,这说明输入是在phase_4
处进行的。其输入格式为"%d %d %s"
,同时有*(0x402622) == DrEvil
,之后要进行字符比对且相同才进入secret_phase。那么我们大胆猜测,应该在phase_4
处输入0 0 DrEvil
才能进入彩蛋关。
#果然如此
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2. Keep going!
7 327
Halfway there!
0 0 DrEvil #将0 0替换为0 0 DrEvil
So you got that one. Try this one.
9/.567
Good work! On to the next...
4 3 2 1 6 5
Curses, you've found the secret phase!
But finding it and solving it are quite different...
开始正式分析,首先查看secret_phase
的汇编代码:(说明调用fun7之后,返回值必须为2)
0x0000000000401242 <+0>: push %rbx
0x0000000000401243 <+1>: callq 0x40149e <read_line>
0x0000000000401248 <+6>: mov $0xa,%edx #base = 10D
0x000000000040124d <+11>: mov $0x0,%esi
0x0000000000401252 <+16>: mov %rax,%rdi #input
0x0000000000401255 <+19>: callq 0x400bd0 <strtol@plt>
0x000000000040125a <+24>: mov %rax,%rbx #返回值rax赋给%rbx
0x000000000040125d <+27>: lea -0x1(%rax),%eax
0x0000000000401260 <+30>: cmp $0x3e8,%eax #满足-0x1(%rax) <= 0x3e8,跳转至+42,否则爆炸
0x0000000000401265 <+35>: jbe 0x40126c <secret_phase+42>
0x0000000000401267 <+37>: callq 0x40143a <explode_bomb>
0x000000000040126c <+42>: mov %ebx,%esi #esi是输入
0x000000000040126e <+44>: mov $0x6030f0,%edi #rdi = 0x24
0x0000000000401273 <+49>: callq 0x401204 <fun7>
0x0000000000401278 <+54>: cmp $0x2,%eax #返回值必须等于2,否则爆炸
0x000000000040127b <+57>: je 0x401282 <secret_phase+64>
0x000000000040127d <+59>: callq 0x40143a <explode_bomb>
0x0000000000401282 <+64>: mov $0x402438,%edi
0x0000000000401287 <+69>: callq 0x400b10 <puts@plt>
0x000000000040128c <+74>: callq 0x4015c4 <phase_defused>
0x0000000000401291 <+79>: pop %rbx
0x0000000000401292 <+80>: retq
查看fun7
的汇编代码:
0x0000000000401204 <+0>: sub $0x8,%rsp
0x0000000000401208 <+4>: test %rdi,%rdi #rdi == 0时,返回-1
0x000000000040120b <+7>: je 0x401238 <fun7+52>
0x000000000040120d <+9>: mov (%rdi),%edx
0x000000000040120f <+11>: cmp %esi,%edx #(%rdi) <= %esi
0x0000000000401211 <+13>: jle 0x401220 <fun7+28>
0x0000000000401213 <+15>: mov 0x8(%rdi),%rdi #rdi += 0x8
0x0000000000401217 <+19>: callq 0x401204 <fun7>
0x000000000040121c <+24>: add %eax,%eax
0x000000000040121e <+26>: jmp 0x40123d <fun7+57>
0x0000000000401220 <+28>: mov $0x0,%eax
0x0000000000401225 <+33>: cmp %esi,%edx
0x0000000000401227 <+35>: je 0x40123d <fun7+57> #%esi == %edx
0x0000000000401229 <+37>: mov 0x10(%rdi),%rdi #rdi += 0x10
0x000000000040122d <+41>: callq 0x401204 <fun7>
0x0000000000401232 <+46>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401236 <+50>: jmp 0x40123d <fun7+57>
0x0000000000401238 <+52>: mov $0xffffffff,%eax
0x000000000040123d <+57>: add $0x8,%rsp
0x0000000000401241 <+61>: retq
分析:
这是一棵二叉树,思路如图所示:
方法一:实现eax = 2。(ps:递归调用顺序与返回顺序相反)
eax = 0 --> eax = 2*eax+1 --> eax = 2*eax
0 --> 1 --> 2
故返回顺序为:
D --> C --> B
因而递归调用顺序为:(与返回顺序相反)
B --> C --> D
下面描述调用过程:(ps:esi是input,每次调用完都会更新rid指向的地址)
-
B:第一次调用fun7,
(rdi) = (0x6030f0) = 0x24
。同时输入要不满足(rdi) <= esi
,也就说输入小于0x24,才能走路径B。
此次调用结束时更新rdi指向的地址,即:rdi+0x8 = 0x603110
。 -
C:第二次调用fun7,
(rdi) = (0x603110) = 0x8
。同时输入要满足(rdi) <= esi
且不满足edx == esi
,也就说输入大于0x8且不等于0x8,才能走路径C。
此次调用结束时更新rdi指向的地址,即:rdi+0x10 = 0x603150
。 -
D:第三次调用fun7,
(rdi) = (0x603150) = 0x16
。同时输入要满足(rdi) <= esi
且满足edx == esi
,也就说输入等于0x16即20D,才能走路径D。
三次调用结束,得出input为20
。
#这是gdb指令的访问过程:(使用的是打印指令,更清晰)
(gdb) p/a *(0x6030f0)
$1 = 0x24
(gdb) p/a *(0x6030f0+0x8)
$2 = 0x603110 <n21>
(gdb) p/a *(0x603110)
$3 = 0x8
(gdb) p/a *(0x603110+0x10)
$4 = 0x603150 <n32>
(gdb) p/a *(0x603150)
$5 = 0x16
方法二:实现eax = 2。
eax = 0 --> eax = 2*eax--> eax = 2*eax+1 --> eax = 2*eax
0 --> 0 --> 1 --> 2
故返回顺序为:
D --> B --> C --> B
因而递归调用顺序为:(与返回顺序相反)
B --> C --> B --> D
我们按照递归调用访问(rdi)得出结果:0x14 = 20D(使用的是打印指令,更便捷)
(gdb) x/a 0x6030f0+0x8 #b
0x6030f8 <n1+8>: 0x603110 <n21>
(gdb) x/a 0x603110+0x10 #c
0x603120 <n21+16>: 0x603150 <n32>
(gdb) x/a 0x603150+0x8 #b
0x603158 <n32+8>: 0x603270 <n43>
(gdb) x/a 0x603270 #d
0x603270 <n43>: 0x14
key:我们取20
这个答案。
//before input
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2. Keep going!
7 327
Halfway there!
0 0 DrEvil
So you got that one. Try this one.
9/.567
Good work! On to the next...
4 3 2 1 6 5
Curses, you've found the secret phase!
But finding it and solving it are quite different...
//after input
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Border relations with Canada have never been better.
Phase 1 defused. How about the next one?
1 2 4 8 16 32
That's number 2. Keep going!
7 327
Halfway there!
0 0 DrEvil
So you got that one. Try this one.
9/.567
Good work! On to the next...
4 3 2 1 6 5
Curses, you've found the secret phase!
But finding it and solving it are quite different...
20
Wow! You've defused the secret stage!
Congratulations! You've defused the bomb!
[Inferior 1 (process 48781) exited normally]
无聊地查看一下二叉树内容:(此处对解题不是很有必要)
(gdb) x/116wx 0x6030f0
0x6030f0 <n1>: 0x00000024 0x00000000 0x00603110 0x00000000
0x603100 <n1+16>: 0x00603130 0x00000000 0x00000000 0x00000000
0x603110 <n21>: 0x00000008 0x00000000 0x00603190 0x00000000
0x603120 <n21+16>: 0x00603150 0x00000000 0x00000000 0x00000000
0x603130 <n22>: 0x00000032 0x00000000 0x00603170 0x00000000
0x603140 <n22+16>: 0x006031b0 0x00000000 0x00000000 0x00000000
0x603150 <n32>: 0x00000016 0x00000000 0x00603270 0x00000000
0x603160 <n32+16>: 0x00603230 0x00000000 0x00000000 0x00000000
0x603170 <n33>: 0x0000002d 0x00000000 0x006031d0 0x00000000
0x603180 <n33+16>: 0x00603290 0x00000000 0x00000000 0x00000000
0x603190 <n31>: 0x00000006 0x00000000 0x006031f0 0x00000000
0x6031a0 <n31+16>: 0x00603250 0x00000000 0x00000000 0x00000000
0x6031b0 <n34>: 0x0000006b 0x00000000 0x00603210 0x00000000
0x6031c0 <n34+16>: 0x006032b0 0x00000000 0x00000000 0x00000000
0x6031d0 <n45>: 0x00000028 0x00000000 0x00000000 0x00000000
0x6031e0 <n45+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x6031f0 <n41>: 0x00000001 0x00000000 0x00000000 0x00000000
0x603200 <n41+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x603210 <n47>: 0x00000063 0x00000000 0x00000000 0x00000000
0x603220 <n47+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x603230 <n44>: 0x00000023 0x00000000 0x00000000 0x00000000
0x603240 <n44+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x603250 <n42>: 0x00000007 0x00000000 0x00000000 0x00000000
0x603260 <n42+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x603270 <n43>: 0x00000014 0x00000000 0x00000000 0x00000000
0x603280 <n43+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x603290 <n46>: 0x0000002f 0x00000000 0x00000000 0x00000000
0x6032a0 <n46+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x6032b0 <n48>: 0x000003e9 0x00000000 0x00000000 0x00000000
#c语言描述二叉树
typedef struct BiTNode{
TElemType data;//数据
struct BiTNode *light, *right;//左右孩子指针
} BiTNode, *BiTree;
整理后的图状展示:
└─ 36
├─ 8
│ ├─ 6
│ │ ├─ left: 1
│ │ └─ right: 7
│ └─ 22
│ ├─ left: 20
│ └─ right: 35
└─ 50
├─ 45
│ ├─ left: 40
│ └─ right: 47
└─ 107
├─ left: 99
└─ right: 1001
结语
-
But you know, Alan, sometimes it's the very people who no one imagines anything of who do the things no one can imagine. ---The Imitation Game
-
从9.27到10.11,总算完结了。期间有几天是没有写的。总共是8天,最后两关都用了2天。当然不是一整天,5、6个小时应该是有的。完全靠自己写的是phase_4,奈何没啥基础。前几个都在适应gdb是怎么调试的,后几个难度比较大,phase_5是没联系到ASCII,后两个是链表、二叉树都不熟悉。也参考了不少其它大佬的解答。
-
自己的汇编阅读能力是提升了不少,后面看都是比较自然,但是联系到数据结构就比较不了解,还是得继续提高姿势水平。
-
如有谬误,敬请指正。