计算机系统基础《拆弹专家》 实验报告 (汇编语句详细解析)

前言:

        本人(TJUer)之前做拆弹实验的时候在网上搜了很久,也没有找到十分详细的汇编语句解析的文章,看了感觉似懂非懂,于是在这篇文章中,我几乎将每一句有用的汇编语句都做了注释,并且配图,希望能对做这个拆弹实验学弟学妹们有所帮助。

     《手把手教你拆解 CSAPP 的 炸弹实验室 BombLab》是一个很好的文章(我当时就跟着他做的),涵盖的面很广。

    (本文前置知识包括GDB调试工具的使用)

       每个phase中,内容布局:图片->汇编语句分析->综述

      下面就是实验报告了

        

实验3:拆弹专家

  1. 实验目的

进⼀步掌握程序的机器级表示⼀章的知识。理解程序控制、过程调⽤的汇编级实现,熟练掌握 汇编语⾔程序的阅读。

     2.实验内容

  程序bomb是⼀个电⼦炸弹,当该程序运⾏时,需要按照⼀定的顺序输⼊⼝令,才能阻 ⽌炸弹的引爆。当输⼊错误的密码时,炸弹将会引爆。此时控制台将会产⽣如下输出,并结束 程序

在炸弹程序中,你需要输⼊多组⼝令,且每⼀组⼝令都正确才能够防⽌引爆。

⽬前已知的内容只有炸弹程序的⼆进制可执⾏⽂件bomb(⽬标平台为:x86-64)和 bomb的main函数框架代码,⻅main.c。其他的细节均不会以c语⾔的⽅式呈现。 你的任务是:利⽤现有的资源以及相关的⼯具,猜出炸弹的全部⼝令,并输⼊⾄炸弹程序中,以完成最终的拆弹⼯作。

    3.实验要求

1)在Unbuntu18.04LTS操作系统下,按照实验指导说明书,使用gdb和objdump等工具,以反向工程方式完成Bomb拆弹。

2)需提交:拆弹口令文本文件、电子版实验报告全文。

    4.实验结果

(拆弹phase1到phase6的拆弹分析过程,可以包括典型的汇编码片段、拆弹思路、拆弹过程中的结果截图。可选项:隐藏卡的破解过程分析)

Phase 1

先分析,再综述。

图一

图二

图三:

Phase 1主要用到了一下三个函数

0000000000400ee0 <phase_1>:

  400ee0:       48 83 ec 08          sub    $0x8,%rsp栈帧

  400ee4:       be 00 24 40 00         mov    $0x402400,%esi这是将某数据的地址放进来

我们可以使用x/s 加上这个地址的方式直接访问这段地址中的内容,得到的是:Border relations with Canada have never been better.这一字符串,见图一

并且可以看出这个地址被放在了%rsi

  400ee9:       e8 4a 04 00 00         callq  401338 <strings_not_equal>调用函数

  400eee:       85 c0                      test   %eax,%eax

  400ef0:       74 05                      je     400ef7 <phase_1+0x17>从这里可以看出string_not_equal函数必须返回0才可以,否则会爆炸

  400ef2:       e8 43 05 00 00         callq  40143a <explode_bomb>

  400ef7:       48 83 c4 08          add    $0x8,%rsp

  400efb:       c3                         retq  

0000000000401338 <strings_not_equal>:此时%rsi中是传入的一个地址,放的是一个字符串,%rdi可以通过测试得知,存放的是我们输入的字符串的地址,见图二

  401338:      41 54                      push   %r12这是保护寄存器的数据

  40133a:      55                        push   %rbp保护

  40133b:      53                        push   %rbx

  40133c:      48 89 fb             mov    %rdi,%rbx

  40133f:       48 89 f5             mov    %rsi,%rbp

  401342:      e8 d4 ff ff ff             callq  40131b <string_length>调用length函数

  401347:      41 89 c4             mov    %eax,%r12d这是将刚刚测得的rdi中指向的string(我自己输入的string)的长度存到%r12d

  40134a:      48 89 ef             mov    %rbp,%rdi  这是完成了rdi rsi 的交换,为了使用同一个测长度的函数来测量用来匹配的string

  40134d:      e8 c9 ff ff ff             callq  40131b <string_length>

  401352:      ba 01 00 00 00         mov    $0x1,%edx

  401357:      41 39 c4             cmp    %eax,%r12d这是将测得的匹配string长度和我自己输入的string的长度进行比较,

  40135a:      75 3f                       jne    40139b <strings_not_equal+0x63>如果两个string的长度不一样,就会爆炸

  40135c:      0f b6 03             movzbl (%rbx),%eax将此时的string的第一个字符放进rax

  40135f:       84 c0                      test   %al,%al  al 就是rax8 bit)看看是不是结尾

  401361:      74 25                      je     401388 <strings_not_equal+0x50>如果是结尾,再加上长度相同,那就表示两个string一样,返回0,如果不是结尾,才进入到下方的一个一个测试的循环中

  401363:      3a 45 00             cmp    0x0(%rbp),%al  把待匹配的两个string的首位先进行比较,rbp中是他的,al 中是我的string

  401366:      74 0a                      je     401372 <strings_not_equal+0x3a>相同则跳转,继续

  401368:      eb 25                      jmp    40138f <strings_not_equal+0x57>不同就返回1,会引起爆炸

  40136a:      3a 45 00             cmp    0x0(%rbp),%al这是循环的起点

  40136d:      0f 1f 00             nopl   (%rax)

  401370:      75 24                      jne    401396 <strings_not_equal+0x5e>

  401372:      48 83 c3 01          add    $0x1,%rbx

  401376:      48 83 c5 01          add    $0x1,%rbp    rbxrbp分别是指向对方和我方字符串的指针,一开始指向首位,每执行一次这两个语句,会同时向后移动一个字符

  40137a:      0f b6 03             movzbl (%rbx),%eax  跟上面一样,进行比较

  40137d:      84 c0                      test   %al,%al  比,看看是不是到结尾了

  40137f:       75 e9                      jne    40136a <strings_not_equal+0x32> 如果不是结尾,那就重复这个循环

  401381:      ba 00 00 00 00         mov    $0x0,%edx  到结尾了,若中途没有失配,那就返回0,表示匹配成功

  401386:      eb 13                      jmp    40139b <strings_not_equal+0x63>返回0

  401388:      ba 00 00 00 00         mov    $0x0,%edx

  40138d:      eb 0c                       jmp    40139b <strings_not_equal+0x63>返回0

  40138f:       ba 01 00 00 00         mov    $0x1,%edx

  401394:      eb 05                      jmp    40139b <strings_not_equal+0x63>1(爆)

  401396:      ba 01 00 00 00         mov    $0x1,%edx

  40139b:      89 d0                      mov    %edx,%eax

  40139d:      5b                        pop    %rbx

  40139e:      5d                        pop    %rbp

  40139f:       41 5c                      pop    %r12

  4013a1:      c3                         retq  

000000000040131b <string_length>可知测的是%rdi中的地址指向的字符串的长度

  40131b:      80 3f 00             cmpb   $0x0,(%rdi)  看当前rdi指向的内存中的第一个值是不是‘\0’,如果是 那就表示长度是零,所以对应接下来的返回值是0的操作,(这是进入循环前先判断了一下)

  40131e:      74 12                      je     401332 <string_length+0x17>

  401320:      48 89 fa             mov    %rdi,%rdx到这里说明长度>0

  401323:      48 83 c2 01          add    $0x1,%rdx

  401327:      89 d0                      mov    %edx,%eax

  401329:      29 f8                       sub    %edi,%eax//上面三个语句是把rdx这个指针向后移动了一个,并且使得rax变成了1,这是一个循环,每一次循环都会让rax的值增加1

  40132b:      80 3a 00             cmpb   $0x0,(%rdx)这句和下面一句是循环的结束条件,当读取到string末尾的反斜杠0时,就停止,然后会返回string的长度

  40132e:      75 f3                       jne    401323 <string_length+0x8>

  401330:      f3 c3                       repz retq

  401332:      b8 00 00 00 00         mov    $0x0,%eax返回0

  401337:      c3                         retq  

分析一遍可以知道,phase1就是把一个准备好的string和我输入的string进行比较,如果两者一致,那就不会爆炸。所以我们将刚刚读取到的0x402400中的那个string拿出来,输入,然后就成功了。(见图三)

Phase 2

图一:

图二:

图三:

图四:

图五:

图六:

0000000000400efc <phase_2>:

  400efc: 55                        push   %rbp

  400efd:       53                        push   %rbx

  400efe: 48 83 ec 28          sub    $0x28,%rsp    保护和开辟栈帧

  400f02:       48 89 e6             mov    %rsp,%rsi把这时的栈顶给rsi

  400f05:       e8 52 05 00 00         callq  40145c <read_six_numbers> 调用函数,输入六个数

  400f0a:       83 3c 24 01           cmpl   $0x1,(%rsp)此时的rsp是读入的第一个数,()原因在后面已经说了)这条语句和下面一条表示的是,第一个数必须是1,否则爆炸

  400f0e:       74 20                      je     400f30 <phase_2+0x34>

  400f10:       e8 25 05 00 00         callq  40143a <explode_bomb>

  400f15:       eb 19                      jmp    400f30 <phase_2+0x34>

  400f17:       8b 43 fc             mov    -0x4(%rbx),%eax  这是循环的开始

   这里是将rbx-4(因为刚刚加四了)的位置的值放进rax,注意此时rbx没有变化

  400f1a:       01 c0                      add    %eax,%eax  变成二倍

  400f1c:       39 03                      cmp    %eax,(%rbx)  比较,相同则不爆炸

  400f1e:       74 05                      je     400f25 <phase_2+0x29>

  400f20:       e8 15 05 00 00         callq  40143a <explode_bomb>

  400f25:       48 83 c3 04          add    $0x4,%rbx

  400f29:       48 39 eb             cmp    %rbp,%rbx

  400f2c:       75 e9                      jne    400f17 <phase_2+0x1b>

  400f2e:       eb 0c                       jmp    400f3c <phase_2+0x40>

  400f30:       48 8d 5c 24 04         lea    0x4(%rsp),%rbxrsp的地址加上4后给rbx,也就是后移了一个数,或者说每运行到这里rbx都会加四

  400f35:       48 8d 6c 24 18         lea    0x18(%rsp),%rbp

  400f3a:       eb db                      jmp    400f17 <phase_2+0x1b>

  400f3c:       48 83 c4 28          add    $0x28,%rsp

  400f40:       5b                        pop    %rbx

  400f41:       5d                        pop    %rbp

  400f42:       c3                         retq   返回

000000000040145c <read_six_numbers>:

  40145c:      48 83 ec 18          sub    $0x18,%rsp 开辟栈帧

  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  注意这个地址,读取一下是“d d d d d d”可以知道传入的是六个int类型的数据,见图一

  401485:      b8 00 00 00 00         mov    $0x0,%eax

  40148a:      e8 61 f7 ff ff            callq  400bf0 <__isoc99_sscanf@plt>

  40148f:       83 f8 05              cmp    $0x5,%eax

  401492:      7f 05                       jg     401499 <read_six_numbers+0x3d> scanf函数会返回读取的数据的个数,放到rax里边(图二),这条跳转语句的意思是,必须读取大于5个数,否则爆炸

  401494:      e8 a1 ff ff ff             callq  40143a <explode_bomb>

  401499:      48 83 c4 18          add    $0x18,%rsp

  40149d:      c3                         retq  

这个函数把刚刚读入的六个数从下向上放在栈帧中,可以通过读取栈顶的数据来验证,栈顶地址加四后就是第二个数,见图二图三

这个phase是考循环的,输入六个数,第一个数强制要求是1,并且每次倍增,自然而然,得出这六个数是1 2 4 8 16 32,输入验证见图六

Phase 3

图一:

图二:

图三:

图四:

图五:

图六:

0000000000400f43 <phase_3>:    

  400f43:       48 83 ec 18          sub    $0x18,%rsp  分配栈帧

  400f47:       48 8d 4c 24 0c         lea    0xc(%rsp),%rcx 

  400f4c:       48 8d 54 24 08         lea    0x8(%rsp),%rdx  

  400f51:       be cf 25 40 00          mov    $0x4025cf,%esi 在这里有一个地址0x4025cf 我用x命令读取这个内存 ,得到“%d %d”,可见这是输入了两个数字(图一

  400f56:       b8 00 00 00 00         mov    $0x0,%eax 让返回值是0

  400f5b:       e8 90 fc ff ff            callq  400bf0 <__isoc99_sscanf@plt>

  400f60:       83 f8 01             cmp    $0x1,%eax 返回了输入的数字的个数,和1比较,根据下面的一个指令,可以看出,如果返回值大于一,则会跳转继续,否则爆炸,说明我们至少输入两个数

  400f63:       7f 05                       jg     400f6a <phase_3+0x27>

  400f65:       e8 d0 04 00 00         callq  40143a <explode_bomb>

  400f6a:       83 7c 24 08 07         cmpl   $0x7,0x8(%rsp)

上面这一句指令是将%rsp中的地址+8处的值和7比较,如果大于7,就会爆炸。

根据测试发现在这个%rsp+8地址处的值就是我输入的第一个数(图二

图二中,我输入的第一个数是12321,图中可见rsp中的值是0x7fffffffdfe0,于是我访问0x7fffffffdfe8,使用十进制输出,输出的正好是我输入的第一个值。

  400f6f: 77 3c                      ja     400fad <phase_3+0x6a> 大了就会爆炸,所以我们输入的第一个值必须<=7,我们以输入0为例

  400f71:       8b 44 24 08          mov    0x8(%rsp),%eax 把第一个输入的数放进%rax

  400f75:       ff 24 c5 70 24 40 00      jmpq   *0x402470(,%rax,8)

这条语句是无条件跳转到*0x402470+8*%rax),即跳转到0x402470+8*%rax这个地址中存放的那个地址。这个跳转方式和跳转表很符合。

我们打印一下在0x402470处的跳转表(图三 ),可见其中的前四行是我们要找的跳转表,

以第一个0x00400f7c为例,这一个地址值占了4B,而刚刚的跳转语句是每八个字节为一个单位,正好和这个跳转表是对应的。

以我们的输入的第一个数字是0为例,这时候我们会跳转到0x402470+8*0处存放的地址值,观察跳转表可以知道,这个位置是0x00400f7c

使用si指令,单步执行,可以观察到指令进行到了0x00400f7c处(图四

然后我们直接从这个位置继续看

  400f7c:       b8 cf 00 00 00          mov    $0xcf,%eax(刚刚跳转到了这里)把0xcf207)放到了%rax里面

  400f81:       eb 3b                      jmp    400fbe <phase_3+0x7b>

  400f83:       b8 c3 02 00 00         mov    $0x2c3,%eax

  400f88:       eb 34                      jmp    400fbe <phase_3+0x7b>

  400f8a:       b8 00 01 00 00         mov    $0x100,%eax

  400f8f: eb 2d                      jmp    400fbe <phase_3+0x7b>

  400f91:       b8 85 01 00 00         mov    $0x185,%eax

  400f96:       eb 26                      jmp    400fbe <phase_3+0x7b>

  400f98:       b8 ce 00 00 00         mov    $0xce,%eax

  400f9d:       eb 1f                       jmp    400fbe <phase_3+0x7b>

  400f9f: b8 aa 02 00 00         mov    $0x2aa,%eax

  400fa4:       eb 18                      jmp    400fbe <phase_3+0x7b>

  400fa6:       b8 47 01 00 00         mov    $0x147,%eax

  400fab:       eb 11                       jmp    400fbe <phase_3+0x7b>

  400fad:       e8 88 04 00 00         callq  40143a <explode_bomb>

  400fb2:       b8 00 00 00 00         mov    $0x0,%eax

  400fb7:       eb 05                      jmp    400fbe <phase_3+0x7b>

  400fb9:       b8 37 01 00 00         mov    $0x137,%eax

  400fbe:       3b 44 24 0c          cmp    0xc(%rsp),%eax

%rsp+0xc处的值和刚刚放进%rax中的207比较,如果相同就不会爆炸,猜测%rsp中放的就是我们输入的第二个数字,为了证明,我们输入第二个数是333,访问这个地址的值,也是333图五)。

  400fc2:       74 05                      je     400fc9 <phase_3+0x86> 相等就不会炸

  400fc4:       e8 71 04 00 00         callq  40143a <explode_bomb> 爆炸

  400fc9:       48 83 c4 18           add    $0x18,%rsp 回收栈帧

  400fcd:       c3                         retq   返回

