实验答案托管在我的Github上
用了一周的时间读完了《深入理解计算机系统》的第三章:程序的机器级表示。对于x86_64汇编及其逆向工程有了一定的了解。于2017年4月16日完成了其第二个Lab - Bomb Lab.
实验简介
Bomb Lab - Defusing a Binary Bomb主要是关于反汇编的实验,对应于书本的第三章:程序的机器级表示。
本实验非常的有趣。实验给定了一个二进制程序Bomb(炸弹)。这个炸弹由若干阶段组成,每个阶段都要求你在标准输入流中输入一个字符串,如果字符串正确,那么该阶段就会被解除并且程序会进入下一个阶段。如果字符串错误,那么炸弹会爆炸,并且退出。当每个阶段都被解除后,整个炸弹将会被解除。
在正式的实验中,每个学生将会从服务器处得到一个独一无二的炸弹,解除每个炸弹所需要的字符串都是不同的。同时,引爆炸弹将会导致扣分。当实验结束后,可以从服务器的计分板上看到每个学生的成绩。
本实验主要要求学生能够理解汇编代码,同时要求学生能学会使用一个Debugger(调试器)。
本实验提供的答案仅针对官网给出的自学者讲义中的炸弹。
实验提示
- 学会使用一个debugger(调试器),在本次实验中,使用GDB - GNU Debugger
- 使用objdump -t得到bomb的符号表。符号表包含了bomb中所有函数,全局变量,以及调用的函数的名字及地址。
- 使用objdump -d得到bomb的反汇编结果。但是注意,系统级别的函数调用将会以被加密的形式出现在反汇编代码中,如果需要知道这些信息,需要在gdb中对相应的函数进行反汇编。
实验过程及分析
实验准备
- 熟悉GDB的使用,并且使用objdump -t bomb > bomb-symboltable生成bomb的符号表,再使用objdump -d bomb > bomb-disassemble生成bomb的反汇编文件。
- 确保即时输入了错误的字符串炸弹也不会爆炸。观察bomb的符号表可以发现函数explode_bomb,推测该函数的作用是引爆炸弹,因此在GDB中使用break explode_bomb为该函数添加断点。这样,我们可以在bomb爆炸前提前截获并处理。
- 观察符号表可以看到phase_1到phase_6这几个函数以及其他的相关辅助函数,我们要做的就是给每一个阶段的函数添加断点,反汇编,并且理解这些函数的作用,并推测出答案。
实验过程
主函数 - main
首先在bomb-disassemble中观察main的反汇编代码,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
720000000000400da0 <main>:
  400da0:       53                      push   %rbx
  400da1:       83 ff 01                cmp    $0x1,%edi
  400da4:       75 10                   jne    400db6 <main+0x16>
  400da6:       48 8b 05 9b 29 20 00    mov    0x20299b(%rip),%rax        # 603748 <stdin@@GLIBC_2>
  400dad:       48 89 05 b4 29 20 00    mov    %rax,0x2029b4(%rip)        # 603768 <infile>
  400db4:       eb 63                   jmp    400e19 <main+0x79>
  400db6:       48 89 f3                mov    %rsi,%rbx
  400db9:       83 ff 02                cmp    $0x2,%edi
  400dbc:       75 3a                   jne    400df8 <main+0x58>
  400dbe:       48 8b 7e 08             mov    0x8(%rsi),%rdi
  400dc2:       be b4 22 40 00          mov    $0x4022b4,%esi
  400dc7:       e8 44 fe ff ff          callq  400c10 <fopen@plt>
  400dcc:       48 89 05 95 29 20 00    mov    %rax,0x202995(%rip)        # 603768 <infile>
  400dd3:       48 85 c0                test   %rax,%rax
  400dd6:       75 41                   jne    400e19 <main+0x79>
  400dd8:       48 8b 4b 08             mov    0x8(%rbx),%rcx
  400ddc:       48 8b 13                mov    (%rbx),%rdx
  400ddf:       be b6 22 40 00          mov    $0x4022b6,%esi
  400de4:       bf 01 00 00 00          mov    $0x1,%edi
  400de9:       e8 12 fe ff ff          callq  400c00 <__printf_chk@plt>
  400dee:       bf 08 00 00 00          mov    $0x8,%edi
  400df3:       e8 28 fe ff ff          callq  400c20 <exit@plt>
  400df8:       48 8b 16                mov    (%rsi),%rdx
  400dfb:       be d3 22 40 00          mov    $0x4022d3,%esi
  400e00:       bf 01 00 00 00          mov    $0x1,%edi
  400e05:       b8 00 00 00 00          mov    $0x0,%eax
  400e0a:       e8 f1 fd ff ff          callq  400c00 <__printf_chk@plt>
  400e0f:       bf 08 00 00 00          mov    $0x8,%edi
  400e14:       e8 07 fe ff ff          callq  400c20 <exit@plt>
  400e19:       e8 84 05 00 00          callq  4013a2 <initialize_bomb>
  400e1e:       bf 38 23 40 00          mov    $0x402338,%edi
  400e23:       e8 e8 fc ff ff          callq  400b10 <puts@plt>
  400e28:       bf 78 23 40 00          mov    $0x402378,%edi
  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   
