深入理解计算机系统之二进制炸弹实验

深入理解计算机系统——二进制炸弹实验

实验简介

二进制炸弹是一个作为目标代码文件的程序。运行时,它提示用户输入若干个不同的字符串。如果其中一个不正确,炸弹就会“爆炸”,打印出一条错误信息。用户必须通过对程序的反汇编和逆向工程来求出这六个字符串,解决这些炸弹。该实验的主要目的是,深入理解汇编语言,并学习使用 gdb 调试器。

在本次实验中,我们需要拆解七个炸弹(其中一个为隐藏炸弹)。

实验预备

下载完实验材料后,有两个文件值得关注:bombbomb.cbomb 就是要“拆解”的目标文件代码,而 bomb.c 只是辅助理解的部分源代码文件。首先,对 bomb 进行逆向:

1
objdump -d bomb >> bomb.s

得到反汇编文件 bomb.s ,然后,在该文件中找到相关函数的反汇编代码,开始拆解炸弹!

拆弹

phase_1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
phase_1:
push %ebp
mov %esp,%ebp
sub $0x8,%esp
movl $0x8049948,0x4(%esp)

mov 0x8(%ebp),%eax
mov %eax,(%esp)
call <strings_not_equal>
test %eax,%eax
je <phase_1+0x22>
call <explode_bomb>
leave
ret

比较字符串,若相等就不会爆炸。找到目标字符串。

gdb找到:

成功!

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
phase_2:
push %ebp ; set up stack size==0x28
mov %esp,%ebp
sub $0x28,%esp
lea -0x1c(%ebp),%eax ; eax=ebp-0x1c
mov %eax,0x4(%esp) ; ebp-0x1c=(esp+0x4)
mov 0x8(%ebp),%eax ; (ebp+0x8)=(esp)
mov %eax,(%esp)
call <read_six_numbers> ; read_six_numbers() needs 2 para
movl $0x1,-0x4(%ebp) ; loop begin i=0x1
jmp <phase_2+0x3f>
mov -0x4(%ebp),%eax ; eax=i
mov -0x1c(%ebp,%eax,4),%edx ;edx=(ebp-0x1c+4*eax)
mov -0x4(%ebp),%eax
dec %eax ; eax=i-1
mov -0x1c(%ebp,%eax,4),%eax ; eax=(ebp-0x1c+4*eax)
add $0x5,%eax ; eax+=0x5
cmp %eax,%edx ; is equal? explode if not
je <phase_2+0x3c>
call <explode_bomb>
incl -0x4(%ebp) ; (ebp-0x4)++
cmpl $0x5,-0x4(%ebp) ; loop unit i==5
jle <phase_2+0x21>
leave
ret

仔细阅读后发现,程序先调用了 read_six_numbers 的函数,而在之前,程序准备了一个参数放在 %esp+4 处,该参数的意义需要进一步阅读下面的代码才能知晓。之后,程序就进入了一个循环,用于不断测试条件,如果条件不符,炸弹就会爆炸!显然,该循环就是关键,这个炸弹就是考验汇编语言的循环部分了。经整理,循环用 C 代码表示如下:

1
2
3
4
5
6
7
*a = &(ebp-0x1c);
for (int i = 1; i <= 5; i++) {
edx=a[i];
eax=a[i-1]+5;
if (eax != edx)
explode_bomb();
}

现在,明白 read_six_numbers 函数的参数 ebp-0x1c 是一个数组地址。大致可以猜到,只要输入 6 个数字,相邻两个差为 5 即可拆解炸弹。

等等,并不是任意的差为 5 的等差数列都满足要求!对首项,read_six_numbers 函数也有要求:

1
2
3
4
5
6
7
8
9
10
read_six_numbers:
; ....
mov 0x8(%ebp),%eax
mov %eax,(%esp)
call <sscanf@plt>
mov %eax,-0xc(%ebp)
cmpl $0x5,-0xc(%ebp) ; compare with (ebp-0xc) 5
jg <read_six_numbers+0x62>
call <explode_bomb>
add $0x30,%esp

看起来,输入的首项必须比 5 大,那么首项就是 6 吧 :smile:。

验证一下:

phase_3