根据上面的分析可以知道,这是一个Switch语句类似的跳转表的结构,输入的第一个数字决定了后面跳到哪里,也就决定了第二个数字是多少。第一个数字可以是0~7的任意一个数字,我们可以选0,对应的是0xcf (207),输入这两个数就可以不引爆炸弹。(图六

综上,此题多解,而且只要前两个数是对的,再多输入几个数也不影响,比如可以输0 207 2

Phase 4

图一:

图二:

图三:

图四:

000000000040100c <phase_4>:

  40100c:      48 83 ec 18          sub    $0x18,%rsp 开辟栈帧

  401010:      48 8d 4c 24 0c         lea    0xc(%rsp),%rcx 存放rsp+0xc的值(存第二个输入的数)

  401015:      48 8d 54 24 08         lea    0x8(%rsp),%rdx  存放rsp+0x8的值(存第一个输入的数)

  40101a:      be cf 25 40 00          mov    $0x4025cf,%esi  这个地址用x/s读内存得到“%d %d ”可见是输入两个数字(图一

  40101f:       b8 00 00 00 00         mov    $0x0,%eax 0放进%rax

  401024:      e8 c7 fb ff ff            callq  400bf0 <__isoc99_sscanf@plt>

  401029:      83 f8 02             cmp    $0x2,%eax  和上一个题略有不同,这道题必须输入两个数,多一个数也不行

  40102c:      75 07                      jne    401035 <phase_4+0x29> 

  40102e:      83 7c 24 08 0e         cmpl   $0xe,0x8(%rsp)   %rsp+8放的是第一个输入的数字,根据这条指令和下面一个指令,可知第一个数必须小于等于0xe(十进制的14)

  401033:      76 05                      jbe    40103a <phase_4+0x2e>

  401035:      e8 00 04 00 00         callq  40143a <explode_bomb>

  40103a:      ba 0e 00 00 00         mov    $0xe,%edx14放进%edx

  40103f:       be 00 00 00 00         mov    $0x0,%esi 0%rsi

  401044:      8b 7c 24 08          mov    0x8(%rsp),%edi 把第一个输入的数放到%rdi

  401048:      e8 81 ff ff ff             callq  400fce <func4开始递归

  40104d:      85 c0                      test   %eax,%eax 可知这个递归函数的返回值必须是0才可以

  40104f:       75 07                      jne    401058 <phase_4+0x4c>

  401051:      83 7c 24 0c 00         cmpl   $0x0,0xc(%rsp) 可知第二个输入的数必须是0才不爆炸

  401056:      74 05                      je     40105d <phase_4+0x51>

  401058:      e8 dd 03 00 00         callq  40143a <explode_bomb>

  40105d:      48 83 c4 18          add    $0x18,%rsp 回收栈帧

  401061:      c3                         retq   返回

0000000000400fce <func4>:  这是一个递归

  400fce: 48 83 ec 08          sub    $0x8,%rsp 分配栈帧

  400fd2:       89 d0                      mov    %edx,%eax 

  400fd4:       29 f0                       sub    %esi,%eax

  400fd6:       89 c1                      mov    %eax,%ecx

  400fd8:       c1 e9 1f              shr    $0x1f,%ecx  取符号位

  400fdb:       01 c8                      add    %ecx,%eax 

  400fdd:       d1 f8                       sar    %eax  算数右移1bit,相当于除以二

  400fdf: 8d 0c 30             lea    (%rax,%rsi,1),%ecx  

  400fe2:       39 f9                       cmp    %edi,%ecx  第一个分支点

  400fe4:       7e 0c                       jle    400ff2 <func4+0x24>

  400fe6:       8d 51 ff             lea    -0x1(%rcx),%edx

  400fe9:       e8 e0 ff ff ff             callq  400fce <func4>

  400fee: 01 c0                      add    %eax,%eax

  400ff0: eb 15                      jmp    401007 <func4+0x39>

  400ff2: b8 00 00 00 00         mov    $0x0,%eax    赋返回值为0

  400ff7: 39 f9                       cmp    %edi,%ecx

  400ff9: 7d 0c                      jge    401007 <func4+0x39>

  400ffb: 8d 71 01                    lea    0x1(%rcx),%esi

  400ffe: e8 cb ff ff ff             callq  400fce <func4>

  401003:      8d 44 00 01           lea    0x1(%rax,%rax,1),%eax   rax变成1了(图四

只有在输入的第一个值是大于7小于等于14时会在第一层调用中由于这个原因返回1导致爆炸。

  401007:      48 83 c4 08           add    $0x8,%rsp  回收栈帧

  40100b:      c3                         retq  

表示递归的结构是

其中,可以最快走出递归且让返回值为 0的一条道路可以是一直走图中最左侧的道路,即第一次调用就满足%ecx<=%edi%ecx>=%edi也就是%ecx=%edi,追踪寄存器可以知道此时的%ecx=%edi=7(其实这是由于调用函数之前放里边的14经过算数右移1位得到的),当func4运行到结尾时,rax中的值还是0,满足条件。

此题是多解的,若不使用第一个数是7的方法,继续分析,从左向右数第二条路中,会继续递归func4函数且将返回值赋值为1,故此路不通。

其他情况下,只有走最右侧道路是合理的,即第一个传入的数(此时在%rdi里边)必须满足小于7,又因为函数递归中的  shr  $0x1f,%ecx这一条指令是取符号位,当负数的时候就和正数的时候不同,所以我们可以在0~6这七个数中直接试正确性。经试验,得知6, 3, 1, 0 等都可被接受。

故,其中一种可行的答案是7 0图三

Phase 5

图一:

图二:

图三:

图四:

0000000000401062 <phase_5>:

  401062:      53                        push   %rbx 调用者保护

  401063:      48 83 ec 20          sub    $0x20,%rsp      开辟栈帧

  401067:      48 89 fb             mov    %rdi,%rbx  

  40106a:      64 48 8b 04 25 28 00     mov    %fs:0x28,%rax       金丝雀值传到%rax

  401071:      00 00

  401073:      48 89 44 24 18         mov    %rax,0x18(%rsp)    将金丝雀放在栈帧所在位置上面18字节的地方

  401078:      31 c0                      xor    %eax,%eax rax全清零

  40107a:      e8 9c 02 00 00         callq  40131b <string_length>  string的长度

  40107f:       83 f8 06              cmp    $0x6,%eax      长度是6才不会爆炸,跳转

  401082:      74 4e                      je     4010d2 <phase_5+0x70>

  401084:      e8 b1 03 00 00         callq  40143a <explode_bomb>

  401089:      eb 47                      jmp    4010d2 <phase_5+0x70>

  40108b:      0f b6 0c 03            movzbl (%rbx,%rax,1),%ecx   这是循环的开始

注意,%rbx里存放的是我输入的六个字符长度string的地址,证:我输入的stringYonefg,使用x指令访问%rbx中的地址值也是Yonefg,见图一

Movzbl是移动一个字节长度的内容到两个字节的位置,以无符号的方式。

上面这一条语句的含义是将我输入的string的第rax位置的一个字符(因为是读一个字节)放进rcx中,放到低三十二位。

  40108f:       88 0c 24             mov    %cl,(%rsp)

这条指令是将%rcx的低八位(一个字节)放到栈顶位置

  401092:      48 8b 14 24          mov    (%rsp),%rdx

将刚刚放到栈顶位置的数据拿出来放进%rdx

  401096:      83 e2 0f             and    $0xf,%edx

And 是与操作,目的是将%rdx的高60bit全赋值为0,仅剩低4bit是有效的值

  401099:      0f b6 92 b0 24 40 00     movzbl 0x4024b0(%rdx),%edx

这条语句是将0x4024b0这个地址加上%rdx中存储的偏移量后,取出一个字节的内容,放进%rdx中,覆盖掉之前的数据。

我们不防读取一下这个地址的内容,得到的是"maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"

  4010a0:      88 54 04 10          mov    %dl,0x10(%rsp,%rax,1)

这是将刚刚拿出来的%rdx中的数据的低32bit放到栈顶+10字节+%rax的偏移量的位置(这个循环每循环一次,%rax的值加一)

  4010a4:      48 83 c0 01          add    $0x1,%rax  %rax自增一

  4010a8:      48 83 f8 06            cmp    $0x6,%rax   这是上面这个循环的结束条件,当已经循环六次,退出循环

  4010ac:       75 dd                      jne    40108b <phase_5+0x29> 没有足够六次,去往循环开始的位置

  4010ae:       c6 44 24 16 00         movb   $0x0,0x16(%rsp)   这时已经退出了循环,从栈顶+10的位置一直到栈顶+15的位置,可以看做是六个char型的数据,上面这一条指令是在string的末尾加’\0’表示结尾

  4010b3:      be 5e 24 40 00         mov    $0x40245e,%esi  注意这个地址,是用来匹配的string,是目标,打印这个地址存储的string是“flyers”(图二

  4010b8:      48 8d 7c 24 10         lea    0x10(%rsp),%rdi  将刚刚的string的开头的地址放进%rdi,作为参数传入下方的string_not_equal中,与目标string相匹配

  4010bd:      e8 76 02 00 00         callq  401338 <strings_not_equal>

  4010c2:      85 c0                      test   %eax,%eax  返回值是0时说明是匹配的 不会炸

  4010c4:      74 13                      je     4010d9 <phase_5+0x77>

  4010c6:      e8 6f 03 00 00          callq  40143a <explode_bomb>

  4010cb:      0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

  4010d0:      eb 07                      jmp    4010d9 <phase_5+0x77>

  4010d2:      b8 00 00 00 00         mov    $0x0,%eax     rax清零

  4010d7:      eb b2                      jmp    40108b <phase_5+0x29>

  4010d9:      48 8b 44 24 18         mov    0x18(%rsp),%rax  这是开头部分放置的金丝雀值,将其放进%rax中等待检测

  4010de:      64 48 33 04 25 28 00     xor    %fs:0x28,%rax  做异或操作,相等时结果是零,才不会爆炸,若不是零,说明栈被破坏

  4010e5:      00 00

  4010e7:      74 05                      je     4010ee <phase_5+0x8c> 

  4010e9:      e8 42 fa ff ff            callq  400b30 <__stack_chk_fail@plt> 终止

  4010ee:       48 83 c4 20          add    $0x20,%rsp 回收栈帧

  4010f2:       5b                        pop    %rbx  恢复

  4010f3:       c3                         retq   返回

看完一遍上面的汇编指令可以知道,这个函数的原理是根据我输入的字符串的每一个charASCII码的最后4bit,将这4bit看做一个数,作为已给的string的下标,挑出这个string中合适位置的字符,拼凑成为一个新的string,使其和带匹配的string相同。

已知待匹配字符串是“flyers

原料字符串是“maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?

想要拼出flyer可以分别取原料字符串的第(十六进制表示)9 f e 5 6 7个位置的字符,

即用4bit表示这六个数是 1001111111100101011001111

查询ASCII码表可以得到后4bit对应的六个字符(不唯一)

1001->i I y Y 9 )

1111->? / _ o O

1110->> n N .

0101->u U e E %

0110->f F v V &

0111->’ 7 g G w W

上面字符任意组合即可,以“)?>%&’”为例,输入后可见正确。(图四)

其他答案例如ionUfG  9oN%V7    y_.E&’都是可以的

Phase 6 链表,栈

图一:

图二:

图三:

图四(该图片来源于https://zhuanlan.zhihu.com/p/451623574):

图五:

图六:

图七:

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  开辟50字节的栈帧

  401100:      49 89 e5             mov    %rsp,%r13  %r13存放栈顶 地址

  401103:      48 89 e6             mov    %rsp,%rsi   栈顶地址做第二个参数

  401106:      e8 51 03 00 00         callq  40145c <read_six_numbers>

 读入六个数字,这六个数字从栈顶向上排列(从低到高),我们可以通过依次读取%rsp+0,%rsp+4,%rsp+8,%rsp+c,%rsp+0x10,%rsp+0x14来验证。(图一中我输入的前三个数是4 3 2

  40110b:      49 89 e6             mov    %rsp,%r14    栈顶

  40110e:       41 bc 00 00 00 00       mov    $0x0,%r12d   %r12d置零

下面一部分是一个嵌套循环

  401114:       4c 89 ed             mov    %r13,%rbp   栈顶 外层循环(保证大于零小于六)

:每循环一次,%r13d会向后移动一个数,这是外层循环中变化的下标,遍历六个数

  401117:       41 8b 45 00          mov    0x0(%r13),%eax   将第一个数放进%rax

  40111b:       83 e8 01             sub    $0x1,%eax  第一个数减一

  40111e:       83 f8 05             cmp    $0x5,%eax  然后和5比较

  401121:      76 05                      jbe    401128 <phase_6+0x34>   (无符号数)小于等于五就不会爆炸,即传入的第一个数<=6且不能是比1小的数,即1~6

  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

  401130:      74 21                      je     401153 <phase_6+0x5f>这是外层循环结束判定条件,棕色的%r12d是计数器,当加到6的时候会结束循环

  401132:      44 89 e3             mov    %r12d,%ebx

  401135:      48 63 c3             movslq %ebx,%rax循环(查重作用),其中%rbx是循环的计数器,每循环一次加一

  401138:      8b 04 84                    mov    (%rsp,%rax,4),%eax  当前是循环的第几轮,就把栈中的第几个数(输入的第几个数)放进%rax中,或者这是根据%ebx选择第几个数,然后比较

  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    %rbx中是刚刚的轮次数,从1开始

  401148:      83 fb 05             cmp    $0x5,%ebx   %rbx<=6(有符号)跳转

  40114b:      7e e8                       jle    401135 <phase_6+0x41>循环:为了保证当前的这个数和其他所有的数不一样,直到%rbx到了最后一个数比完了结束,从外循环当前处理的数的位置开始,到最后一个数结束

  40114d:      49 83 c5 04          add    $0x4,%r13

每循环一次,%r13向后移动一个数

  401151:      eb c1                       jmp    401114 <phase_6+0x20> 外层循环  401153:      48 8d 74 24 18         lea    0x18(%rsp),%rsi   运行到这里,嵌套的循环结束

对于刚刚的这个循环,如果使用c语言描述,请见图二

上面这条指令是为了将%rsi指向最后一个输入的数,用来做下面循环的结束条件

接下来一部分是用7减去输入的数,放在原来的位置

  401158:      4c 89 f0             mov    %r14,%rax   0x40110b已知%r14中存放的是当前栈顶的地址

  40115b:      b9 07 00 00 00         mov    $0x7,%ecx   7->rcx

  401160:      89 ca                       mov    %ecx,%edx  rcx->rdx循环,(用7减)

  401162:      2b 10                      sub    (%rax),%edx  这是用7减去当前rax中指向的值,放在rdx

  401164:      89 10                      mov    %edx,(%rax)  然后把rdx放回rax指向的内存

  401166:      48 83 c0 04          add    $0x4,%rax  rax中存放的地址加4,向后移动一个数据

  40116a:       48 39 f0             cmp    %rsi,%rax  结束循环的条件,当rax==rsi说明这六个数都已经处理完了

  40116d:      75 f1                       jne    401160 <phase_6+0x6c>

经历完这个循环,最初输入的a b c d e f 已经变成了7-a 7-b …… 7-f,存放在%rsp%rsp+0x18内存中,每个数据占了4byte

打印这些值,发现确实已经变成了被7减后的值(图三

下面一段(0x40116f~0x4011a9)是根据我输入的数字将对应的链表的节点的地址放在stack中对应的顺序存储的位置上(流程图见图四

  40116f:       be 00 00 00 00         mov    $0x0,%esi  首先%rsi赋初值为0注意,这里的%rsi中的值每次循环会加四,相当于数组的下标向后移动,遍历这输入的六个数字

  401174:      eb 21                      jmp    401197 <phase_6+0xa3> 

  401176:      48 8b 52 08          mov    0x8(%rdx),%rdx    循环开始,(找到目标节点)

此时,%rdx是第一个节点的地址,这是将rdx变成当前节点的下一个节点的地址

  40117a:       83 c0 01             add    $0x1,%eax%rax+1

可以看出,%rdx%rax的关系是,%rdx中存放的是第%rax个节点的地址

  40117d:      39 c8                      cmp    %ecx,%eax  看看是否已经循环到了要找的节点

  40117f:       75 f5                       jne    401176 <phase_6+0x82>  没找到,继续找

  401181:      eb 05                      jmp    401188 <phase_6+0x94> 已经到了,跳转

  401183:      ba d0 32 60 00         mov    $0x6032d0,%edx

注意:这里出现了一个地址值,打印后发现这是六个节点,每个节点占用了十六个字节,前八个字节是数据域,后八个字节显然是指针域,指向了下一个节点的地址。(图五

因为是从输入的数是6(7-6=1)的情况下才跳转过来的,所以这里直接把第一个节点的地址放进%edx

  401188:      48 89 54 74 20         mov    %rdx,0x20(%rsp,%rsi,2)

这是把找到的对应的节点的地址放到0x20(%rsp,%rsi,2)的位置中,也可以看出,这些地址从%rsp+0x20开始存储,每次偏移2*%rsi字节,也可推论出,%rsi每次循环后至少改变4个字节

  40118d:      48 83 c6 04          add    $0x4,%rsi  自增4

  401191:      48 83 fe 18            cmp    $0x18,%rsi   rsi0x18说明已经循环了六次了,该结束了

  401195:      74 14                      je     4011ab <phase_6+0xb7>退出当前的循环

  401197:      8b 0c 34             mov    (%rsp,%rsi,1),%ecx  将当前要处理的数放在%exc,或说继续处理下一个数

  40119a:       83 f9 01             cmp    $0x1,%ecx

  40119d:      7e e4                       jle    401183 <phase_6+0x8f>根据前面分析已知,输入的值不可能小于1,所以,当%ecx中的数是1,才会跳转(为了简化找到对应地址的过程,避免了循环)

  40119f:       b8 01 00 00 00         mov    $0x1,%eax 如果当前处理的数不是1,会到这里执行,把1放进%rax

  4011a4:       ba d0 32 60 00         mov    $0x6032d0,%edx  把第一个节点的地址放到%rdx

  4011a9:       eb cb                       jmp    401176 <phase_6+0x82>

执行完上面一段指令后,栈中的数据如图(图六

下边这一段指令的内容是更新节点指针域。

把第一个数对应的节点的指针域指向第二个节点,以此类推。

  4011ab:       48 8b 5c 24 20         mov    0x20(%rsp),%rbx 第一个数对应的节点的地址放进%rbx

  4011b0:      48 8d 44 24 28         lea    0x28(%rsp),%rax 第二个数对应的节点的地址在栈中的地址放进%rax

  4011b5:      48 8d 74 24 50         lea    0x50(%rsp),%rsi  将不存在的第七个数对应节点的地址在栈中的地址放在%rsi,作为循环结束的终止条件

  4011ba:       48 89 d9                    mov    %rbx,%rcx  第一个数对应的节点的地址放进%rcx

  4011bd:      48 8b 10                    mov    (%rax),%rdx 第二个数对应的节点的地址放进%rdx

  4011c0:       48 89 51 08           mov    %rdx,0x8(%rcx)  把第二个数对应的节点的地址放进第一个数对应的节点的指针域中,使得第一个数对应的节点指向第二个数对应的节点在内存中的位置(不是栈中)

  4011c4:       48 83 c0 08          add    $0x8,%rax

 %rax从第二个数对应的节点的地址在栈中的地址变成第三个数对应的节点的地址在栈中的地址

  4011c8:       48 39 f0             cmp    %rsi,%rax%rsi已经指向第七个数对应的那个地址,结束该循环

  4011cb:       74 05                      je     4011d2 <phase_6+0xde>

  4011cd:       48 89 d1                    mov    %rdx,%rcx

  4011d0:      eb eb                       jmp    4011bd <phase_6+0xc9> 循环,将这六个节点全部链接

  4011d2:      48 c7 42 08 00 00 00     movq   $0x0,0x8(%rdx) 此时的%rdx是第六个数对应的节点的地址,此节点的指针域赋值为NULL

  4011d9:      00

下边最后一段指令的内容是要第一个数对应的节点的数据域的后4bit大于第二个的

,第二个的大于第三个的,以此类推。

  4011da:       bd 05 00 00 00         mov    $0x5,%ebp  计数器,一共比五次,每次减一

  4011df:       48 8b 43 08           mov    0x8(%rbx),%rax   1~5个数对应节点的指针域的内容,循环开始

  4011e3:       8b 00                      mov    (%rax),%eax 2~6个数的数据域的内容

  4011e5:       39 03                      cmp    %eax,(%rbx) 2~6个数的数据域的内容和第1~5个数的数据域的内容相比较 ,而且只比较后面四个字节的内容

  4011e7:       7d 05                      jge    4011ee <phase_6+0xfa> 当大于等于,不爆

  4011e9:       e8 4c 02 00 00         callq  40143a <explode_bomb>

  4011ee:       48 8b 5b 08          mov    0x8(%rbx),%rbx 将前一个节点的地址变成后一个节点的地址,相当于p=p->next

  4011f2:       83 ed 01              sub    $0x1,%ebp 更改计数器的值

  4011f5:       75 e8                      jne    4011df <phase_6+0xeb>

当计数器变成零则跳出循环

  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   返回

这道题考察链表,循环,stack。

分析一遍后可知,我们要输入六个数(互不相同,在1~6这里面),空格隔开,并且根据这六个数(相当于索引),必须满足前一节点数据域后4bit大于后一节点数据域后4bit的节点顺序。

此题需要倒推,根据打印出来的节点的数据域的值,可以看出

这些节点必须按照node3 node4 node5 node6 node1 node2的顺序才可以

即被七减完后stack中的值应该是(从%rsp+0x0到%rsp+0x14)3 4 5 6 1 2

也就是说,输入的六个数应该是 4 3 2 1 6 5

验证结果如图(图七

secret_phase

图一:

图二:

图三:

图四:

图五:

图六:

图七:

图八:

浏览反汇编的.s文件发现在phase_6下方有一个fun7函数,顺藤摸瓜可以发现还有两个函数,phase_defusedsecret_phase

他们的调用关系是phase_defused->secret_phase-> fun7->fun7……

观察主函数main(部分)可以发现

下面是main函数的一部分

  400e2d:      e8 de fc ff ff            callq  400b10 <puts@plt>

  400e32:      e8 67 06 00 00         callq  40149e <read_line>

  400e37:      48 89 c7              mov    %rax,%rdi

  400e3a:       e8 a1 00 00 00         callq  400ee0 <phase_1>

  400e3f:       e8 80 07 00 00         callq  4015c4 <phase_defused>

  400e44:      bf a8 23 40 00          mov    $0x4023a8,%edi

  400e49:      e8 c2 fc ff ff            callq  400b10 <puts@plt>

  400e4e:       e8 4b 06 00 00         callq  40149e <read_line>

  400e53:      48 89 c7             mov    %rax,%rdi

  400e56:      e8 a1 00 00 00         callq  400efc <phase_2>

  400e5b:      e8 64 07 00 00         callq  4015c4 <phase_defused>

  400e60:      bf ed 22 40 00          mov    $0x4022ed,%edi

  400e65:      e8 a6 fc ff ff            callq  400b10 <puts@plt>

  400e6a:       e8 2f 06 00 00          callq  40149e <read_line>

  400e6f:       48 89 c7             mov    %rax,%rdi

  400e72:      e8 cc 00 00 00         callq  400f43 <phase_3>

  400e77:      e8 48 07 00 00         callq  4015c4 <phase_defused>

  400e7c:       bf 0b 23 40 00         mov    $0x40230b,%edi

  400e81:      e8 8a fc ff ff            callq  400b10 <puts@plt>

  400e86:      e8 13 06 00 00         callq  40149e <read_line>

  400e8b:      48 89 c7             mov    %rax,%rdi

  400e8e:       e8 79 01 00 00         callq  40100c <phase_4>

  400e93:      e8 2c 07 00 00         callq  4015c4 <phase_defused>

  400e98:      bf d8 23 40 00         mov    $0x4023d8,%edi

  400e9d:      e8 6e fc ff ff            callq  400b10 <puts@plt>

  400ea2:       e8 f7 05 00 00          callq  40149e <read_line>

  400ea7:       48 89 c7              mov    %rax,%rdi

  400eaa:       e8 b3 01 00 00         callq  401062 <phase_5>

  400eaf: e8 10 07 00 00         callq  4015c4 <phase_defused>

  400eb4:      bf 1a 23 40 00          mov    $0x40231a,%edi

  400eb9:      e8 52 fc ff ff            callq  400b10 <puts@plt>

  400ebe:       e8 db 05 00 00         callq  40149e <read_line>

  400ec3:       48 89 c7             mov    %rax,%rdi

  400ec6:       e8 29 02 00 00         callq  4010f4 <phase_6>

  400ecb:       e8 f4 06 00 00          callq  4015c4 <phase_defused>

  400ed0:      b8 00 00 00 00         mov    $0x0,%eax

  400ed5:      5b                        pop    %rbx

  400ed6:      c3                         retq  

  400ed7:      90                        nop

  400ed8:      90                        nop

  400ed9:      90                        nop

观察主函数可以发现,在调用phase_1~6后,都会调用一次phase_defused函数,猜想是否进入phase中的secret_phasephase_1~6的输入有关

下面我们来解析phase_defused函数

00000000004015c4 <phase_defused>:

  4015c4:      48 83 ec 78          sub    $0x78,%rsp  分配栈帧

  4015c8:      64 48 8b 04 25 28 00     mov    %fs:0x28,%rax  金丝雀

  4015cf:       00 00

  4015d1:      48 89 44 24 68         mov    %rax,0x68(%rsp)

  4015d6:      31 c0                      xor    %eax,%eax 清零

  4015d8:      83 3d 81 21 20 00 06     cmpl  $0x6,0x202181(%rip)  

# 603760 <num_input_strings>

注释的意思是输入的string的个数,结合上面这个指令可知,如果输入的string的个数不是六,那就会跳转到该函数末尾,无法调用secret_phase函数

  4015df:       75 5e                      jne    40163f <phase_defused+0x7b>

  4015e1:      4c 8d 44 24 10         lea    0x10(%rsp),%r8

  4015e6:      48 8d 4c 24 0c         lea    0xc(%rsp),%rcx

  4015eb:      48 8d 54 24 08         lea    0x8(%rsp),%rdx

  4015f0:       be 19 26 40 00         mov    $0x402619,%esi    "%d %d %s"图一

这意味着输入的值必须是“数字,数字,字符串”类型的,观察已知的前六个题的密码,只有phase_3phase_4是符合的,也就是说要在三、四的密码的后边加上一个字符串

  4015f5:       bf 70 38 60 00         mov    $0x603870,%edi   ""

  4015fa:       e8 f1 f5 ff ff             callq  400bf0 <__isoc99_sscanf@plt>

  4015ff: 83 f8 03             cmp    $0x3,%eax  这里也说明输入值是3个才有可能调用secret_phase,否则会跳转到结尾

  401602:      75 31                      jne    401635 <phase_defused+0x71>

  401604:      be 22 26 40 00         mov    $0x402622,%esi   "DrEvil"图一

  401609:      48 8d 7c 24 10         lea    0x10(%rsp),%rdi

  40160e:      e8 25 fd ff ff            callq  401338 <strings_not_equal>调用了strings_not_euqal函数,结合前边几个phase的经验,可以知道,这是rdirsi的对比,也就是说,待输入的string就是%esi中的"DrEvil"将三、四题的答案后加上这个string,可以进入secret_phase(图二)

经测试,在四题输入0 0 时才会进入secret_phase

  401613:      85 c0                      test   %eax,%eax

  401615:      75 1e                      jne    401635 <phase_defused+0x71>

  401617:      bf f8 24 40 00          mov    $0x4024f8,%edi  "Curses, you've found the secret phase!" 图一

  40161c:      e8 ef f4 ff ff             callq  400b10 <puts@plt>

  401621:      bf 20 25 40 00         mov    $0x402520,%edi     "But finding it and solving it are quite different..." 图一

  401626:      e8 e5 f4 ff ff            callq  400b10 <puts@plt>

  40162b:      b8 00 00 00 00         mov    $0x0,%eax

  401630:      e8 0d fc ff ff            callq  401242 <secret_phase>

  401635:      bf 58 25 40 00         mov    $0x402558,%edi      "Congratulations! You've defused the bomb!" 图一

  40163a:      e8 d1 f4 ff ff            callq  400b10 <puts@plt>

  40163f:       48 8b 44 24 68         mov    0x68(%rsp),%rax

  401644:      64 48 33 04 25 28 00     xor    %fs:0x28,%rax  检验stack是否被破坏

  40164b:      00 00

  40164d:      74 05                      je     401654 <phase_defused+0x90>

  40164f:       e8 dc f4 ff ff            callq  400b30 <__stack_chk_fail@plt>

  401654:      48 83 c4 78          add    $0x78,%rsp

  401658:      c3                         retq  

  401659:      90                        nop

  40165a:      90                        nop

  40165b:      90                        nop

  40165c:      90                        nop

  40165d:      90                        nop

  40165e:      90                        nop

  40165f:       90                        nop

0000000000401242 <secret_phase>:

  401242:      53                        push   %rbx 开栈帧

  401243:      e8 56 02 00 00         callq  40149e <read_line>  读入数据

  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

  401255:      e8 76 f9 ff ff            callq  400bd0 <strtol@plt>    

Strtolstring to long ,将字符串变成一个长整型的数。

  40125a:      48 89 c3             mov    %rax,%rbx  返回值是我输入的数(图五

  40125d:      8d 40 ff             lea    -0x1(%rax),%eax 

  401260:      3d e8 03 00 00         cmp    $0x3e8,%eax  这是要求刚刚调用的函数的返回值(就是我输入的那个数)要小于等于0x3e8+1,这是为了避免访问内存越界等情况

  401265:      76 05                      jbe    40126c <secret_phase+0x2a>满足条件跳转

  401267:      e8 ce 01 00 00         callq  40143a <explode_bomb>

  40126c:      89 de                      mov    %ebx,%esi

  40126e:      bf f0 30 60 00          mov    $0x6030f0,%edi

出现了一个地址,使用x命令看看是什么(图六),发现是一堆节点,仔细观察可以发现,一个节点由以下几个部分构成:data(4 字节)0(4 字节)add(8 字节)add(8 字节)0(8 字节)。每个节点有两个地址,看做左子树和右子树的指针,猜测这是一个二叉树。(图六

 并且在图中可以看到,每个节点前边的0x1,0x21,0x22,0x32,0x33,0x31,0x34…可以正好表示该节点是树的第几层的第几个节点。

于是我把树的结构和节点的值画出来,见图七

 此时rsi中存放的是我输入的那个数字,rdi中是树的根节点

  401273:      e8 8c ff ff ff             callq  401204 <fun7>调用函数

  401278:      83 f8 02             cmp    $0x2,%eax 可见,返回值必须是2

  40127b:      74 05                      je     401282 <secret_phase+0x40>

  40127d:      e8 b8 01 00 00         callq  40143a <explode_bomb>

  401282:      bf 38 24 40 00         mov    $0x402438,%edi  这是拆弹成功后的输出内容

  401287:      e8 84 f8 ff ff            callq  400b10 <puts@plt>

  40128c:      e8 33 03 00 00         callq  4015c4 <phase_defused>

  401291:      5b                        pop    %rbx

  401292:      c3                         retq  

  401293:      90                        nop

  401294:      90                        nop

  401295:      90                        nop

  401296:      90                        nop

  401297:      90                        nop

  401298:      90                        nop

  401299:      90                        nop

  40129a:      90                        nop

  40129b:      90                        nop

  40129c:      90                        nop

  40129d:      90                        nop

  40129e:      90                        nop

  40129f:       90                        nop

0000000000401204 <fun7>:

  401204:      48 83 ec 08          sub    $0x8,%rsp  开辟栈帧

  401208:      48 85 ff             test   %rdi,%rdi  如果%rdi==0,那就表示这个传入的根节点是个空指针,直接返回

  40120b:      74 2b                      je     401238 <fun7+0x34>

  40120d:      8b 17                      mov    (%rdi),%edx  得节点的data rdx中。

  40120f:       39 f2                       cmp    %esi,%edx   将目前这个节点的data值和我输入的值进行比较

  401211:      7e 0d                      jle    401220 <fun7+0x1c> 如果我输入的值大于等于等于节点的(有符号),那就跳转

  401213:      48 8b 7f 08            mov    0x8(%rdi),%rdi   如果我输入的值小于节点的值,那就将指向当前节点的指针变成当前节点指向的位置加8的地方存储的值(是指向左子树的值)

  401217:      e8 e8 ff ff ff             callq  401204 <fun7然后继续调用,传入的是左子树的指针

  40121c:      01 c0                      add    %eax,%eax  然后将返回值乘二

  40121e:      eb 1d                      jmp    40123d <fun7+0x39>  跳转到结尾

  401220:      b8 00 00 00 00         mov    $0x0,%eax  rax赋值为0

  401225:      39 f2                       cmp    %esi,%edx  继续比较

  401227:      74 14                      je     40123d <fun7+0x39> 如果相等,那就返回当前rax中的值

  401229:      48 8b 7f 10            mov    0x10(%rdi),%rdi   将指右子树的地址放进%rdi,接下来在右子树中查找我输入的值

  40122d:      e8 d2 ff ff ff             callq  401204 <fun7>

  401232:      8d 44 00 01          lea    0x1(%rax,%rax,1),%eax 然后rax=rax*2+1

  401236:      eb 05                      jmp    40123d <fun7+0x39>

  401238:      b8 ff ff ff ff              mov    $0xffffffff,%eax  空树,返回-1

  40123d:      48 83 c4 08          add    $0x8,%rsp回收栈帧

  401241:      c3                         retq    返回

最后在树中查找这一段可以简要地总结为,进入左子树就将返回值乘二,进入右子树就将返回值乘二再加一,返回值初始是零。这一过程见图四

这一环节等价于C语言的:

int fun7(int cmp, Node* addr){

  if(addr == 0){

    return -1;

  }

  int v = addr->value;

  if (v == cmp){

    return 0;

  }else if( v < cmp){

        return 1 + 2*fun7(cmp, addr->right);

  }else{

    return 2*func7(cmp, addr->left);

  }

}

显然,要让返回值先乘二加一再乘二就可以得到二了,即先进入左子树再右子树(反着的),即0x16,所以答案输入22即可。(图八)

实验总结及心得体会

首先学习linux终端的基本使用方法,比如cd(更改工作路径)、pwd(查看当前路径)、whoami(查当前用户)、gdb(调试工具)、tree(显示文件结构)、..(返回上一级)、touch(新建或刷新)、vi(编辑器)、gcc(编译)等命令。

其次,学会熟练使用gdb调试工具,其中我学会的一部分命令有:

Si单步执行、disas显示当前运行到的位置、info reg显示寄存器状态、info b显示设置的断点的状态、layout r同时显示命令,汇编,寄存器、x/指令用来显示内存中数据,r 开始运行、r+文件名 带参数运行、b+行号或函数名是设置断点、clear b是清除所有断点、c继续运行到断点或结尾……

Gdb是十分有力的调试工具,让我感受到了计算机底层的繁琐且有序。

实验中遇到的问题:

  • 对汇编语言阅读不熟练,多加练习才能逐步熟悉汇编语言。
  • 虚拟机遇到问题,我自己在win上搭建虚拟机,独立完成了配置环境,学习了C文件编辑并编译等操作。
  • 实验难度大,难以攻克,需要坚定信心。

拆弹过程中,遇到问题我会上网求解,找相似的文章看一看,解决了问题,也开拓了视野。

总结一下这7个phase:难度由简到难,层层递进。分别的考点是:内存中的字符串匹配,对栈的理解、跳转表的结构、递归与结束条件、文本加密破解、链表中结点排序以及最后的树以及树中查找数据。

解决这7个问题虽然耗时很长,但是一点一点攻克总能够解出来的,可谓:世上无难事只怕有心人。

  • 8
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值