计算机系统基础实验2——拆解炸弹

14847 字
38 分钟

实验二:拆解炸弹实验

要想拆解炸弹我们首先需要知道bomb程序是如何工作的,所以我们先通过 objdump -d bomb >bomb.s 指令来得到程序的汇编代码。

由于这关需要查看大量的汇编代码,同时需要通过查找在很多函数中来回跳转,通过原有的方式登录服务器的方式并不方便,所以通过vscode进行远程连接:

1730367583566

通过vscode连接到远端服务器后,可以方便的查看汇编代码,同时添加注释也更加方便快捷。左侧显示目录下的文件,下方时命令的输入窗口,中间则显示打开的文件内容。

第一关

我们先阅读bomb.c中的内容。找到第一关的函数名:phase_1


input = read_line();             /* Get input                   */

phase_1(input);                  /* Run the phase               */

phase_defused();

在vscode中同时按 ctrl +F 开启查找搜索框

1730367839519

找到第一关的汇编代码。

我们对第一关的汇编代码添加注释,方便我们理解这一关的步骤。


0000000000400f2d <phase_1>:

  400f2d: 48 83 ec 08           sub    $0x8,%rsp //%rsp-=8

  400f31: be 80 26 40 00        mov    $0x402680,%esi //将地址$0x402680赋给%esi

  400f36: e8 99 04 00 00        call   4013d4 <strings_not_equal> //调用函数strings_not_equal

  400f3b: 85 c0                 test   %eax,%eax //检测%eax是否为0

  400f3d: 74 05                 je     400f44 <phase_1+0x17> //如果相等则跳转 400f44

  400f3f: e8 64 07 00 00        call   4016a8 <explode_bomb>  //引爆炸弹

  400f44: 48 83 c4 08           add    $0x8,%rsp //%rsp+=8

  400f48: c3                    ret  

这里第一关中,又调用了 strings_not_equal函数,那么我们在查找 strings_not_equal

函数看一下:


00000000004013d4 <strings_not_equal>:

  4013d4: 41 54                 push   %r12

  4013d6: 55                    push   %rbp

  4013d7: 53                    push   %rbx

  4013d8: 48 89 fb              mov    %rdi,%rbx

  4013db: 48 89 f5              mov    %rsi,%rbp

  4013de: e8 d3 ff ff ff        call   4013b6 <string_length>

  4013e3: 41 89 c4              mov    %eax,%r12d

  4013e6: 48 89 ef              mov    %rbp,%rdi

  4013e9: e8 c8 ff ff ff        call   4013b6 <string_length>

这里我们先看一下这个函数的前半部分,我们可以发现这个函数有两个参数,第一关参数存放在 %rdi中,第二个参数存放在 %rsi中,结合函数的名字可得,这个函数的功能大概是比较输入字符串和一个固定字符串是否相等。

我们可以在地址 4013d8 上设置一个断点来看一下:

通过 gdb bomb 来调试bomb


break *(0x4013d8)

然后我们运行:

1730369281458

这里我们先随便输入一个字符串,不用在意。

1730380971842

分别打印两个寄存器中内存地址中的内容,可以看到一个字符串是我们自己输入的字符串,另一个字符串就是对比的字符串,也就是这一题的答案。

1730383509616

输入这串字符后,第一关就通关啦。

第二关

我们同样先找到 phase_2 函数的汇编代码:


0000000000400f49 <phase_2>:

  400f49: 55                    push   %rbp     //将寄存器值入栈

  400f4a: 53                    push   %rbx     //将寄存器值入栈

  400f4b: 48 83 ec 28           sub    $0x28,%rsp   //栈指针减去40 

  400f4f: 64 48 8b 04 25 28 00  mov    %fs:0x28,%rax  //%fs中的基地址偏移40个字节的位置保存在%rax中

  400f56: 00 00 

  400f58: 48 89 44 24 18        mov    %rax,0x18(%rsp)  //把%rax中的值保存在栈指针偏移24的位置

  400f5d: 31 c0                 xor    %eax,%eax  //%eax = 0

  400f5f: 48 89 e6              mov    %rsp,%rsi  //%rsp的值移到%rsi

  400f62: e8 77 07 00 00        call   4016de <read_six_numbers>

这里我们先看调用 read_six_numbers之前的内容,这里在调用函数之前,先给栈开辟了一块40字节的空间,然后将栈顶指针当作函数参数传递给了 %rsi。我们可以知道读取的6个数应该是存在了栈中

