CSAPP 之 BombLab 详解

前言

本篇博客将会展示 CSAPP 之 BombLab 的拆弹过程,粉碎 Dr.Evil 的邪恶阴谋。Dr.Evil 总共设置了 6 个炸弹,每个炸弹对应一串字符串,如果字符串错误,炸弹就会被引爆💣,如下图所示:

CSAPP 之 BombLab 详解

字符串的长度未知,所以暴力破解是不可取的,也就是说这个实验就是要逼着拆弹小分队将 bomb 可执行文件反汇编,根据汇编代码推测出每个炸弹对应的字符串。在终端输入 objdump -d bomb > bomb.asm ,就可以将汇编代码写入 bomb.asm 文件中,方便后续分析。同时,Dr.Evil 还提供了 bomb.c 源文件,通过它可以看到炸弹程序的主要结构,代码如下:

/***************************************************************************
 * Dr. Evil's Insidious Bomb, Version 1.1
 * Copyright 2011, Dr. Evil Incorporated. All rights reserved.

 *
 * LICENSE:
 *
 * Dr. Evil Incorporated (the PERPETRATOR) hereby grants you (the
 * VICTIM) explicit permission to use this bomb (the BOMB).  This is a
 * time limited license, which expires on the death of the VICTIM.

 * The PERPETRATOR takes no responsibility for damage, frustration,
 * insanity, bug-eyes, carpal-tunnel syndrome, loss of sleep, or other
 * harm to the VICTIM.  Unless the PERPETRATOR wants to take credit,
 * that is.  The VICTIM may not distribute this bomb source code to
 * any enemies of the PERPETRATOR.  No VICTIM may debug,
 * reverse-engineer, run "strings" on, decompile, decrypt, or use any
 * other technique to gain knowledge of and defuse the BOMB.  BOMB
 * proof clothing may not be worn when handling this program.  The
 * PERPETRATOR will not apologize for the PERPETRATOR's poor sense of
 * humor.  This license is null and void where the BOMB is prohibited
 * by law.

 ***************************************************************************/

#include
#include
#include "support.h"
#include "phases.h"

/*
 * Note to self: Remember to erase this file so my victims will have no
 * idea what is going on, and so they will all blow up in a
 * spectaculary fiendish explosion. -- Dr. Evil
 */

FILE *infile;

int main(int argc, char *argv[])
{
    char *input;

    /* Note to self: remember to port this bomb to Windows and put a
     * fantastic GUI on it. */

    /* When run with no arguments, the bomb reads its input lines
     * from standard input. */
    if (argc == 1) {
    infile = stdin;
    }

    /* When run with one argument , the bomb reads from
     * until EOF, and then switches to standard input. Thus, as you
     * defuse each phase, you can add its defusing string to  and
     * avoid having to retype it. */
    else if (argc == 2) {
    if (!(infile = fopen(argv[1], "r"))) {
        printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
        exit(8);
    }
    }

    /* You can't call the bomb with more than 1 command line argument. */
    else {
    printf("Usage: %s []\n", argv[0]);
    exit(8);
    }

    /* Do all sorts of secret stuff that makes the bomb harder to defuse. */
    initialize_bomb();

    printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
    printf("which to blow yourself up. Have a nice day!\n");

    /* Hmm...  Six phases must be more secure than one phase! */
    input = read_line();             /* Get input                   */
    phase_1(input);                  /* Run the phase               */
    phase_defused();                 /* Drat!  They figured it out!
                      * Let me know how they did it. */
    printf("Phase 1 defused. How about the next one?\n");

    /* The second phase is harder.  No one will ever figure out
     * how to defuse this... */
    input = read_line();
    phase_2(input);
    phase_defused();
    printf("That's number 2.  Keep going!\n");

    /* I guess this is too easy so far.  Some more complex code will
     * confuse people. */
    input = read_line();
    phase_3(input);
    phase_defused();
    printf("Halfway there!\n");

    /* Oh yeah?  Well, how good is your math?  Try on this saucy problem! */
    input = read_line();
    phase_4(input);
    phase_defused();
    printf("So you got that one.  Try this one.\n");

    /* Round and 'round in memory we go, where we stop, the bomb blows! */
    input = read_line();
    phase_5(input);
    phase_defused();
    printf("Good work!  On to the next...\n");

    /* This phase will never be used, since no one will get past the
     * earlier ones.  But just in case, make this one extra hard. */
    input = read_line();
    phase_6(input);
    phase_defused();

    /* Wow, they got it!  But isn't something... missing?  Perhaps
     * something they overlooked?  Mua ha ha ha ha! */

    return 0;
}

不过我们也可以使用下述指令在终端查看 bomb.c

$ gdb bomb
(gdb) l

可以看到六个炸弹分别对应着 phase_1()phase_6() 函数,有了这些信息就可以着手分析汇编代码了。

拆弹过程

第一炸弹