再接再厉,开始拆解第三个炸弹。phase_3 函数的汇编代码明显长了不少,先看第一部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
phase_3:
; ...
movl $0x0,-0x8(%ebp)
movl $0x0,-0x4(%ebp)
lea -0x10(%ebp),%eax
mov %eax,0xc(%esp) ; (esp+0xc)=ebp-0x10
lea -0xc(%ebp),%eax
mov %eax,0x8(%esp) ; (esp+0x8)=ebp-0xc
movl $0x8049972,0x4(%esp) ; (esp+0x4)=??

mov 0x8(%ebp),%eax
mov %eax,(%esp)
call <sscanf@plt>
mov %eax,-0x4(%ebp)
cmpl $0x1,-0x4(%ebp) ; eax > 1, if not explode!
jg <phase_3+0x43>
call <explode_bomb>
mov -0xc(%ebp),%eax ; eax > 7 will explode!
mov %eax,-0x14(%ebp) ; (ebp-0xc)->(ebp-0x14)
cmpl $0x7,-0x14(%ebp)
ja <phase_3+0x95> ; jmp to call <explode_bomb>

注意到,程序调用了函数 sscanf 。该函数的意义是,从第一个参数的字符串中读取变量的值。就本次调用而言,函数共准备了四个参数,分别位于%esp %esp+0x4%esp+0x8%esp+0xc 。其中,%esp 就是我们输入的行字符串,这 0x8049972 表示的是:

看来是需要输入两个整数。那么,最后的两个参数就一定是输入的整数了,%esp+0x8 是第一个整数地址,%esp+0xc 应该是第二个整数地址。sscanf 函数完成后,读入的两个整数就位于 ebp-0xcebp-0x10 中。

然后对这两个整数做简单判断:要求第一个整数要大于 1 且小于等于 7,否则炸弹会爆炸。接下来,看函数的第二部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
;...
mov -0x14(%ebp),%edx ; edx=(ebp-0x14)
mov 0x8049978(,%edx,4),%eax ; eax=(edx*4+0x8049978)
jmp *%eax ; a switch table
addl $0x108,-0x8(%ebp)
subl $0x1e5,-0x8(%ebp)
addl $0x19a,-0x8(%ebp)
subl $0x35f,-0x8(%ebp)
addl $0x239,-0x8(%ebp)
subl $0x38e,-0x8(%ebp)
addl $0x38e,-0x8(%ebp)
subl $0xe3,-0x8(%ebp)
jmp <phase_3+0x9a>
call <explode_bomb>
mov -0xc(%ebp),%eax ; eax=(ebp-0xc)
cmp $0x5,%eax ; eax > 5 explode!
jg <phase_3+0xaa>
mov -0x10(%ebp),%eax ; eax=(ebp-0x10)
cmp %eax,-0x8(%ebp) ; compare eax with (ebp-0x8)
je <phase_3+0xaf>
call <explode_bomb>
leave
ret

第二句汇编又出现了一个奇怪的数字 0x8049978 ,在gdb 调试程序中,打开一看,就会发现其中的奥秘:

0x8049978 处,存放了后面 8 条语句的地址!显然,这就是一个跳转表,PC 接下来该指向哪里,完全取决于 %edx ,也即输入的第一个数字的值。如果第一个数为 2 ,那么程序最终计算 %ebp-0x8 处的值就是 0xff91 (当然不是计算得到的,通过gdb调试器得出的结果)。于是输入的数值可以为 2 和 -111。

经验证,第三颗炸弹已被拆除,感兴趣的话,也可以尝试一下其他数值。不过注意到,程序最后规定,第一个数字不能大于 5 !

phase_4

第四颗炸弹:前面和第三颗炸弹一样,调用了 sscanf 函数。那么这次,sscanf 函数使用了什么字符串呢?看起来只要输入一个整数就行:

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
phase_4:
push %ebp
mov %esp,%ebp
sub $0x28,%esp
lea -0xc(%ebp),%eax
mov %eax,0x8(%esp) ; (esp+0x8)=(ebp-0xc)
movl $0x8049998,0x4(%esp) ;(esp+0x4)=??
mov 0x8(%ebp),%eax
mov %eax,(%esp)
call <sscanf@plt>
mov %eax,-0x4(%ebp)
cmpl $0x1,-0x4(%ebp) ; eax!= 0x1 explode!
jne <phase_4+0x30>
mov -0xc(%ebp),%eax
test %eax,%eax ; eax=(ebp-0xc)
jg <phase_4+0x35> ; eax & eax > 0, if not explode!
call <explode_bomb>
mov -0xc(%ebp),%eax
mov %eax,(%esp)
call <func4>
mov %eax,-0x8(%ebp)
cmpl $0x37,-0x8(%ebp)
je <phase_4+0x4e> ; 0x37==eax if not explode!
call <explode_bomb>
leave
ret