然后我们看看读取了6个数之后的汇编代码:


  400f67: 83 3c 24 00           cmpl   $0x0,(%rsp)           //将栈顶元素与0进行比较

  400f6b: 79 05                 jns    400f72 <phase_2+0x29> //如果(%rsp)-0 大于0,则跳转 400f72

  400f6d: e8 36 07 00 00        call   4016a8 <explode_bomb> //若未跳转则爆炸

  400f72: 48 89 e5              mov    %rsp,%rbp             //将栈顶指针赋给%rbp

  400f75: bb 01 00 00 00        mov    $0x1,%ebx             //令%ebx = 1

  400f7a: 89 d8                 mov    %ebx,%eax             //令%ebx = %eax

  400f7c: 03 45 00              add    0x0(%rbp),%eax        //令%eax += (%rbp) 

  400f7f: 39 45 04              cmp    %eax,0x4(%rbp)        //令比较%eax 和 (%rbp)的下一个数

  400f82: 74 05                 je     400f89 <phase_2+0x40> //如果相等就跳转400f89

  400f84: e8 1f 07 00 00        call   4016a8 <explode_bomb> //不等就会爆炸

  400f89: 83 c3 01              add    $0x1,%ebx             //%ebx+=1

  400f8c: 48 83 c5 04           add    $0x4,%rbp             //让%rbp指向下一个数字

  400f90: 83 fb 06              cmp    $0x6,%ebx             //比较%ebx和6

  400f93: 75 e5                 jne    400f7a <phase_2+0x31> //不等就跳转(判断是否进行6次判断)

我们根据这段代码的逻辑可以发现这6个数字有以下要求

  • 第一个数字要大于0
  • a2-a1 = 1,a3-a2 = 2,a4-a3 = 3,a5-a4 = 4,a6-a5 = 5

既然已经知道这6个数字的要求,那么我们接下来看一下 read_six_numbers的内容:


00000000004016de <read_six_numbers>:

  4016de: 48 83 ec 08           sub    $0x8,%rsp           // 栈指针减8,分配栈空间

  4016e2: 48 89 f2              mov    %rsi,%rdx          // %rdx = %rsi(指向第一个数的存储位置)

  4016e5: 48 8d 4e 04           lea    0x4(%rsi),%rcx     // %rcx = %rsi + 4(第二个数)

  4016e9: 48 8d 46 14           lea    0x14(%rsi),%rax    // %rax = %rsi + 20(第六个数)

  4016ed: 50                    push   %rax               // 将第六个数的地址压栈(第8个参数)

  4016ee: 48 8d 46 10           lea    0x10(%rsi),%rax    // %rax = %rsi + 16(第五个数)

  4016f2: 50                    push   %rax               // 将第五个数的地址压栈(第7个参数)

  4016f3: 4c 8d 4e 0c           lea    0xc(%rsi),%r9      // %r9 = %rsi + 12(第四个数)

  4016f7: 4c 8d 46 08           lea    0x8(%rsi),%r8      // %r8 = %rsi + 8(第三个数)

  4016fb: be 71 29 40 00        mov    $0x402971,%esi     // %esi = 格式化字符串地址

  401700: b8 00 00 00 00        mov    $0x0,%eax          // 初始化%eax = 0

  401705: e8 36 f5 ff ff        call   400c40 <__isoc99_sscanf@plt>

  40170a: 48 83 c4 10           add    $0x10,%rsp         // 恢复栈指针

  40170e: 83 f8 05              cmp    $0x5,%eax          // 判断是否成功读取6个数

  401711: 7f 05                 jg     401718 <read_six_numbers+0x3a>  // 如果大于5则继续

  401713: e8 90 ff ff ff        call   4016a8 <explode_bomb> // 否则引爆炸弹

  401718: 48 83 c4 08           add    $0x8,%rsp          // 恢复栈指针

  40171c: c3                    ret                        // 返回

// 读取的6个数在栈中的顺序:

// 数字1 -> 地址 %rsi

// 数字2 -> 地址 %rsi + 4

// 数字3 -> 地址 %rsi + 8

// 数字4 -> 地址 %rsi + 12

// 数字5 -> 地址 %rsi + 16

// 数字6 -> 地址 %rsi + 20

这里我们根据每个寄存器作为第几个参数以及其中对应的地址,我们可以得到6个数字是按照输入顺序存储的在,所以这里无需额外注意。

这里我们需要查看以下地址 $0x402971上的内容,这个字符串规定了6个数字的输入格式。

我们输入代码 p (char*) 0x402971

1730386841495

所以我们按照规定格式输入满足要求的数字: 1 2 4 7 11 16

1730386937017

这样我们就顺利通过第二关啦。

第三关

1730791784748

我们从而得知这关需要我们输入两个正整数。

然后我们就可以分析一下第三关的汇编代码:


0000000000400fb1 <phase_3>:

  400fb1: 48 83 ec 18           sub    $0x18,%rsp   //栈指针减24

  400fb5: 64 48 8b 04 25 28 00  mov    %fs:0x28,%rax    //%fs中的基地址偏移40个字节的位置保存在%rax中  

  400fbc: 00 00 

  400fbe: 48 89 44 24 08        mov    %rax,0x8(%rsp)   //(%rsp+8) = %rax(栈中的第三个值)

  400fc3: 31 c0                 xor    %eax,%eax        //%eax^=%eax(%eax = 0)

  400fc5: 48 8d 4c 24 04        lea    0x4(%rsp),%rcx   //%rcx = %rsp+4 (第4个参数)(第2个读取的数的位置在栈的第2个位置)

  400fca: 48 89 e2              mov    %rsp,%rdx        //%rdx = %rsp (作为第3个传入参数)(第1个读取的数的位置在栈的第1个位)

  400fcd: be 7d 29 40 00        mov    $0x40297d,%esi   //%esi = 0x40297(输入格式字符串的地址)(第2个参数)

  400fd2: e8 69 fc ff ff        call   400c40 <__isoc99_sscanf@plt>//按照格式进行输入(%d %d)

  400fd7: 83 f8 01              cmp    $0x1,%eax        //比较%eax和1的大小(检测是否成功读取两个数字)

  400fda: 7f 05                 jg     400fe1 <phase_3+0x30>    //如果有符号大于则跳转到400fe1

  400fdc: e8 c7 06 00 00        call   4016a8 <explode_bomb>    //如果小于则爆照

  400fe1: 83 3c 24 07           cmpl   $0x7,(%rsp)    //比较*(%rsp)的值和7的大小(0<=d1<=7)

  400fe5: 77 63                 ja     40104a <phase_3+0x99>  //无符号大于则跳转40104a(会爆炸)

  400fe7: 8b 04 24              mov    (%rsp),%eax    //%exa = *(%rsp)    //%eax = d1

  400fea: ff 24 c5 e0 26 40 00  jmp    *0x4026e0(,%rax,8)   //无条件跳转到*(0x4026e0+%rax*8)的位置(d1 = 0时不跳转)

  400ff1: b8 0f 03 00 00        mov    $0x30f,%eax          //%eax = $0x30f

  400ff6: eb 05                 jmp    400ffd <phase_3+0x4c>//直接跳转到:400ffd

  400ff8: b8 00 00 00 00        mov    $0x0,%eax    //%eax = 0(暂时看来无效)(d1 = 1时跳转到这里)

  400ffd: 83 e8 77              sub    $0x77,%eax   //%eax-=119

  401000: eb 05                 jmp    401007 <phase_3+0x56>//直接跳转到:401007

  401002: b8 00 00 00 00        mov    $0x0,%eax    //%eax = 0(暂时看来无效)(d1 = 2时跳转到这里)

  401007: 05 95 02 00 00        add    $0x295,%eax  //%eax+=661

  40100c: eb 05                 jmp    401013 <phase_3+0x62>//直接跳转到:401013

  40100e: b8 00 00 00 00        mov    $0x0,%eax    //%eax = 0(暂时看来无效)(d1 = 3时跳转到这里)

  401013: 2d bc 01 00 00        sub    $0x1bc,%eax  //%eax-=444

  401018: eb 05                 jmp    40101f <phase_3+0x6e>//直接跳转到:40101f

  40101a: b8 00 00 00 00        mov    $0x0,%eax    //%eax = 0(暂时看来无效)(d1 = 4时跳转到这里)

  40101f: 05 bc 01 00 00        add    $0x1bc,%eax  //%eax+=444

  401024: eb 05                 jmp    40102b <phase_3+0x7a>//直接跳转到:40102b

  401026: b8 00 00 00 00        mov    $0x0,%eax    //%eax = 0(暂时看来无效)(d1 = 5时跳转到这里)

  40102b: 2d bc 01 00 00        sub    $0x1bc,%eax  //%eax-=444

  401030: eb 05                 jmp    401037 <phase_3+0x86>//直接跳转到:401037

  401032: b8 00 00 00 00        mov    $0x0,%eax    //%eax = 0(暂时看来无效)(d1 = 6时跳转到这里)

  401037: 05 bc 01 00 00        add    $0x1bc,%eax  //%eax+=444

  40103c: eb 05                 jmp    401043 <phase_3+0x92>//直接跳转到:401043

  40103e: b8 00 00 00 00        mov    $0x0,%eax    //%eax = 0(暂时看来无效)(d1 = 7时跳转到这里)

  401043: 2d bc 01 00 00        sub    $0x1bc,%eax  //%eax-=444

  401048: eb 0a                 jmp    401054 <phase_3+0xa3>//直接跳转到:401054

  40104a: e8 59 06 00 00        call   4016a8 <explode_bomb> //

  40104f: b8 00 00 00 00        mov    $0x0,%eax//%eax = 0(暂时看来无效)

  401054: 83 3c 24 05           cmpl   $0x5,(%rsp)    //比较栈顶的值和5的大小(b1<=5)

  401058: 7f 06                 jg     401060 <phase_3+0xaf>//有符号数更大则跳转(爆炸)

  40105a: 3b 44 24 04           cmp    0x4(%rsp),%eax //比d2和%eax的大小

  40105e: 74 05                 je     401065 <phase_3+0xb4>  //相等则跳转(避免爆炸)

  401060: e8 43 06 00 00        call   4016a8 <explode_bomb>  

  401065: 48 8b 44 24 08        mov    0x8(%rsp),%rax     //将栈中第三个值赋给%rax

  40106a: 64 48 33 04 25 28 00  xor    %fs:0x28,%rax      //%rax的值和%fs中的基地址偏移40个字节的位置的值异或

  401071: 00 00 

  401073: 74 05                 je     40107a <phase_3+0xc9> //如果相等就跳转

  401075: e8 16 fb ff ff        call   400b90 <__stack_chk_fail@plt>

  40107a: 48 83 c4 18           add    $0x18,%rsp   //%rsp+=24,恢复栈的大小

  40107e: c3                    ret

