【深入理解计算机系统】CSAPP Bomb Lab实验

零碎记事

  久违的,昨天打了一整天的游戏,玩的LOL,就只玩刀妹这个英雄,本人绝活。

  不得不说,刀妹这个英雄设计得太好玩了,可以说是将游戏中的博弈部分放大到了极致。这个容错率极低的英雄,每一步都要操作谨慎,但高操作带来的高收益,在我还没出现失误的时候,我的对手也在时刻警惕着我的动作。我Q上去,瞬间接近对方,他以为我要打了,赶紧回头扔技能,我却往回Q走跑了,又以为我不想打的时候,我又突然以两个小兵作为跳板Q回来,接近拉E晕人几乎一瞬间完成,还叠满了被动,这时对方才知道大事不妙,然而已经跑不了了,两段突进之下只能被我活活A死。这个英雄所带来的的线上支配感,让我深受喜欢。

  一个游戏为什么会好玩?很大一部分,是因为游戏的博弈在暗中会使人产生一种情感变化,知乎上有个游戏设计师就对游戏设计的情感变化这部分做过很精彩的讲解:

  假设你正好在与一个陌生人下国际象棋,现在轮到你动子,而且看起来你要输。由于想不出一步好棋,你会感觉有压力,并且精神紧张。在分析过棋盘上的局势之后,你更加紧张了。接下来却峰回路转,你发现了一招好棋——如果你向后方跳马,不但可以保护自己的王,同时还可以威胁到对方!你紧绷的神经得到了极大的缓解,随之带来一丝成就感。你移动棋子,对方因为发现了你的意图而面色沉重。看到对方的样子,你会感觉自己有一种支配力,你的脸上露出了一丝得意。对方陷入了沉思,在你仍然感到志得意满的时候,你发现了自己的一个薄弱环节。如果对方飞象过来,就能吃掉你自己的马。这步棋比较隐蔽,对方会注意到吗?你的满足感逐渐被焦虑所取代。你努力让自己保持神情自若,同时时间一点一点的流逝,这种焦虑在你的脑子里乱撞,你整个大脑都被这种焦虑的情感所淹没,你的肾上腺素开始飙升,心跳加快。终于,对手只是移动了一个卒。你紧绷的神经再次得到了缓解,甚至比上次有过之而无不及。你感觉这一局已经稳操胜券了。

  从旁观者的角度来看,这盘棋平淡无奇。两个人面色凝重地坐在桌旁对弈,各自在棋盘上移动棋子,甚至连对于双方都没有意识到自己所经历的这些情感。即便如此,他们从下棋的竞技过程中感受到了过山车般的情感变化。并且之后,他们还将一次又一次经历这种情感的变化。

可以说,玩家与游戏机制之间的互动是产生感情波动的来源,对于一个有吸引力的游戏来说,这些互动需要能够激发人类热血沸腾的情感。当这些时间真正激发出了自豪、欢乐、可怕或者恐怖等情感的时候,这个游戏才好玩。

  在优秀的游戏设计中,玩家不会注意到这种情感是由游戏机制本身产生的。比如你在《LOL》里使用龙龟打野,背后一群人在追着你打,但你一直开着反伤,最后追你的人成为了被你追的人。

  而劣质的游戏设计里,玩家在遭受挫折时,通常第一个想到的就是游戏机制的不公平,比如在《一刀999》游戏里,你开着和平模式在野外挂机,这时候一个氪金玩家突然出现把你击杀,你最好的装备掉在了地上被氪金玩家拾取,并分解。

  《LOL》里每个英雄的技能设计,都可以让玩家在艾欧尼亚的战斗中产生丰富的情感。当你用雪人在地上逐渐推起一个大雪球时;当你用瞎子站在敌方玩家看不到的地方,扔出一个Q将敌方玩家击杀时;当你用瑞文的Q跨过一道墙时;当你残血闪现,结果撞到墙时;当你用炸弹人的W成功逃生时;当你用寒冰千里之外命中控场时。这些设计产生的情感才是游戏存在的真正意义,这也是玩家愿意花时间、精力和金钱在棋盘上移动棋子,或者把球投进篮筐里的真正原因。

 


 Bomb Lab

实验说明

  这个实验是依靠汇编代码解开六道谜题也就是phase_1到phase_6,都是以函数形式给出,我们只需要看汇编代码的函数部分,让输入数据进入函数后能绕过所有炸弹就行。

  个人感觉,其实只要耐心看下去,这个实验谁都能解开所有谜题,唯一的难点就是刚开始对汇编和gdb一无所知的时候,几乎都不知道这个实验到底要自己干嘛。我建议,就算是打算自己完全独立去解开这些炸弹谜题的,最好也是先看看phase_1和phase_2别人是怎么分析怎么解开的,熟悉下这个实验到底是在干嘛后,自己再往后做,少做那两道简单的谜题,是完全不影响你的能力成长的。遵循这个思路,我在讲解phase_1和phase_2的时候,会连同去讲解gdb的一些基本操作和一些反汇编后看汇编代码的经验逻辑,方便大家先入个门,但往后面,我还是主要介绍我在解题过程中的思路,不再过于详细地展开。


