Bomb Lab解析

前置知识:

  • 汇编
  • GDB

Bomb Lab解析

前言

Bomb lab是《深入理解计算机系统》配套实验之一。可执行程序包含着“炸弹”,必须输入六个正确的字符串来进行拆弹。字符串必须通过对程序的汇编码进行分析来得到。本文通过分析拆弹流程,对反汇编技术技巧进行总结。

准备

进入CSAPP LAB 官网进行文件的下载。自学者点击self-study handout下载tar压缩包。解压后得到3个文件,Bomb可执行文件,Bomb.c主函数文件,与README。
安装gdb进行反汇编准备。
观察主函数文件,根据函数来打断点。例如在第一阶段主函数有:

1
2
3
4
input = read_line();             
phase_1(input); /* Run the phase */
phase_defused(); /* Drat! They figured it out!
* Let me know how they did it. */

read_line()该行在文件第73行,就可以先命令行执行gdb bomb,再break 73来打断点,在执行run之后,就会在该地方停顿下来,便于调试。
常用gdb指令:
i r xxx 查看xxx寄存器存储的信息。
x xxx查看xxx内存位置的值。该指令可以带参数来改变值的输出格式。
disas xxx将xxx函数或者xxx内存位置的函数反汇编。可以查看对应函数的汇编码。

phase_1

第一阶段的炸弹,主函数代码如上所示。对phase_1函数进行反汇编,可以得到如下:

1
2
3
4
5
6
7
8
0x0000000000400ee0 <+0>:	    sub    $0x8,%rsp
0x0000000000400ee4 <+4>: mov $0x402400,%esi
0x0000000000400ee9 <+9>: callq 0x401338 <strings_not_equal>
0x0000000000400eee <+14>: test %eax,%eax
0x0000000000400ef0 <+16>: je 0x400ef7 <phase_1+23>
0x0000000000400ef2 <+18>: callq 0x40143a <explode_bomb>
0x0000000000400ef7 <+23>: add $0x8,%rsp
0x0000000000400efb <+27>: retq

rsp为栈顶指针,第一行减去栈顶指针是为函数留出栈空间,可以忽略。
第二行mov了一行数据,并且后面调用了strings_not_equal函数。eax为函数返回值,test函数用于检测返回值是否为0。后面je指令为0则跳转到+23即add行,不为0则爆炸。
那么我们猜测0x402400位置存放了字符串。当输入字符串和该字符串相同则不爆炸。通过x指令可以看出该内存位置为:

1
2
(gdb) x /s 0x402400
0x402400: "Border relations with Canada have never been better."

输入该字符串,即可拆弹成功。

phase_2

同上,对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
Dump of assembler code for function phase_2:
0x0000000000400efc <+0>: push %rbp
0x0000000000400efd <+1>: push %rbx
0x0000000000400efe <+2>: sub $0x28,%rsp
0x0000000000400f02 <+6>: mov %rsp,%rsi
0x0000000000400f05 <+9>: callq 0x40145c <read_six_numbers>
0x0000000000400f0a <+14>: cmpl $0x1,(%rsp)
0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
0x0000000000400f10 <+20>: callq 0x40143a <explode_bomb>
0x0000000000400f15 <+25>: jmp 0x400f30 <phase_2+52>
0x0000000000400f17 <+27>: mov -0x4(%rbx),%eax
0x0000000000400f1a <+30>: add %eax,%eax
0x0000000000400f1c <+32>: cmp %eax,(%rbx)
0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
0x0000000000400f20 <+36>: callq 0x40143a <explode_bomb>
0x0000000000400f25 <+41>: add $0x4,%rbx
0x0000000000400f29 <+45>: cmp %rbp,%rbx
0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
0x0000000000400f2e <+50>: jmp 0x400f3c <phase_2+64>
0x0000000000400f30 <+52>: lea 0x4(%rsp),%rbx
0x0000000000400f35 <+57>: lea 0x18(%rsp),%rbp
0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27>
0x0000000000400f3c <+64>: add $0x28,%rsp
0x0000000000400f40 <+68>: pop %rbx
0x0000000000400f41 <+69>: pop %rbp
0x0000000000400f42 <+70>: retq
End of assembler dump.