在汇编代码中搜索 phase_1,可以看到 phase_1 共出现三次,左侧是 phase_1() 被调用,右侧是 phase_1() 的代码:

CSAPP 之 BombLab 详解

可以看到 phase_1 中把 $0x402400 写到了寄存器 %rsi 中,接着调用了 strings_not_equal 函数,相当于 strings_not_equal(%rdi, 0x402400),如果字符串不相等即 %rax 为 1 时,第一炸弹就会被引爆。容易猜到 0x402400 应该是正确字符串的起始地址,而 %rdi 中存的应该是拆弹小分队输入的字符串的起始地址。可以使用 GDB 来验证一下这个猜想:

CSAPP 之 BombLab 详解

先在 phase_1 的第三行 callq 401338 <strings_not_equal></strings_not_equal> 处打上断点,然后运行程序并输入 zhiyiYo,使用 display /x $rdi 查看 %rdi 的值 0x603780,最后使用 x/8c 0x603780 查看从该地址开始的8个字符,发现和我们输入的一样(多了结束符),验证了上述猜想。

由于我们还不知道真实字符串的长度,只知道它的起始地址是 0x402400,所以可以试下长一点的,只要看到 \000 就能说明字符串结束了。

(gdb) x/64c 0x402400
0x402400:       66 'B'  111 'o' 114 'r' 100 'd' 101 'e' 114 'r' 32 ' '  114 'r'
0x402408:       101 'e' 108 'l' 97 'a'  116 't' 105 'i' 111 'o' 110 'n' 115 's'
0x402410:       32 ' '  119 'w' 105 'i' 116 't' 104 'h' 32 ' '  67 'C'  97 'a'
0x402418:       110 'n' 97 'a'  100 'd' 97 'a'  32 ' '  104 'h' 97 'a'  118 'v'
0x402420:       101 'e' 32 ' '  110 'n' 101 'e' 118 'v' 101 'e' 114 'r' 32 ' '
0x402428:       98 'b'  101 'e' 101 'e' 110 'n' 32 ' '  98 'b'  101 'e' 116 't'
0x402430:       116 't' 101 'e' 114 'r' 46 '.'  0 '\000'        0 '\000'        0 '\000'        0 '\000'
0x402438:       87 'W'  111 'o' 119 'w' 33 '!'  32 ' '  89 'Y'  111 'o' 117 'u'

将上述字符连接得到 Border relations with Canada have never been better.,这个就是第一炸弹的正确答案。

CSAPP 之 BombLab 详解

第二炸弹 —— 枯萎穿心攻击

phase_2 的汇编代码如下所示:

0000000000400efc :
  400efc:   55                      push   %rbp
  400efd:   53                      push   %rbx
  400efe:   48 83 ec 28             sub    $0x28,%rsp
  400f02:   48 89 e6                mov    %rsp,%rsi
  400f05:   e8 52 05 00 00          callq  40145c
  400f0a:   83 3c 24 01             cmpl   $0x1,(%rsp)
  400f0e:   74 20                   je     400f30
  400f10:   e8 25 05 00 00          callq  40143a
  400f15:   eb 19                   jmp    400f30
  400f17:   8b 43 fc                mov    -0x4(%rbx),%eax
  400f1a:   01 c0                   add    %eax,%eax
  400f1c:   39 03                   cmp    %eax,(%rbx)
  400f1e:   74 05                   je     400f25
  400f20:   e8 15 05 00 00          callq  40143a
  400f25:   48 83 c3 04             add    $0x4,%rbx
  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
  400f35:   48 8d 6c 24 18          lea    0x18(%rsp),%rbp
  400f3a:   eb db                   jmp    400f17
  400f3c:   48 83 c4 28             add    $0x28,%rsp
  400f40:   5b                      pop    %rbx
  400f41:   5d                      pop    %rbp
  400f42:   c3                      retq

可以看到 phase_2 显示进行了被调用者保护,然后将 %rsp 栈指针减小 28 以腾出空间并将 %rsp 的值赋给 %rsi。接着调用了 read_six_numbers 函数,从名字可以看出这个函数应该用来读入 6 个数字,具体代码为:

000000000040145c :
  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
  401485:   b8 00 00 00 00          mov    $0x0,%eax
  40148a:   e8 61 f7 ff ff          callq  400bf0
  40148f:   83 f8 05                cmp    $0x5,%eax
  401492:   7f 05                   jg     401499
  401494:   e8 a1 ff ff ff          callq  40143a
  401499:   48 83 c4 18             add    $0x18,%rsp
  40149d:   c3                      retq

read_six_numbers 可以看出,6个数字的地址分别为 %rsp%rsp+0x4%rsp+0x8%rsp+0xc%rsp+0x10%rsp+0x14。调用完 read_six_numbers 之后, phase_2 又将 (%rsp)0x1 进行比较,如果不相等就引爆第二炸弹。接下来的代码可以翻译为:

rbx = rsp + 0x4;
rbp = rsp + 0x18;
while (rbx != rbp) {
    rax = 2 * M[rbx - 0x4];
    if (rax != M[rbx]) {
        explode_bomb();
    }
    rbx += 0x4;
}

说明读入的 6 个数字应该是一个等比数列,且 a[n] = 2*a[n-1],由于 (%rsp) 为 1,后续的几个数字就是 2、4、8、16 和 32。测试一下发现第二炸弹确实被成功拆除了:

CSAPP 之 BombLab 详解

第三炸弹 —— 败者食尘

phase_3 的汇编代码如下所示:

0000000000400f43 :
  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
  400f56:   b8 00 00 00 00          mov    $0x0,%eax
  400f5b:   e8 90 fc ff ff          callq  400bf0
  400f60:   83 f8 01                cmp    $0x1,%eax
  400f63:   7f 05                   jg     400f6a
  400f65:   e8 d0 04 00 00          callq  40143a
  400f6a:   83 7c 24 08 07          cmpl   $0x7,0x8(%rsp)
  400f6f:   77 3c                   ja     400fad
  400f71:   8b 44 24 08             mov    0x8(%rsp),%eax
  400f75:   ff 24 c5 70 24 40 00    jmpq   *0x402470(,%rax,8)
  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          callq  40143a
  400fb2:   b8 00 00 00 00          mov    $0x0,%eax
  400fb7:   eb 05                   jmp    400fbe
  400fb9:   b8 37 01 00 00          mov    $0x137,%eax
  400fbe:   3b 44 24 0c             cmp    0xc(%rsp),%eax
  400fc2:   74 05                   je     400fc9
  400fc4:   e8 71 04 00 00          callq  40143a
  400fc9:   48 83 c4 18             add    $0x18,%rsp
  400fcd:   c3                      retq

可以看到 phase_3 先读入了一个数字,接着跳转到 0x400f6a, 将 (%rsp + 0x8)0x7 进行比较,如果比它大就引爆炸弹,其中 (%rsp + 0x8) 的值就是我们输入的第一个数字。可以运行 GDB 来验证一下这个猜想,将断点打在 0x400f6a 处,对第三炸弹输入 8 9,结果如下图所示:

CSAPP 之 BombLab 详解

如果输入的第一个数字小于等于 7,就暂时不会引爆炸弹。接着运行了 jmpq *0x402470(,%rax,8) 指令,这个指令的作用就是让程序跳转到 M[0x402470 + %rax * 8] 处,此处 %rax 就是输入的第一个数字,由于每个地址 64 位为 8 个字节,所以将 %rax 乘上了 8。下图显示了跳转之前 %rax 的值和跳转表的 8 个地址:

CSAPP 之 BombLab 详解

可以看到输入的第一个数字 0~7 分别对应着: 0x400f7c0x400fb90x400f830x400f8a0x400f910x400f980x400f9f0x400fa6。在 phase_3 中,上述地址都是一条对 %rax 进行赋值的指令,赋的值的十进制为 207、311、707、256、389、206、682 和 327。接着将 (%rsp + 0xc)%rax 进行比较,如果不相等就引爆炸弹,否则退出 phase_3,解除炸弹。也就是说第三炸弹的前 2 个数字对应着 8 种正确答案(后面的字符串就无所谓了),当前两个数字为 1 311 时运行结果如下图所示:

CSAPP 之 BombLab 详解

第四炸弹

phase_4 的汇编代码如下:

000000000040100c :
  40100c:   48 83 ec 18             sub    $0x18,%rsp
  401010:   48 8d 4c 24 0c          lea    0xc(%rsp),%rcx
  401015:   48 8d 54 24 08          lea    0x8(%rsp),%rdx
  40101a:   be cf 25 40 00          mov    $0x4025cf,%esi
  40101f:   b8 00 00 00 00          mov    $0x0,%eax
  401024:   e8 c7 fb ff ff          callq  400bf0
  401029:   83 f8 02                cmp    $0x2,%eax
  40102c:   75 07                   jne    401035
  40102e:   83 7c 24 08 0e          cmpl   $0xe,0x8(%rsp)
  401033:   76 05                   jbe    40103a
  401035:   e8 00 04 00 00          callq  40143a
  40103a:   ba 0e 00 00 00          mov    $0xe,%edx
  40103f:   be 00 00 00 00          mov    $0x0,%esi
  401044:   8b 7c 24 08             mov    0x8(%rsp),%edi
  401048:   e8 81 ff ff ff          callq  400fce
  40104d:   85 c0                   test   %eax,%eax
  40104f:   75 07                   jne    401058
  401051:   83 7c 24 0c 00          cmpl   $0x0,0xc(%rsp)
  401056:   74 05                   je     40105d
  401058:   e8 dd 03 00 00          callq  40143a
  40105d:   48 83 c4 18             add    $0x18,%rsp
  401061:   c3                      retq