结合讲义中的bomb.c可以得出,main函数每次调用read_line从标准输入流中读入一行字符串,并将其返回值(即字符串的首地址)设置为第一个参数,然后依次调用phase_1到phase_6。
在每一个phase函数内部检查输入的字符串是否正确,如果不正确,则调用explode_bomb函数引爆bomb;否则则返回,返回之后由主函数再继续调用phase_defused解除该阶段。
因此,在每一个阶段中,我们关注的重点应当是phase函数内部以及phase函数所调用的其他函数。
阶段1 - phase_1
在bomb-disassemble中观察phase_1的反汇编代码,如下:1
2
3
4
5
6
7
8
90000000000400ee0 <phase_1>:
  400ee0:       48 83 ec 08             sub    $0x8,%rsp
  400ee4:       be 00 24 40 00          mov    $0x402400,%esi #设置第二个参数为指针
  400ee9:       e8 4a 04 00 00          callq  401338 <strings_not_equal> #调用strings_not_equal函数
  400eee:       85 c0                   test   %eax,%eax
  400ef0:       74 05                   je     400ef7 <phase_1+0x17>
  400ef2:       e8 43 05 00 00          callq  40143a <explode_bomb> #若返回值不为0则引爆bomb
  400ef7:       48 83 c4 08             add    $0x8,%rsp
  400efb:       c3                      retq  
可以看出,phase_1调用了strings_not_equal这个函数,并为该函数配置了第二个参数0x402400,并判断该函数的返回值是否为0,如果为0则返回,否则引爆bomb。
从函数名可以推测,该函数的作用是判断两个字符串是否不相同,如果不相同,则返回非0值,相同则返回0。
还可以推测,该函数的第一个参数%rdi中存放了我们输入的字符串的首地址,而第二个参数%rsi指向了待比较的字符串的首地址——也就是阶段1的答案。
下面在GDB中验证推测。使用GDB运行bomb,为phase_1和explode_bomb添加断点。然后输入测试字符串”Test”,进入phase_1后用print分别打印输出%rdi和%rsi所指向的字符串。
首先是设置断点的相关命令及结果:1
2
3
4(gdb) break explode_bomb
Breakpoint 1 at 0x40143a
(gdb) break phase_1
Breakpoint 2 at 0x400ee0
然后是运行程序及输入测试字符串的相关命令及结果:1
2
3
4
5
6
7(gdb) run
Starting program: /home/blackdragon/CSAPPLabs/BombLab/bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Test
Breakpoint 2, 0x0000000000400ee0 in phase_1 ()
然后我们查看%rdi所指向的字符串:1
2(gdb) print (char *)($rdi)
$1 = 0x603780 <input_strings> "Test"
发现%rdi果然指向了我们输入的字符串。然后我们单步执行程序至调用strings_not_equal函数之前,查看%rsi所指向的字符串:1
2(gdb) print (char *)($rsi)
$2 = 0x402400 "Border relations with Canada have never been better."
我们已经得到了phase_1的答案,就是”Border relations with Canada have never been better.”
下面我们重新执行bomb并测试,输出”Phase 1 defused. How about the next one?”,phase_1解决。
阶段2 - phase_2
在bomb-disassemble中观察phase_2的反汇编代码,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
280000000000400efc <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
  400f05:       e8 52 05 00 00          callq  40145c <read_six_numbers> #从输入的字符串中读入6个数字
  400f0a:       83 3c 24 01             cmpl   $0x1,(%rsp)
  400f0e:       74 20                   je     400f30 <phase_2+0x34>
  400f10:       e8 25 05 00 00          callq  40143a <explode_bomb> #如果第一个数字不为1则引爆炸弹
  400f15:       eb 19                   jmp    400f30 <phase_2+0x34>
  400f17:       8b 43 fc                mov    -0x4(%rbx),%eax
  400f1a:       01 c0                   add    %eax,%eax
  # 计算前一个元素*2
  400f1c:       39 03                   cmp    %eax,(%rbx)
  400f1e:       74 05                   je     400f25 <phase_2+0x29>
  400f20:       e8 15 05 00 00          callq  40143a <explode_bomb> #若当前元素不等于前一个元素*2则引爆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),%rbx
  400f35:       48 8d 6c 24 18          lea    0x18(%rsp),%rbp #设置b和bEnd
  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   
