这学期做完了计组实验后,我终于有时间挑战一下一直心心念念的CMU CS15-213的拆炸弹实验了。
看了几眼题目,发现需要读懂汇编代码,于是又屁颠屁颠跑回去学CSAPP第三章……
学完之后,我大致描述一下这个Lab:这个Lab重点考察x86-64汇编代码阅读能力,但考察的难度不深,主要是读懂条件分支、循环和数组链表等代码的实现。
而入门x86-64汇编代码的最好教程就是CSAPP第三章!
而入门x86-64汇编代码的最好教程就是CSAPP第三章!
而入门x86-64汇编代码的最好教程就是CSAPP第三章!
(此发言综合了找资源等时间精力上的消耗的考量,含有较强的个人主观成分;当然若您有更好的资料,您对,您也可分享)
在学CSAPP第三章的时候,首先要简单掌握各个指令分别是什么意思,而重点在于后面的“控制”“过程”两节,讲解了分支、循环、函数调用过程中的返回值
、被调用者保存
、局部变量
和参数
等概念的汇编实现,学会了对看懂汇编程序大有帮助!
Bomb Lab 提供两个文件,可执行文件bomb
和程序主函数bomb.c
,我们目前只有程序的主函数,需要使用objdump反编译bomb得到炸弹程序完整的汇编代码,再用所学知识反编译为C代码。
因此实际上呀,是有6个阶段,要拆6个炸弹!
但是这个Lab并没有想象中的难,最好是尽量自己做,不会再查相关资料,而不是直接看别人的总结。
bomb
可执行文件与bomb.c
objdump -S -d main > main.txt
,从bomb
中反汇编出汇编文件bomb.txt
gbd bomb
进入到gdb功能(main的源码我就不贴了)
我们从bomb.c
文件中得知,我们的目标就是解决6个phase
函数,每次一个输入,当6次输入都不会引爆各自phase
函数中的
时,就能够正常通过phase
函数啦!
那就开始看
函数吧!
0000000000400ee0 :
phase_1():
400ee0: 48 83 ec 08 sub $0x8,%rsp # 开8字节的栈
400ee4: be 00 24 40 00 mov $0x402400,%esi # 第二个参数(第一个是你刚输进入的数)
400ee9: e8 4a 04 00 00 call 401338 #查一下
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7 # BOMB if %eax == 0
400ef2: e8 43 05 00 00 call 40143a
400ef7: 48 83 c4 08 add $0x8,%rsp # 关栈
400efb: c3 ret
(p.s. 里面的两个函数
和
,先姑且按字面意思理解,一个是比较两字符串是否相等并返回布尔值,另一个是炸弹爆炸。lab做完后再去看源码相信对你来说已是小菜一碟)
书上说,x86-64给函数传参使用的寄存器是(依次列出):%rdi
%rsi
%dx
rcx
r8
r9
。因此我们在调用函数
前设置的%rsi
就是它的第二个参数,那我们的第一个参数则是我们的%rdi
。为了验证想法,我们可以用GDB查看在0x400ee9
位置时这两个参数的值(此时0x400ee9
位置的指令尚未执行):
忽略中间执行信息,我们直接看头尾,我先是在0x400ee9
设置了一个断点,然后输入r
执行调试,输入hhhhhhhWhat?
。随后达到断点,查看两寄存器指向位置的字符串,证实了我们的想法。
继续看
的代码:
400ee9: e8 4a 04 00 00 call 401338 #查一下
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7 # BOMB if %eax == 0
400ef2: e8 43 05 00 00 call 40143a
这一段是非常经典的分支代码了,我们知道调用函数
会返回一个布尔值,而这个返回值是放在%rax
中传出的。test %eax,%eax
使%eax = %eax & %eax
,与je
指令一起使用则是比较%rax == 0
,满足则跳转,不满足则不跳转然后炸弹爆炸。
那就简单了嘛,只要我的输入和%rsi
指向的字符串相同,那我就成了嘻嘻~
这是
开头部分,信息量比较小:
0000000000400efc :
400efc: 55 push %rbp
400efd: 53 push %rbx # 保存被调用者保存寄存器,可以不理会
400efe: 48 83 ec 28 sub $0x28,%rsp # 开栈(开栈的用途有多种,看后面才能知道这里的目的)
400f02: 48 89 e6 mov %rsp,%rsi # 把栈交给%rsi
这时候也还看不出%rsi
的作用:
接下来是一个函数调用与一个分支判断,“read_six_numbers”是吧,那我就随便输入个1 1 1 1 1 1
吧。暂时不管函数
内部进行了什么工作,直接看调用后的结果。
400f05: e8 52 05 00 00 call 40145c # 获取6个int数
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp)
400f0e: 74 20 je 400f30 # 若nums[0]==1
400f10: e8 25 05 00 00 call 40143a
这不就来了嘛,看来这个栈,相当于一个int数组呀!(姑且命名为nums数组)
然后的分支判断比较1 == (%rsp)
相当于1 == nums[0]
,不满足就爆炸,还好我满足了哇。
下面又直接跳到了位置0x400f30
,不好,书上说,这是循环的征兆!
# 跳到循环初始化
400f15: eb 19 jmp 400f30
# 循环体begin
400f17: 8b 43 fc mov -0x4(%rbx),%eax # %eax = nums[i-1]
400f1a: 01 c0 add %eax,%eax
# if(2*nums[i-1] != nums[i]) BOMB();
400f1c: 39 03 cmp %eax,(%rbx) # if(2*nums[i-1] != nums[i]) BOMB()
400f1e: 74 05 je 400f25
400f20: e8 15 05 00 00 call 40143a
# 即总要有nums[i] == 2 * nums[i-1]!!
400f25: 48 83 c3 04 add $0x4,%rbx # i++
# 循环终止判断
400f29: 48 39 eb cmp %rbp,%rbx
400f2c: 75 e9 jne 400f17
400f2e: eb 0c jmp 400f3c
# 循环初始化
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx # %rbx加4,rbx即for循环中的i
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp # 终止条件
400f3a: eb db jmp 400f17
# 循环体end
循环的流程已经给出来了,直接看注释。目前得到的最重要的信息有:
因此答案就是1 2 4 8 12 32
啦! 剩下只是收尾代码:
# 退出函数
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 ret
(p.s.有兴趣的同学可以看看
函数,我只补充其中函数<__isoc99_sscanf@plt>
的返回值是读取到的数字的数目,在read_six_numbers
中若其返回值不大于5,则爆炸。至于它的参数%rsi
,我用GDB
看了一下:指向"%d %d %d %d %d %d"
。
000000000040145c :
40145c: 48 83 ec 18 sub $0x18,%rsp # 开18字节的栈
401460: 48 89 f2 mov %rsi,%rdx
401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx # 要打草稿
401467: 48 8d 46 14 lea 0x14(%rsi),%rax
40146b: 48 89 44 24 08 mov %rax,0x8(%rsp)
401470: 48 8d 46 10 lea 0x10(%rsi),%rax
401474: 48 89 04 24 mov %rax,(%rsp)
401478: 4c 8d 4e 0c lea 0xc(%rsi),%r9
40147c: 4c 8d 46 08 lea 0x8(%rsi),%r8
401480: be c3 25 40 00 mov $0x4025c3,%esi
401485: b8 00 00 00 00 mov $0x0,%eax
40148a: e8 61 f7 ff ff call 400bf0 <__isoc99_sscanf@plt>
40148f: 83 f8 05 cmp $0x5,%eax # if(5 >= %eax) BOMB();
401492: 7f 05 jg 401499
401494: e8 a1 ff ff ff call 40143a
401499: 48 83 c4 18 add $0x18,%rsp
40149d: c3 ret
phase_3
真长呀。
这次,上面讲过的知识点我只用注释注明。再提一嘴,我是用GDB研究地址指向内容的。
下面的代码获取两个数,姑且设为x,y。
0000000000400f43 :
400f43: 48 83 ec 18 sub $0x18,%rsp # 开栈
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx # 指针%rcx
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx # 指针%rdx
400f51: be cf 25 40 00 mov $0x4025cf,%esi # 指向"%d %d"
400f56: b8 00 00 00 00 mov $0x0,%eax
# 获取两个数,设为x,y,存储在(%rdx)和(%rcx),返回读取数目,保存在%rax返回
400f5b: e8 90 fc ff ff call 400bf0 <__isoc99_sscanf@plt>
# 和``一样的把戏,检查输入的数量
400f60: 83 f8 01 cmp $0x1,%eax
400f63: 7f 05 jg 400f6a
400f65: e8 d0 04 00 00 call 40143a
# x 无符号超过7则爆炸
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp)
400f6f: 77 3c ja 400fad
从上面代码得到的最重要的信息是0 <= x <= 7
。
下面有好长一段代码呀,而且很有规律,都是jmp-mov-jmp-mov
的,我做的时候不知道这是啥呀就反推分析了,直接去看要避开爆炸需要什么条件了,但现在我决心要弄懂整串代码的逻辑。
# 间接跳转到(8*x + 0x402470)保存的值
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax
400f75: ff 24 c5 70 24 40 00 jmp *0x402470(,%rax,8)
# 分别为0x400f7c 0x400fb9 0x400f83 0x400f8a 0x400f91 0x400f98 0x400f9f 0x400fa6
400f7c: b8 cf 00 00 00 mov $0xcf,%eax
400f81: eb 3b jmp 400fbe
400f83: b8 c3 02 00 00 mov $0x2c3,%eax
400f88: eb 34 jmp 400fbe
400f8a: b8 00 01 00 00 mov $0x100,%eax
400f8f: eb 2d jmp 400fbe
400f91: b8 85 01 00 00 mov $0x185,%eax
400f96: eb 26 jmp 400fbe
400f98: b8 ce 00 00 00 mov $0xce,%eax
400f9d: eb 1f jmp 400fbe
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax
400fa4: eb 18 jmp 400fbe
400fa6: b8 47 01 00 00 mov $0x147,%eax
400fab: eb 11 jmp 400fbe
# 这个爆炸函数只和上一段代码的条件分支有关,不知为何出现于此
400fad: e8 88 04 00 00 call 40143a
400fb2: b8 00 00 00 00 mov $0x0,%eax
400fb7: eb 05 jmp 400fbe
400fb9: b8 37 01 00 00 mov $0x137,%eax # x == 1
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax # y == 0x137(即311) 则结束
400fc2: 74 05 je 400fc9
400fc4: e8 71 04 00 00 call 40143a
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 ret
这段代码开头的间接跳转(书上有介绍间接跳转),它的可能跳转目标地址有8个(根据x
的取值)为:
而中间一连串代码的共同特征是:
400fbe
,即最后的爆炸判断代码。而最后不爆炸的条件是"y == %eax
"。
综上,我们要先输入一个0~7范围的整数x
,然后输入一个y
等于对应目标地址程序的%eax
值(注意程序与地址不是按顺序对应的),即可能的答案有8种。
我用的是1 311
。
000000000040100c :
40100c: 48 83 ec 18 sub $0x18,%rsp
401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx # y
401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx # x
40101a: be cf 25 40 00 mov $0x4025cf,%esi
40101f: b8 00 00 00 00 mov $0x0,%eax
401024: e8 c7 fb ff ff call 400bf0 <__isoc99_sscanf@plt>
401029: 83 f8 02 cmp $0x2,%eax # 两个输入
40102c: 75 07 jne 401035
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) # x <= 14
401033: 76 05 jbe 40103a
401035: e8 00 04 00 00 call 40143a
# 调用函数: int func4(x,0,14);
40103a: ba 0e 00 00 00 mov $0xe,%edx
40103f: be 00 00 00 00 mov $0x0,%esi
401044: 8b 7c 24 08 mov 0x8(%rsp),%edi
401048: e8 81 ff ff ff call 400fce
40104d: 85 c0 test %eax,%eax # eax == 0(即x==7)
40104f: 75 07 jne 401058
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) # 0 == y
401056: 74 05 je 40105d
401058: e8 dd 03 00 00 call 40143a
40105d: 48 83 c4 18 add $0x18,%rsp
401061: c3 ret
上面
函数的逻辑还是很清晰的,我们得到了两个信息:
y == 0
函数的返回值%eax == 0
函数究竟返回了个什么%eax
了。我发现
使用大量的数值运算和分支跳转,更要命的是还是递归函数,我就先将它写成C语言函数再来分析了:# 写成C函数(源码我就不贴了):
# 初始时si = 0,dx = 14
int func4(int x, int si, int dx){
int ax = dx - si;
int cx = ax / (2^31);
ax = (ax+cx) / 2;
cx = ax + si;
if(cx <= x){
ax = 0;
if(cx >= x){ # cx == x 则返回0
return ax;
}
si = cx + 1;
func(x,si,dx);
return 2 * ax + 1;
}
else {
dx = cx - 1;
func(x,si,dx);
return ax * 2;
}
}
看,多么简单,甚至不用在意那两处自调用,只需要开始时"x == 7
"就能获得为0
的返回值!
综上答案是“7 0
”。
这题可有意思嘿嘿。
0000000000401062 :
401062: 53 push %rbx
401063: 48 83 ec 20 sub $0x20,%rsp
401067: 48 89 fb mov %rdi,%rbx # rbx指向我的输入
# fs是一个段寄存器,但此时%fs == 0(具体不清楚不管先)
40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
401071: 00 00
401073: 48 89 44 24 18 mov %rax,0x18(%rsp) #
# 输入长度为6
401078: 31 c0 xor %eax,%eax # 令eax = 0
40107a: e8 9c 02 00 00 call 40131b
40107f: 83 f8 06 cmp $0x6,%eax
401082: 74 4e je 4010d2
401084: e8 b1 03 00 00 call 40143a
接着是一段循环:
# 跳到循环的初始化位置 (设eax=i,rbx=str)
401089: eb 47 jmp 4010d2
# 循环体begin # 'b'证明了数组str里存放的是char型
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx # ecx = str[i]
40108f: 88 0c 24 mov %cl,(%rsp)
401092: 48 8b 14 24 mov (%rsp),%rdx
401096: 83 e2 0f and $0xf,%edx # edx = ecx & 0xf
# 又是一个字符串,查查看!
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx # 以%edx为偏移取一个char型
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) # 放进另一个字符串内
4010a4: 48 83 c0 01 add $0x1,%rax # i++
# 循环终止判断
4010a8: 48 83 f8 06 cmp $0x6,%rax # rax == 6 则终止循环
4010ac: 75 dd jne 40108b # 否则接着遍历
# 离开循环,竟马上判断爆炸。(很有趣,虽然被编译进循环体里,却不会被循环访问到)
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp) # 设置字符串最后的'\0'
4010b3: be 5e 24 40 00 mov $0x40245e,%esi # 又一字符串,查到为"flyers"
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi # 刚才我们的一番操作得到的字符串
4010bd: e8 76 02 00 00 call 401338
4010c2: 85 c0 test %eax,%eax # 两字符串相等
4010c4: 74 13 je 4010d9 # 否则爆炸
4010c6: e8 6f 03 00 00 call 40143a
4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
4010d0: eb 07 jmp 4010d9
# 循环初始化
4010d2: b8 00 00 00 00 mov $0x0,%eax # i=0
4010d7: eb b2 jmp 40108b # 跳到循环体首行
# 循环end(因此循环的作用是遍历我的输入字符串,算出另一个字符串)
我查看0x4024b0位置的内容(作者真皮):
“maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?”
综上我们的任务就是,输入一串6位字符串,将每个字符的二进制码的后8位表示的数字作为偏移量,在上面的字符串中选出"flyers
"即可。因此答案也有多种,只要满足最后8位为9、15、14、5、6、7
即可,我使用的是“IONEFG”。(有空格也不行哦)
剩下一些无伤大雅的函数收尾工作,我就不赘述了。不过我还是不知道%fs
是什么呜呜。
终于来到的最终环节,这次的函数也是至今为止最长的家伙(虽然结构挺明显的):
00000000004010f4 :
4010f4: 41 56 push %r14
4010f6: 41 55 push %r13
4010f8: 41 54 push %r12
4010fa: 55 push %rbp
4010fb: 53 push %rbx
4010fc: 48 83 ec 50 sub $0x50,%rsp # %rsp 保存了我的输入
401100: 49 89 e5 mov %rsp,%r13 # %rsi = &nums[0]
401103: 48 89 e6 mov %rsp,%rsi # %rsi = &nums[0]
# 读六位int型,姑且称为数组nums
401106: e8 51 03 00 00 call 40145c
40110b: 49 89 e6 mov %rsp,%r14 # %r14 = &nums[0]
# 开头便是2层嵌套数组真猛# 外循环初始化
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d
# 外循环
401114: 4c 89 ed mov %r13,%rbp
401117: 41 8b 45 00 mov 0x0(%r13),%eax
40111b: 83 e8 01 sub $0x1,%eax
# 确保 1 <= nums[i] <= 6
40111e: 83 f8 05 cmp $0x5,%eax
401121: 76 05 jbe 401128 # 0 <= eax <= 5
401123: e8 12 03 00 00 call 40143a
401128: 41 83 c4 01 add $0x1,%r12d # i++
40112c: 41 83 fc 06 cmp $0x6,%r12d # r12 == 5 则离开外循环
401130: 74 21 je 401153
401132: 44 89 e3 mov %r12d,%ebx # ebx = r12
# 内循环
401135: 48 63 c3 movslq %ebx,%rax # j = i
401138: 8b 04 84 mov (%rsp,%rax,4),%eax # nums[j]
# 6个输入互相不能相等
40113b: 39 45 00 cmp %eax,0x0(%rbp) # num[j~5] != nums[i-1]
40113e: 75 05 jne 401145
401140: e8 f5 02 00 00 call 40143a
401145: 83 c3 01 add $0x1,%ebx # ebx++
401148: 83 fb 05 cmp $0x5,%ebx # ebx > 5 则离开内循环
40114b: 7e e8 jle 401135
# 内循环end
40114d: 49 83 c5 04 add $0x4,%r13 # i++
401151: eb c1 jmp 401114
# 外循环end (综上,6个输入的值只能在1~6之间)
# 又是循环 # 循环初始化
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi # %rsi指向空栈
401158: 4c 89 f0 mov %r14,%rax # %rax指向nums[0],相当于i
40115b: b9 07 00 00 00 mov $0x7,%ecx
# 循环体begin
401160: 89 ca mov %ecx,%edx
401162: 2b 10 sub (%rax),%edx # edx = 7 - nums[0]
401164: 89 10 mov %edx,(%rax) # 令nums元素等于7-自己
401166: 48 83 c0 04 add $0x4,%rax # i++
40116a: 48 39 f0 cmp %rsi,%rax # 循环遍历终止条件
40116d: 75 f1 jne 401160
# 循环end (将nums中的元素变为"7-自己的值")
# 下面是循环套循环套分支跳转,因此显得复杂(先大致看看代码,然后我会讲解)
# 外循环初始化
40116f: be 00 00 00 00 mov $0x0,%esi # 设%rsi为i
401174: eb 21 jmp 401197 # 直接跳到1
# 内循环体begin
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx # %rdx=next # 着陆点2
40117a: 83 c0 01 add $0x1,%eax # %rax++
# 内循环终止判断
40117d: 39 c8 cmp %ecx,%eax # %eax == nums[i]则退出
40117f: 75 f5 jne 401176 # 即遍历链表找到与nums[i]相等的结点
# 内循环体end
401181: eb 05 jmp 401188 # 直接跳到3
401183: ba d0 32 60 00 mov $0x6032d0,%edx # 着陆点4
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) # 着陆点3,执行操作
# 外循环终止判断
40118d: 48 83 c6 04 add $0x4,%rsi # i++
401191: 48 83 fe 18 cmp $0x18,%rsi
401195: 74 14 je 4011ab
# 外循环体begin
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx # 着陆点1
# 跳过内循环判断(内循环的功能是遍历链表找对应节点)
40119a: 83 f9 01 cmp $0x1,%ecx # nums[i]==1则跳到4
40119d: 7e e4 jle 401183
# 内循环初始化
40119f: b8 01 00 00 00 mov $0x1,%eax # 通过内循环找到对应结点
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx # 链表首地址
# 进入内循环
4011a9: eb cb jmp 401176
# 外循环体end
# (即按照输入的序号,将链表的地址重新排列到新的数组(0x20+%rsp)上(姑且称为locations))
讲一下这个“循环套循环套分支跳转”哈,现已经梳理了大致的程序流程了,我们重点来看看这个程序它干了什么。这个外循环,除开和内循环有关的代码,它就只做了一件事:把%rdx
指向的内容以8
字节长度赋值到%rsp
指向的数组location
中。来看看当"nums[i]==1
"时%rdx
(即0x6032d0
)指向的内容:
有没有看出来呀,这是一个链表!结点内部结构为:|val1
|val2
|*next
|0
|
看来外循环就是把指向结点的指针
放进了location
里。
再来看内循环,私以为内循环的工作好懂一些,即遍历链表找到val2
与nums[i]
相等的结点,并将结点指针交给%rdx
。
破案拉破案啦,这一串操作猛如虎下来,其实就只是遍历结点,并按照我们的输入重新“排列”结点。
# 继续看代码吧(又是循环) # 循环的初始化
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx # rbx = loca[i]
4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax # rax = &loca[i+1]
4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi # rsi终止条件
4011ba: 48 89 d9 mov %rbx,%rcx # rcx = loca[i]
# 循环体begin
4011bd: 48 8b 10 mov (%rax),%rdx # rdx = loca[i+1]
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) # loca[i+1] = loca[i+1](有意义吗)
4011c4: 48 83 c0 08 add $0x8,%rax # i++
# 终止判断
4011c8: 48 39 f0 cmp %rsi,%rax
4011cb: 74 05 je 4011d2 # 离开循环
4011cd: 48 89 d1 mov %rdx,%rcx # rcx = loca[i]
4011d0: eb eb jmp 4011bd
# 循环体end(似乎啥事也没做?)
# 最后这段程序要求location中,结点的val1递减
# 循环初始化 # 此时rbx指向链表首地址
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) # 尾指针指向null
4011d9: 00
4011da: bd 05 00 00 00 mov $0x5,%ebp # 终止条件 i = 5
# 循环体begin
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax # rax = rbx->next
4011e3: 8b 00 mov (%rax),%eax # eax = rax->val1
4011e5: 39 03 cmp %eax,(%rbx) # %eax <= (%rbx)
4011e7: 7d 05 jge 4011ee
4011e9: e8 4c 02 00 00 call 40143a
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx # rbx = rbx->next
4011f2: 83 ed 01 sub $0x1,%ebp # i--
4011f5: 75 e8 jne 4011df
# 循环体end
综上,
做了这些事:
7-自己
(结果还是1~6)val2
的顺序重新排序链表val1
必须递减因此答案是4 3 2 1 6 5
!!
Border relations with Canada have never been better.
1 2 4 8 16 32
1 311
7 0
IONEFG
4 3 2 1 6 5
如果你已经能够独立做出上面的6个phase
,说明你已具有较强的能力,可以用这个隐藏关卡来试试手,这里因为我不会画图等原因,只简要地展示一下注释与答案。
对了,其中涉及的几个函数<__isoc99_sscanf@plt>
、
你最好自己查一下,加深理解。
00000000004015c4 :
# 不重要
4015c4: 48 83 ec 78 sub $0x78,%rsp
4015c8: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
4015cf: 00 00
4015d6: 31 c0 xor %eax,%eax
# 输入6串字符串(在第六个炸弹时触发) # 0x603760指向一个数字,记载着你输入的字符串数目(也就是你到哪关了)
4015d8: 83 3d 81 21 20 00 06 cmpl $0x6,0x202181(%rip) # 603760
4015df: 75 5e jne 40163f # 跳过秘密
# 炸弹4要输入3个数才能解锁秘密关卡(第三个数是字符串)
4015e1: 4c 8d 44 24 10 lea 0x10(%rsp),%r8 # r8 = 1
4015e6: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx # rcx = 6
4015eb: 48 8d 54 24 08 lea 0x8(%rsp),%rdx # rdx = 5
4015f0: be 19 26 40 00 mov $0x402619,%esi # esi指向"%d %d %s"
4015f5: bf 70 38 60 00 mov $0x603870,%edi # edi指向"7 0 某某"
4015fa: e8 f1 f5 ff ff call 400bf0 <__isoc99_sscanf@plt>
4015ff: 83 f8 03 cmp $0x3,%eax
401602: 75 31 jne 401635 #跳过秘密
# 函数<__isoc99_sscanf@plt> 的输入是:输入字符串,格式化字符串,一些赋值的变量;返回值是成功赋值的个数
# 炸弹4的第三个输入是"DrEvil",phase_1用过的把戏了
401604: be 22 26 40 00 mov $0x402622,%esi
401609: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
40160e: e8 25 fd ff ff call 401338
401613: 85 c0 test %eax,%eax
401615: 75 1e jne 401635 #跳过秘密
# 不重要
401617: bf f8 24 40 00 mov $0x4024f8,%edi
40161c: e8 ef f4 ff ff call 400b10
401621: bf 20 25 40 00 mov $0x402520,%edi
401626: e8 e5 f4 ff ff call 400b10
# 进入秘密关卡
40162b: b8 00 00 00 00 mov $0x0,%eax
401630: e8 0d fc ff ff call 401242
# 不重要
401635: bf 58 25 40 00 mov $0x402558,%edi
40163a: e8 d1 f4 ff ff call 400b10
40163f: 48 8b 44 24 68 mov 0x68(%rsp),%rax
401644: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
40164b: 00 00
40164d: 74 05 je 401654
40164f: e8 dc f4 ff ff call 400b30 <__stack_chk_fail@plt>
401654: 48 83 c4 78 add $0x78,%rsp
401658: c3 ret
0000000000401242 :
401242: 53 push %rbx
401243: e8 56 02 00 00 call 40149e
# longint strtol(str,&ptr,base);
# // 将字符串以base进制转化为长整数,并产生一个字符串(转化不成功返回0然后爆炸)
# 翻译成人话:输入一个0~1000的数,它会帮你把字符串变成long int型数
401248: ba 0a 00 00 00 mov $0xa,%edx # 获取返回的数字
40124d: be 00 00 00 00 mov $0x0,%esi # 获取返回的字符串
401252: 48 89 c7 mov %rax,%rdi # eax是指向输入的指针
401255: e8 76 f9 ff ff call 400bd0
40125a: 48 89 c3 mov %rax,%rbx # eax = 1
40125d: 8d 40 ff lea -0x1(%rax),%eax
401260: 3d e8 03 00 00 cmp $0x3e8,%eax # 0 <= eax <= 0x3e8
401265: 76 05 jbe 40126c
401267: e8 ce 01 00 00 call 40143a
# 0x6030f0是什么? 二叉树!?
# 调用返回2则通过
40126c: 89 de mov %ebx,%esi
40126e: bf f0 30 60 00 mov $0x6030f0,%edi
401273: e8 8c ff ff ff call 401204
401278: 83 f8 02 cmp $0x2,%eax
40127b: 74 05 je 401282
40127d: e8 b8 01 00 00 call 40143a
# 不重要,祝贺语罢了
401282: bf 38 24 40 00 mov $0x402438,%edi
401287: e8 84 f8 ff ff call 400b10
40128c: e8 33 03 00 00 call 4015c4
401291: 5b pop %rbx
401292: c3 ret
贴上的C函数:
ax = input-1;
si = input;
BinaryTree *di;
long int func7(BinaryTree * di, int si){
if(di == null) return ax;
int dx = di->val;
if(dx == si) return 0;
else if(dx < si){
ax = func(di->right,si);
return 2 * ax + 1;
}
else{
ax = func(di->left,si);
return 2 * ax;
}
}
而树长这样:
0x24
0x8 0x32
0x6 0x16 0x2d 0x66
0x1 0x7 0x14 0x23 0x28 0x2f 0x63 0x2f
答案是22
(即0x16)