汇编码较上个阶段明显变长,这里分段解析。 可以看到首先调用了read_six_numbers来进行数据的读取。可以猜测并证实该函数作用为读取6个数,并放入栈中。
第一个引爆出现在cmpl 0x1 (rsp)后。即将栈顶元素与1相比较,不相等则爆炸。那么可以得到,第一个数为1。
在jmp之后将0x4(%rsp)与0x18(%rsp)存放了起来。根据一个数占4个字节可以得出,前者为存放下一个数值到rbx,后者为六个数的边界。
再次jmp之后可以看出,eax存放了rbx的前一个数,并且乘以2了。cmp即将前一个数×2与本数字相比较,不相等则爆炸。那么可以得到后一个数是前一个数的2倍的规律。
后面的代码不言而喻,依次进行数的比较,最后边界检查,到达边界则退出。那么可以得到拆弹代码:

1
1 2 4 8 16 32

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
0x0000000000400f43 <+0>:	sub    $0x18,%rsp
0x0000000000400f47 <+4>: lea 0xc(%rsp),%rcx
0x0000000000400f4c <+9>: lea 0x8(%rsp),%rdx
0x0000000000400f51 <+14>: mov $0x4025cf,%esi
0x0000000000400f56 <+19>: mov $0x0,%eax
0x0000000000400f5b <+24>: callq 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000400f60 <+29>: cmp $0x1,%eax
0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39>
0x0000000000400f65 <+34>: callq 0x40143a <explode_bomb>
0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp)
0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106>
0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax
0x0000000000400f75 <+50>: jmpq *0x402470(,%rax,8)
0x0000000000400f7c <+57>: mov $0xcf,%eax
0x0000000000400f81 <+62>: jmp 0x400fbe <phase_3+123>
0x0000000000400f83 <+64>: mov $0x2c3,%eax
0x0000000000400f88 <+69>: jmp 0x400fbe <phase_3+123>
0x0000000000400f8a <+71>: mov $0x100,%eax
0x0000000000400f8f <+76>: jmp 0x400fbe <phase_3+123>
0x0000000000400f91 <+78>: mov $0x185,%eax
0x0000000000400f96 <+83>: jmp 0x400fbe <phase_3+123>
0x0000000000400f98 <+85>: mov $0xce,%eax
0x0000000000400f9d <+90>: jmp 0x400fbe <phase_3+123>
0x0000000000400f9f <+92>: mov $0x2aa,%eax
0x0000000000400fa4 <+97>: jmp 0x400fbe <phase_3+123>
0x0000000000400fa6 <+99>: mov $0x147,%eax
0x0000000000400fab <+104>: jmp 0x400fbe <phase_3+123>
0x0000000000400fad <+106>: callq 0x40143a <explode_bomb>
0x0000000000400fb2 <+111>: mov $0x0,%eax
0x0000000000400fb7 <+116>: jmp 0x400fbe <phase_3+123>
0x0000000000400fb9 <+118>: mov $0x137,%eax
0x0000000000400fbe <+123>: cmp 0xc(%rsp),%eax
0x0000000000400fc2 <+127>: je 0x400fc9 <phase_3+134>
0x0000000000400fc4 <+129>: callq 0x40143a <explode_bomb>
0x0000000000400fc9 <+134>: add $0x18,%rsp
0x0000000000400fcd <+138>: retq

代码依旧很长,分部分进行解析。
最开始调用了sscanf,sscanf的字符串可以通过前面的mov指令猜出,位于0x4025cf。查看可以得知,该字符串为:
“%d %d”
读取了两个数,加上前面两个lea指令,可以猜出数存放在0x8(rsp)和0xc(rsp)。
第一个引爆在cmp 0x1 eax,可以得知这是根据sscanf返回值来爆炸。当sscanf匹配数字大于1则不爆炸。
跳转后进行了cmpl $0x7,0x8(%rsp),即第一个值和7进行比较。大于则爆炸。即第一个输入值需要小于等于7。
下一个将第一个值存入eax,并使用jmp跳转。jmpq star0x402470(,%rax,8)。我们可以先看看0x402470存放的是什么:

1
2
(gdb) print *(0x402470)
$1 = 4198268