可以看出,在phase_2的一开始调用了read_six_numbers这个函数,初步推测这是用来从输入字符串中读取六个数字的函数,其将输入字符串的地址作为第一个参数,将栈顶指针%rsp作为第二个参数,下面我们查看read_six_numbers的反汇编代码,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20000000000040145c <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
  401485:       b8 00 00 00 00          mov    $0x0,%eax
  # 为sscanf配置参数
  40148a:       e8 61 f7 ff ff          callq  400bf0 <__isoc99_sscanf@plt>
  # 调用sscanf(input, "%d %d %d %d %d %d", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5]);
  40148f:       83 f8 05                cmp    $0x5,%eax
  401492:       7f 05                   jg     401499 <read_six_numbers+0x3d>
  401494:       e8 a1 ff ff ff          callq  40143a <explode_bomb> #若读取成功的数字数小于6则引爆bomb
  401499:       48 83 c4 18             add    $0x18,%rsp
  40149d:       c3                      retq   
可以看出,该函数在0x401460-0x401480为函数的调用构造了参数,在0x401485将返回值置0,并在0x40148a处调用了sscanf函数。并且检查sscanf函数的返回值(读取成功的变量个数),如果返回值<=5,则引爆bomb,否则则正确返回。
下面我们重点观察调用函数的参数构造过程。%rdi是第1个参数,指向了输入的字符串,推测第2个参数%rsi应该指向了格式化字符串的首地址,且该字符串为”%d %d %d %d %d %d”。%rdx是第3个参数,为%rsi+0,%rcx是第4个参数,为%rsi+4,%r8是第5个参数,为%rsi+8,%r9是第6个参数,为%rsi+12。(%rsp)是第7个参数,为%rsi+16,(%rsp+8)是第8个参数,为%rsi+20。
可以归纳出read_six_numbers接收2个参数,其中第1个参数为输入字符串的首地址,第2个参数为6元素的int型数组的首地址,该函数从输入字符串中读取6个数字,并且将这6个数字存入第2个参数所指向的int型数组中。但如果读取的数字不足6个,则会引爆bomb。
然后我们回到phase_2函数中,将phase_2函数逆向,可以得到如下的结果:1
2
3
4
5
6
7
8
9
10
11
12void phase_2(char * input) {
	int a[6];
	read_six_numbers(input, a);
	if (a[0] == 1) explode_bomb();
	int * b = &a[1], * bEnd = &a[6];
	do {
		int val = (*(b-1)) * 2;
		if (*b != val) explode_bomb();
		b++;
	} while (b != bEnd);
	return;
}
从上述的代码可以明显地看出编译器对于定长数组的优化,编译器使用了指针间接引用替代了数组引用。
上述代码依次检查了读入的6个数字,第1个数字必须为1,从第2个数字开始,每一个数字都必须是上一个数字的两倍,否则将会引爆bomb。显然,phase_2的答案为”1 2 4 8 16 32”。
下面我们在GDB中验证得出的结果。我们为explode_bomb、phase_2和read_six_numbers设置断点,在phase_2中输入”1 2 4 8 16 32”,并且在进入read_six_numbers函数后观察%rsi所指向的字符串是否为”%d %d %d %d %d %d”。这里为了方便起见,我们将phase_1的结果写入test.txt并读入。相关命令与结果如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25(gdb) break explode_bomb
Breakpoint 1 at 0x40143a
(gdb) break phase_2
Breakpoint 2 at 0x400efc
(gdb) break read_six_numbers
Breakpoint 3 at 0x40145c
(gdb) run test.txt
Starting program: /home/blackdragon/CSAPPLabs/BombLab/bomb test.txt
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
1 2 4 8 16 32
Breakpoint 2, 0x0000000000400efc in phase_2 ()
(gdb) continue
Continuing.
Breakpoint 3, 0x000000000040145c in read_six_numbers ()
(gdb) stepi 10
0x0000000000401485 in read_six_numbers ()
(gdb) print (char *)($rsi)
$1 = 0x4025c3 "%d %d %d %d %d %d"
(gdb) continue
Continuing.
That's number 2.  Keep going!
阶段3 - phase_3
在bomb-disassemble中观察phase_3的反汇编代码,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
380000000000400f43 <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
  400f56:       b8 00 00 00 00          mov    $0x0,%eax
  400f5b:       e8 90 fc ff ff          callq  400bf0 <__isoc99_sscanf@plt>
  # 从输入的字符串中读取两个数字 并存入局部变量中
  400f60:       83 f8 01                cmp    $0x1,%eax
  400f63:       7f 05                   jg     400f6a <phase_3+0x27>
  400f65:       e8 d0 04 00 00          callq  40143a <explode_bomb> # 若读取成功的数字不足两个 则引爆bomb
  400f6a:       83 7c 24 08 07          cmpl   $0x7,0x8(%rsp)
  400f6f:       77 3c                   ja     400fad <phase_3+0x6a> # 若第一个数字(无符号)大于7 则引爆bomb
  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 <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 # switch语句
  400fbe:       3b 44 24 0c             cmp    0xc(%rsp),%eax
  400fc2:       74 05                   je     400fc9 <phase_3+0x86> # 判断a和b是否相等 不想等则引爆炸弹
  400fc4:       e8 71 04 00 00          callq  40143a <explode_bomb>
  400fc9:       48 83 c4 18             add    $0x18,%rsp
  400fcd:       c3                      retq
推测phase_3的一开始用sscanf从输入字符串中读入了2个int型数字。
然后观察phase_3中的jmpq ×0x402470(,%rax,8)以及下面的mov与jmp指令,基本上可以肯定这是使用跳转表实现的switch语句。
下面在GDB中验证并记录不同%rax的值对应跳转表的不同跳转位置。具体的命令及结果如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33(gdb) break explode_bomb
Breakpoint 1 at 0x40143a
(gdb) break phase_3
Breakpoint 2 at 0x400f43
(gdb) run test.txt
Starting program: /home/blackdragon/CSAPPLabs/BombLab/bomb test.txt
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2.  Keep going!
1 1
Breakpoint 2, 0x0000000000400f43 in phase_3 ()
(gdb) stepi 4
0x0000000000400f56 in phase_3 ()
(gdb) print (char *)($rsi)
$1 = 0x4025cf "%d %d"
(gdb) print /x *(long *)(0x402470+0)
$2 = 0x400f7c
(gdb) print /x *(long *)(0x402470+8)
$3 = 0x400fb9
(gdb) print /x *(long *)(0x402470+16)
$4 = 0x400f83
(gdb) print /x *(long *)(0x402470+24)
$5 = 0x400f8a
(gdb) print /x *(long *)(0x402470+32)
$6 = 0x400f91
(gdb) print /x *(long *)(0x402470+40)
$7 = 0x400f98
(gdb) print /x *(long *)(0x402470+48)
$8 = 0x400f9f
(gdb) print /x *(long *)(0x402470+56)
$9 = 0x400fa6
从GDB运行的结果可以看出,sscanf确实读入了两个int *数字,并且也得到了所有的%rax对应的跳转表的不同跳转位置。我们已经有了足够的信息来进行phase_3的逆向。
将phase_3函数逆向,得到如下的结果:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18void phase_3(char * input) {
	int a, b;
	int val = sscanf(input, "%d %d", &a, &b);
	if (val <= 1) explode_bomb();
	switch (a) {
	case 0: a = 207; break;
	case 1: a = 311; break;
	case 2: a = 707; break;
	case 3: a = 256; break;
	case 4: a = 389; break;
	case 5: a = 206; break;
	case 6: a = 682; break;
	case 7: a = 327; break;
	default: explode_bomb(); a = 0; break;
	}
	if (a != b) explode_bomb();
	return;
}
根据逆向的结果,我们可以得知,本阶段要求输入2个数,且第1个数只能为0到7,且根据第1个数取值的不同,满足条件的第2个数的取值也不同,可能的答案如下:
| 第1个数 | 第2个数 | 
|---|---|
| 0 | 207 | 
| 1 | 311 | 
| 2 | 707 | 
| 3 | 256 | 
| 4 | 389 | 
| 5 | 206 | 
| 6 | 682 | 
| 7 | 327 | 
在GDB中运行bomb并验证结果,结果正确。
阶段4 - phase_4
在bomb-disassemble中观察phase_4的反汇编代码,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23000000000040100c <phase_4>:
  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 <__isoc99_sscanf@plt> # 从输入的字符串中读取两个数字 并存入局部变量中
  401029:       83 f8 02                cmp    $0x2,%eax
  40102c:       75 07                   jne    401035 <phase_4+0x29> # 若数字不为两个 则引爆bomb
  40102e:       83 7c 24 08 0e          cmpl   $0xe,0x8(%rsp)
  401033:       76 05                   jbe    40103a <phase_4+0x2e> # 若第一个数字大于14 则引爆bomb
  401035:       e8 00 04 00 00          callq  40143a <explode_bomb>
  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 <func4> # 调用func4
  40104d:       85 c0                   test   %eax,%eax
  40104f:       75 07                   jne    401058 <phase_4+0x4c>
  401051:       83 7c 24 0c 00          cmpl   $0x0,0xc(%rsp)
  401056:       74 05                   je     40105d <phase_4+0x51> # 当返回值和b均为0时 解除阶段 否则 引爆bomb
  401058:       e8 dd 03 00 00          callq  40143a <explode_bomb>
  40105d:       48 83 c4 18             add    $0x18,%rsp
  401061:       c3  
可以注意到,phase_4的反汇编代码前半部分与phase_3相似,均是从输入的字符串中读入2个数字。
将phase_4逆向,得到如下的结果:1
2
3
4
5
6
7void phase_4(char * input) {
	int a, b;
	int val = sscanf(input, "%d %d", &a, &b);
	if (val != 2 || ((unsigned)a > 14)) explode_bomb();
	val = func4(a, 0, 14);
	if (val != 0 || b != 0) explode_bomb();
	return;
从逆向的结果我们可以看出,phase_4从输入的字符串中读入2个数字a和b,并且a必须小于等于14且大于等于0,然后phase_4调用func4(a, 0, 14),且只有该函数的返回值和b均为0时,该阶段解除。
现在我们将注意转向phase_4调用的func4函数,func4的反汇编代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
280000000000400fce <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
  # 实际上是/2过程,为了保证负数时舍入正确才使用了位移
  400fdf:       8d 0c 30                lea    (%rax,%rsi,1),%ecx
  # %rax加上b
  400fe2:       39 f9                   cmp    %edi,%ecx
  400fe4:       7e 0c                   jle    400ff2 <func4+0x24>
  # if分支
  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
  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
  401007:       48 83 c4 08             add    $0x8,%rsp
  40100b:       c3                      retq   
观察反汇编的func4函数,发现func4函数中调用了func4函数自身,由此可以推测func4函数是一个递归过程。
将func4逆向,得到如下的结果:1
2
3
4
5
6
7
8
9
10int func4(int a, int b, int c) {
	int returnVal = (c - b) / 2;
	int val = returnVal + b;
	if (val == a) return 0;
	if (val < a) {
		return 2 * func4(a, val + 1, c) + 1;
	} else {
		return 2 * func4(a, b, val - 1);
	}
}
观察func4的逆向结果,我们可以发现func4非常类似于二分查找的过程。要另func4的返回值为0,则一定不能让func4执行val<a的分支即必须一直保证(b+c)/2 >= a。考虑到0<=a<=14,所以满足条件的a有a=7 a=3 a=1 a=0 共4个。
在GDB中运行bomb并验证结果,结果正确。
阶段5 - phase_5
在bomb-disassemble中观察phase_5的反汇编代码,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
500000000000401062 <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
  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 <string_length>
  # 计算输入字符串长度
  40107f:       83 f8 06                cmp    $0x6,%eax
  401082:       74 4e                   je     4010d2 <phase_5+0x70>
  # 若长度不为6 则引爆bomb
  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
  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)
  实际上是a[i] = 0x4024b0[input[i] & 0xf]
  4010a4:       48 83 c0 01             add    $0x1,%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)
  # 为构造的字符串尾部加上'\0'
  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 <strings_not_equal>
  # 调用strings_not_equal函数
  4010c2:       85 c0                   test   %eax,%eax
  4010c4:       74 13                   je     4010d9 <phase_5+0x77>
  # 若返回值不为0 则引爆bomb
  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
  4010d7:       eb b2                   jmp    40108b <phase_5+0x29>
  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 <phase_5+0x8c>
  4010e9:       e8 42 fa ff ff          callq  400b30 <__stack_chk_fai