实验前置准备

  解压bomb.tar

  bomb的tar包解压完后,我们会得到下面几个文件

  

   其中bomb是我们的可执行程序,README我个人建议做实验前先看看,bomb.c的话看不看都无所谓,看了也没什么用的。

  我们的任务就是运行这个可执行程序,做六次输入,每次都不能触发炸弹。

  就拿第一次输入作为例子,运行程序,会出现这么个东西。。。

  

   其实这个就是phase_1,你要输入数据他才会继续往下走,如果胡乱输入东西按下回车,就会理所当然的触发炸弹。。。

  

   如果我输入了正确的数据。。。

  

  就会有提示phase_1已通过,并且要求你做下一个输入,也就是phase_2的输入,phase_2输入不对,那么phase_2就会炸,后边以此类推了,直到phase_6都是这样。

  那么问题来了,我们怎么知道正确的输入啊?

  这个就要反汇编,用汇编代码去反推了,也就是这篇文章的重点。

  输入objdump -d bomb > bomb.asm,可以得到bomb程序的反汇编代码

  

   推荐传到windows上拿Notepad++看看,直接vim看也是可以,但因为想方便后边打注释分析,迟早要拿到Notepad++上面的。

  打开汇编代码看看

  

   嗯,没问题。

  Linux上装个gdb,不会装的自己百度吧,一搜一大把。

  输入gdb bomb,把程序装载到调试器上方便调试

  

  简单说明下要用到的gdb指令

  r    运行程序

  b <*0x某某某>    在某个地址设置断点,具体哪里,可以看反汇编的代码,可以根据那个直接复制粘贴设断点的

  d 删除所有断点

  d <断点号>    删除指定断点

  info b    查看所有断点信息

  continue 从断点处继续执行

  display <$寄存器>    跟踪寄存器,碰到断点停下时会显示出所有跟踪的寄存器的当前值,非常好用的一个命令,注意的是gdb中表示寄存器的话前面用的不是百分符号%,而是美元符号$

  x/参数 <地址>    访问地址的内存,其实就是间接访问,也是很好用的指令,关于参数,s是输出为字符串,d为输出为十进制,x为输出为十六进制,b、w、l、q控制输出字节,默认是w,四字节,s字符串不受这个控制除外。

  info r    查看所有寄存器的值

  前置工作就这些了,接下来正式解题


phase_1

  看汇编代码,直接拉到phase_1函数部分

  

 

  这就是个HelloWorld让你入门的东西,看看我是怎么从这些代码中分析得到正确的输出的。

  首先要让大家知道一个很关键的东西

  

   看到圈起来的那部分吗?你的代码要是运行了那行指令,那你就触发炸弹凉凉了。所以你得绕过它,这个绕过是什么意思呢?别急,看看前面一行代码。

  

   看到没,上面一行有个跳转指令,直接跳过这个炸弹的,当然,你得满足这个跳转条件才行,怎么满足呢?结合第350行那个,我们可以知道只要%eax为0就满足这个跳转条件,怎么让%eax为0呢?首先要知道%eax其实就是%rax的后32位,那%rax又是什么?前面有函数调用的话,一般%rax的值就是程序的返回值,实际上,我们确实看到它在前面调用了一个函数

  

   从函数的名字我们很容易猜出来这个函数是用来判断字符串是否相等的,不等返回1,相等返回0,也就说,想让%rax等于0,就等让字符串相等,什么字符串相等?当然是我们输入的串跟答案的串相等啊。可是我们又怎么才能知道答案的串是什么呢?先别急,至少现在我们明白了绕过炸弹的条件了。

  先看看上边前面两行

  

   上面一行是开栈操作,与之对应的是第353行的返回前栈指针回收,这个是每个函数首尾都会有的,表明了函数中开了多大的局部变量。看这个有什么用呢?我们输入的可能就是输入到函数里面的局部变量的,那这样我们顺着%rsp指针就能找到我们输入的内容,嘛,这么说而已,其实这个phase里边并不是这样的,可以试给你们看看

  首先我在0x400ee9那设了个断点,也就是callq  401338 <strings_not_equal>这一行,那么程序会在碰到这条汇编指令但还没执行的时候停下来,这样我就可以查看程序运行到那里时寄存器或者内存的一些信息。

  

   然后我开始跑我的程序,并随便输入了点东西,再往下,碰到断点

  

   我们的目的是找出正确的字符串,%rsp开了8个字节的空间,这里很可能有放东西,比如我们的输入之类的,先看看吧,输入x/s $rsp,也就是以字符串形式查看栈顶的东西

   

  然而并不是,里面的东西我们啥也看不懂,什么用都没有

  我们看看下一行

   

   要知道call的函数需要传参的话,参数一般得实现准备好,然后这个%rdi往往就是放第一个参数的地方,其实这里很明显%rsi就是作为参数传进<strings_not_equal>的,我们可以大胆猜测这传的是我们所输入的字符串或者是正确的字符串,查它!。

  

  一查,果然有个字符串在里面,然而并不是我输入的串,但是应该就是正确的字符串了,至于我输入的串去哪了,我也不知道,但是到这为止至少我们可以确定输入行为是在这个phase_1函数的外部发生的,现在先不管这些,先试试刚刚拿到的串对不对,对的话也不用进一步深究了。

  因为之前随便输入的串肯定不对,往下走肯定是callq  40143a <explode_bomb>被执行然后炸掉,就不continue了,直接r重新开始吧,这次我们输入刚刚的的得到的疑似正确的字符串。

  

  嗯,看来还真是这个,phase_1就过了,反推就是这么一回事。