第三关需要注意的是:当d1不同的时候,我们的程序会跳转到不同的地方,就需要不同的d2的值。注意在函数的最后,还限制了第一个数必须小于等于5,所以6和7虽然能通过前面的验证,但是过不了后面的检测。

d1可能的情况一共有5种,对应的正确的d2如下:

  • 0 881
  • 1 98
  • 2 217
  • 3 -444
  • 4 0
  • 5 -444

只要输入其中任一一组值,就可以通过第三关。每一个答案都经过验证(甚至在验证的过程种不小心爆了一次)。

第四关

我们同样首先来看第四关的汇编代码:


00000000004010b2 <phase_4>:

  4010b2: 48 83 ec 18           sub    $0x18,%rsp   //%rsp-=24

  4010b6: 64 48 8b 04 25 28 00  mov    %fs:0x28,%rax  

  4010bd: 00 00 

  4010bf: 48 89 44 24 08        mov    %rax,0x8(%rsp)   //%rax放在(%rsp+8)的地址上

  4010c4: 31 c0                 xor    %eax,%eax      //令%eax = 0

  4010c6: 48 8d 4c 24 04        lea    0x4(%rsp),%rcx   //%rcx = %rsp+4 (第四个参数)

  4010cb: 48 89 e2              mov    %rsp,%rdx    //%rdx = %rsp  (第三个参数)

  4010ce: be 7d 29 40 00        mov    $0x40297d,%esi   //%esi = 0x40297d (第二个参数)(%d %d)

  4010d3: e8 68 fb ff ff        call   400c40 <__isoc99_sscanf@plt>   //进行输入

  4010d8: 83 f8 02              cmp    $0x2,%eax    //是否正确读入两个数据

  4010db: 75 06                 jne    4010e3 <phase_4+0x31>    //如果不相等就跳转(爆炸)

  4010dd: 83 3c 24 0e           cmpl   $0xe,(%rsp)    //比较d1和和14的大小  (d1 <= 14)

  4010e1: 76 05                 jbe    4010e8 <phase_4+0x36>    //如果无符号小于或等于就跳转(避免爆炸)

  4010e3: e8 c0 05 00 00        call   4016a8 <explode_bomb>

  4010e8: ba 0e 00 00 00        mov    $0xe,%edx    //%edx = 14 (第三个参数 = 14)

  4010ed: be 00 00 00 00        mov    $0x0,%esi    //%esi = 0  (第二个参数 = 0)

  4010f2: 8b 3c 24              mov    (%rsp),%edi  //%edi = d1 (第一个参数 = d1)

  4010f5: e8 85 ff ff ff        call   40107f <func4> //调用函数func4

  4010fa: 83 f8 07              cmp    $0x7,%eax    //比较返回值和7的大小(返回值 == 7)

  4010fd: 75 07                 jne    401106 <phase_4+0x54>    //如果不相等跳转401106,爆炸

  4010ff: 83 7c 24 04 07        cmpl   $0x7,0x4(%rsp)   //比较b2和7的大小(b2 == 7)

  401104: 74 05                 je     40110b <phase_4+0x59>      //相等跳转(避免爆炸)

  401106: e8 9d 05 00 00        call   4016a8 <explode_bomb>

  40110b: 48 8b 44 24 08        mov    0x8(%rsp),%rax   //

  401110: 64 48 33 04 25 28 00  xor    %fs:0x28,%rax

  401117: 00 00 

  401119: 74 05                 je     401120 <phase_4+0x6e>

  40111b: e8 70 fa ff ff        call   400b90 <__stack_chk_fail@plt>

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

  401124: c3                    ret