可以看到, phase_4 读入了两个数字(并且只允许输入两个),然后判断输入的第一个数字是否小于等于 14,大于 14 就会引爆炸弹。接着调用了 func4(&#x8F93;&#x5165;&#x7684;&#x7B2C;&#x4E00;&#x4E2A;&#x6570;&#x5B57;, 0, 14) 。从 func4() 返回后判断 %eax 是否为 0,不为 0 就引爆炸弹,如果 (%rsp + 0xc) 也就是输入的第二个数字不为 0 也会引爆炸弹。为了顺利拆除第四炸弹,有必要分析一下 func4 的执行流程。

0000000000400fce :
  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
  400fdf:   8d 0c 30                lea    (%rax,%rsi,1),%ecx
  400fe2:   39 f9                   cmp    %edi,%ecx
  400fe4:   7e 0c                   jle    400ff2
  400fe6:   8d 51 ff                lea    -0x1(%rcx),%edx
  400fe9:   e8 e0 ff ff ff          callq  400fce
  400fee:   01 c0                   add    %eax,%eax
  400ff0:   eb 15                   jmp    401007
  400ff2:   b8 00 00 00 00          mov    $0x0,%eax
  400ff7:   39 f9                   cmp    %edi,%ecx
  400ff9:   7d 0c                   jge    401007
  400ffb:   8d 71 01                lea    0x1(%rcx),%esi
  400ffe:   e8 cb ff ff ff          callq  400fce
  401003:   8d 44 00 01             lea    0x1(%rax,%rax,1),%eax
  401007:   48 83 c4 08             add    $0x8,%rsp
  40100b:   c3                      retq

func4 的操作可以使用如下的代码来描述:

do {
    rsp -= 0x8;
    rax = rdx;
    rax -= rsi;

    // 接下来的三行代码感觉没啥大用,因为正数逻辑右移 31 位之后一定为 0
    rcx = rax;
    rcx >>= 31 // 此处为逻辑右移
    rax += rcx;

    rax >>= 1; // rax = rax / 2
    rcx = rax + rsi;

    if (rcx = rdi) {
            rsp += 0x8;
            continue;
        }

        rsi = rcx + 1;
        // 下面这条语句应该在 rsp -= 0x8 后执行
        // M[rsp] = 0x401003;
    } else {
        rdx = rcx - 1;
        // 下面这条语句应该在 rsp -= 0x8 后执行
        // M[rsp] = 0x400fee;
    }

} while (rsp != 0x40104d)

有上述伪代码可以看到,要想返回的时候 %rax 为 0,就一定不能走到第 23 行,不然执行完 retq 语句后会将 M[%rsp] 的值也就是 0x401003 送到 %rip,程序跳到 lea 0x1(%rcx),%esi 继续执行,之后 %rax 不可能变成 0。在第 23 行没被执行的情况下, %rsi 的值一直都是 0, %rcx 的值可以为下面几种:

  • 14/2 = 7,当 %rdi = 7 时可以直接返回 phase_4 并解除炸弹
  • (7 – 1) / 2 = 3,当 %rdi = 3 时会跳到 add %eax,%eax 1 次才返回 phase_4 并解除炸弹
  • (3 – 1) / 2 = 1,当 %rdi = 1 时会跳到 add %eax,%eax 2 次才返回 phase_4 并解除炸弹
  • (1 – 1) / 2 = 0,当 %rdi = 0 时会跳到 add %eax,%eax 3 次才返回 phase_4 并解除炸弹

来测试一下最后一种情况,因为它最复杂:

(gdb) b *0x40100b
Breakpoint 1 at 0x40100b
(gdb) r
Starting program: /home/zhiyiyo/Documents/CSAPP/bomblab/bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!

Border relations with Canada have never been better.

Phase 1 defused. How about the next one?

1 2 4 8 16 32
That's number 2.  Keep going!

0 207
Halfway there!

0 0

Breakpoint 1, 0x000000000040100b in func4 ()
(gdb) disassemble
Dump of assembler code for function func4:
   0x0000000000400fce :     sub    $0x8,%rsp
   0x0000000000400fd2 :     mov    %edx,%eax
   0x0000000000400fd4 :     sub    %esi,%eax
   0x0000000000400fd6 :     mov    %eax,%ecx
   0x0000000000400fd8 :    shr    $0x1f,%ecx
   0x0000000000400fdb :    add    %ecx,%eax
   0x0000000000400fdd :    sar    %eax
   0x0000000000400fdf :    lea    (%rax,%rsi,1),%ecx
   0x0000000000400fe2 :    cmp    %edi,%ecx
   0x0000000000400fe4 :    jle    0x400ff2
   0x0000000000400fe6 :    lea    -0x1(%rcx),%edx
   0x0000000000400fe9 :    callq  0x400fce
   0x0000000000400fee :    add    %eax,%eax
   0x0000000000400ff0 :    jmp    0x401007
   0x0000000000400ff2 :    mov    $0x0,%eax
   0x0000000000400ff7 :    cmp    %edi,%ecx
   0x0000000000400ff9 :    jge    0x401007
   0x0000000000400ffb :    lea    0x1(%rcx),%esi
   0x0000000000400ffe :    callq  0x400fce
   0x0000000000401003 :    lea    0x1(%rax,%rax,1),%eax
   0x0000000000401007 :    add    $0x8,%rsp