4798268 = 0x400f7c,即下一行。那么根据地址可以得到,该跳转是利用第一个输入的值作为索引进行跳转,跳到对应指令。
下面结构都是类似的,将一个常数放入eax,并且跳转到与0xc(rsp)进行比较。相等则不会爆炸。以f7c行举例,对应值为0xcf=207那么可以得到一个解为:

1
0 207  

phase_4

同上,看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0x000000000040100c <+0>:	sub    $0x18,%rsp
0x0000000000401010 <+4>: lea 0xc(%rsp),%rcx
0x0000000000401015 <+9>: lea 0x8(%rsp),%rdx
0x000000000040101a <+14>: mov $0x4025cf,%esi
0x000000000040101f <+19>: mov $0x0,%eax
0x0000000000401024 <+24>: callq 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000401029 <+29>: cmp $0x2,%eax
0x000000000040102c <+32>: jne 0x401035 <phase_4+41>
0x000000000040102e <+34>: cmpl $0xe,0x8(%rsp)
0x0000000000401033 <+39>: jbe 0x40103a <phase_4+46>
0x0000000000401035 <+41>: callq 0x40143a <explode_bomb>
0x000000000040103a <+46>: mov $0xe,%edx
0x000000000040103f <+51>: mov $0x0,%esi
0x0000000000401044 <+56>: mov 0x8(%rsp),%edi
0x0000000000401048 <+60>: callq 0x400fce <func4>
0x000000000040104d <+65>: test %eax,%eax
0x000000000040104f <+67>: jne 0x401058 <phase_4+76>
0x0000000000401051 <+69>: cmpl $0x0,0xc(%rsp)
0x0000000000401056 <+74>: je 0x40105d <phase_4+81>
0x0000000000401058 <+76>: callq 0x40143a <explode_bomb>
0x000000000040105d <+81>: add $0x18,%rsp
0x0000000000401061 <+85>: retq

sscanf与上个阶段一样,比较返回值并且跳转。同时将第一个数与0xe进行比较,可以得到不大于0xe才能不爆炸。
然后将0xe,0x0和数组第一个值放入相应寄存器,调用func4,最后检查返回值是否为0。可以看出需要继续解析func4。func4代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0x0000000000400fce <+0>:	sub    $0x8,%rsp
0x0000000000400fd2 <+4>: mov %edx,%eax
0x0000000000400fd4 <+6>: sub %esi,%eax
0x0000000000400fd6 <+8>: mov %eax,%ecx
0x0000000000400fd8 <+10>: shr $0x1f,%ecx
0x0000000000400fdb <+13>: add %ecx,%eax
0x0000000000400fdd <+15>: sar %eax
0x0000000000400fdf <+17>: lea (%rax,%rsi,1),%ecx
0x0000000000400fe2 <+20>: cmp %edi,%ecx
0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36>
0x0000000000400fe6 <+24>: lea -0x1(%rcx),%edx
0x0000000000400fe9 <+27>: callq 0x400fce <func4>
0x0000000000400fee <+32>: add %eax,%eax
0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
0x0000000000400ff2 <+36>: mov $0x0,%eax
0x0000000000400ff7 <+41>: cmp %edi,%ecx
0x0000000000400ff9 <+43>: jge 0x401007 <func4+57>
0x0000000000400ffb <+45>: lea 0x1(%rcx),%esi
0x0000000000400ffe <+48>: callq 0x400fce <func4>
0x0000000000401003 <+53>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401007 <+57>: add $0x8,%rsp
0x000000000040100b <+61>: retq

func4代码使用了递归,不是很容易理解,这里我对着每一行翻译了一下,写了个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
int func4(int edx,int esi,int edi)
{
int eax = edx;
eax-=esi;
int ecx = eax;
ecx=(unsigned int)ecx>>0x1f;
eax+=ecx;
eax>>=1;
ecx = esi*1+eax;
if(ecx<=edi)
{
eax = 0;
if(ecx>=edi)
{
return eax;
}else{
esi = ecx+1;
eax = 2*func4(edx,esi,edi)+1;
}
}
else{
edx = ecx-1;
eax = 2* func4(edx,esi,edi);
}
return eax;
}

由于是一行一行翻译的,理解不困难。第一个数必定小于等于11,那么可以直接带入值遍历进行计算,实践得出返回值为0的情况。当输入为0 1 3 7时候返回值为0。
接下来代码不需要过多解释,即验证第二个数是不是为0。那么可以得到一个解:

1
0 0

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
0x0000000000401062 <+0>:	push   %rbx
0x0000000000401063 <+1>: sub $0x20,%rsp
0x0000000000401067 <+5>: mov %rdi,%rbx
0x000000000040106a <+8>: mov %fs:0x28,%rax
0x0000000000401073 <+17>: mov %rax,0x18(%rsp)
0x0000000000401078 <+22>: xor %eax,%eax
0x000000000040107a <+24>: callq 0x40131b <string_length>
0x000000000040107f <+29>: cmp $0x6,%eax
0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112>
0x0000000000401084 <+34>: callq 0x40143a <explode_bomb>
0x0000000000401089 <+39>: jmp 0x4010d2 <phase_5+112>
0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx
0x0000000000401096 <+52>: and $0xf,%edx
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)
0x00000000004010a4 <+66>: add $0x1,%rax
0x00000000004010a8 <+70>: cmp $0x6,%rax
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>
0x00000000004010ae <+76>: movb $0x0,0x16(%rsp)
0x00000000004010b3 <+81>: mov $0x40245e,%esi
0x00000000004010b8 <+86>: lea 0x10(%rsp),%rdi
0x00000000004010bd <+91>: callq 0x401338 <strings_not_equal>
0x00000000004010c2 <+96>: test %eax,%eax
0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119>
0x00000000004010c6 <+100>: callq 0x40143a <explode_bomb>
0x00000000004010cb <+105>: nopl 0x0(%rax,%rax,1)
0x00000000004010d0 <+110>: jmp 0x4010d9 <phase_5+119>
0x00000000004010d2 <+112>: mov $0x0,%eax
0x00000000004010d7 <+117>: jmp 0x40108b <phase_5+41>
0x00000000004010d9 <+119>: mov 0x18(%rsp),%rax
0x00000000004010de <+124>: xor %fs:0x28,%rax
0x00000000004010e7 <+133>: je 0x4010ee <phase_5+140>
0x00000000004010e9 <+135>: callq 0x400b30 <__stack_chk_fail@plt>
0x00000000004010ee <+140>: add $0x20,%rsp
0x00000000004010f2 <+144>: pop %rbx
0x00000000004010f3 <+145>: retq

分步骤来进行解析。首先可以看到调用了string_length,后面检测了输入长度是否为6。即输入长度为6的字符串。
经过一番跳转,到达了movzbl (%rbx,%rax,1),%ecx。经过查看可以得知rbx存放的是输入的字符串,rax为索引,最开始为0,然后经过传输后,到达了and 0xf edx。即将该字符ascii码与0xf取与,即取后四位。
然后movzbl 0x4024b0(%rdx),%edx,即以and后的值索引并覆盖该寄存器,在该内存位置可以看到字符串为:

1
2
(gdb) print (char*) 0x4024b0
$2 = 0x4024b0 <array> "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"

覆盖寄存器后另一个mov,即将该字符放入栈。在之后指进行六次循环,得到新的六个字符。
在得到新字符串后又调用了string_not_equal,而目标字符串则在0x40245e,查看可以得知:

1
2
(gdb) print (char*) 0x40245e
$3 = 0x40245e "flyers"

那么该输入的字符串,后四位所形成的索引需要构成flyers新字符串。以0开始计数,flyer分别在原字符串的位置是9 15 14 5 6 7 。
经过查表可以得知,要使ascii后四位等于上述数列,一个可行的答案为:

1
)/.%&'

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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
0x00000000004010f4 <+0>:	push   %r14
0x00000000004010f6 <+2>: push %r13
0x00000000004010f8 <+4>: push %r12
0x00000000004010fa <+6>: push %rbp
0x00000000004010fb <+7>: push %rbx
0x00000000004010fc <+8>: sub $0x50,%rsp
0x0000000000401100 <+12>: mov %rsp,%r13
0x0000000000401103 <+15>: mov %rsp,%rsi
0x0000000000401106 <+18>: callq 0x40145c <read_six_numbers>
0x000000000040110b <+23>: mov %rsp,%r14 栈顶放到r14
0x000000000040110e <+26>: mov $0x0,%r12d 0放到r12
0x0000000000401114 <+32>: mov %r13,%rbp r13放到rbp
0x0000000000401117 <+35>: mov 0x0(%r13),%eax *r13放到eax
0x000000000040111b <+39>: sub $0x1,%eax eax减1
0x000000000040111e <+42>: cmp $0x5,%eax eax和5比大小
0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52> eax小于等于5则不炸。
0x0000000000401123 <+47>: callq 0x40143a <explode_bomb>
0x0000000000401128 <+52>: add $0x1,%r12d r12加一
0x000000000040112c <+56>: cmp $0x6,%r12d
0x0000000000401130 <+60>: je 0x401153 <phase_6+95> r12等于6则跳到95 边界检查
0x0000000000401132 <+62>: mov %r12d,%ebx r12放进ebx
0x0000000000401135 <+65>: movslq %ebx,%rax ebx放进rax
0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax *(4乘rax+rsp)放进eax/第ebx个数放进eax
0x000000000040113b <+71>: cmp %eax,0x0(%rbp) 边界检查
0x000000000040113e <+74>: jne 0x401145 <phase_6+81> 第ebx个数与第r13存放的数不相等则跳过
0x0000000000401140 <+76>: callq 0x40143a <explode_bomb>
0x0000000000401145 <+81>: add $0x1,%ebx ebx加一
0x0000000000401148 <+84>: cmp $0x5,%ebx 比较ebx和5
0x000000000040114b <+87>: jle 0x401135 <phase_6+65> 小于等于则跳到65
0x000000000040114d <+89>: add $0x4,%r13 r13加4
0x0000000000401151 <+93>: jmp 0x401114 <phase_6+32> 跳到32
ebx为内层循环变量j。
r12为外层循环变量i。
for(i=0;i<=5;i++)
{
if((*r13)-1<=5) not explode;
for(j=i+1;j<=5;j++)
{
if((*r13)!=(*(rsp+j))) not explode;
}
r13++;
}


0x0000000000401153 <+95>: lea 0x18(%rsp),%rsi 数组的末尾放进rsp放进rsi
0x0000000000401158 <+100>: mov %r14,%rax r14放进rax 数组开头放入rax
0x000000000040115b <+103>: mov $0x7,%ecx 0x7放进ecx
0x0000000000401160 <+108>: mov %ecx,%edx ecx放进edx
0x0000000000401162 <+110>: sub (%rax),%edx edx减去rax值
0x0000000000401164 <+112>: mov %edx,(%rax) edx值存入rax值
0x0000000000401166 <+114>: add $0x4,%rax rax地址加4
0x000000000040116a <+118>: cmp %rsi,%rax 比较rax和rsi
0x000000000040116d <+121>: jne 0x401160 <phase_6+108> 不等于则跳回108
a[i] = 7-a[i]

0x000000000040116f <+123>: mov $0x0,%esi esi归零
0x0000000000401174 <+128>: jmp 0x401197 <phase_6+163> 跳到163
0x0000000000401176 <+130>: mov 0x8(%rdx),%rdx edx+8的值放进edx
0x000000000040117a <+134>: add $0x1,%eax eax+1
0x000000000040117d <+137>: cmp %ecx,%eax 比较ecx和eax
0x000000000040117f <+139>: jne 0x401176 <phase_6+130> 不等于则跳回130
0x0000000000401181 <+141>: jmp 0x401188 <phase_6+148> 跳到148
0x0000000000401183 <+143>: mov $0x6032d0,%edx 0x6032d0放到edx
0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2) edx放进*(rsp+2*rsi)+0x20
0x000000000040118d <+153>: add $0x4,%rsi rsi+4
0x0000000000401191 <+157>: cmp $0x18,%rsi 边界检查
0x0000000000401195 <+161>: je 0x4011ab <phase_6+183> 跳到183
0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx 把rsp+rsi放进ecx rsi为0 4 8 16 32 64
0x000000000040119a <+166>: cmp $0x1,%ecx 比较ecx和1 比较数组值
0x000000000040119d <+169>: jle 0x401183 <phase_6+143> 数组值小于等于1则跳回143
0x000000000040119f <+171>: mov $0x1,%eax eax放成1
0x00000000004011a4 <+176>: mov $0x6032d0,%edx 0x6032d0放进edx
0x00000000004011a9 <+181>: jmp 0x401176 <phase_6+130> 跳到130
130-139 循环使得edx = 0x6032d0+8*a[i]
然后放入*(rsp+2*rsi+0x20) rsi为数组的索引