l@plt>
  4010ee:       48 83 c4 20             add    $0x20,%rsp
  4010f2:       5b                      pop    %rbx
  4010f3:       c3                      retq   
首先,我们注意到phase_5采用了栈破坏检测机制,并且设置了相应的哨兵值防止栈缓冲区溢出。
然后我们将phase_5逆向,得到如下的结果:1
2
3
4
5
6
7
8
9void phase_5(char * input) {
	char a[7];
	if (strlen(input) != 6) explode_bomb();
	for (int i = 0 ; i < 6 ; i++)
		a[i] = 0x4024b0[input[i] & 0xf];
	a[6] = '\0';
	int val = strings_not_equal(a, 0x40245e);
	if (val != 0) explode_bomb();
	return;
在GDB中打印0x4024b0和0x40245e所对应的字符串,命令和结果如下所示:1
2
3
4(gdb) print (char *)0x4024b0
$1 = 0x4024b0 <array> "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
(gdb) print (char *)0x40245e
$2 = 0x40245e "flyers"
可以看出,phase_5实际上是phase_1的提高。
phase_5要求输入一个6个字符的字符串,通过将取出输入字符串中每个字符的后四位当做偏移地址,以该偏移地址取出地址为0x4024b0处的字符数组中对应的字符,并依次保存在临时数组a中构造一个新的字符串。
再将这个新的字符串与0x40245e处的字符串相比较,如果两个字符串相等,则该阶段解除。
0x40245e处的字符串为”flyers”,其每个字符在0x4024b0处的字符数组”maduiersnfotvbyl”中的对应的偏移量为9,15,14,5,6,7。因此只要输入的字符串的后四位换算成10进制分别为9,15,14,5,6,7即可。
本phase的答案不唯一。一个可能的答案为”yo~uvw”,在GDB中运行bomb并验证结果,结果正确。
阶段6 - phase_6
本phase是所有phase中最难的一个,但是仍然需要一步一步的分解反汇编代码,并最终得到正确的字符串。
在bomb-disassemble中观察phase_6的反汇编代码,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
9300000000004010f4 <phase_6>:
  4010f4:       41 56                   push   %r14
  4010f6:       41 55                   push   %r13
  4010f8:       41 54                   push   %r12
  4010fa:       55                      push   %rbp
  4010fb:       53                      push   %rbx
  4010fc:       48 83 ec 50             sub    $0x50,%rsp
  401100:       49 89 e5                mov    %rsp,%r13
  401103:       48 89 e6                mov    %rsp,%rsi
  401106:       e8 51 03 00 00          callq  40145c <read_six_numbers>
  # 读入6个int型数字
  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 <phase_6+0x34>
  401123:       e8 12 03 00 00          callq  40143a <explode_bomb>
  # 判断数字必须小于等于6
  401128:       41 83 c4 01             add    $0x1,%r12d
  40112c:       41 83 fc 06             cmp    $0x6,%r12d
  401130:       74 21                   je     401153 <phase_6+0x5f>
  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 <phase_6+0x51>
  401140:       e8 f5 02 00 00          callq  40143a <explode_bomb>
  401145:       83 c3 01                add    $0x1,%ebx
  401148:       83 fb 05                cmp    $0x5,%ebx
  40114b:       7e e8                   jle    401135 <phase_6+0x41>
  40114d:       49 83 c5 04             add    $0x4,%r13
  401151:       eb c1                   jmp    401114 <phase_6+0x20>
  # 判断这6个数字两两不重复
  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 <phase_6+0x6c>
  # 分别用7减去这6个数
  40116f:       be 00 00 00 00          mov    $0x0,%esi
  401174:       eb 21                   jmp    401197 <phase_6+0xa3>
  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 <phase_6+0x82>
  401181:       eb 05                   jmp    401188 <phase_6+0x94>
  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 <phase_6+0xb7>
  401197:       8b 0c 34                mov    (%rsp,%rsi,1),%ecx
  40119a:       83 f9 01                cmp    $0x1,%ecx
  40119d:       7e e4                   jle    401183 <phase_6+0x8f>
  40119f:       b8 01 00 00 00          mov    $0x1,%eax
  4011a4:       ba d0 32 60 00          mov    $0x6032d0,%edx
  4011a9:       eb cb                   jmp    401176 <phase_6+0x82>
  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 <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)
  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 <phase_6+0xfa>
  4011e9:       e8 4c 02 00 00          callq  40143a <explode_bomb>
  4011ee:       48 8b 5b 08             mov    0x8(%rbx),%rbx
  4011f2:       83 ed 01                sub    $0x1,%ebp
  4011f5:       75 e8                   jne    4011df <phase_6+0xeb>
  # 主要是重排链表 并且判断重排后的链表递减 否则引爆bomb
  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的逆向比较复杂,这里只大致介绍该函数的作用——