phase_2

  这是phase_2的函数的反汇编代码,我们可以先看看,我是边分析边注释的,所以有些自己加的东西,注意,当时到phase_2时我还不太懂汇编,注释的东西都只是顺着想法写脑海中某一步的情况,你理解我的注释不一定跟我实际注释的意思一样,而且突然知道怎么解后我就直接不注释到下一关了,显得很有跳跃性,不建议看这个注释的。

  

 1 0000000000400efc <phase_2>:
 2   400efc:    55                       push   %rbp
 3   400efd:    53                       push   %rbx
 4   400efe:    48 83 ec 28              sub    $0x28,%rsp //开栈
 5   400f02:    48 89 e6                 mov    %rsp,%rsi //%rsi = %rsp
 6   400f05:    e8 52 05 00 00           callq  40145c <read_six_numbers>
 7   400f0a:    83 3c 24 01              cmpl   $0x1,(%rsp) //第一个数是1
 8   400f0e:    74 20                    je     400f30 <phase_2+0x34>
 9   400f10:    e8 25 05 00 00           callq  40143a <explode_bomb>
10   400f15:    eb 19                    jmp    400f30 <phase_2+0x34>
11   400f17:    8b 43 fc                 mov    -0x4(%rbx),%eax //%rax = %rbx - 4
12   400f1a:    01 c0                    add    %eax,%eax //%rax += %rax
13   400f1c:    39 03                    cmp    %eax,(%rbx) //第二个数在%rax里
14   400f1e:    74 05                    je     400f25 <phase_2+0x29>
15   400f20:    e8 15 05 00 00           callq  40143a <explode_bomb>
16   400f25:    48 83 c3 04              add    $0x4,%rbx %rbx += 4
17   400f29:    48 39 eb                 cmp    %rbp,%rbx
18   400f2c:    75 e9                    jne    400f17 <phase_2+0x1b>
19   400f2e:    eb 0c                    jmp    400f3c <phase_2+0x40>
20   400f30:    48 8d 5c 24 04           lea    0x4(%rsp),%rbx
21   400f35:    48 8d 6c 24 18           lea    0x18(%rsp),%rbp
22   400f3a:    eb db                    jmp    400f17 <phase_2+0x1b>
23   400f3c:    48 83 c4 28              add    $0x28,%rsp
24   400f40:    5b                       pop    %rbx
25   400f41:    5d                       pop    %rbp
26   400f42:    c3                       retq 

  看看第6行,有个callq  40145c <read_six_numbers>,可以推测phase_2是要我们输入6个数,输入的时候我们用空格隔开就行了

  看看第7行,发生了一个比较cmpl $0x1,(%rsp),结合下两行的跳转+bomb触发,我们可以知道要不触发第一个bomb,那么就得让栈顶的东西,也就是(%rsp)的值等于1才行。首先我们看看栈顶专门存什么的,其实可以猜测我们输入的数就是从栈顶开始放的,那么这样一来(%rsp)就会是我们输入的第一个数,验证下这个想法先,在第八行设个断点,我随便输入了六个数字,然后查看了下(%rsp)的值

  

  正如我所猜,输入的数从栈顶开始放的,那这就好说了啊,满足第8行的跳转条件,我输入的第一个数等于1就行。继续往下分析。

  满足条件后是一个jmp

  

  jmp后执行了两个计算,分别是lea    0x4(%rsp),%rbx 和 0x18(%rsp),%rbp,再往后是到上面的jmp语句

 

   这个是无条件跳转的,Nodepad++很方便,我们还发现了再往上有同样跳转目的的一个条件跳转

   从有条件的跳转入手,我们容易发现那里是个循环,这里我为了方便后续分析,就做了标记

   这种习惯是非常有利于我们去分析反汇编代码的,分析循环,发现里面有个炸弹,我们得绕开它,我上边因为当时还不是很懂汇编,所以有些注释是有问题的,尤其是这循环里的注释,你大可不管。反正分析知道,%rbx里的东西是我们输入的数,而拿来比较的(%rbx),是他的正确答案,我们可以慢慢查看这个(%rbx),就是在炸弹前设断点,获得一个正确的数字就重新来重新输入再看这样,具体就不弄了,大家可以试试的,最后知道要输入的内容就是1 2 4 8 16 32。

  

   OK,phase_2解掉