经gdb调试后,%ebp-0xc 是输入的整数,必须大于 0 ,而 sscanf 函数的返回值必须为 1,否则炸弹会引爆。然后,程序就开始调用 func4 函数了。注意到,func4 函数的返回值必须等于 0x37 否则炸弹会爆炸。那么 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
func4:
push %ebp
mov %esp,%ebp
push %ebx
sub $0x8,%esp
cmpl $0x1,0x8(%ebp) ; (ebp+0x8)>0x1?
jg <func4+0x16>
movl $0x1,-0x8(%ebp) ; (ebp-0x8)=0x1
jmp <func4+0x37>
mov 0x8(%ebp),%eax ; eax=(ebp+0x8)
dec %eax
mov %eax,(%esp) ; esp=eax-1
call <func4>
mov %eax,%ebx
mov 0x8(%ebp),%eax
sub $0x2,%eax
mov %eax,(%esp)
call <func4>
add %eax,%ebx ; ebx += eax
mov %ebx,-0x8(%ebp) ; (ebp-0x8)=ebx
mov -0x8(%ebp),%eax ; eax=ebx
add $0x8,%esp
pop %ebx
pop %ebp
ret

仔细阅读后,整理成如下 C 代码:看起来就是一个斐波那契数列 :joy:。结合 func4 函数的返回值必须是 0x37 ,那么很容易想到,问题是让我们求出,斐波那契数列的哪一项是 0x37

1
2
3
4
5
6
7
8
9
int func4(int n) {
if (n > 1) {
sum = func4(n-1)+func4(n-2);
return sum;
} else {
eax=0x1
return eax;
}
}

嗯,第 9 项,输入 9 就可以拆除炸弹了。

phase_5

与炸弹四一样,phase_5 阶段一开始也调用了 sscanf 函数,用 gdb 调试,找到要求输入的字符串:需要给出两个整数。

1
2
3
4
5
6
7
8
9
10
11
12
phase_5:
push %ebp
mov %esp,%ebp
sub $0x38,%esp
lea -0x18(%ebp),%eax
mov %eax,0xc(%esp) ; (%esp+0xc)=&%ebp-0x18
lea -0x14(%ebp),%eax
mov %eax,0x8(%esp) ; (%esp+0x8)=&%ebp-0x14
movl $0x8049972,0x4(%esp) ; (%esp+0x4)=??
mov 0x8(%ebp),%eax
mov %eax,(%esp)
call 8048868 <sscanf@plt>

当输入了两个整数后(已经很熟悉了, %esp+0x8%esp+0xc 指向了两个输入整数的地址,而%ebp-x14%ebp-0x18 存放了整数值),接下来看看炸弹怎么对这两个整数进行操作。

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
mov    -0x14(%ebp),%eax
and $0xf,%eax
mov %eax,-0x14(%ebp) ; first int & 0xf, write back
mov -0x14(%ebp),%eax
mov %eax,-0x8(%ebp)
movl $0x0,-0x10(%ebp)
movl $0x0,-0xc(%ebp)
jmp <phase_5+0x6a>
incl -0x10(%ebp) ; loop i=(%ebp-0x10)
mov -0x14(%ebp),%eax
mov 0x804a5c0(,%eax,4),%eax ; 0x804a5c0 ??
mov %eax,-0x14(%ebp) ; write back
mov -0x14(%ebp),%eax
add %eax,-0xc(%ebp) ; accumulate (%ebp-0xc)
mov -0x14(%ebp),%eax
cmp $0xf,%eax ;; %eax == 0xf ? ne to loop
jne 8048d80 <phase_5+0x54>
cmpl $0xb,-0x10(%ebp)
jne 8048dac <phase_5+0x80> ; i must be 0xb, or explode!
mov -0x18(%ebp),%eax
cmp %eax,-0xc(%ebp)
je 8048db1 <phase_5+0x85> ; second int == (%ebp-0xc). or explode!
call 8049656 <explode_bomb>
leave
ret