=> 0x000000000040100b :    retq
End of assembler dump.

(gdb) si
0x0000000000400fee in func4 ()
(gdb) disassemble
Dump of assembler code for function func4:
   0x0000000000400fce :     sub    $0x8,%rsp
   0x0000000000400fd2 :     mov    %edx,%eax
   0x0000000000400fd4 :     sub    %esi,%eax
   0x0000000000400fd6 :     mov    %eax,%ecx
   0x0000000000400fd8 :    shr    $0x1f,%ecx
   0x0000000000400fdb :    add    %ecx,%eax
   0x0000000000400fdd :    sar    %eax
   0x0000000000400fdf :    lea    (%rax,%rsi,1),%ecx
   0x0000000000400fe2 :    cmp    %edi,%ecx
   0x0000000000400fe4 :    jle    0x400ff2
   0x0000000000400fe6 :    lea    -0x1(%rcx),%edx
   0x0000000000400fe9 :    callq  0x400fce
=> 0x0000000000400fee :    add    %eax,%eax
   0x0000000000400ff0 :    jmp    0x401007
   0x0000000000400ff2 :    mov    $0x0,%eax
   0x0000000000400ff7 :    cmp    %edi,%ecx
   0x0000000000400ff9 :    jge    0x401007
   0x0000000000400ffb :    lea    0x1(%rcx),%esi
   0x0000000000400ffe :    callq  0x400fce
   0x0000000000401003 :    lea    0x1(%rax,%rax,1),%eax
   0x0000000000401007 :    add    $0x8,%rsp
   0x000000000040100b :    retq
End of assembler dump.

(gdb) c
Continuing.

Breakpoint 1, 0x000000000040100b in func4 ()
(gdb) c
Continuing.

Breakpoint 1, 0x000000000040100b in func4 ()
(gdb) c
Continuing.

Breakpoint 1, 0x000000000040100b in func4 ()
(gdb) c
Continuing.

So you got that one.  Try this one.

可以看到当输入的第一数字为 0 时,确实跳转到 add %eax,%eax 才能返回,最终第四炸弹被成功拆除。

第五炸弹

phase_5 的汇编代码如下所示:

0000000000401062 :
  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
  401071:   00 00
  401073:   48 89 44 24 18          mov    %rax,0x18(%rsp)
  401078:   31 c0                   xor    %eax,%eax
  40107a:   e8 9c 02 00 00          callq  40131b
  40107f:   83 f8 06                cmp    $0x6,%eax
  401082:   74 4e                   je     4010d2
  401084:   e8 b1 03 00 00          callq  40143a
  401089:   eb 47                   jmp    4010d2
  40108b:   0f b6 0c 03             movzbl (%rbx,%rax,1),%ecx
  40108f:   88 0c 24                mov    %cl,(%rsp)
  401092:   48 8b 14 24             mov    (%rsp),%rdx
  401096:   83 e2 0f                and    $0xf,%edx
  401099:   0f b6 92 b0 24 40 00    movzbl 0x4024b0(%rdx),%edx
  4010a0:   88 54 04 10             mov    %dl,0x10(%rsp,%rax,1)
  4010a4:   48 83 c0 01             add    $0x1,%rax
  4010a8:   48 83 f8 06             cmp    $0x6,%rax
  4010ac:   75 dd                   jne    40108b
  4010ae:   c6 44 24 16 00          movb   $0x0,0x16(%rsp)
  4010b3:   be 5e 24 40 00          mov    $0x40245e,%esi
  4010b8:   48 8d 7c 24 10          lea    0x10(%rsp),%rdi
  4010bd:   e8 76 02 00 00          callq  401338
  4010c2:   85 c0                   test   %eax,%eax
  4010c4:   74 13                   je     4010d9
  4010c6:   e8 6f 03 00 00          callq  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
  4010d7:   eb b2                   jmp    40108b
  4010d9:   48 8b 44 24 18          mov    0x18(%rsp),%rax
  4010de:   64 48 33 04 25 28 00    xor    %fs:0x28,%rax
  4010e5:   00 00
  4010e7:   74 05                   je     4010ee
  4010e9:   e8 42 fa ff ff          callq  400b30
  4010ee:   48 83 c4 20             add    $0x20,%rsp
  4010f2:   5b                      pop    %rbx
  4010f3:   c3                      retq

可以看到 phase_5 开启了金丝雀保护机制,用于防范缓冲区溢出可能造成的安全问题。接着判断输入的字符串的长度,如果不为 6 就引爆炸弹。接下来的代码可以翻译为:

rbx = rdi; // %rdi 保存了输入的字符串的起始地址
for (rax = 0; rax < 6; rax++) {
    rcx = M[rbx + rax];
    M[rsp] = cl;    // %rcx 低八位,将值限制在 0~255 之间
    rdx = M[rsp];
    rdx &= 0xF;     // 将 %rdx 的值限制在 0~15 之间
    rdx = M[rdx + 0x4024b0];
    M[rsp + rax + 0x10] = dl; // %rdx 的低八位
}

M[rsp + 0x16] = 0;
rsi = 0x40245e;
rdi = rsp + 0x10;
if (strings_not_equal(rdi, rsi)) {
    explode_bomb()
}

根据第一炸弹的套路, %rsi 中存的应该是正确字符串的起始地址,由于字符串的长度为 6,所以结果如下图所示:

CSAPP 之 BombLab 详解

可以看到字符串是 flyers,当然第五炸弹可能没有第一炸弹那么简单,直接将 flyers 输到终端就企图解除炸弹只会无功而返,毕竟在 strings_not_equal 之前还有一段干了脏活的代码。再来认真分析一下 for 循环里面都发生了什么。

由于 %rdx 保存了输入的字符串的起始地址,所以 M[rbx + rax] 取出了字符串的一个字符,只留下字符的低 8 位并赋值给 %rdx%rdx 更进一步,只留下了低四位(取值范围 0~15)。之后 rdx = M[rdx + 0x4024b0]0x4024b0 为基地址,加上 0~15 的偏移量,从内存中取出了一个字符并赋给 %rdx。最后把 %rdx 的低 8 位赋值给 M[rsp + rax + 0x10],由于循环进行了 6 次,所以 strings_not_equal 是将 %rsp + 0x10%rsp + 0x15 组成的字符串和 flyers 进行比较。

下图显示了 0x4024b0 开始的 16 个( rdx &= 0xF )字符,里面出现了 flyers 所需的 6 个字母:

CSAPP 之 BombLab 详解

只要让给 0x4024b0 加上 9 就能取到 f,所以字符串的第一个字符应该是 9。而为了取到 l,我们应该给 0x4024b0 加上 15,但是这里对应的字符就不该是 15 的十六进制 F,而应该是低四位全为 1 的某个字符,字符 ? 的的二进制值为 0b11111,满足低四位全 1 的条件,所以字符串的第二位取 ?。根据这个原理可以分别确定出后面几个字符为 >567,最终结果是 9?>567

CSAPP 之 BombLab 详解

第六炸弹

phase_6 的汇编代码如下所示:

00000000004010f4 :
  4010f4:   41 56                   push   %r14
  4010f6:   41 55                   push   %r13
  4010f8:   41 54                   push   %r12
  4010fa:   55                      push   %rbp
  4010fb:   53                      push   %rbx
  4010fc:   48 83 ec 50             sub    $0x50,%rsp
  401100:   49 89 e5                mov    %rsp,%r13
  401103:   48 89 e6                mov    %rsp,%rsi
  401106:   e8 51 03 00 00          callq  40145c
  40110b:   49 89 e6                mov    %rsp,%r14
  40110e:   41 bc 00 00 00 00       mov    $0x0,%r12d
  401114:   4c 89 ed                mov    %r13,%rbp
  401117:   41 8b 45 00             mov    0x0(%r13),%eax
  40111b:   83 e8 01                sub    $0x1,%eax
  40111e:   83 f8 05                cmp    $0x5,%eax
  401121:   76 05                   jbe    401128
  401123:   e8 12 03 00 00          callq  40143a
  401128:   41 83 c4 01             add    $0x1,%r12d
  40112c:   41 83 fc 06             cmp    $0x6,%r12d
  401130:   74 21                   je     401153
  401132:   44 89 e3                mov    %r12d,%ebx
  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
  401140:   e8 f5 02 00 00          callq  40143a
  401145:   83 c3 01                add    $0x1,%ebx
  401148:   83 fb 05                cmp    $0x5,%ebx
  40114b:   7e e8                   jle    401135
  40114d:   49 83 c5 04             add    $0x4,%r13
  401151:   eb c1                   jmp    401114
  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
  401160:   89 ca                   mov    %ecx,%edx
  401162:   2b 10                   sub    (%rax),%edx
  401164:   89 10                   mov    %edx,(%rax)
  401166:   48 83 c0 04             add    $0x4,%rax
  40116a:   48 39 f0                cmp    %rsi,%rax
  40116d:   75 f1                   jne    401160
  40116f:   be 00 00 00 00          mov    $0x0,%esi
  401174:   eb 21                   jmp    401197
  401176:   48 8b 52 08             mov    0x8(%rdx),%rdx
  40117a:   83 c0 01                add    $0x1,%eax
  40117d:   39 c8                   cmp    %ecx,%eax
  40117f:   75 f5                   jne    401176
  401181:   eb 05                   jmp    401188
  401183:   ba d0 32 60 00          mov    $0x6032d0,%edx
  401188:   48 89 54 74 20          mov    %rdx,0x20(%rsp,%rsi,2)
  40118d:   48 83 c6 04             add    $0x4,%rsi
  401191:   48 83 fe 18             cmp    $0x18,%rsi
  401195:   74 14                   je     4011ab
  401197:   8b 0c 34                mov    (%rsp,%rsi,1),%ecx
  40119a:   83 f9 01                cmp    $0x1,%ecx
  40119d:   7e e4                   jle    401183
  40119f:   b8 01 00 00 00          mov    $0x1,%eax
  4011a4:   ba d0 32 60 00          mov    $0x6032d0,%edx
  4011a9:   eb cb                   jmp    401176
  4011ab:   48 8b 5c 24 20          mov    0x20(%rsp),%rbx
  4011b0:   48 8d 44 24 28          lea    0x28(%rsp),%rax
  4011b5:   48 8d 74 24 50          lea    0x50(%rsp),%rsi
  4011ba:   48 89 d9                mov    %rbx,%rcx
  4011bd:   48 8b 10                mov    (%rax),%rdx
  4011c0:   48 89 51 08             mov    %rdx,0x8(%rcx)
  4011c4:   48 83 c0 08             add    $0x8,%rax
  4011c8:   48 39 f0                cmp    %rsi,%rax
  4011cb:   74 05                   je     4011d2
  4011cd:   48 89 d1                mov    %rdx,%rcx
  4011d0:   eb eb                   jmp    4011bd
  4011d2:   48 c7 42 08 00 00 00    movq   $0x0,0x8(%rdx)
  4011d9:   00
  4011da:   bd 05 00 00 00          mov    $0x5,%ebp
  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
  4011e9:   e8 4c 02 00 00          callq  40143a
  4011ee:   48 8b 5b 08             mov    0x8(%rbx),%rbx
  4011f2:   83 ed 01                sub    $0x1,%ebp
  4011f5:   75 e8                   jne    4011df
  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