phase_3

  汇编代码+注释,从这个phase开始往后,这个注释和思路是没问题的,大家可以看看,有些地方可能有些跳跃性,可自行分析接上,不影响大体

  

 1 0000000000400f43 <phase_3>:
 2   400f43:    48 83 ec 18              sub    $0x18,%rsp //开栈
 3   400f47:    48 8d 4c 24 0c           lea    0xc(%rsp),%rcx //%rcx = (%rsp + 0xc)
 4   400f4c:    48 8d 54 24 08           lea    0x8(%rsp),%rdx //%rdx = (%rsp + 0x8)
 5   400f51:    be cf 25 40 00           mov    $0x4025cf,%esi //%rsi = 0x4025cf
 6   400f56:    b8 00 00 00 00           mov    $0x0,%eax //%rax = 0
 7   400f5b:    e8 90 fc ff ff           callq  400bf0 <__isoc99_sscanf@plt> //该函数返回值是输入数字的个数
 8   400f60:    83 f8 01                 cmp    $0x1,%eax //进入函数后返回值要大于1,说明输入要超过1个数字
 9   400f63:    7f 05                    jg     400f6a <phase_3+0x27>
10   400f65:    e8 d0 04 00 00           callq  40143a <explode_bomb>
11   400f6a:    83 7c 24 08 07           cmpl   $0x7,0x8(%rsp) //((%rsp + 0x8) > 7),此条件成立就引爆炸弹,所以输入的第一个数要小于或等于7
12   400f6f:    77 3c                    ja     400fad <phase_3+0x6a> //跳到炸弹
13   400f71:    8b 44 24 08              mov    0x8(%rsp),%eax //%rax = 第一个数
14   400f75:    ff 24 c5 70 24 40 00     jmpq   *0x402470(,%rax,8) //jump到8*第一个数 + 0x402470
15   400f7c:    b8 cf 00 00 00           mov    $0xcf,%eax //%rax = 0xcf
16   400f81:    eb 3b                    jmp    400fbe <phase_3+0x7b>
17   400f83:    b8 c3 02 00 00           mov    $0x2c3,%eax //%rax = 0x2c3
18   400f88:    eb 34                    jmp    400fbe <phase_3+0x7b>
19   400f8a:    b8 00 01 00 00           mov    $0x100,%eax //%rax = 0x100
20   400f8f:    eb 2d                    jmp    400fbe <phase_3+0x7b>
21   400f91:    b8 85 01 00 00           mov    $0x185,%eax //%rax = 0x185
22   400f96:    eb 26                    jmp    400fbe <phase_3+0x7b>
23   400f98:    b8 ce 00 00 00           mov    $0xce,%eax //%rax = 0xce
24   400f9d:    eb 1f                    jmp    400fbe <phase_3+0x7b>
25   400f9f:    b8 aa 02 00 00           mov    $0x2aa,%eax //%rax = 0x2aa
26   400fa4:    eb 18                    jmp    400fbe <phase_3+0x7b>
27   400fa6:    b8 47 01 00 00           mov    $0x147,%eax //%rax = 0x147
28   400fab:    eb 11                    jmp    400fbe <phase_3+0x7b>
29   400fad:    e8 88 04 00 00           callq  40143a <explode_bomb>
30   400fb2:    b8 00 00 00 00           mov    $0x0,%eax //%rax = 0
31   400fb7:    eb 05                    jmp    400fbe <phase_3+0x7b>
32   400fb9:    b8 37 01 00 00           mov    $0x137,%eax //%rax = 0x137
33   400fbe:    3b 44 24 0c              cmp    0xc(%rsp),%eax //%rax不等于(%rsp + 12)就引爆炸弹,(%rsp + 12)是第二个数,试了下,第一个数等于3时,到这里rax = 256
34   400fc2:    74 05                    je     400fc9 <phase_3+0x86> //到达函数出口
35   400fc4:    e8 71 04 00 00           callq  40143a <explode_bomb>
36   400fc9:    48 83 c4 18              add    $0x18,%rsp
37   400fcd:    c3                       retq

