CSapp bomblab实验

#csapp实验2-bomb lab实验
##实验准备
Linux操作系统或虚拟机,GDB调试器,这里我用的是kali linux。

在用wget下载并解压试验文件后,输入gdb bomb即可开始调试程序。

本实验共分为六个关卡,phase_1-phase_6,需要通过查看反编译代码并分析来得到每一关的答案,避免炸弹程序爆炸。

##phase_1
反编译的汇编代码如下

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

分析

第一关代码比较少,比较简单,我们可以重头看,这里第一行分配栈空间不用多说,第二行向寄存器%esi存入了一个常数,我们很容易看出这是一个内存地址,然后将%esi作为一个参数传入到了函数,通过名字我们可以猜出这是比较字符串是否相等的。
第四行第五行来检查函数返回值是否为0,如果%eax值为0,则跳转到程序结束过关,为1则调用bomb函数炸弹爆炸,闯关失败

结论

所以这个函数要求我们输入特定的字符串,需要与 0x402400地址内存的字符串一致,通过
函数对比,一致返回0程序结束,不一致返回1炸弹爆炸闯关失败,所以我们只需要用x/s命令查看0x402400地址内的内容即可得到答案。

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

##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
28
(gdb) disas phase_2
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>: call 0x40145c <read_six_numbers>
0x0000000000400f0a <+14>: cmpl $0x1,(%rsp)
0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
0x0000000000400f10 <+20>: call 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>: call 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>: ret
End of assembler dump.

###分析
我们通过分析的汇编代码和名字可以知道该函数是读取用户输入的六个四字节数字,然后通过下面的cmpl我们可以知道第一个数字必须为1,接着查看跳转到的+52行,这里给rbx传入了第二个数的地址,给rbp传入了不存在的第七个数的地址,并跳转到+
27行,通过分析我们发现此时程序进入循环,只有在rbx和rbp的值相等时才跳出循环天转到+64结束程序,不然rsp内存的地址会一直变为下一个数的地址,并与上一个数比较,只有这个数是上一个数二倍时才能继续,如果从第二个数开始其中任何一个数不是上一个的二倍,则调用bomb函数闯关失败。

###结论

这里我们要输入六个数,第一个数必须是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
37
Dump of assembler code for function phase_3:
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>: call 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000400f60 <+29>: cmp $0x1,%eax
0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39>
0x0000000000400f65 <+34>: call 0x40143a <explode_bomb>
0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp)
0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106>
0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax
0x0000000000400f75 <+50>: jmp *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>: call 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>: call 0x40143a <explode_bomb>
0x0000000000400fc9 <+134>: add $0x18,%rsp
0x0000000000400fcd <+138>: ret

###分析
通过前几行我们可以得知需要传入两个参数,(至于<__isoc99_sscanf@plt>函数的作用大概是一个通过eax返回参数个数的,在phase_2中的函数就出现过。)通过+39行的cmpl我们可以知道第一个参数是无符号数且必须在0-7之间,然后存入%eax并进入跳转表

1
0x0000000000400f75 <+50>:    jmp    *0x402470(,%rax,8)

这是一个跳转表,通过rax的值以八个字节为单位选择跳转位置,这里我们用

1
x/8xg 0x402470

该命令表示查看8个8字节为单位的16进制形式显示的地址单元
可以看到

1
2
3
4
0x402470:       0x0000000000400f7c      0x0000000000400fb9
0x402480: 0x0000000000400f83 0x0000000000400f8a
0x402490: 0x0000000000400f91 0x0000000000400f98
0x4024a0: 0x0000000000400f9f 0x0000000000400fa6

当我们选择任意一个跳转位置后,系统会给%eax存入一个相应的值,然后跳转到+123行,把第二个参数与%eax比较,相同则程序结束,不同则调用爆炸函数,闯关失败。

###结论
该函数类似于一个switch(),第一个参数为索引,第二个参数为对应case值
我们需要输入两个整数,第一个在0-7之间,第二个数为该索引数对应的case值
这里我把他们列出来

1
2
3
4
5
6
7
8
9
第一个参数     第二个参数
0 0xcf
1 0x137
2 0x2c3
3 0x100
4 0x185
5 0xce
6 0x2aa
7 0x147

记得输入时换成10进制
所以我们可以输入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
23
24
Dump of assembler code for function phase_4:
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>: call 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>: call 0x40143a <explode_bomb>
0x000000000040103a <+46>: mov $0xe,%edx
0x000000000040103f <+51>: mov $0x0,%esi
0x0000000000401044 <+56>: mov 0x8(%rsp),%edi
0x0000000000401048 <+60>: call 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>: call 0x40143a <explode_bomb>
0x000000000040105d <+81>: add $0x18,%rsp
0x0000000000401061 <+85>: ret
End of assembler dump.

###分析
通过前几行我们知道我们需要输入两个参数,第一个参数不能大于14,然后将调用函数func4,将%edx=14,%esi=0,%edi=第一个输入数a作为参数传入func4,然后需要func4函数返回值为0,然后我们输入的第二个参数必须为0.
那么我们进一步分析func4函数
反编译代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Dump of assembler code for function func4:
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>: call 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>: call 0x400fce <func4>
0x0000000000401003 <+53>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401007 <+57>: add $0x8,%rsp
0x000000000040100b <+61>: ret

经分析这个函数主要实现把第一个输入的数据a与%edx和%esi的均值做比较,这里第一次调用%edx为14,%esi为0,所以均值为7,这里我们只需要输入第一个参数为7即可过关,但是如果我们仔细观察这个函数,可以发现他是一个递归的类似于二分查找所以答案不止一个,我们把该二分查找映像成一个二叉树,均值为节点

1
2
3
4
5
6
7
           7
/ \
3 11
/ \ / \
1 5 9 13
/ \ / \ / \ / \
0 2 4 6 8 10 12 14

###结论
由分析代码可知,如果任一递归过程中走的是右子树,则返回值会不为0,所以只有递归过程一直走的左子树,才可以返回0,所以答案第一个输入可以为7,3,1,0。第二个输入为0。