深入理解计算机系统CMU-15213 CSAPP(三):BombLab暨GDB调试
拆弹实验是csapp中比较巧妙的一个实验设计之一,在这个实验中我们会阅读一系列Linux64代码和掌握软件调试的基本工具gdb以及方法,以达成实验目的,总而言之在这个实验中,你的两方面能力会得到基本的培养:
阅读x64_86汇编代码的能力,了解代码意图是解题的前提;
使用gdb的能力,帮助验证你的猜想和获取信息;
在进入BombLab前我们会记录一些必须的x86_64汇编基础和gdb指令基础。
x86_64 Linux寄存器
x86_64架构是比较常用的64位Linux系统指令集,大部分Linux系统的x86_64架构遵循System V ABI规范,其提供了16个64位的通用寄存器,其用处如下:
rax:函数返回值;
rdi、rsi、rdx、rcx、
r8、r9:一般用作六个函数参数寄存器,对应1-6个参数,若有更多的参数会保存于函数栈中;rsp、rbp:栈指针(指向栈顶)和栈帧基址指针,栈是从高地址向低地址延申;
其余通用寄存器:
r10-r15、rbx(常作基址),用作临时变量存储等;
其中rbx、rbp及r12-r15共六个寄存器是Callee寄存器,由被调用者(Callee)负责保存和恢复,其余寄存器都是Caller寄存器。
在汇编中,还会看到edi、esi等寄存器,并不是新的通用寄存器,而是指rdi、rsi寄存器的低32位,di、si等则指向其低16位,dih、dil则指向其低16位的高八位和低八位,具体如下:

gdb基础
区分x(examine)命令和p(print)命令
查看内存x命令和内容打印p命令:x命令和p命令都能够打印变量值、寄存器值等,但是其并不是等效的:
x命令的对象是指针(地址),能够直接查看该地址的值。
p命令的对象是值,直接输出该值。
因为寄存器既可能存储值,也可能存储地址,因此效果不同,例如当eax寄存器存储一个整数值时,使用x就不能正常输出了,因为该整数值往往不能被当成地址值访问:
1
2
3
4
5(gdb) x/d $eax #异常
0x7: Cannot access memory at address 0x7
(gdb) p /d $eax #ok
$2 = 7
而当寄存器存储的是地址时,使用p打印的是存储的地址值,x能直接解析出该存储的地址指向位置存储的值是什么:
1
2
3
4
5(gdb) p /d $rax
$5 = 140737488346656
(gdb) x /d $rax
0x7fffffffde20: 1
打印地址
1 | (gdb) p *(int*)0x6032d0 |
lea指令
1 | lea 0x4(%rsi),%rcx |
lea指令是一条地址计算指令,不会触发内存访问,故其意思是将rsi+0x4记录的地址值赋予rcx寄存器。
括号对寄存器解析影响
在做phase_6时,一个括号基本让整个汇编解释垮掉。在高级程序语言中,括号往往只有计算顺序影响,但对寄存器是会影响语义的,这三条语句效果完全不同:
1
2
3mov %edx, %rax #将edx存储的值拷贝到rax
mov %edx, (%rax) #将edx存储的值拷贝到rax存储地址指向的值,rax本身存储的地址不会变化
mov (%rax), %edx #读取rax寄存器存储的值拷贝到edx存储
无符号和带符号比较
一个负数被解析成无符号数往往是很大的,此时要注意大小比较关系:
1
2
3
4
5mov $-1, %eax #eax = -1
cmp $0x5, %eax
jl label #-1小于5,会跳转
jb label #-1不小于5,不会跳转(-1对应最大无符号)1
2
3
4
5
6
7int a = -1;
if(a < 5){
...
}
if((unsigned int)(a) < 5){
...
}
rip寄存器
rip是x64_86架构下的指令寄存器,存储着下一条指令地址,在比较运算符下容易出错:问题是num_input_strings存储地址是什么?
1
24015d8: 83 3d 81 21 20 00 06 cmpl $0x6,0x202181(%rip) # 603760 <num_input_strings>
4015df: 75 5e jne 40163f <phase_defused+0x7b>
在0x4015d8断点,用这样的命令查看是一个误区:
1
2(gdb) x/d $rip+0x202181 #这是错误指令
0x603759: 04015d8指令真正执行前,rip会更新为0X4015df,所以正确的num_input_strings地址应该是:
1
2
3
4
5(gdb) ni #应该先走到rip的下一条指令
0x00000000004015df in phase_defused ()
(gdb) x/d $rip+0x202181
0x603760 <num_input_strings>: 1num_input_strings = 1。
gdb调试初试
CSAPP中给学生的参考资料提及了来自诺曼·马特洛夫(Norman Matloff)的更快、更轻松的调试手册,这里通过简单的素数程序排查让学生快速上手gdb,事实上真正常用的gdb命令是非常有限的,因此在实验前我们重新回顾一下这段来自2002年的程序,无兴趣或具备相关gdb基础的完全可以跳过此段叙述,因为确实价值一般。
一个有问题的素数查找程序
在使用gdb以前你必须读清代码意图:该示例是一种素数查找程序,用于查找[2-UpperBound]范围中所有的质数,其思想是一种迭代方法,例如判断19是否一个质数,要先检查19能否整除19以前的质数,而且根据定理可知你不需要遍历所有19以前的质数,只需要保证不能整除那些小于根号19的那些质数即可,即2和3,但是此前,你必须算出19前的所有质数,因为只有知道所有质数,你才能确定小于根号19的那些质数(例如你怎么知道4是合数还是质数),这被称为埃拉托色尼筛选(Sieve
of Eratosthenes)方法,程序如下: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//main.c
int primeArray[MaxPrimes],UpperBound;
int main(){
printf("enter UpperBound: \n");
scanf("%d",UpperBound);
primeArray[2] = 1;
for(int n=3; n<=UpperBound; n+=2){ //大于3不存在相邻的质数,步长为2
checkPrime();
if(primeArray[n]) //是质数
printf("%d is a prime\n", n);
}
return 0;
}
1 | //checkPrime.c |
1 | //defs.h |
1 | //externs.h |
将文件编译和链接:-c代表仅编译不链接,-g表示保留调试信息,对gdb调试-g是必要选项:
1
2
3gcc -c -g main.c #得到main.o
gcc -c -g checkPrime.c #得到checkPrime.o
gcc -g -o findPrimeTest main.o checkPrime.o #链接成可执行程序findPrimeTest
尝试运行: 1
2
3
4
5
6./findPrimeTest
#效果:
# enter UpperBound:
# 20
# zsh: segmentation fault (core dumped) ./findPrimeTest
上述示例存在3处错误,如果你对素数相关原理清晰,可能一下子就发现它们,但我们要使用gdb将问题准确定位。
gdb Test
每条命令前都标识了(gdb): 1
2
3
4
5(gdb) gdb findPrimeTest
(gdb) r
#input:
20 1
2
3Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7e0b335 in __vfscanf_internal (s=<optimized out>, format=<optimized out>, argptr=argptr@entry=0x7fffffffde30, mode_flags=mode_flags@entry=2) at vfscanf-internal.c:1895
1895 vfscanf-internal.c: No such file or directory.
查看调用栈: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16(gdb) bt
#0 0x00007ffff7e0b335 in __vfscanf_internal (s=<optimized out>, format=<optimized out>, argptr=argptr@entry=0x7fffffffde30, mode_flags=mode_flags@entry=2) at vfscanf-internal.c:1895
#1 0x00007ffff7e06162 in __isoc99_scanf (format=<optimized out>) at isoc99_scanf.c:30
#2 0x00005555555551ba in main () at main.c:9
(gdb) list main.c:9
# 5 int primeArray[MaxPrimes],UpperBound;
# 6
# 7 int main(){
# 8 printf("enter UpperBound: \n");
# 9 scanf("%d",UpperBound);
# 10
# 11 primeArray[2] = 1;
# 12 for(int n=3; n<=UpperBound; n+=2){
# 13 checkPrime();
可见第一个问题出在第9行,打印变量值: 1
2(gdb) p UpperBound
$1 = 01
2//main.c
scanf("%d", &UpperBound); //加入引号
重新编译和运行: 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(gdb) shell gcc -g -o findPrimeTest main.c checkPrime.c
(gdb) r
enter UpperBound:
20
Program received signal SIGSEGV, Segmentation fault.
0x000055555555526c in checkPrime (k=-9776) at checkPrime.c:9
9 primeArray[k] = 0;
(gdb) bt
#0 0x000055555555526c in checkPrime (k=-9776) at checkPrime.c:9
#1 0x00005555555551d6 in main () at main.c:13
(gdb) list checkPrime.c:9
4 void checkPrime(int k){
5 int j = 2;
6 while(1){
7 if(primeArray[j] == 1){
8 if(k%j==0){
9 primeArray[k] = 0;
10 return;
11 }
12 }
13 j++;
(gdb) p k
$3 = -97761
2//main.c
checkPrime(n);
再来一次: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25enter UpperBound:
20
Program received signal SIGSEGV, Segmentation fault.
0x0000555555555248 in checkPrime (k=3) at checkPrime.c:7
7 if(primeArray[j] == 1){
(gdb) bt
#0 0x0000555555555248 in checkPrime (k=3) at checkPrime.c:7
#1 0x00005555555551db in main () at main.c:13
(gdb) list checkPrime.c:7
# 2 #include "externs.h"
# 3
# 4 void checkPrime(int k){
# 5 int j = 2;
# 6 while(1){
# 7 if(primeArray[j] == 1){
# 8 if(k%j==0){
# 9 primeArray[k] = 0;
# 10 return;
# 11 }
(gdb) p j
# $4 = 347921
2
3
4//checkPrime.c
while(j*j <= k){
...
}
于是完成了: 1
2
3
4
5
6
7
8
9
10
11
12
13
14(gdb) shell gcc -g -o findPrimeTest main.c checkPrime.c
(gdb) r
enter UpperBound:
20
3 is a prime
5 is a prime
7 is a prime
11 is a prime
13 is a prime
17 is a prime
19 is a prime
[Inferior 1 (process 394872) exited normall]
BombLab
进入bomb文件夹查看bomb.c代码: 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
93
/*
* 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 <file>, the bomb reads from <file>
* until EOF, and then switches to standard input. Thus, as you
* defuse each phase, you can add its defusing string to <file> 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 [<input_file>]\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;
}
设计者没有提供这六个函数源码,包括屏蔽了gdb查看源码的功能,因此这里只能反汇编,使用objdump工具:
1 | objdump -d bomb >bomb.S |
初始化代码印证了C代码的入口,即定向输入流、读取输入、初始化炸弹:
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
31400da0: 53 push %rbx
400da1: 83 ff 01 cmp $0x1,%edi #argc == 1 ?
400da4: 75 10 jne 400db6 <main+0x16> #不等时跳转至#1
400da6: 48 8b 05 9b 29 20 00 mov 0x20299b(%rip),%rax # 603748 <stdin@@GLIBC_2.2.5>
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 #1:rbx = rsi = argv[0]
400db9: 83 ff 02 cmp $0x2,%edi #argc == 2 ?
400dbc: 75 3a jne 400df8 <main+0x58> # 不等时跳转至#2
400dbe: 48 8b 7e 08 mov 0x8(%rsi),%rdi #rdi = rsi+8 = argv[1]
400dc2: be b4 22 40 00 mov $0x4022b4,%esi #字符串"r"
400dc7: e8 44 fe ff ff callq 400c10 <fopen@plt> #fopen
400dcc: 48 89 05 95 29 20 00 mov %rax,0x202995(%rip) # 603768 <infile>,infile = fopen(...)
400dd3: 48 85 c0 test %rax,%rax #若infile等于0,ZF = 1
400dd6: 75 41 jne 400e19 <main+0x79> #infile != 0, 跳入正常入口函数(initialize_bomb)
400dd8: 48 8b 4b 08 mov 0x8(%rbx),%rcx # rcx = argv[1]
400ddc: 48 8b 13 mov (%rbx),%rdx # rdx = argv[0]
400ddf: be b6 22 40 00 mov $0x4022b6,%esi #printf字符串类型
400de4: bf 01 00 00 00 mov $0x1,%edi #printf合法性检查启用
400de9: e8 12 fe ff ff callq 400c00 <__printf_chk@plt> #printf
400dee: bf 08 00 00 00 mov $0x8,%edi
400df3: e8 28 fe ff ff callq 400c20 <exit@plt> #exit(8)
400df8: 48 8b 16 mov (%rsi),%rdx #2 :rsi(argv[0])-> rdx
400dfb: be d3 22 40 00 mov $0x4022d3,%esi #printf字符串类型
400e00: bf 01 00 00 00 mov $0x1,%edi #printf合法性检查启用
400e05: b8 00 00 00 00 mov $0x0,%eax #打印0个浮点数
400e0a: e8 f1 fd ff ff callq 400c00 <__printf_chk@plt> #调用printf
400e0f: bf 08 00 00 00 mov $0x8,%edi #edi = 8
400e14: e8 07 fe ff ff callq 400c20 <exit@plt> #调用exit(8)
First:phase_1
从C源码和汇编都可以看到,将read_line的返回值rax放到了phase_1第一个参数地址(rdi):
1
2
3400e32: 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>
phase_1将0x402400位置的字符串放在第二个参数esi位置(rsi的低32位),然后test返回值是否为0,若为0(je)则字符串相等,跳过炸弹函数explode_bomb,故第一个字符串答案来自0x402400。
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>
400eee: 85 c0 test %eax,%eax
400ef0: 74 05 je 400ef7 <phase_1+0x17>
400ef2: e8 43 05 00 00 callq 40143a <explode_bomb>
400ef7: 48 83 c4 08 add $0x8,%rsp
400efb: c3 retq 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22gdb bomb #开始调试
(gdb) b explode_bomb #防止意外爆炸
Breakpoint 1 at 0x40143a
(gdb) b phase_1
Breakpoint 2 at 0x400ee0
(gdb) b *0x400ee4
Breakpoint 3 at 0x400ee4
(gdb) r
Starting program: /home/linux/Desktop/CMU_csapp/csapplab/bomblab/bomb/bomb
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
iamironman #先随便一个输入
Breakpoint 2, 0x0000000000400ee0 in phase_1 ()
(gdb) c
Continuing.
Breakpoint 3, 0x0000000000400ee4 in phase_1 ()
(gdb) p (char*)0x402400
$1 = 0x402400 "Border relations with Canada have never been better."Border relations with Canada have never been better.。
Second:phase_2
第二个phase_2,此处可不止一个炸弹,入口程序将读取到的参数传入phase_2:
1
2
3
4
5400e4e: 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
查看phase_2,将rsp放到第二个参数就调用了read_six_numbers函数,将栈指针放到第二个参数似乎有点奇怪,这是传入数组指针典型的代码,而第一个参数是readline返回的字符流,所以对应的C语言大概是类似void
read_six_numbers(str, int num*): 1
2
3
4
5
6
70000000000400efc <phase_2>:
400efc: 55 push %rbp #保存Callee寄存器
400efd: 53 push %rbx
400efe: 48 83 ec 28 sub $0x28,%rsp #开辟40字节函数栈,如定义了int num[10];
400f02: 48 89 e6 mov %rsp,%rsi
400f05: e8 52 05 00 00 callq 40145c <read_six_numbers>
......
从read_six_numbers汇编可以看到,read_six_numbers仅使用了其中6个参数,即将num[0]-num[5]设置为scanf函数的参数,scanf会将str中包含的数字分别填充到六个参数中,其中num[0]-num[3]四个是寄存器存储,而num[4]-num[5]存储在函数栈上,如果提供scanf函数的字符串解析出的int小于6个,炸弹爆炸,所以此处的答案之一是必须传入6个及以上的int类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18000000000040145c <read_six_numbers>:
40145c: 48 83 ec 18 sub $0x18,%rsp
401460: 48 89 f2 mov %rsi,%rdx # rdx = num[0]
401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx # rcx = num[1]
401467: 48 8d 46 14 lea 0x14(%rsi),%rax
40146b: 48 89 44 24 08 mov %rax,0x8(%rsp) # rsp+0x8 = num[5]
401470: 48 8d 46 10 lea 0x10(%rsi),%rax
401474: 48 89 04 24 mov %rax,(%rsp) # rsp = num[4]
401478: 4c 8d 4e 0c lea 0xc(%rsi),%r9 #r9 = num[3]
40147c: 4c 8d 46 08 lea 0x8(%rsi),%r8 #r8 = num[2]
401480: be c3 25 40 00 mov $0x4025c3,%esi #scanf函数设置
401485: b8 00 00 00 00 mov $0x0,%eax
40148a: e8 61 f7 ff ff callq 400bf0 <__isoc99_sscanf@plt>
40148f: 83 f8 05 cmp $0x5,%eax #scanf结果 >5 个?
401492: 7f 05 jg 401499 <read_six_numbers+0x3d> #大于则跳过爆炸函数
401494: e8 a1 ff ff ff callq 40143a <explode_bomb>
401499: 48 83 c4 18 add $0x18,%rsp #返回函数帧
40149d: c3 retq
查看完整的phase_2,可见这六个值是有大小关系的:其一是num[0]必须为1,而且num[1]-num[5],后面元素必须是前一个元素的两倍。当循环来到6,跳出解析,所以尽管传入大于6数量的int,也不会导致炸弹爆炸,因此答案是至少输入1 2 4 8 16 32,
后面任何输入都不会导致爆炸。 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
260000000000400efc <phase_2>:
400efc: 55 push %rbp #保存Callee寄存器
400efd: 53 push %rbx
400efe: 48 83 ec 28 sub $0x28,%rsp #开辟40字节函数栈
400f02: 48 89 e6 mov %rsp,%rsi
400f05: e8 52 05 00 00 callq 40145c <read_six_numbers>
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) #long值(低32位)比较,rsp = num[0] = 1 ?
400f0e: 74 20 je 400f30 <phase_2+0x34> #等于则跳过爆炸函数
400f10: e8 25 05 00 00 callq 40143a <explode_bomb>
400f15: eb 19 jmp 400f30 <phase_2+0x34>
400f17: 8b 43 fc mov -0x4(%rbx),%eax #eax = num[0]
400f1a: 01 c0 add %eax,%eax #eax = 2*num[0]
400f1c: 39 03 cmp %eax,(%rbx) #num[1] == 2*num[0] ?
400f1e: 74 05 je 400f25 <phase_2+0x29> #相等则跳过炸弹函数
400f20: e8 15 05 00 00 callq 40143a <explode_bomb>
400f25: 48 83 c3 04 add $0x4,%rbx # rbx = num[2]
400f29: 48 39 eb cmp %rbp,%rbx # num[6] != num[2]
400f2c: 75 e9 jne 400f17 <phase_2+0x1b> # 不等则继续,要求num[2] == 2*num[1]......直到num[5]==2*num[4]
400f2e: eb 0c jmp 400f3c <phase_2+0x40>
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx #rbx = num[1]
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp #rbq = num[5] + 0x04,预先开辟了40字节,不会越界
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
Third:phase_3
为了方便后面phase的gdb调试,可以先把前面的答案写在myans文件中:
来到phase_3,仅读代码可知需要输入2个以上的数字参数: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24linux@ubuntu:~/Desktop/CMU_csapp/csapplab/bomblab/bomb$ gdb --args ./bomb myans
......
Reading symbols from ./bomb...done.
(gdb) b phase_3
Breakpoint 1 at 0x400f43
(gdb) r
Starting program: /home/linux/Desktop/CMU_csapp/csapplab/bomblab/bomb/bomb myans
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 2 3 4 5 6 #随便一个输入
Breakpoint 1, 0x0000000000400f43 in phase_3 ()
(gdb) b *0x400f51
Breakpoint 2 at 0x400f51
(gdb) c
Continuing.
Breakpoint 2, 0x0000000000400f51 in phase_3 ()
(gdb) p (char*)0x4025cf
$1 = 0x4025cf "%d %d" #可见需要输入两个数字0x402470+(rax*8),第二个参数必须等于这个指令位置赋的值,使用gdb全部打出来即可:
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(gdb) p /x 0x402470
$3 = 0x402470
(gdb) x /x 0x402470
0x402470: 0x00400f7c
(gdb) p/d 0xcf
$4 = 207 #对应第一个参数 = 0
(gdb) x /x 0x402478
0x402478: 0x00400fb9
(gdb) p/d 0x137
$5 = 311 #对应第一个参数 = 1
(gdb) x /x 0x402480
0x402480: 0x00400f83
(gdb) p/d 0x2c3
$6 = 707 #对应第一个参数 = 2
(gdb) x /x 0x402488
0x402488: 0x00400f8a
(gdb) p/d 0x100
$7 = 256 #对应第一个参数 = 3
(gdb) x /x 0x402490
0x402490: 0x00400f91
(gdb) p/d 0x185
$8 = 389 #对应第一个参数 = 4
(gdb) x /x 0x402498
0x402498: 0x00400f98
(gdb) p/d 0xce
$9 = 206 #对应第一个参数 = 5
(gdb) x /x 0x4024a0
0x4024a0: 0x00400f9f
(gdb) p/d 0x2aa
$10 = 682 #对应第一个参数 = 6
(gdb) x /x 0x4024a8
0x4024a8: 0x00400fa6 #对应第一个参数 = 7
(gdb) p/d 0x147
$11 = 3271
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
370000000000400f43 <phase_3>:
400f43: 48 83 ec 18 sub $0x18,%rsp
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx #rcx = rsp + 0xc
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx #rdx = rsp + 0x8
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 #scanf参数个数大于1跳过explode_bomb
400f63: 7f 05 jg 400f6a <phase_3+0x27>
400f65: e8 d0 04 00 00 callq 40143a <explode_bomb>
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp) #scanf第一个参数小于等于7跳过explode_bomb
400f6f: 77 3c ja 400fad <phase_3+0x6a>
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax #eax = num[0]
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8) #跳转到 0x402470+(rax*8)储存的地址值
400f7c: b8 cf 00 00 00 mov $0xcf,%eax # 207
400f81: eb 3b jmp 400fbe <phase_3+0x7b>
400f83: b8 c3 02 00 00 mov $0x2c3,%eax # 707
400f88: eb 34 jmp 400fbe <phase_3+0x7b>
400f8a: b8 00 01 00 00 mov $0x100,%eax # 256
400f8f: eb 2d jmp 400fbe <phase_3+0x7b>
400f91: b8 85 01 00 00 mov $0x185,%eax # 389
400f96: eb 26 jmp 400fbe <phase_3+0x7b>
400f98: b8 ce 00 00 00 mov $0xce,%eax # 206
400f9d: eb 1f jmp 400fbe <phase_3+0x7b>
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax # 682
400fa4: eb 18 jmp 400fbe <phase_3+0x7b>
400fa6: b8 47 01 00 00 mov $0x147,%eax # 327
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 # 311
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax #eax == num[1] ?
400fc2: 74 05 je 400fc9 <phase_3+0x86> #等于则正常返回
400fc4: e8 71 04 00 00 callq 40143a <explode_bomb>
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 retq
Fourth:phase_4
在func4前从phase_4首先可以获取到两条信息:
phase_4接收两个参数的输入,其中第一个参数num[0]应小于等于0xe,第二个参数num[1]应该为0;
func4只使用了num[0], num[0]需满足让func_4返回0;
满足此二条件,得以跳过所有的explode_bomb,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> #非两个参数即爆炸
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) #num[0]需 <= 0xe
401033: 76 05 jbe 40103a <phase_4+0x2e>
401035: e8 00 04 00 00 callq 40143a <explode_bomb>
40103a: ba 0e 00 00 00 mov $0xe,%edx # edx = 0xe,func4第三个参数
40103f: be 00 00 00 00 mov $0x0,%esi # esi = 0,func4第二个参数
401044: 8b 7c 24 08 mov 0x8(%rsp),%edi # edi = num[0],func4第一个参数
401048: e8 81 ff ff ff callq 400fce <func4>
40104d: 85 c0 test %eax,%eax
40104f: 75 07 jne 401058 <phase_4+0x4c> #func4返回不是0,跳转爆炸
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp)
401056: 74 05 je 40105d <phase_4+0x51> #num[1]需为0,否则爆炸
401058: e8 dd 03 00 00 callq 40143a <explode_bomb>
40105d: 48 83 c4 18 add $0x18,%rsp
401061: c3 retq
现在只需要看一下func4,可见只需要通过gdb算出ecx =
rax+rsi*1存储的地址值指向的值是什么即可: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19(gdb) b *0x400fe2
Breakpoint 2 at 0x400fe2
(gdb) c
Continuing.
Breakpoint 2, 0x0000000000400fe2 in func4 ()
(gdb) p/d $ecx
$1 = 7
(gdb) c
Continuing.
Breakpoint 2, 0x0000000000400fe2 in func4 ()
(gdb) p/d $ecx
$2 = 3
Breakpoint 2, 0x0000000000400fe2 in func4 ()
(gdb) p/d $ecx
$3 = 17 0或3 0或1 0。
func4注释: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
230000000000400fce <func4>:
400fce: 48 83 ec 08 sub $0x8,%rsp
400fd2: 89 d0 mov %edx,%eax
400fd4: 29 f0 sub %esi,%eax # eax = 参数3 - 参数2 = 14
400fd6: 89 c1 mov %eax,%ecx # ecx = eax = 14
400fd8: c1 e9 1f shr $0x1f,%ecx # 第一次逻辑右移,变为0(而不是7)
400fdb: 01 c8 add %ecx,%eax # eax = eax + ecx = 14
400fdd: d1 f8 sar %eax # eax = eax/2 = 7
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx # ecx = rax+rsi*1处存储的地址值存储的值
400fe2: 39 f9 cmp %edi,%ecx # 第一次ecx = 7,第二次ecx = 3, 第三次ecx = 1
400fe4: 7e 0c jle 400ff2 <func4+0x24> # ecx <= edi时跳转,edi为第一个输入参数
400fe6: 8d 51 ff lea -0x1(%rcx),%edx # 否则edx = rcx-0x1 处存储的值
400fe9: e8 e0 ff ff ff callq 400fce <func4> # 调用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 # ecx >= edi,返回0退出
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
Fifth:phase_5
第五段代码其实不难看懂,但是一个地方误导性很大,具体来看看爆炸条件:
1
2
3
4
5
64010b3: be 5e 24 40 00 mov $0x40245e,%esi #第二个参数字符串来自0x40245e
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi #第一个参数字符串来自0x10(%rsp)
4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal>
4010c2: 85 c0 test %eax,%eax #字符串不等,爆炸
4010c4: 74 13 je 4010d9 <phase_5+0x77>
4010c6: e8 6f 03 00 00 callq 40143a <explode_bomb>1
2
3
4
5
6
7
8(gdb) b *0x4010b3
Breakpoint 3 at 0x4010b3
(gdb) c
Continuing.
Breakpoint 3, 0x00000000004010b3 in phase_5 ()
(gdb) p (char*)0x40245e
$1 = 0x40245e "flyers"
还是得脚踏实地分析一下: 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
410000000000401062 <phase_5>:
401062: 53 push %rbx
401063: 48 83 ec 20 sub $0x20,%rsp
401067: 48 89 fb mov %rdi,%rbx # rbx = rdi = readline输入字符流
40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax # rax = fs段寄存器偏移0x28存储的值,仅内存备份,与题目无关
401071: 00 00
401073: 48 89 44 24 18 mov %rax,0x18(%rsp)
401078: 31 c0 xor %eax,%eax #自身异或,eax = 0
40107a: e8 9c 02 00 00 callq 40131b <string_length>
40107f: 83 f8 06 cmp $0x6,%eax #字符串长度需等于6,否则爆炸
401082: 74 4e je 4010d2 <phase_5+0x70>
401084: e8 b1 03 00 00 callq 40143a <explode_bomb>
401089: eb 47 jmp 4010d2 <phase_5+0x70>
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx # ecx = (rbx + rax*1)地址的储值,输入的6个字符
40108f: 88 0c 24 mov %cl,(%rsp) # rsp = cl(rcx的低八位) ,取6个输入的低八位
401092: 48 8b 14 24 mov (%rsp),%rdx # rdx = rsp
401096: 83 e2 0f and $0xf,%edx # edx = edx低四位,6个输入的低四位
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx # edx = (0x4024b0+rdx)的储值
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) # (rsp + rax*1)+0x10 = dl,可见这是逐一赋值到0x10(%rsp)
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”,刚好6个字节
4010b3: be 5e 24 40 00 mov $0x40245e,%esi #第二个参数字符串来自0x40245e
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi #第一个参数字符串来自0x10(%rsp)
4010bd: e8 76 02 00 00 callq 401338 <strings_not_equal>
4010c2: 85 c0 test %eax,%eax #字符串不等,爆炸
4010c4: 74 13 je 4010d9 <phase_5+0x77>
4010c6: e8 6f 03 00 00 callq 40143a <explode_bomb>
4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) # 字节对齐
4010d0: eb 07 jmp 4010d9 <phase_5+0x77>
4010d2: b8 00 00 00 00 mov $0x0,%eax
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_fail@plt>
4010ee: 48 83 c4 20 add $0x20,%rsp
4010f2: 5b pop %rbx
4010f3: c3 retq1
2
3
4
5
6
7
8
9
10
11
12
13
14
15gdb --args ./bomb myans
(gdb) r
Starting program: /home/linux/Desktop/CMU_csapp/csapplab/bomblab/bomb/bomb myans
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!
Halfway there!
So you got that one. Try this one.
iamman #随便输入一个6字符
Breakpoint 1, 0x0000000000401099 in phase_5 ()
(gdb) p (char*)0x4024b0
$1 = 0x4024b0 <array> "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"man ascii命令恰好提供了ASCII码的低四位值:
在这六行中,按索引顺序每行任选一个组成字符串即可,为了方便此处均选取小写字母作为答案:ionuvw。
Sixth:phase_6
循环体1信息:是一个双层循环,跳出外循环只能让r12d == 6,跳出内循环只能让ebx大于5,而且ebx依赖于r12d,故这结构类似
1
2
3
4
5for(int i=0; i<6; i++){
for(int j=i+1; j<=5; j++){
...
}
}触发爆炸的两个条件是eax == rbp, 以及eax-1大于5,揣摩一下即可知前者是让6个数字必须互不相等,而且六个输入数字都不能大于6,这个双层循环完全可以推断出来;另一个细节是判断eax-1大于5时使用的是无符号数:
1
240111e: 83 f8 05 cmp $0x5,%eax
401121: 76 05 jbe 401128 <phase_6+0x34> #eax大于5则爆炸,注意此处是无符号比较;如果eax-1是负数,jbe的小于等于条件是不成立的,因为-1对应的无符号数正好是最大的无符号数,所以第三个信息是此处输入的六个数不能含0。
循环体2信息:循环体短短几行代码简直是非汇编圣体的滑铁卢,其中会诞生著名的为什么3+4 = 5、1+4 = 0等违背宇宙常识的结果,问题出在:
edx是一个值,因为(%rax)是带括号的,代表mov效果是将其拷贝到rax存储的地址指向的值,而rax本身存储的地址是不变的,因此add的效果不是在edx值上加4,而是在rax存储的地址上加偏移4,因此问题应该变成找到哪个地址存储的内容是0。1
2mov %edx,(%rax) # rax存储地址指向的值 = edx(rax本身存储的地址不变)
add $0x4,%rax # rax地址 += 4,是存储的地址+=4,而不是指向的值+=4 !!根据gdb,rsp的起始指针
0x7fffffffde20,24个字节(0x7fffffffde20-0x7fffffffde34)对应输入6个数字:因为从循环1条件中可知输入6个数不能为0,因此循环只会在1
2x/w $rsp
0x7fffffffde20: 10x7fffffffde38处(即空栈,num[6]==0)出退出,所以循环体2的作用只是将6个数变成7的补数,例如1,2,3,4,5,6会依次称为6,5,4,3,2,1。循环体3和4信息:这两个循环要在一起看,因为存在可能循环4的循环先运行,再到循环3。循环体3同样是一个工具人,旨在找到第一个大于1的数,如原始输入1,2,3,4,5,6,经循环2输出是6,5,4,3,2,1,那么6就是大于1的,令ecx = 6,这种假设情况不会进入#4所在的循环体3中的循环,而是跳转到循环4,循环4是一个链表结构,具体而言是这样的:为了方便,0x6032d0简记为0x2d0,不带0x的都是十进制整数value,有:
可见这是一段连续内存,0结尾的地址存储了数值数据value,8结尾的地址存储了下一个地址的指针。1
2
3
4
5
6
7
8
9
10
110x2d0 - 332 --value1
0x2d8 - 0x2e0
0x2e0 - 168 --value2
0x2e8 - 0x2f0
0x2f0 - 924 --value3
0x2f8 - 0x300
0x300 - 691 --value4
0x308 - 0x310
0x310 - 477 --value5
0x318 - 0x320
0x320 - 443 --value6若ecx = 6,那么循环4出口的rdx返回的就是链表对应的value6,此时回到#4所在的循环3,这个value6将被赋值到rsp后面的一段内存中,地址计算公式是
(rsp+2*rsi) + 0x20 = rdx,即rsp + 0x20 = value6;总而言之,循环3和4完成的结果是,当输入1,2,3,4,5,6,循环2后变体为6,5,4,3,2,1,(rsp+0x20 - rsp+0x48)依次存放了链表结点的value6 - value1;
循环体5信息:一个循环,rcx + 0x8固定存入rsp+0x48的值;此处我们用
num[0]-num[5]描述rsp - rsp+0x18的六个数字(每个数字占4字节),在循环2前依次存储6个输入数字,在循环2后依次变成7的补数;用array[0]-array[5]形容rsp + 0x20 - rsp + 0x48的六个数字(此处每个数字占8字节);循环体6信息:可见这是一个关于array数组的大小比较,要求array前面的数字必须大于后面的数字,否则爆炸,即array[0] > array[1] > ... > array[6];
综上所述,输入的6个数字是结点的索引,每个结点存储一个整数,输入的数字索引必须满足使得结点存储整数从大到小排列,根据循环体3列出的6个数,可见使得结点存储整数从大到小的排序索引应该是3
4 5 6 1 2,
该结果是循环2求7的补数之后的,所以原始答案输入应该为4 3 2 1 6 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
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
93
94
95
96
97
98
99
100
101
10200000000004010f4 <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 #begin......
401100: 49 89 e5 mov %rsp,%r13 # r13 = rsp
401103: 48 89 e6 mov %rsp,%rsi # rsi = rsp
401106: e8 51 03 00 00 callq 40145c <read_six_numbers>
40110b: 49 89 e6 mov %rsp,%r14 # r14 = rsp
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d # r12d(r12寄存器的低32位)清零
#循环体1
401114: 4c 89 ed mov %r13,%rbp # rbp = r13 = num[0]
401117: 41 8b 45 00 mov 0x0(%r13),%eax # eax = r13 = num[0]
40111b: 83 e8 01 sub $0x1,%eax # eax -= 1
40111e: 83 f8 05 cmp $0x5,%eax
401121: 76 05 jbe 401128 <phase_6+0x34> #eax大于5则爆炸,注意此处是无符号比较;
401123: e8 12 03 00 00 callq 40143a <explode_bomb>
401128: 41 83 c4 01 add $0x1,%r12d # r12d += 1
40112c: 41 83 fc 06 cmp $0x6,%r12d
401130: 74 21 je 401153 <phase_6+0x5f> # r12d == 6则跳转#1
401132: 44 89 e3 mov %r12d,%ebx # 不等时,ebx = r12d
#循环体1之内循环
401135: 48 63 c3 movslq %ebx,%rax # 符号扩展,32 to 64bits,rax = ebx
401138: 8b 04 84 mov (%rsp,%rax,4),%eax # eax = (rsp + rax*4)的储值,可见这是遍历5个数字
40113b: 39 45 00 cmp %eax,0x0(%rbp) # eax == 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 # ebx += 1
401148: 83 fb 05 cmp $0x5,%ebx # ebx <= 5则小循环
40114b: 7e e8 jle 401135 <phase_6+0x41>
40114d: 49 83 c5 04 add $0x4,%r13 # 否则r13 += 4,大循环
401151: eb c1 jmp 401114 <phase_6+0x20>
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi #1:rsi = rsp + 0x18 = num[6] = 0
401158: 4c 89 f0 mov %r14,%rax # rax = r14 = num[0]
40115b: b9 07 00 00 00 mov $0x7,%ecx # ecx = 7
#循环体2
401160: 89 ca mov %ecx,%edx # edx = ecx = 7
401162: 2b 10 sub (%rax),%edx # edx -= rax, 7-num[0]
401164: 89 10 mov %edx,(%rax) # rax存储地址指向的值 = edx(rax本身存储的地址不变)
401166: 48 83 c0 04 add $0x4,%rax # rax地址 += 4,是存储的地址+=4,而不是指向的值+=4 !!
40116a: 48 39 f0 cmp %rsi,%rax # rsi == rax?
40116d: 75 f1 jne 401160 <phase_6+0x6c> # 不等则循环
40116f: be 00 00 00 00 mov $0x0,%esi # esi = 0
401174: eb 21 jmp 401197 <phase_6+0xa3> # 跳转至#2
#循环体4
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx #3:rdx = rdx+0x8
40117a: 83 c0 01 add $0x1,%eax # eax += 1
40117d: 39 c8 cmp %ecx,%eax
40117f: 75 f5 jne 401176 <phase_6+0x82> # eax != ecx == 1 循环
401181: eb 05 jmp 401188 <phase_6+0x94> # 相等则跳转#4
#循环体3
401183: ba d0 32 60 00 mov $0x6032d0,%edx # edx = *0x6032d0 = 332
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) #4:(rsp+2*rsi) + 0x20 = rdx
40118d: 48 83 c6 04 add $0x4,%rsi # rsi += 4
401191: 48 83 fe 18 cmp $0x18,%rsi # rsi == 18?
401195: 74 14 je 4011ab <phase_6+0xb7> # 相等则跳转#5
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx #2: ecx = rsp+rsi储值 = num[0]
40119a: 83 f9 01 cmp $0x1,%ecx
40119d: 7e e4 jle 401183 <phase_6+0x8f> # ecx小于等于1,循环
40119f: b8 01 00 00 00 mov $0x1,%eax # eax = 1
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx # edx = 0x6032d0,存储是地址值
4011a9: eb cb jmp 401176 <phase_6+0x82> # 跳转至#3
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx #5:rbx = rsp + 0x20 = array[0]地址值
4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax # rax = rsp + 28 = array[1]地址值
4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi # rsi = rsp + 50 = 0地址值
4011ba: 48 89 d9 mov %rbx,%rcx # rcx = rbx = array[0]地址值
#循环体5
4011bd: 48 8b 10 mov (%rax),%rdx # rdx = rax, 初值rax = array[1]地址值 = 0x28
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) # rcx+0x8 = rdx, rcx初值array[0]
4011c4: 48 83 c0 08 add $0x8,%rax # rax += 8
4011c8: 48 39 f0 cmp %rsi,%rax # rax == rsi ?0x30 to 0x50, 固定需循环4次
4011cb: 74 05 je 4011d2 <phase_6+0xde> # 相等跳转#6
4011cd: 48 89 d1 mov %rdx,%rcx # rcx = rdx
4011d0: eb eb jmp 4011bd <phase_6+0xc9> # 循环
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) #6: rdx + 0x8 = 0
4011d9: 00
4011da: bd 05 00 00 00 mov $0x5,%ebp #ebq = 5
#循环体6
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax # rax = rbx+0x8 = 初值array[1]
4011e3: 8b 00 mov (%rax),%eax # eax = rax,rax存储的是地址,现在直接转为存储值
4011e5: 39 03 cmp %eax,(%rbx) # eax == rbx? rbx初值为array[0]
4011e7: 7d 05 jge 4011ee <phase_6+0xfa>
4011e9: e8 4c 02 00 00 callq 40143a <explode_bomb> # rbx小于eax,爆炸
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx # rbx = rbx + 8
4011f2: 83 ed 01 sub $0x1,%ebp # ebp -= 1
4011f5: 75 e8 jne 4011df <phase_6+0xeb> # rbx != eax 循环
4011f7: 48 83 c4 50 add $0x50,%rsp # end.......
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
Final: secret_phase
最后的脚本提示: 1
2/* Wow, they got it! But isn't something... missing? Perhaps
* something they overlooked? Mua ha ha ha ha! */
原来因为phase_defused还藏着一个secret_phase的调用,说明还有隐藏关卡,分析一下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
3600000000004015c4 <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关
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>
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
参考链接:
