问题描述

Dr. Evil 投放了一个二进制炸弹(bomb),你需要向它输入正确的内容才能解除,否则炸弹就会被引爆。所幸的是,我们得到了 bomb 的主要源代码文件 bomb.c,通过其内容我们发现拆除它实际上分为 $6$ 个阶段(phase),每个阶段都要我们输入一行字符串。但是从源文件中除了函数名外没有更多的信息了,我们目前唯一的破解办法是分析 bomb 程序的机器语言(汇编)描述,寻找各个阶段期望的输入。

网络上关于 Bomb Lab 的答案五花八门,因为“炸弹”确实有很多种:据说选修 CSAPP 的学生都将会得到一个独属于他们的“炸弹”,书本的配套资源也为自习的同学提供了练习的选择。所以在这篇文章中我不侧重于答案是什么,而是针对各个 phase 我的一些求解思路,一方面给读者提供一种参考,另一方面希望借此巩固我的知识基础。

Phase 1:访问地址

最开始时我用过反汇编工具 objdump -d 得到了 bomb 的汇编源码,它长到让人顿时失去了分析的兴致。不过,由 C 源码中我们看到各个 phase 的求解其实有一个统一的范式:

input = read_line();
phase_1(input);
phase_defused();

虽然我们不知道函数到底是如何实现的,但是从名字中也可以分析出 read_line 函数从某个地方(stdin 或者某个文件,从 main 函数开头的配置可以推测出来)得到字符串 input,并输入到判定函数 phase_1,如果失败程序直接退出,否则执行 phase_defused 函数表示这个 phase 已被解决。所以我们只需要分析 phase_1 函数的细节即可,其他 phase 也是这样。

为了体验上更舒适,我实际上利用了 gdb 来分析汇编,因为它可以很方便地显示出程序当前时刻各寄存器以及某个地址的值。具体的用法推荐看 Bomb Lab 说明中的推荐资源。这里使用 disassemble phase_1 得到的结果如下:

0x0000000000400ee0 <+0>:     sub    $0x8,%rsp
0x0000000000400ee4 <+4>:     mov    $0x402400,%esi
0x0000000000400ee9 <+9>:     call   0x401338 <strings_not_equal>
0x0000000000400eee <+14>:    test   %eax,%eax
0x0000000000400ef0 <+16>:    je     0x400ef7 <phase_1+23>
0x0000000000400ef2 <+18>:    call   0x40143a <explode_bomb>
0x0000000000400ef7 <+23>:    add    $0x8,%rsp
0x0000000000400efb <+27>:    ret

需要指出,这里的汇编片段采用 AT&T 语法。不愧是 phase_1,看起来就不难分析。我们不妨逐行看:

sub    $0x8,%rsp

这一行和倒数第二行的 add $0x8,%rsp 一起作为栈空间的申请和释放,不过说实话我看不出来这一段哪里用到了空出来的栈空间,AI 解释说这是为了保证 call 指令要求的对齐:总之不是特别重要了。接下来是一个函数调用:

mov    $0x402400,%esi
call   0x401338 <strings_not_equal>

这里把一个值传给寄存器 %esi,并调用了一个名为 strings_not_equal 的函数(这一看就知道干了什么)。我们知道,对于函数调用,%edi 这一系作为第一个参数,而 %esi 为第二个参数。%edi 在之前的过程没有被更改过,仍然为传入 phase_1 的第一个参数,即输入的字符串。到这里相比读者已经知道 phase_1 就是比较输入和某个特定的字符串是否相等来判断是否通过,接着看剩余的部分:

test   %eax,%eax
je     0x400ef7 <phase_1+23>
call   0x40143a <explode_bomb>

这里是一个 if 语句,test %eax,%eax 是一个常用的办法,常常和后面的 je 部分一起判断 %eax 是否为 $0$,如果是则执行跳转。可以看到如果不跳转程序就会调用 explode_bomb 函数,所以正确的情况下 %eax 为 $0$。%eax 作为函数 strings_not_equal 的返回值,为 $0$ 极有可能代表 false,也就是指两个字符串相等。综上,要解决 phase_1 只需要输入的字符串和给定的字符串相等。诚然我们没分析 strings_not_equal 的具体实现,不应该这么妄下结论,但事实可以作为佐证。为了获取这个给定的字符串,我们使用 gdbx/s 命令,得到地址 0x402400 实际上存储的字符串是

Border relations with Canada have never been better.

将这个作为输入,我们发现 phase_1 通过了。顺便我们也确信了 strings_not_equal 函数的返回值含义。

未完待续……