0x00000000004011ab <+183>: mov 0x20(%rsp),%rbx rsp+20放进rbx
0x00000000004011b0 <+188>: lea 0x28(%rsp),%rax rsp+28放进rax
0x00000000004011b5 <+193>: lea 0x50(%rsp),%rsi rsp+50放进rsi
0x00000000004011ba <+198>: mov %rbx,%rcx rbx放进rcx
0x00000000004011bd <+201>: mov (%rax),%rdx rax值放入rdx
0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx) rdx放入rcx值+8
0x00000000004011c4 <+208>: add $0x8,%rax rax+8
0x00000000004011c8 <+212>: cmp %rsi,%rax 比较rsi和rax 边界检查
0x00000000004011cb <+215>: je 0x4011d2 <phase_6+222> 相等则跳入222
0x00000000004011cd <+217>: mov %rdx,%rcx rdx放进rcx
0x00000000004011d0 <+220>: jmp 0x4011bd <phase_6+201> 跳回201
0x00000000004011d2 <+222>: movq $0x0,0x8(%rdx) rdx+8置零
类似于链表操作。rdx = *rax *(rcx+8) = *rax rax+=8 rcx = rdx 按照数组的顺序重新连接链表。

0x00000000004011da <+230>: mov $0x5,%ebp ebp放5
0x00000000004011df <+235>: mov 0x8(%rbx),%rax rbx+8值放进rax
0x00000000004011e3 <+239>: mov (%rax),%eax rax值放进eax
0x00000000004011e5 <+241>: cmp %eax,(%rbx) 比较eax和rbx值
0x00000000004011e7 <+243>: jge 0x4011ee <phase_6+250> rbx值大于等于eax则跳到250
0x00000000004011e9 <+245>: callq 0x40143a <explode_bomb>
0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx rbx+8值放入rbx
0x00000000004011f2 <+254>: sub $0x1,%ebp ebp减去1
0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235> 如果不为0则跳回235
遍历链表,保证链表的遍历顺序为数组的降序。

0x00000000004011f7 <+259>: add $0x50,%rsp
0x00000000004011fb <+263>: pop %rbx
0x00000000004011fc <+264>: pop %rbp
0x00000000004011fd <+265>: pop %r12
0x00000000004011ff <+267>: pop %r13
0x0000000000401201 <+269>: pop %r14
0x0000000000401203 <+271>: retq

具体注释如上,该阶段使用了类似于链表的操作。通过输入六个数字,然后使用7减去每个数字,再将数字的索引顺序匹配为链表的连接顺序,再观察链表里的值实现降序排列。
再本次中,链表的值如下所示:

1
2
3
4
5
6
7
8
(gdb) x /12xg 0x6032d0
0x6032d0 <node1>: 0x000000010000014c 0x00000000006032e0
0x6032e0 <node2>: 0x00000002000000a8 0x00000000006032f0
0x6032f0 <node3>: 0x000000030000039c 0x0000000000603300
0x603300 <node4>: 0x00000004000002b3 0x0000000000603310
0x603310 <node5>: 0x00000005000001dd 0x0000000000603320
0x603320 <node6>: 0x00000006000001bb 0x0000000000000000

由于使用32位寄存器,只用看后八位进行排序。39c>2b3>1dd>1bb>14c>0a8 即为3 4 5 6 1 2 。在进行7-之后可以得到答案:

1
4 3 2 1 6 5

隐藏关卡

实在写不动了,据说是个二叉树。我放弃了。

总结与心得

本次实验主要是熟悉了汇编语言以及反汇编流程,熟悉了gdb的使用。最开始做lab的时候,我对汇编完全没有任何了解,从最开始的每一行都需要百度,到最后理解了每一行的含义,我确实了解了很多计算机相关知识。总而言之我自己认为这次收获还是很大的。有人说汇编是最后的底线,我之前一直觉得汇编对于我这种写高级语言的没什么关系,毕竟编译器优化后的汇编本来就很难读得懂,但现在觉得只要默认不优化的情况下,多了一种找bug的方法。

Author

王钦砚

Posted on

2020-08-04

Licensed under

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×