answer:输入超过1个数,第一个数要小于或等于7,最终测试得知,过关条件之一是 3 256


 

phase_4

 1 0000000000400fce <func4>:
 2   400fce:    48 83 ec 08              sub    $0x8,%rsp
 3   400fd2:    89 d0                    mov    %edx,%eax //%rax = 14
 4   400fd4:    29 f0                    sub    %esi,%eax //%rax = 14 - 0 = 14
 5   400fd6:    89 c1                    mov    %eax,%ecx //%rcx = 14
 6   400fd8:    c1 e9 1f                 shr    $0x1f,%ecx //%rcx = 0
 7   400fdb:    01 c8                    add    %ecx,%eax //%rax = 14
 8   400fdd:    d1 f8                    sar    %eax //%rax = 14 / 2 = 7
 9   400fdf:    8d 0c 30                 lea    (%rax,%rsi,1),%ecx //%rcx = 1 * 0 + 7 = 7
10   400fe2:    39 f9                    cmp    %edi,%ecx //第一个数小于等于7,就跳转到%rax = 0
11   400fe4:    7e 0c                    jle    400ff2 <func4+0x24>
12   400fe6:    8d 51 ff                 lea    -0x1(%rcx),%edx
13   400fe9:    e8 e0 ff ff ff           callq  400fce <func4>
14   400fee:    01 c0                    add    %eax,%eax
15   400ff0:    eb 15                    jmp    401007 <func4+0x39>
16   400ff2:    b8 00 00 00 00           mov    $0x0,%eax //%rax = 0
17   400ff7:    39 f9                    cmp    %edi,%ecx //第一个数大于等于7,跳转到函数尾巴,由此可知,第一个数等于7
18   400ff9:    7d 0c                    jge    401007 <func4+0x39>
19   400ffb:    8d 71 01                 lea    0x1(%rcx),%esi
20   400ffe:    e8 cb ff ff ff           callq  400fce <func4>
21   401003:    8d 44 00 01              lea    0x1(%rax,%rax,1),%eax
22   401007:    48 83 c4 08              add    $0x8,%rsp
23   40100b:    c3                       retq   
24 
25 000000000040100c <phase_4>:
26   40100c:    48 83 ec 18              sub    $0x18,%rsp
27   401010:    48 8d 4c 24 0c           lea    0xc(%rsp),%rcx 
28   401015:    48 8d 54 24 08           lea    0x8(%rsp),%rdx
29   40101a:    be cf 25 40 00           mov    $0x4025cf,%esi
30   40101f:    b8 00 00 00 00           mov    $0x0,%eax
31   401024:    e8 c7 fb ff ff           callq  400bf0 <__isoc99_sscanf@plt>
32   401029:    83 f8 02                 cmp    $0x2,%eax //从这个下一行可推断要输入两个数
33   40102c:    75 07                    jne    401035 <phase_4+0x29>
34   40102e:    83 7c 24 08 0e           cmpl   $0xe,0x8(%rsp) //第一个数要小于等于14
35   401033:    76 05                    jbe    40103a <phase_4+0x2e>
36   401035:    e8 00 04 00 00           callq  40143a <explode_bomb>
37   40103a:    ba 0e 00 00 00           mov    $0xe,%edx //%rdx = 14
38   40103f:    be 00 00 00 00           mov    $0x0,%esi //%rsi = 0
39   401044:    8b 7c 24 08              mov    0x8(%rsp),%edi //%rdi = 第一个数
40   401048:    e8 81 ff ff ff           callq  400fce <func4> //进入一个函数
41   40104d:    85 c0                    test   %eax,%eax
42   40104f:    75 07                    jne    401058 <phase_4+0x4c> //函数返回值不等于0就引爆炸弹
43   401051:    83 7c 24 0c 00           cmpl   $0x0,0xc(%rsp) //第二个数等于0才行
44   401056:    74 05                    je     40105d <phase_4+0x51>
45   401058:    e8 dd 03 00 00           callq  40143a <explode_bomb>
46   40105d:    48 83 c4 18              add    $0x18,%rsp
47   401061:    c3                       retq   

answer:直接看汇编,甚至不用gdb都能推出来,答案是7 0


 