- 该函数在栈上分配了80个字节,包括了一个有6个int型数字的数组a和一个有6个指针的数组b。
- 同phase_2一样,该函数调用了read_six_numbers从输入的字符串中读取了6个数字,并且保存到数组a中。
- 判断这6个数字是否都小于等于6并且互相不重复,如果不满足则引爆炸弹。
- 分别用7减去这6个数,并且用计算结果覆盖这6个数。既a[i] = 7 - a[i]。
- phase_6中引用了来自0x6032d0的结构体数组,该结构体共16个字节,其中前8个字节存放了一个int型的数字(结构体对齐),后8个字节存放了一个指针,该指针在指向结构体数组中的下一个元素。这实际上是一个有6个元素的单向链表。
- 程序从i=1开始,将结构体数组中的第a[i]个元素的首地址存放进b[i]中,循环6次,直到i=6。
- 程序从b[1]开始,将b[1]对应的元素的后8个字节对应的地址指向b[2],直到b[5]->next = b[6],将b[6]所对应的元素的后8个字节对应的地址指向0。
- 然后从b[1]所对应的节点开始,依次判断整个链表是否降序排列,如果不满足降序,则引爆bomb。
phase_6是所有phases中最难的一个,其本质是输入1组数字从而实现对单向链表的重排,使得重排后的单向链表降序。
原链表的情况如下:
| 节点 | 1 | 2 | 3 | 4 | 5 | 6 | 
|---|---|---|---|---|---|---|
| 值 | 322 | 168 | 924 | 691 | 477 | 443 | 
因此使得链表降序的输入为:”3 4 5 6 1 2”
注意这是用7减过之后的数字序列,原输入序列也就是答案应为:”4 3 2 1 6 5”
隐藏阶段 - secret_phase
观察bomb-symboltable和bomb-disassemble我们都能发现一个叫做secret_phase的函数,这就是本次实验中被隐藏起来的phase。
经过观察反汇编代码,我们发现call secret_phase位于phase_defused函数中。
在bomb-disassemble中观察phase_defused的反汇编代码,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
3900000000004015c4 <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>
  # 输入过的字符串数目必须为6 意味着必须是phase_6之后
  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
  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
  401602:       75 31                   jne    401635 <phase_defused+0x71>
  401604:       be 22 26 40 00          mov    $0x402622,%esi
  401609:       48 8d 7c 24 10          lea    0x10(%rsp),%rdi
  40160e:       e8 25 fd ff ff          callq  401338 <strings_not_equal>
  # 第4个输入字符串读入两个数字和一个字符串 且字符串必须与0x402622处的字符串相等
  401613:       85 c0                   test   %eax,%eax
  401615:       75 1e                   jne    401635 <phase_defused+0x71>
  401617:       bf f8 24 40 00          mov    $0x4024f8,%edi
  40161c:       e8 ef f4 ff ff          callq  400b10 <puts@plt>
  401621:       bf 20 25 40 00          mov    $0x402520,%edi
  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
  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
  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   