这里输入的方式和第三关的地址是一样的,所以这一关照样是读取两个整数。

通过阅读汇编代码,我们发现第四关中还调用了一个func4函数,并且这个函数有三个参数,分别为

  • %edi = d1(我们输入的第一个数)
  • %esi = 0
  • %edx = 14

同时,func4还有一个返回值,返回值的结果需要等于7

我们输入的第二个数字b2也需要等于7

就可以通过这一关了。

弄清了这一点,我们再来看func4的汇编代码:


000000000040107f <func4>:

  40107f: 53                    push   %rbx   

  401080: 89 d0                 mov    %edx,%eax    //%eax = %edx = 14

  401082: 29 f0                 sub    %esi,%eax    //%eax-= %esi = 14

  401084: 89 c3                 mov    %eax,%ebx    //%ebx = %eax = 14

  401086: c1 eb 1f              shr    $0x1f,%ebx   //%ebx >> 31(%ebx = 0)

  401089: 01 d8                 add    %ebx,%eax    //%eax += %ebx (=14)

  40108b: d1 f8                 sar    $1,%eax      //逻辑右移1位 %eax/=2 = 7

  40108d: 8d 1c 30              lea    (%rax,%rsi,1),%ebx   //%ebx = %rax+ %rsi = 7

  401090: 39 fb                 cmp    %edi,%ebx    //比较%edi(d1)和%ebx(7)

  401092: 7e 0c                 jle    4010a0 <func4+0x21>    //有符号小于等于就跳转4010a0 (d1 <= 14)

  401094: 8d 53 ff              lea    -0x1(%rbx),%edx    //%edx = %rbx-1

  401097: e8 e3 ff ff ff        call   40107f <func4>     //再次调用func4

  40109c: 01 d8                 add    %ebx,%eax    //%eax+=%ebx

  40109e: eb 10                 jmp    4010b0 <func4+0x31>    //跳转4010b0

  4010a0: 89 d8                 mov    %ebx,%eax    //%eax = %ebx(7)

  4010a2: 39 fb                 cmp    %edi,%ebx    //比较%edi(d1)和%ebx(7)

  4010a4: 7d 0a                 jge    4010b0 <func4+0x31>    //有符号大于等于就跳转4010b0

  4010a6: 8d 73 01              lea    0x1(%rbx),%esi     //%esi = %rbx+1

  4010a9: e8 d1 ff ff ff        call   40107f <func4>     //调用func4

  4010ae: 01 d8                 add    %ebx,%eax    //%eax+=%ebx

  4010b0: 5b                    pop    %rbx   

  4010b1: c3  

在分析的中途,我发现func4中有两次调用了函数自己,这会让分析变的非常复杂,好在我们发现只要d1满足的一定条件,我们就可以跳过再次调用func4。要满足的条件如下:

  • d1 <= 7
  • d1 >=7

只要满足这两个个条件,我们就可以让两次条件跳转都进行跳转,从而避免再次调用func4

我们可以化简func4代码如下:


000000000040107f <func4>:

  40107f: 53                    push   %rbx   

  401080: 89 d0                 mov    %edx,%eax    //%eax = %edx = 14

  401082: 29 f0                 sub    %esi,%eax    //%eax-= %esi = 14

  401084: 89 c3                 mov    %eax,%ebx    //%ebx = %eax = 14

  401086: c1 eb 1f              shr    $0x1f,%ebx   //%ebx >> 31(%ebx = 0)

  401089: 01 d8                 add    %ebx,%eax    //%eax += %ebx (=14)

  40108b: d1 f8                 sar    $1,%eax      //逻辑右移1位 %eax/=2 = 7

  40108d: 8d 1c 30              lea    (%rax,%rsi,1),%ebx   //%ebx = %rax+ %rsi = 7

  4010a0: 89 d8                 mov    %ebx,%eax    //%eax = %ebx(7)

  4010b0: 5b                    pop    %rbx   

  4010b1: c3                    ret

此时,返回值为%eax = %ebx = 7

满足第四关中对于函数返回值的要求,所以这一关的一个答案就是 7 7

第五关

我们直接看第五关的汇编代码:


0000000000401125 <phase_5>:

  401125: 53                    push   %rbx

  401126: 48 83 ec 10           sub    $0x10,%rsp //栈指针减16

  40112a: 48 89 fb              mov    %rdi,%rbx  //%rbx = %rdi(传进来的第一个参数)

  40112d: 64 48 8b 04 25 28 00  mov    %fs:0x28,%rax

  401134: 00 00 

  401136: 48 89 44 24 08        mov    %rax,0x8(%rsp)//(%rsp+8) = %rax

  40113b: 31 c0                 xor    %eax,%eax//%eax = 0

  40113d: e8 74 02 00 00        call   4013b6 <string_length>//调用string_length函数

  401142: 83 f8 06              cmp    $0x6,%eax//比较%eax和6的大小(字符串长度为6)

  401145: 74 05                 je     40114c <phase_5+0x27>//相等跳转,避免爆炸

  401147: e8 5c 05 00 00        call   4016a8 <explode_bomb>

  40114c: b8 00 00 00 00        mov    $0x0,%eax//%eax = 0

  401151: 0f b6 14 03           movzbl (%rbx,%rax,1),%edx//%edx = %rbx+%rax(%edx = Si)

  401155: 83 e2 0f              and    $0xf,%edx  //%edx &=15(保留前4位)(%edx = Si & 15)

  401158: 0f b6 92 20 27 40 00  movzbl 0x402720(%rdx),%edx  //%edx = 0x402720+%edx

  40115f: 88 14 04              mov    %dl,(%rsp,%rax,1)    //(%rsp+%rax) = %edx

  401162: 48 83 c0 01           add    $0x1,%rax    //%rax+=1

  401166: 48 83 f8 06           cmp    $0x6,%rax    //比较%rax和6的大小

  40116a: 75 e5                 jne    401151 <phase_5+0x2c>不等就跳转401151

  40116c: c6 44 24 06 00        movb   $0x0,0x6(%rsp)   //(%rsp+6) = 0

  401171: be d6 26 40 00        mov    $0x4026d6,%esi   //%esi = 0x4026d6

  401176: 48 89 e7              mov    %rsp,%rdi    //%rdi = %rsi

  401179: e8 56 02 00 00        call   4013d4 <strings_not_equal>//比较字符串是否相等

  40117e: 85 c0                 test   %eax,%eax    //%eax是否等于0

  401180: 74 05                 je     401187 <phase_5+0x62>    //等于则跳转,避免爆炸

  401182: e8 21 05 00 00        call   4016a8 <explode_bomb>

  401187: 48 8b 44 24 08        mov    0x8(%rsp),%rax   

  40118c: 64 48 33 04 25 28 00  xor    %fs:0x28,%rax

  401193: 00 00 

  401195: 74 05                 je     40119c <phase_5+0x77>

  401197: e8 f4 f9 ff ff        call   400b90 <__stack_chk_fail@plt>

  40119c: 48 83 c4 10           add    $0x10,%rsp

  4011a0: 5b                    pop    %rbx

  4011a1: c3                    ret

这一关的代码要我们输入长度为6的字符串。其中,这个函数给出了两个固定的地址,我们都先打印出来看看:

  • 0x402720的串为:maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?
  • 0x4026d6的串为:bruin

我们阅读汇编代码可以得到如下c代码:



intphase_5(char str[6])

{

    charans[6]; 

    char  str2[100] ="maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?";

    char  str3[10] ="bruins";

    for(int i =0;i<6;i++)

    {

        ans[i] =str2[(int)str[i] &15];

    }

    if(strings_not_equal(ans,str3))

    {

        printf("BOOM!!!\n");

        return0;

    }

    return1;

}