phase_5

 1 0000000000401062 <phase_5>:
 2   401062:    53                       push   %rbx //%rbx是保存的字符串
 3   401063:    48 83 ec 20              sub    $0x20,%rsp
 4   401067:    48 89 fb                 mov    %rdi,%rbx
 5   40106a:    64 48 8b 04 25 28 00     mov    %fs:0x28,%rax
 6   401071:    00 00 
 7   401073:    48 89 44 24 18           mov    %rax,0x18(%rsp)
 8   401078:    31 c0                    xor    %eax,%eax //%rax清0
 9   40107a:    e8 9c 02 00 00           callq  40131b <string_length> //计算输入的字符串长度
10   40107f:    83 f8 06                 cmp    $0x6,%eax //字符串长度等于6才合法
11   401082:    74 4e                    je     4010d2 <phase_5+0x70>
12   401084:    e8 b1 03 00 00           callq  40143a <explode_bomb>
13   401089:    eb 47                    jmp    4010d2 <phase_5+0x70>
14   
15   Loop:for(%rax=0; %rax!=6 ;%rax++)
16   40108b:    0f b6 0c 03              movzbl (%rbx,%rax,1),%ecx //%rcx = %rax + %rbx。其实这里是%rcx从第一个字符遍历到第六个字符
17   40108f:    88 0c 24                 mov    %cl,(%rsp) //(%rsp) = %rcx的后8位
18   401092:    48 8b 14 24              mov    (%rsp),%rdx //%rdx = %rcx的后8位
19   401096:    83 e2 0f                 and    $0xf,%edx //%rdx = %rcx的后4位
20   401099:    0f b6 92 b0 24 40 00     movzbl 0x4024b0(%rdx),%edx //%rdx = (%rdx + 0x4024b0) //0x4024b0的前16位是"maduiersnfotvbyl"
21   4010a0:    88 54 04 10              mov    %dl,0x10(%rsp,%rax,1) //(%rsp + %rax + 16) = %rdx的最后四位。这里推出我们输入字符的ASCII码会的后四位会对应上上边字符串的字符
22   4010a4:    48 83 c0 01              add    $0x1,%rax //%rax += 1
23   4010a8:    48 83 f8 06              cmp    $0x6,%rax //%rax != 6就继续回到上边的循环
24   4010ac:    75 dd                    jne    40108b <phase_5+0x29>
25   Loop End
26   4010ae:    c6 44 24 16 00           movb   $0x0,0x16(%rsp) //(%rsp + 0x16) = 0。实际上就是给字符串加结束符
27   4010b3:    be 5e 24 40 00           mov    $0x40245e,%esi //%rsi = 0x40245e。是字符串"flyers",可推出我们输入的字符串的每个字符的ASCII码的后四位应分别为9、F、E、567 ==> 符合条件的字符串之一是"9ON567"
28   4010b8:    48 8d 7c 24 10           lea    0x10(%rsp),%rdi //%rdi = (%rsp + 0x10)
29   4010bd:    e8 76 02 00 00           callq  401338 <strings_not_equal> //这里有个字符串检测,设断点看看前面的值
30   4010c2:    85 c0                    test   %eax,%eax
31   4010c4:    74 13                    je     4010d9 <phase_5+0x77>
32   4010c6:    e8 6f 03 00 00           callq  40143a <explode_bomb>
33   4010cb:    0f 1f 44 00 00           nopl   0x0(%rax,%rax,1)
34   4010d0:    eb 07                    jmp    4010d9 <phase_5+0x77>
35   4010d2:    b8 00 00 00 00           mov    $0x0,%eax //%rax = 0
36   4010d7:    eb b2                    jmp    40108b <phase_5+0x29>
37   4010d9:    48 8b 44 24 18           mov    0x18(%rsp),%rax
38   4010de:    64 48 33 04 25 28 00     xor    %fs:0x28,%rax
39   4010e5:    00 00 
40   4010e7:    74 05                    je     4010ee <phase_5+0x8c>
41   4010e9:    e8 42 fa ff ff           callq  400b30 <__stack_chk_fail@plt>
42   4010ee:    48 83 c4 20              add    $0x20,%rsp
43   4010f2:    5b                       pop    %rbx
44   4010f3:    c3                       retq   

answer:在贴出代码的第20行和第26行结合起来看,注释已经充分说明了答案,输入的字符串符合这个规律“输入的字符串的每个字符的ASCII码的后四位应分别为9、F、E、5、6、7”就行,比如符合条件之一的字符串是"9ON567"


phase_6

  这是最难的一关,也是我注释得最详细的一关