仔细阅读汇编代码,初步得出结论:输入的第一个整数会作为一个数组的索引,在一个循环中不断累加,得到的累加值会与输入的第二个整数比较。炸弹不爆炸的条件是:循环次数必须为 11 次(0x1-0xb),且第二个整数的值与累加值相等。

首先,来看一下位于 0x804a5c0 的数组吧:

由于要求循环次数必须为 11 次,而终止循环的条件是 %eax == 0xf ,也就是说取到数组中的 0xf 才能结束循环,那我们从后往前推,要取到 0xf ,必须取到 0x6 (因为 0xf 的索引是 6),取到 0x6 ,必须先取到 0xe ……

于是有:0xf -> 0x6 -> 0xe -> 0x2 -> 0x1 -> 0xa -> 0x0 -> 0x8 -> 0x4 -> 0x9 -> 0xd -> 0xb。累加起来值为(不算0xb)82。于是,输入 11 和 82 就可以拆解炸弹。

phase_6

最后一个炸弹,继续前进。注意到,phase_6 先调用了 atoi 函数,看来是要把某个字符串变为整数。

1
2
3
4
5
6
7
8
phase_6:
push %ebp
mov %esp,%ebp
sub $0x18,%esp
movl $0x804a66c,-0x8(%ebp) ; 0x804a66c ???
mov 0x8(%ebp),%eax ; %ebp+0x8 ??
mov %eax,(%esp)
call <atoi@plt>

首先,还是要用 gdb 调试器看看 0x804a66c 和传入的参数 %ebp+0x8 是什么东西。

得出的信息有点少(主要是看不懂这个字符串),而且 0x804a66c 处的字符串为空。随后,程序很快就调用了 fun6 函数:

1
2
3
4
5
6
7
call     <atoi@plt>
mov %eax,%edx
mov -0x8(%ebp),%eax
mov %edx,(%eax) ; ((%ebp-0x8)) = %edx
mov -0x8(%ebp),%eax
mov %eax,(%esp) ; %eax as arg
call 8048db3 <fun6>

fun6 函数完成后,会进入一个循环,从下面的汇编代码可以看到,循环中不断重复的一条有效指令就是 (%ebp-0x4)=((%ebp-0x4)+0x8) 。从这点上看,可以理解为 (%ebp-0x4) 是一个指针,而这种循环更像是对链表的遍历。后来,根据我的不断尝试,发现该炸弹确实是对一个链表进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mov    %eax,-0x8(%ebp)
mov -0x8(%ebp),%eax ; fun6 return value is %eax
mov %eax,-0x4(%ebp) ; (%ebp-0x4)=%eax
movl $0x1,-0xc(%ebp) ; (%ebp-0xc)=1 i=1
jmp 8048e8f <phase_6+0x48> ; loop1
mov -0x4(%ebp),%eax
mov 0x8(%eax),%eax
mov %eax,-0x4(%ebp) ; (%ebp-0x4)=((%ebp-0x4)+0x8)
incl -0xc(%ebp) ; i++
cmpl $0x4,-0xc(%ebp)
jle 8048e83 <phase_6+0x3c> ; (%ebp-0xc) <= 4 to loop
mov -0x4(%ebp),%eax
mov (%eax),%edx ; %edx=((%ebp-0x4)) =[0x804a60c]
mov 0x804a66c,%eax
cmp %eax,%edx ; %edx == 0x804a66c ??
je 8048ea8 <phase_6+0x61> ; if not eq, explode!
call 8049656 <explode_bomb>
leave
ret

fun6 函数非常复杂,但仔细观察发现,该函数运行后产生的结果与我们给的输入无关。那么就有一个技巧,可以用gdb调试器在 fun6 函数返回后设置断点,观察其返回值 %eax==0x804a618 。然后,上述汇编代码的循环会执行 5 次,我使用 gdb 调试器将整个链表的值都打印在下图中。0x804a618 对应的是 node7,而上面汇编代码中,要求 %edx==[0x804a66c] ,其对应的是 node0,经过多次实验,我发现,node0 为用户输入的值(见下图,输入 5 时)。

