【CMU CS15213】Bomb Lab CSAPP 实验报告
前言
这学期做完了计组实验后,我终于有时间挑战一下一直心心念念的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并没有想象中的难,最好是尽量自己做,不会再查相关资料,而不是直接看别人的总结。
文章目录
- 前言
- 准备工作
- 正式开始吧
- main函数
- phase_1
- phase_2
- phase_3
- phase_4
- phase_5
- phase_6
- 答案汇总
- 隐藏关卡
准备工作
- 在VM安装Ubuntu(bomb程序在Linux下才能运行)
- 下载lab文件夹(CASPP实验官网、官方学生资源界面)
- 使用objdump反汇译得到汇编代码程序(objdump安装方式上网搜索即可,windows也可以反汇编)
- 学习使用GDB调试bomb程序,用Linux的gdb才能运行bomb(GDB指令手册)
- 学习CSAPP第三章
正式开始吧
- 下载并解压程序文件,得到
bomb可执行文件与bomb.c - 在该文件夹下终端输入指令
objdump -S -d main > main.txt,从bomb中反汇编出汇编文件bomb.txt - 再用指令
gbd bomb进入到gdb功能
那现在,我们就有汇编代码与gdb啦

main函数
(main的源码我就不贴了)
我们从bomb.c文件中得知,我们的目标就是解决6个phase函数,每次一个输入,当6次输入都不会引爆各自phase函数中的 时,就能够正常通过phase函数啦!
phase_1
那就开始看函数吧!
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,%eax400ef0: 74 05 je 400ef7 # BOMB if %eax == 0400ef2: 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指向的字符串相同,那我就成了嘻嘻~
成功通过:

phase_2
这是开头部分,信息量比较小:
0000000000400efc :400efc: 55 push %rbp400efd: 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
循环的流程已经给出来了,直接看注释。目前得到的最重要的信息有:
- nums[0] == 1
- 总要有nums[i] == 2 * nums[i-1]
因此答案就是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),%rax40146b: 48 89 44 24 08 mov %rax,0x8(%rsp)401470: 48 8d 46 10 lea 0x10(%rsi),%rax401474: 48 89 04 24 mov %rax,(%rsp)401478: 4c 8d 4e 0c lea 0xc(%rsi),%r940147c: 4c 8d 46 08 lea 0x8(%rsi),%r8401480: be c3 25 40 00 mov $0x4025c3,%esi401485: b8 00 00 00 00 mov $0x0,%eax40148a: 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,%rsp40149d: c3 ret
phase_3
phase_3真长呀。
这次,上面讲过的知识点我只用注释注明。再提一嘴,我是用GDB研究地址指向内容的。
下面的代码获取两个数,姑且设为x,y。
0000000000400f43 :400f43: 48 83 ec 18 sub $0x18,%rsp # 开栈400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx # 指针%rcx400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx # 指针%rdx400f51: 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的取值)为:

而中间一连串代码的共同特征是:
- 都能从开头的间接跳转访问,
- 给%eax赋值,
- 再跳转到目标地址
400fbe,即最后的爆炸判断代码。
而最后不爆炸的条件是"y == %eax"。
综上,我们要先输入一个0~7范围的整数x,然后输入一个y等于对应目标地址程序的%eax值(注意程序与地址不是按顺序对应的),即可能的答案有8种。
我用的是1 311。
phase_4
000000000040100c :40100c: 48 83 ec 18 sub $0x18,%rsp401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx # y401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx # x40101a: be cf 25 40 00 mov $0x4025cf,%esi40101f: b8 00 00 00 00 mov $0x0,%eax401024: 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 <= 14401033: 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,%edx40103f: be 00 00 00 00 mov $0x0,%esi401044: 8b 7c 24 08 mov 0x8(%rsp),%edi401048: 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 == y401056: 74 05 je 40105d 401058: e8 dd 03 00 00 call 40143a 40105d: 48 83 c4 18 add $0x18,%rsp401061: 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 则返回0return 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”。
phase_5
这题可有意思嘿嘿。
0000000000401062 :401062: 53 push %rbx401063: 48 83 ec 20 sub $0x20,%rsp401067: 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) # # 输入长度为6401078: 31 c0 xor %eax,%eax # 令eax = 040107a: 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是什么呜呜。
phase_6
终于来到的最终环节,这次的函数也是至今为止最长的家伙(虽然结构挺明显的):
00000000004010f4 :4010f4: 41 56 push %r144010f6: 41 55 push %r134010f8: 41 54 push %r124010fa: 55 push %rbp4010fb: 53 push %rbx4010fc: 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型,姑且称为数组nums401106: 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,%rbp401117: 41 8b 45 00 mov 0x0(%r13),%eax 40111b: 83 e8 01 sub $0x1,%eax# 确保 1 <= nums[i] <= 640111e: 83 f8 05 cmp $0x5,%eax401121: 76 05 jbe 401128 # 0 <= eax <= 5401123: 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 = i401138: 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],相当于i40115b: b9 07 00 00 00 mov $0x7,%ecx# 循环体begin401160: 89 ca mov %ecx,%edx401162: 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为i401174: eb 21 jmp 401197 # 直接跳到1# 内循环体begin401176: 48 8b 52 08 mov 0x8(%rdx),%rdx # %rdx=next # 着陆点240117a: 83 c0 01 add $0x1,%eax # %rax++# 内循环终止判断40117d: 39 c8 cmp %ecx,%eax # %eax == nums[i]则退出40117f: 75 f5 jne 401176 # 即遍历链表找到与nums[i]相等的结点# 内循环体end401181: eb 05 jmp 401188 # 直接跳到3401183: ba d0 32 60 00 mov $0x6032d0,%edx # 着陆点4401188: 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则跳到440119d: 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
综上,做了这些事:
- 输入一个六位整型数组
- 元素值范围必须在1~6
- 元素值依次被替换为
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,%rsp4015c8: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax4015cf: 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 = 14015e6: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx # rcx = 64015eb: 48 8d 54 24 08 lea 0x8(%rsp),%rdx # rdx = 54015f0: 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,%eax401602: 75 31 jne 401635 #跳过秘密# 函数<__isoc99_sscanf@plt> 的输入是:输入字符串,格式化字符串,一些赋值的变量;返回值是成功赋值的个数# 炸弹4的第三个输入是"DrEvil",phase_1用过的把戏了401604: be 22 26 40 00 mov $0x402622,%esi401609: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi40160e: e8 25 fd ff ff call 401338 401613: 85 c0 test %eax,%eax401615: 75 1e jne 401635 #跳过秘密# 不重要401617: bf f8 24 40 00 mov $0x4024f8,%edi40161c: e8 ef f4 ff ff call 400b10 401621: bf 20 25 40 00 mov $0x402520,%edi401626: e8 e5 f4 ff ff call 400b10 # 进入秘密关卡40162b: b8 00 00 00 00 mov $0x0,%eax401630: e8 0d fc ff ff call 401242 # 不重要401635: bf 58 25 40 00 mov $0x402558,%edi40163a: e8 d1 f4 ff ff call 400b10 40163f: 48 8b 44 24 68 mov 0x68(%rsp),%rax401644: 64 48 33 04 25 28 00 xor %fs:0x28,%rax40164b: 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,%rsp401658: c3 ret 0000000000401242 :401242: 53 push %rbx401243: 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 = 140125d: 8d 40 ff lea -0x1(%rax),%eax401260: 3d e8 03 00 00 cmp $0x3e8,%eax # 0 <= eax <= 0x3e8401265: 76 05 jbe 40126c 401267: e8 ce 01 00 00 call 40143a # 0x6030f0是什么? 二叉树!?# 调用返回2则通过40126c: 89 de mov %ebx,%esi40126e: bf f0 30 60 00 mov $0x6030f0,%edi401273: e8 8c ff ff ff call 401204 401278: 83 f8 02 cmp $0x2,%eax40127b: 74 05 je 401282 40127d: e8 b8 01 00 00 call 40143a # 不重要,祝贺语罢了401282: bf 38 24 40 00 mov $0x402438,%edi401287: e8 84 f8 ff ff call 400b10 40128c: e8 33 03 00 00 call 4015c4 401291: 5b pop %rbx401292: 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;}
}
而树长这样:0x240x8 0x320x6 0x16 0x2d 0x66
0x1 0x7 0x14 0x23 0x28 0x2f 0x63 0x2f
答案是22(即0x16)
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