00000000004010f4 <phase_6>:
  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
  401100:    49 89 e5                 mov    %rsp,%r13
  401103:    48 89 e6                 mov    %rsp,%rsi
  401106:    e8 51 03 00 00           callq  40145c <read_six_numbers>
  40110b:    49 89 e6                 mov    %rsp,%r14 //机子上测试了下,输入的是6个4字节整数,开始地址是%rsp
  40110e:    41 bc 00 00 00 00        mov    $0x0,%r12d //%r12d = 0
  Loop 1:
  401114:    4c 89 ed                 mov    %r13,%rbp //这里跟%r13同值
  401117:    41 8b 45 00              mov    0x0(%r13),%eax //把输入的整数拿进%rax,不出意外应该是遍历操作
  40111b:    83 e8 01                 sub    $0x1,%eax //%rax -= 1
  40111e:    83 f8 05                 cmp    $0x5,%eax //%rax <= 5 ==>输入的数要小于等于6,因为无符号数,上面又-1,所以还要大于等于1
  401121:    76 05                    jbe    401128 <phase_6+0x34>
  401123:    e8 12 03 00 00           callq  40143a <explode_bomb>
  401128:    41 83 c4 01              add    $0x1,%r12d
  40112c:    41 83 fc 06              cmp    $0x6,%r12d //结合上下文,其实已经可以推出%r12d是程序计数变量了
  401130:    74 21                    je     401153 <phase_6+0x5f>
  401132:    44 89 e3                 mov    %r12d,%ebx
  Loop 2:
  401135:    48 63 c3                 movslq %ebx,%rax
  401138:    8b 04 84                 mov    (%rsp,%rax,4),%eax //程序计数变量作为数组索引,是遍历操作
  40113b:    39 45 00                 cmp    %eax,0x0(%rbp) //实际上是在把当前遍历到的数与后面的每个数作比较
  40113e:    75 05                    jne    401145 <phase_6+0x51> //可推出不能输入两个相同的数
  401140:    e8 f5 02 00 00           callq  40143a <explode_bomb>
  401145:    83 c3 01                 add    $0x1,%ebx
  401148:    83 fb 05                 cmp    $0x5,%ebx
  40114b:    7e e8                    jle    401135 <phase_6+0x41>
  Loop 2 End
  40114d:    49 83 c5 04              add    $0x4,%r13
  401151:    eb c1                    jmp    401114 <phase_6+0x20>
  Loop 1 End
  分析循环收获:一、输入的数要小于等于6并且大于等于1 二、不能输入两个相同的数
  401153:    48 8d 74 24 18           lea    0x18(%rsp),%rsi
  401158:    4c 89 f0                 mov    %r14,%rax //是输入的第一个数字
  40115b:    b9 07 00 00 00           mov    $0x7,%ecx //%rcx = 7
  Loop:
  401160:    89 ca                    mov    %ecx,%edx //看循环后面%rcx的值就没动过,可认为这里恒有%rdx=7
  401162:    2b 10                    sub    (%rax),%edx //所以是7减去每个数
  401164:    89 10                    mov    %edx,(%rax) //因为是内存引用,这里改变了数组实际的值,这个循环过后,数组里的值变成7-输入的数
  401166:    48 83 c0 04              add    $0x4,%rax //实际上还是在遍历那六个数
  40116a:    48 39 f0                 cmp    %rsi,%rax
  40116d:    75 f1                    jne    401160 <phase_6+0x6c>
  Loop End
  分析循环收获:数组的值被变成7-每个输入的数
  40116f:    be 00 00 00 00           mov    $0x0,%esi
  401174:    eb 21                    jmp    401197 <phase_6+0xa3>
  Loop 1:Loop 2:
  401176:    48 8b 52 08              mov    0x8(%rdx),%rdx //这里应该是个链表
  40117a:    83 c0 01                 add    $0x1,%eax
  40117d:    39 c8                    cmp    %ecx,%eax //%rax初始值是1,这里可看成是循环条件为%rax<7-input[i] ==>%rdx的值为node[7-input[i]]的地址(链式顺序)
  40117f:    75 f5                    jne    401176 <phase_6+0x82>
  Loop 2 End
  401181:    eb 05                    jmp    401188 <phase_6+0x94>
  Loop 3:
  401183:    ba d0 32 60 00           mov    $0x6032d0,%edx
  401188:    48 89 54 74 20           mov    %rdx,0x20(%rsp,%rsi,2) //栈中存入%rdx的值,也就是node的地址,整个循环的目的就在于这里
  40118d:    48 83 c6 04              add    $0x4,%rsi
  401191:    48 83 fe 18              cmp    $0x18,%rsi //%rsi是计数变量,看看下行跳出大循环的汇编代码,估计又是遍历了。。。
  401195:    74 14                    je     4011ab <phase_6+0xb7>
  401197:    8b 0c 34                 mov    (%rsp,%rsi,1),%ecx //7减过后的输入数字
  40119a:    83 f9 01                 cmp    $0x1,%ecx
  40119d:    7e e4                    jle    401183 <phase_6+0x8f> //小于等于1就回Loop3,意思是某个输入数字等于6就回Loop3
  Loop 3 End
  40119f:    b8 01 00 00 00           mov    $0x1,%eax //%rax要做计数变量了
  4011a4:    ba d0 32 60 00           mov    $0x6032d0,%edx //得看看这个地址装的什么,看到是node1~6,是链表结点,通过查看数据,其链式data为{332, 168, 924, 691, 477, 443},对应node{1, 2, 3, 4, 5, 6}
  4011a9:    eb cb                    jmp    401176 <phase_6+0x82>
  Loop 1 End
  分析循环收获:%rsp+0x20存了6个结点,分别为node[7-input[i]]
  4011ab:    48 8b 5c 24 20           mov    0x20(%rsp),%rbx //%rbx = node[7-input[1]]的地址
  4011b0:    48 8d 44 24 28           lea    0x28(%rsp),%rax //%rax = node栈[2]
  4011b5:    48 8d 74 24 50           lea    0x50(%rsp),%rsi //%rsi = node栈[6]
  4011ba:    48 89 d9                 mov    %rbx,%rcx //%rcx = node[7-input[1]]的地址
  Loop:
  4011bd:    48 8b 10                 mov    (%rax),%rdx //%rdx = node[7-input[i]]
  4011c0:    48 89 51 08              mov    %rdx,0x8(%rcx)
  4011c4:    48 83 c0 08              add    $0x8,%rax
  4011c8:    48 39 f0                 cmp    %rsi,%rax //%rax == node[7-input[6]]是循环退出条件,结合上边有地址偏移8取next,下边又有next赋值之类的,容易猜到这个是在重构链表
  4011cb:    74 05                    je     4011d2 <phase_6+0xde>
  4011cd:    48 89 d1                 mov    %rdx,%rcx
  4011d0:    eb eb                    jmp    4011bd <phase_6+0xc9>
  Loop End
  循环分析收获:栈中的node重构成了链
  4011d2:    48 c7 42 08 00 00 00     movq   $0x0,0x8(%rdx) //node[7-input[6]].next = NULL
  4011d9:    00 
  4011da:    bd 05 00 00 00           mov    $0x5,%ebp
  Loop:
  4011df:    48 8b 43 08              mov    0x8(%rbx),%rax
  4011e3:    8b 00                    mov    (%rax),%eax
  4011e5:    39 03                    cmp    %eax,(%rbx)
  4011e7:    7d 05                    jge    4011ee <phase_6+0xfa> //结合上边三条语句和这个循环,其实意思是:node[7-input[i]]->data >= node[7-input[i+1]]->data
  4011e9:    e8 4c 02 00 00           callq  40143a <explode_bomb>
  4011ee:    48 8b 5b 08              mov    0x8(%rbx),%rbx
  4011f2:    83 ed 01                 sub    $0x1,%ebp //这里看出%ebp是计数变量,循环实际上是for(%rbp=5; %rbp!=0 ;%rbp--)
  4011f5:    75 e8                    jne    4011df <phase_6+0xeb>
  Loop End
  循环分析收获:node[7-input[i]]->data >= node[7-input[i+1]]->data
  至此谜题已经解开
  有6个node,通过查看数据,其链式data为{332, 168, 924, 691, 477, 443},对应node{1, 2, 3, 4, 5, 6}
  7减去我们的输入所获得的node的data要符合其降序
  不考虑7减去时,输入为3, 4, 5, 6, 1, 2
  考虑7减去时,输入为4, 3, 2, 1, 6, 5。其实这个就是正确的输入
  4011f7:    48 83 c4 50              add    $0x50,%rsp
  4011fb:    5b                       pop    %rbx
  4011fc:    5d                       pop    %rbp
  4011fd:    41 5c                    pop    %r12
  4011ff:    41 5d                    pop    %r13
  401201:    41 5e                    pop    %r14
  401203:    c3                       retq   

answer:其实就是我最后注释的内容

有6个node,通过查看数据,其链式data为{332, 168, 924, 691, 477, 443},对应node{1, 2, 3, 4, 5, 6}

7减去我们的输入所获得的node的data要符合其降序

不考虑7减去时,输入为3, 4, 5, 6, 1, 2

考虑7减去时,输入为4, 3, 2, 1, 6, 5。其实这个就是正确的输入

 

posted @ 2020-12-13 22:20  雾里尘埃  阅读(3185)  评论(0编辑  收藏  举报