那么问题来了,应该输入什么才能拆解炸弹呢?顺着程序过一遍,一开始 %eax=0x804a618,指向了 node7,然后根据 (%ebp-0x4)=((%ebp-0x4)+0x8) ,链接到下一个节点,一共执行 5 次,先后是 node3、node5、node9、node8 ,遍历后, %ebp-0x4中的值应该为 0x804a60c(见下图),当从中取出的值也为 0x20e 时,才能拆解炸弹。

如下图,已经成功拆解完 6 个炸弹。

secret_phase

一切还没结束,从汇编代码看,我们还剩一个隐藏炸弹没有拆解掉。首先,我们需要找到隐藏炸弹的输入入口。否则,gdb 调试器是帮不了我们的。全局搜索一下,发现在 phase_defused 函数下找到了 call 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
phase_defused:
; ...
jne 80496fe <phase_defused+0x7e>
mov $0x804a990,%eax ; 0x804a990 ???
mov %eax,%edx
lea -0x54(%ebp),%eax ; arg : (%ebp-0x54)
mov %eax,0xc(%esp)
lea -0x58(%ebp),%eax ; arg : (%ebp-0x58)
mov %eax,0x8(%esp)
movl $0x8049e38,0x4(%esp) ; 0x8049e38 ??

mov %edx,(%esp)
call 8048868 <sscanf@plt> ; sscanf
mov %eax,-0x4(%ebp)
cmpl $0x2,-0x4(%ebp)
jne 80496f2 <phase_defused+0x72>
movl $0x8049e3e,0x4(%esp) ; 0x8049e3e ??

lea -0x54(%ebp),%eax
mov %eax,(%esp)
call 804908f <strings_not_equal>
test %eax,%eax ; to see if string is equal
jne 80496f2 <phase_defused+0x72>
; ...

重点是 sscanf 函数及其参数,第一个参数是 %edx ,指向 0x804a990 ,第二个参数是 0x8049e38,其实是一个字符串(见下图), 第三个参数和第四个参数分别是 %ebp-0x58%ebp-0x54 。从后半部分代码看出,要求输入的字符串必须是 austinpowers,且 sscanf 函数返回值为 2,意思是整数和字符串必须都有对应的输入。

再来看看 0x804a990 (见下图),很奇怪的是,它只有一个 “9”,这样的话,sscanf 函数的返回值就只能是 1 了,我们就没法打开隐藏关卡了。还有,这个 <input_string> 是什么?

看起来,要先解决 <input_string> 的问题,不过,一切来的很轻松(见下图)。看来,我们之前拆炸弹的每一个输入都以 80 字节对齐的方式存放于此!这样看来,就是要在第 4 个炸弹拆解时,后面加上字符串 austinpowers, 就可以发现隐藏炸弹了!

终于,我们来到了隐藏炸弹,现在看看这一炸弹的具体代码。

1
2
3
4
5
6
7
8
9
secret_phase:
push %ebp
mov %esp,%ebp
sub $0x18,%esp
call <read_line>
mov %eax,-0xc(%ebp)
mov -0xc(%ebp),%eax
mov %eax,(%esp)
call <atoi@plt>

看起来,需要读入一行,并且读入的这一行会使用 atoi 函数转为数字。要求数字必须为正数,且小于 0x3e9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mov    %eax,-0x8(%ebp)
cmpl $0x0,-0x8(%ebp)
jle <secret_phase+0x2b> ; %eax <= 0 explode
cmpl $0x3e9,-0x8(%ebp)
jle <secret_phase+0x30> ; %eax <= 0x3e9
call 8049656 <explode_bomb>
mov -0x8(%ebp),%eax
mov %eax,0x4(%esp) ; (%esp+0x4)=int
movl $0x804a720,(%esp) ; 0x804a720 ???
call 8048eaa <fun7>
mov %eax,-0x4(%ebp) ; return val=eax
cmpl $0x3,-0x4(%ebp) ; %eax==0x3, or explode
je <secret_phase+0x51>
call <explode_bomb>
movl $0x804999c,(%esp)
call 80487c8 <puts@plt>
call 8049680 <phase_defused>
leave
ret