所以我们需要从str2中找到前15个字符中的b r u i n s 每个字母的下标,并且给每个下标+=48,再从ascall码表中把每个数字翻译为字符,按顺序输入。

如字符b ,在str2中下标为13,13+48 = 61 为’=’。

按照这样的方法,我们得到字符串 =63487。这就是这一题的其中一个答案。

第六关

第六关涉及多个循环,我们一个一个拆开来看,首先我们来看第一个大循环:


00000000004011a2 <phase_6>:

  4011a2: 41 55                 push   %r13

  4011a4: 41 54                 push   %r12

  4011a6: 55                    push   %rbp

  4011a7: 53                    push   %rbx

  4011a8: 48 83 ec 68           sub    $0x68,%rsp

  4011ac: 64 48 8b 04 25 28 00  mov    %fs:0x28,%rax

  4011b3: 00 00 

  4011b5: 48 89 44 24 58        mov    %rax,0x58(%rsp)  //

  4011ba: 31 c0                 xor    %eax,%eax    //%eax = 0

  4011bc: 48 89 e6              mov    %rsp,%rsi    //%rsi = %rsp (第二个参数)

  4011bf: e8 1a 05 00 00        call   4016de <read_six_numbers> //读取6个整数

  4011c4: 49 89 e4              mov    %rsp,%r12    //%r12 = %rsp

  4011c7: 41 bd 00 00 00 00     mov    $0x0,%r13d   //%r13d = 0

  4011cd: 4c 89 e5              mov    %r12,%rbp    //%rbp = %r12 (1.rbp = &d1,2.rbp = &d2)

  4011d0: 41 8b 04 24           mov    (%r12),%eax  //%eax = *(%r12)

  4011d4: 83 e8 01              sub    $0x1,%eax    //%eax -= 1

  4011d7: 83 f8 05              cmp    $0x5,%eax    //%eax <= 5?(d1,d2,d3,d4,d5,d6 <=6)

  4011da: 76 05                 jbe    4011e1 <phase_6+0x3f>  //满足则跳转4011e1

  4011dc: e8 c7 04 00 00        call   4016a8 <explode_bomb>

  4011e1: 41 83 c5 01           add    $0x1,%r13d   //%r13d +=1

  4011e5: 41 83 fd 06           cmp    $0x6,%r13d   //%r13d == 6?

  4011e9: 74 3d                 je     401228 <phase_6+0x86>  //相等就跳转401228

  4011eb: 44 89 eb              mov    %r13d,%ebx   //%ebx = %r13d

  4011ee: 48 63 c3              movslq %ebx,%rax    //%rax = %ebx

  4011f1: 8b 04 84              mov    (%rsp,%rax,4),%eax //%eax = %rsp+ %rax*4

  4011f44011f4: 39 45 00        cmp    %eax,0x0(%rbp)   //%eax == *(%rbp)?

  4011f7: 75 05                 jne    4011fe <phase_6+0x5c>    //不相等就跳转4011fe

  4011f9: e8 aa 04 00 00        call   4016a8 <explode_bomb>    //相等就爆炸

  4011fe: 83 c3 01              add    $0x1,%ebx    //%ebx +=1

  401201: 83 fb 05              cmp    $0x5,%ebx    //%ebx <= 5?

  401204: 7e e8                 jle    4011ee <phase_6+0x4c>  //小于等于则跳转(d1,d2,d3,d4,d5,d6 不能有重复数字)

  401206: 49 83 c4 04           add    $0x4,%r12    //%r12 += 4 (%r12 = &d2)

  40120a: eb c1                 jmp    4011cd <phase_6+0x2b>//直接跳转4011cd

第一个大循环中还套了一个小循环,大循环确保输入的6个数字范围都在1~6之间,小循环确保输入的6个数字都不重复。那么我们就大概知道了输入的6个数字应该是1到6的排列组合。

后续的要求可能是对于我们输入6个数字顺序的要求。

所以我们来看第二个循环


//让%rdx = (dn * 8 + 0x6042f0)

  40120c: 48 8b 52 08           mov    0x8(%rdx),%rdx //%rdx = *(%rdx+8)

  401210: 83 c0 01              add    $0x1,%eax    //%eax+=1

  401213: 39 c8                 cmp    %ecx,%eax    //%ecx == %eax?

  401215: 75 f5                 jne    40120c <phase_6+0x6a>    //不相等则跳转40120c