当且仅当phase_6解除并且从phase_4的输入字符串依次读取两个数字和一个字符串且这个字符串为”DrEvil”时,可以进入。
在bomb-disassemble中观察secret_phase的反汇编代码,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
250000000000401242 <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>
  # 调用strtol函数
  40125a:       48 89 c3                mov    %rax,%rbx
  40125d:       8d 40 ff                lea    -0x1(%rax),%eax
  401260:       3d e8 03 00 00          cmp    $0x3e8,%eax
  401265:       76 05                   jbe    40126c <secret_phase+0x2a>
  401267:       e8 ce 01 00 00          callq  40143a <explode_bomb> # 如果数字减1之后大于1000 则引爆bomb
  40126c:       89 de                   mov    %ebx,%esi
  40126e:       bf f0 30 60 00          mov    $0x6030f0,%edi
  401273:       e8 8c ff ff ff          callq  401204 <fun7>
  # 调用fun7
  401278:       83 f8 02                cmp    $0x2,%eax
  40127b:       74 05                   je     401282 <secret_phase+0x40>
  40127d:       e8 b8 01 00 00          callq  40143a <explode_bomb> # 如果fun7返回值不为2 则引爆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   
根据反汇编的结果,我们可以得知,secret_phase同样需要读入一个字符串,之后其首先调用strtol(input, NULL, 10)将输入的字符串转换成long型数字a。
如果该数字>1001,则引爆bomb,否则则调用fun7(0x6030f0, a)并判断其返回结果,当且仅当结果为2时,解除secret_phase。
在bomb-disassemble中观察fun7的反汇编代码,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
220000000000401204 <fun7>:
  401204:       48 83 ec 08             sub    $0x8,%rsp
  401208:       48 85 ff                test   %rdi,%rdi
  40120b:       74 2b                   je     401238 <fun7+0x34>
  40120d:       8b 17                   mov    (%rdi),%edx
  40120f:       39 f2                   cmp    %esi,%edx
  401211:       7e 0d                   jle    401220 <fun7+0x1c>
  401213:       48 8b 7f 08             mov    0x8(%rdi),%rdi
  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
  401225:       39 f2                   cmp    %esi,%edx
  401227:       74 14                   je     40123d <fun7+0x39>
  401229:       48 8b 7f 10             mov    0x10(%rdi),%rdi
  40122d:       e8 d2 ff ff ff          callq  401204 <fun7>
  401232:       8d 44 00 01             lea    0x1(%rax,%rax,1),%eax
  401236:       eb 05                   jmp    40123d <fun7+0x39>
  401238:       b8 ff ff ff ff          mov    $0xffffffff,%eax
  40123d:       48 83 c4 08             add    $0x8,%rsp
  401241:       c3                      retq   
  # 同样是递归的二分查找过程 数据结构是BST二叉搜索树
将fun7逆向,结果如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17struct tree {
	int val;
	struct tree * left;
	struct tree * right;
};
int fun7(struct tree * ptr, int num) {
	if (ptr == NULL) return -1;
	if (ptr->val == num) return 0;
	if (ptr->val < num) {
		ptr = ptr->right;
		return 2 * fun7(ptr, num) + 1;
	} else {
		ptr = ptr->left;
		return 2 * fun7(ptr, num);
	}
}		 
其中,ptr所指向的struct tree是一棵二叉搜索树的根节点。对于二叉搜索树的任意一个节点,节点左边孩子的值全部小于该节点的值,节点右边孩子的值全部大于该节点的值。
这棵根节点位于0x6030f0的二叉搜索树的结构如下图所示:

要令fun7返回的结果为2,一种可行的递归返回情况是0->(20+1)=1->(12)=2。在该情况下,递归深度为2,节点的移动路线为左->右,并且在值为22的节点返回0。这表明,fun7的第二个参数,也就是secret_phase的正确答案,为”22”。
在GDB中运行bomb并验证结果,结果正确。
实验答案
注意,有部分答案不唯一,并且Bomb Lab每年都在更新,所以答案可能会有所变化。
| 1 | Border relations with Canada have never been better. | 