重点是 fun7 函数,其参数有两个,一个是我们输入的数字,另一个是立即数 0x804a720,这个立即数不应当是一个“数字”,更有可能是一个指针。由上面的代码可知,返回值应该为3,否则就会爆炸。接下来,仔细研究一下 fun7 的代码。

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
fun7:
push %ebp
mov %esp,%ebp
sub $0xc,%esp
cmpl $0x0,0x8(%ebp) ; (%ebp+0x8)=0x804a720
jne <fun7+0x15>
movl $0xffffffff,-0x4(%ebp) ; low 2 word is 0 then (%ebp-0x4)=-1
jmp 8048f13 <fun7+0x69>
mov 0x8(%ebp),%eax
mov (%eax),%eax
cmp 0xc(%ebp),%eax ; %eax=((%ebp+0x8)) <= (%ebp+0xc)
jle <fun7+0x3b>
mov 0x8(%ebp),%eax
mov 0x4(%eax),%edx ; %edx = ((%ebp+0x8)+0x4)
mov 0xc(%ebp),%eax
mov %eax,0x4(%esp) ; (%esp+0x4)=(%ebp+0xc)
mov %edx,(%esp)
call 8048eaa <fun7>
add %eax,%eax
mov %eax,-0x4(%ebp)
jmp 8048f13 <fun7+0x69>
mov 0x8(%ebp),%eax ;
mov (%eax),%eax
cmp 0xc(%ebp),%eax ; %eax=((%ebp+0x8)) != (%ebp+0xc)
jne 8048ef8 <fun7+0x4e>
movl $0x0,-0x4(%ebp)
jmp 8048f13 <fun7+0x69>
mov 0x8(%ebp),%eax
mov 0x8(%eax),%edx ; %edx = ((%ebp+0x8)+0x8)
mov 0xc(%ebp),%eax ;
mov %eax,0x4(%esp) ; (%esp+0x4)=(%ebp+0xc)
mov %edx,(%esp)
call 8048eaa <fun7> ; call fun7
add %eax,%eax
inc %eax
mov %eax,-0x4(%ebp)
mov -0x4(%ebp),%eax
leave
ret

转化为 C 代码,可能比较方便理解,可见,还是递归函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int fun7(int *a, int b) {
if (*a & 0x0ffff == 0) {
return -1;
} else {
if (*a <= b) {
if (*a != b) {
a+=8 byte;
int ret = fun7(a, b);
ret += ret; ret++;
return ret;
} else
return 0;
} else {
a+=4 byte;
int ret = fun7(a, b);
ret += ret;
return ret;
}
}
}

现在,需要让 fun7 函数返回值为3,应当如何设置a 和 b 的值呢?不难想到,返回值为3,要求最内部的函数返回值为0,然后再返回1,再返回3,但首先,需要知道 0x804a720的值。经过多次尝试,求出如下数据:

根据之前的推算,需要调用三层 fun7 函数,第一次需走入 *a<b 的分支,第二次也是,第三次需要走入 *a==b 的分支,这样返回的值才能为3。倒推回去,第一次指针为 0x804a720 ,第二次需要 +8,指针地址为 0x804a708 ,然后再加8,为 0x804a6d8 ,此时指针指向的值是 0x6b 。要求输入的值与 0x6b 相等,才能返回3,因此,拆解该炸弹的答案是 107。

最后的最后,解答成功!

总结

回头看这几颗炸弹的拆解过程,每颗炸弹难度递增,考点不一但都极具代表性。第一阶段考察了字符串比较,需要我们熟练使用 gdb 调试器。第二阶段考察了汇编语言的循环部分,能否理解循环非常关键。第三阶段考察了汇编语言的 switch 语句的实现。第四阶段考察了递归函数调用,第五阶段考察了数组索引以及循环结构,第六阶段则是关于链表的遍历。最后的秘密阶段更是融合了多个考点,做完之后很有成就感!


深入理解计算机系统之二进制炸弹实验
https://dingfen.github.io/2021/05/26/2021-5-26-CSAPPLab02/
作者
Bill Ding
发布于
2021年5月26日
更新于
2024年4月9日
许可协议