这段代码体积有点大,不过拆弹小分队还是得忍一下。 phase_6 首先读入了 6 个数字,接着从 0x40110e0x401151 的代码可以翻译为:

r12d = 0;
while (True) {
    rbp = r13;      // 输入的数字的地址
    rax = M[r13];
    rax -= 1;

    // M[r13] 的取值范围在 1~6 之间
    if (rax > 5 ) {
        explode_bomb();
    }

    r12d += 1;
    if (r12d == 6) break;

    for (rbx = r12d; rbx

上述代码中 %r13 存储的是输入的数字的地址,如下图所示:

CSAPP 之 BombLab 详解

根据上述代码可以确定输入的数字应该在 1~6 之间,并且不能重复,不然就会引爆第六炸弹。从 0x4011530x40116d 的代码可以翻译为:

rsi = rsp + 18;
rax = r14;  // 输入数字的开始地址
do {
    rdx = 7 - M[rax];
    M[rax] = rdx;
    rax += 4;
} while (rsi != rax)

此处 %r14%r13 一样存的是输入数字的开始地址(不贴图了),而上述代码的作用就是用 7 减去每个输入的数字并作为新值。接下来的代码就有点混乱了,反复横跳,所以这里先看下最后一段代码 0x4011da0x4011f5 的翻译:

for (rbp = 5; rbp > 1; rbp--) {
    rax = M[rbx + 8];
    eax = M[rax];       // 只保留四个字节,高位填充 0

    if (M[rbx] < eax) {
        explode_bomb();
    }

    rbx = M[rbx + 8];   // rbx 滞后 rax
}

由此可见 %rbx 是一个二重指针,需要解引用两次才能拿到正确的值,这段代码用来确定一段内存是否已降序排列,如果不满足降序条件就会引爆第六炸弹。如果输入的 6 个数字为 6~1,在开始检查是否满足倒序条件之前暂停程序,可以看到内存中以 %rbx 为起始地址的 12 个 8 字节数,其中左侧一列的低 32 位用来比较大小,右侧一列用来作为 %rax 和下次 %rbx 存放的地址:

CSAPP 之 BombLab 详解

根据上述信息,再来审视没提及的那一部分汇编代码,其实就是根据用户输入的数字,改变右侧一栏的地址和 %rbx 的初始值。如果将 %rbx 的初始值设置为 0x6032f0,沿着左侧一栏向下,到底之后回到 0x6032d0,一路得到的序列就是递减的。由此可以得到输入的数字为 4 3 2 1 6 5(被 7 减过得到的)。结果如下:

CSAPP 之 BombLab 详解

总结

通过这次实验,可以熟悉 GDB 调试工具的使用方法,同时也能看懂一些汇编代码了(不过有一说一,第六炸弹的代码真的就是又臭又长)。拆了这么久的炸弹,中间还失败过好多次,希望人质没事,以上~~

Original: https://www.cnblogs.com/zhiyiYo/p/16269904.html
Author: 之一Yo
Title: CSAPP 之 BombLab 详解

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/582822/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 上班摸鱼与网络安全

    上班不摸鱼,那这班上的没有灵魂啊。但是不久前爆出的国美网络监控事件,也提示我们网络有风险,摸鱼需谨慎。 https://baijiahao.baidu.com/s?id=17167…

    Linux 2023年6月13日
    0101
  • 2021年3月-第01阶段-Linux基础-Linux系统的启动流程

    Linux系统的启动流程 理解Linux操作系统启动流程,能有助于后期在企业中更好的维护Linux服务器,能快速定位系统问题,进而解决问题。 上图为Linux操作系统启动流程 1….

    Linux 2023年5月27日
    0107
  • Golang 实现 Redis(11): RDB 文件格式

    RDB 文件使用二进制方式存储 Redis 内存中的数据,具有体积小、加载快的优点。本文主要介绍 RDB 文件的结构和编码方式,并借此探讨二进制编解码和文件处理方式,希望对您有所帮…

    Linux 2023年5月28日
    094
  • 【Example】C++ 虚基类与虚继承 (菱形继承问题)

    C++ 是支持多继承的语言, 但是实际项目开发中非必要不要使用多继承以降低代码逻辑的复杂性,当然 C++ 多继承的特性带来一些问题即 菱形继承。 当一个类继承了两个来自同父类的子类…

    Linux 2023年6月13日
    073
  • LeetCode-125. 验证回文串

    题目来源 题目详情 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 说明: 本题中,我们将空字符串定义为有效的回文串。 示例 1: 输入: &#8…

    Linux 2023年6月7日
    084
  • samba服务设置与访问共享文件夹

    samba服务设置与访问共享文件夹 linux设置文件夹共享 windows连接共享文件夹(运行->//IP/route) linux连接共享文件夹 1、基本服务安装与配置 …

    Linux 2023年5月27日
    099
  • Linux 下统计文件夹下文件的数量

    1、查看当前目录下的文件数量(不包含子目录中的文件) 2、查看当前目录下的文件数量(包含子目录中的文件) 3、 查看当前目录下的文件夹目录个数(不包含子目录中的目录),同上述理,如…

    Linux 2023年6月13日
    0100
  • zabbix用户,角色,权限,模板管理

    zabbix用户,角色,权限,模板管理 用户组 用户角色 用户 使用刚才创建的用户登录 模板组 模板 模板的监控项可以自己创建也可以从其他模板复制 posted @2022-09-…

    Linux 2023年6月13日
    0110
  • 安卓投屏助手(ARDC)最新版

    近几年安卓多屏协同非常火爆,以华为小米为首的各大手机厂商都推出了各自的多屏协同软件,打破手机、平板和VR等安卓设备与电脑的边界,通过多屏融合提高办公的生产力。国内安卓投屏软件有To…

    Linux 2023年6月7日
    0313
  • CentOS-7配置fastDFS文件服务器和安装Nginx

    配置步骤实在是很繁琐,听我慢慢道来! 主要是配置管理(tracker)和存储(storage)返回地址样式 –> 域名/组名/磁盘名/目录名/文件名 &#8211…

    Linux 2023年6月13日
    091
  • Ubuntu 忘记登录密码

    重启Ubuntu,随即长按Shift(单系统)进入Grub菜单 选择Ubuntu高级选项 选择recovery mode进入Recovery Menu界面,选择Drop to ro…

    Linux 2023年6月16日
    0187
  • 【电台谈心】2022罗翔老师回答毕业生的4个问题

    罗翔老师:谢谢这位同学的问题。我回想起自己找工作的经历,其实也是 一地鸡毛。本科毕业的时候,也没有多少招聘会。我只有一个单位,一个单位地去找。少数几家单位让你进去了,聊了聊又觉得你…

    Linux 2023年6月13日
    0107
  • 分布式系统下的CAP定理

    本文参考EricBrewer博客加上自己的理解整理。 CAP定理又被成为布鲁尔定理,是加州大学计算机科学家埃里克·布鲁尔提出来的猜想,后来被证明成为分布式计算领域公认的定理。 CA…

    Linux 2023年6月13日
    084
  • 链栈

    #pragma once #ifndef _LINKSTACK_ #define _LINKSTACK_ #include using namespace std; typedef…

    Linux 2023年6月7日
    071
  • 原码反码补码

    3.1 知识点补充 在计算机内部,所有信息都是用二进制数串的形式表示的。整数通常都有正负之分,计算机中的整数分为无符号的和带符号的。无符号的整数用来表示0和正整数,即自然数;带符号…

    Linux 2023年6月13日
    0105
  • Fastadmin引入Layui模块

    Fastadmin,PHP,Fastadmin引入layui模块,Fastadmin使用layui。 ​ Fastadmin基于 RequireJS进行JS模块的管理,因此如果我们…

    Linux 2023年6月6日
    087
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球