//将%rdx存到栈中

  401217: 48 89 54 74 20        mov    %rdx,0x20(%rsp,%rsi,2)   //*(%rsp+%rsi*2+32) = %rdx

  40121c: 48 83 c6 04           add    $0x4,%rsi    //%rsi+=4

  401220: 48 83 fe 18           cmp    $0x18,%rsi   //%rsi == 24

  401224: 75 07                 jne    40122d <phase_6+0x8b>//不相等则跳转40122d

  401226: eb 19                 jmp    401241 <phase_6+0x9f>//相等则跳转401241

//%r13d = 6  %ebx = 6  %rsi = 6  

  401228: be 00 00 00 00        mov    $0x0,%esi    //%esi = 0

  40122d: 8b 0c 34              mov    (%rsp,%rsi,1),%ecx//%ecx = *(%rsp+%rsi)

  401230: b8 01 00 00 00        mov    $0x1,%eax    //%eax = 1

  401235: ba f0 42 60 00        mov    $0x6042f0,%edx   //%edx = 0x6042f0

  40123a: 83 f9 01              cmp    $0x1,%ecx    //%ecx > 1?

  40123d: 7f cd                 jg     40120c <phase_6+0x6a>//dn大于1跳转40120c

  40123f: eb d6                 jmp    401217 <phase_6+0x75>//否则跳转401217

第二个循环的作用是把地址是 0x6042f0的一个数组中的第 dn 个数放在一个新数组的位置 n 上。

我们找到这个数组的前6个数分别是:

  • 251
  • 652
  • 284
  • 382
  • 235
  • 782

所以这段代码的主要作用是根据我们输入的6个数字对于这6个数进行重新排序,比如我们输入的数为:2 1 4 3 6 5

那么这6个数的排序为:

  • 652
  • 251
  • 382
  • 284
  • 782
  • 235

清楚这段代码的作用后,我们再来看下一段代码:



  401241: 48 8b 5c 24 20        mov    0x20(%rsp),%rbx    //%rbx = *(%rsp+32)

  401246: 48 8d 44 24 20        lea    0x20(%rsp),%rax    //%rax = rsp+32(第1个数地址)

  40124b: 48 8d 74 24 48        lea    0x48(%rsp),%rsi    //%rsi = %rsp+72(第6个数地址)

  401250: 48 89 d9              mov    %rbx,%rcx    //%rcx = %rbx


  401253: 48 8b 50 08           mov    0x8(%rax),%rdx   //%rdx = *(%rax+8)

  401257: 48 89 51 08           mov    %rdx,0x8(%rcx)   //*(%rcx+8) = %rdx

  40125b: 48 83 c0 08           add    $0x8,%rax    //%rax += 8

  40125f: 48 89 d1              mov    %rdx,%rcx    //%rcx = %rdx

  401262: 48 39 f0              cmp    %rsi,%rax    //%rax == %rsi

  401265: 75 ec                 jne    401253 <phase_6+0xb1>//不相等则跳转401253

这段代码似乎作用不大,仅仅是将之前我们存在栈中的数据复制到了另一个地方。



//确保数组为升序

  401267: 48 c7 42 08 00 00 00  movq   $0x0,0x8(%rdx)   //*(%rdx+8) = 0

  40126e: 00 

  40126f: bd 05 00 00 00        mov    $0x5,%ebp    //%edp = 5

  401274: 48 8b 43 08           mov    0x8(%rbx),%rax   //%rax = *(%rbx+8) 

  401278: 8b 00                 mov    (%rax),%eax    //%eax = *(%rax)

  40127a: 39 03                 cmp    %eax,(%rbx)    //*(%rbx)<=%eax

  40127c: 7e 05                 jle    401283 <phase_6+0xe1>//满足则跳转401283

  40127e: e8 25 04 00 00        call   4016a8 <explode_bomb> 

  401283: 48 8b 5b 08           mov    0x8(%rbx),%rbx //%rbx = *(%rbx+8)

  401287: 83 ed 01              sub    $0x1,%ebp    //%ebp -=1

  40128a: 75 e8                 jne    401274 <phase_6+0xd2>//%ebp != 0 则跳转

这段代码的作用是确保我们的6个数字必须逐渐增大,结合上面我们的理解,那么我们按照6个数字的大小顺序进行输入:

5 1 3 4 2 6

就可以通关这一题啦。

至此6个炸弹已经全部拆解完毕。

1731399511499