深入了解计算机系统——实验三(Bomb Lab)(详解)

实验概述及思路

程序运行中有7个关卡(6个phase和1个隐藏关卡),每个phase需要用户在终端上输入特定的字符或者数字才能通关,否则会引爆炸弹!因此可以使用gdb工具反汇编出汇编代码,并结合c语言文件找到每个关卡的入口函数。然后查看汇编代码并进行分析,进而各个击破,得到每个函数的通关密码,最终完成整个实验,话不多说,直接开干(出错的地方请各位帮忙指出啦)!

函数分析

一、 phase_1函数

① 反汇编代码
disassemble phase_1            //得到phase_1的反汇编代码

Dump of assembler code for function phase_1:
   0x08048b50 <+0>:		sub    $0x1c,%esp						#函数栈帧准备			
   0x08048b53 <+3>:		movl   $0x804a1c4,0x4(%esp)				#将0x804a1c4存入0x4(%esp)0x08048b5b <+11>:	mov    0x20(%esp),%eax					
   0x08048b5f <+15>:	mov    %eax,(%esp)						#将0x20(%esp)在内存中的值存入(%esp)
   0x08048b62 <+18>:	call   0x8048fe4 <strings_not_equal> 	#调用<strings_not_equal>函数,将比较的结果存入%eax
   0x08048b67 <+23>:	test   %eax,%eax
   0x08048b69 <+25>:	je     0x8048b70 <phase_1+32>			#判断%eax的值,如果%eax为1则调用<explode_bomb>函数
																(根据函数的名字可以得出就是爆炸函数),如果%eax为
																0则直接返回。
   0x08048b6b <+27>:	call   0x80490f6 <explode_bomb>
   0x08048b70 <+32>:	add    $0x1c,%esp
   0x08048b73 <+35>:	ret    
End of assembler dump.

② 函数分析
综上,可以判断这个函数要输入一个字符串,而且要求输入的字符串与题目已经给定的某个字符串相等。而0x804a1c4是一个立即数,可以判断这是题目给定字符串的首地址,而另一个参数就是存储在0x20(%esp)中的我们输入的字符串。因此我们只需要通过0x804a1c4这个地址得到内存中存的字符串,而该字符串就是phase_1的密码。

③ 操作步骤
x/s 0x804a1c4            //查看内存中的字符串
在这里插入图片描述
所以phase_1的通关密码为:Wow! Brazil is big.

二、 phase_2函数

① 反汇编代码
disassemble phase_2            //得到phase_2的反汇编代码

Dump of assembler code for function phase_2:
   0x08048b74 <+0>:		push   %esi
   0x08048b75 <+1>:		push   %ebx
   0x08048b76 <+2>:		sub    $0x34,%esp					#栈帧准备
   0x08048b79 <+5>:		lea    0x18(%esp),%eax				
   0x08048b7d <+9>:		mov    %eax,0x4(%esp)				#0x4(%esp) = 0x18(%esp) 
   0x08048b81 <+13>:	mov    0x40(%esp),%eax
   0x08048b85 <+17>:	mov    %eax,(%esp)					#(%esp) = M[0x40(%esp)]
   0x08048b88 <+20>:	call   0x804922b <read_six_numbers> #读取6个数字
   0x08048b8d <+25>:	cmpl   $0x1,0x18(%esp)				#比较M[0x18(%esp)]1
   0x08048b92 <+30>:	je     0x8048b99 <phase_2+37>		#相等则不会爆炸
   0x08048b94 <+32>:	call   0x80490f6 <explode_bomb>
   0x08048b99 <+37>:	lea    0x1c(%esp),%ebx				#%ebx = 0x1c(%esp)
   0x08048b9d <+41>:	lea    0x30(%esp),%esi				#%esi = 0x30(%esp)
   
   0x08048ba1 <+45>:	mov    -0x4(%ebx),%eax				#%eax = M[-0x4(%ebx)]
   0x08048ba4 <+48>:	add    %eax,%eax					#%eax = 2 * %eax
   0x08048ba6 <+50>:	cmp    %eax,(%ebx)					#比较(%ebx)%eax
   0x08048ba8 <+52>:	je     0x8048baf <phase_2+59>		#相等则不会爆炸
   0x08048baa <+54>:	call   0x80490f6 <explode_bomb>
   0x08048baf <+59>:	add    $0x4,%ebx					#%ebx += 0x4
   0x08048bb2 <+62>:	cmp    %esi,%ebx					#比较%ebx和%esi
   0x08048bb4 <+64>:	jne    0x8048ba1 <phase_2+45>		#不相等则跳转
   
   0x08048bb6 <+66>:	add    $0x34,%esp					#返回
   0x08048bb9 <+69>:	pop    %ebx
   0x08048bba <+70>:	pop    %esi
   0x08048bbb <+71>:	ret    
End of assembler dump.

② 函数分析

  • - :  将0x18(%esp)在内存中的值和1进行比较,也就是将第一个参数和1比较,如果两者相等则不会爆炸,这里得出第一个数必为1
  • - :  这一段代码相当于一个do-while循环,因为前面的代码限制第一个数只能为1,下面的代码就是对剩下的5个数进行限制。首先得到第一个数(-0x4(%ebx))的值,然后将它的2倍存入%eax寄存器,然后将(%ebx)在内存中的值(也就是输入的第二个数)和%eax进行比较,只有两个值相等时才不会爆炸,然后将%ebx加4(也就是下一个数的地址),最后判断当的%ebx和%esi是否相等(因为6个数的地址是从0x18(%esp)到0x2c(%esp),所以这里就相当于判断是否已经有6个数进入循环判断了),如果相等则跳出循环,否则再次进入循环。

综上分析,输入的6个数应该满足后一个数为前一个数的2倍,而因为第一个数只能为1,因此phase_2的通关密码为:1 2 4 8 16 32

三、 phase_3函数

① 反汇编代码
disassemble phase_3           //得到phase_3的反汇编代码

Dump of assembler code for function phase_3:
   0x08048bbc <+0>:		sub    $0x2c,%esp						#栈帧准备
   0x08048bbf <+3>:		lea    0x1c(%esp),%eax					
   0x08048bc3 <+7>:		mov    %eax,0xc(%esp)					#M[0xc(%esp)] = 0x1c(%esp)
   0x08048bc7 <+11>:	lea    0x18(%esp),%eax					
   0x08048bcb <+15>:	mov    %eax,0x8(%esp)					#M[0x8(%esp)] = 0x18(%esp)
   0x08048bcf <+19>:	movl   $0x804a3cb,0x4(%esp)				#M[0x4(%esp)] = 0x804a3cb    "%d %d"
   0x08048bd7 <+27>:	mov    0x30(%esp),%eax					#%eax = M[0x30(%esp)]
   0x08048bdb <+31>:	mov    %eax,(%esp)						#M[(%esp)] = M[0x30(%esp)]
   0x08048bde <+34>:	call   0x8048870 <__isoc99_sscanf@plt>
   0x08048be3 <+39>:	cmp    $0x1,%eax						#比较%eax和1
   0x08048be6 <+42>:	jg     0x8048bed <phase_3+49>			#如果%eax大于1则不会爆炸
   0x08048be8 <+44>:	call   0x80490f6 <explode_bomb>
   0x08048bed <+49>:	cmpl   $0x7,0x18(%esp)					#比较M[0x18(%esp)]0x7
   0x08048bf2 <+54>:	ja     0x8048c5a <phase_3+158>			#如果M[0x18(%esp)]小于等于0x7则不会爆炸
   0x08048bf4 <+56>:	mov    0x18(%esp),%eax					#%eax = M[0x18(%esp)]
   0x08048bf8 <+60>:	jmp    *0x804a1e0(,%eax,4)				#跳转
   0x08048bff <+67>:	mov    $0x0,%eax						#%eax = 0
   0x08048c04 <+72>:	jmp    0x8048c0b <phase_3+79>			#跳转
   0x08048c06 <+74>:	mov    $0x369,%eax						
   0x08048c0b <+79>:	sub    $0x1b1,%eax						#%eax -= 0x1b1
   0x08048c10 <+84>:	jmp    0x8048c17 <phase_3+91>			#跳转
   0x08048c12 <+86>:	mov    $0x0,%eax
   0x08048c17 <+91>:	add    $0x3cc,%eax						#%eax += 0x3cc					
   0x08048c1c <+96>:	jmp    0x8048c23 <phase_3+103>			#跳转
   0x08048c1e <+98>:	mov    $0x0,%eax
   0x08048c23 <+103>:	sub    $0x2c6,%eax						#%eax -= 0x2c6
   0x08048c28 <+108>:	jmp    0x8048c2f <phase_3+115>			#跳转
   0x08048c2a <+110>:	mov    $0x0,%eax
   0x08048c2f <+115>:	add    $0x2c6,%eax						#%eax += 0x2c6
   0x08048c34 <+120>:	jmp    0x8048c3b <phase_3+127>			#跳转
   0x08048c36 <+122>:	mov    $0x0,%eax
   0x08048c3b <+127>:	sub    $0x2c6,%eax						#%eax -= 0x2c6
   0x08048c40 <+132>:	jmp    0x8048c47 <phase_3+139>			#跳转
   0x08048c42 <+134>:	mov    $0x0,%eax
   0x08048c47 <+139>:	add    $0x2c6,%eax						#%eax += 0x2c6
   0x08048c4c <+144>:	jmp    0x8048c53 <phase_3+151>			#跳转
   0x08048c4e <+146>:	mov    $0x0,%eax
   0x08048c53 <+151>:	sub    $0x2c6,%eax						#%eax -= 0x2c6
   0x08048c58 <+156>:	jmp    0x8048c64 <phase_3+168>			#跳转
   0x08048c5a <+158>:	call   0x80490f6 <explode_bomb>
   0x08048c5f <+163>:	mov    $0x0,%eax
   0x08048c64 <+168>:	cmpl   $0x5,0x18(%esp)					#比较M[0x18(%esp)]0x5
   0x08048c69 <+173>:	jg     0x8048c71 <phase_3+181>			#如果小于等于5则不爆炸
   0x08048c6b <+175>:	cmp    0x1c(%esp),%eax					#比较%eax和M[0x1c(%esp)]
   0x08048c6f <+179>:	je     0x8048c76 <phase_3+186>			#如果相等则不爆炸
   0x08048c71 <+181>:	call   0x80490f6 <explode_bomb>
   0x08048c76 <+186>:	add    $0x2c,%esp
   0x08048c79 <+189>:	ret    
End of assembler dump.

② 函数分析

  • :  将0x804a3cb存储在0x4(%esp)中,使用x/s 0x804a3cb命令查看得到"%d %d",因此得出要输入两个整数参数
  • - :  调用< __isoc99_sscanf@plt>输入函数,将输入的参数个数存储在%eax中,并比较%eax和1,如果%eax大于1则不会爆炸,也就是要求输入的参数个数要大于1
  • - :  将0x18(%esp)在内存中的值(也就是第一个参数)和7进行比较,当其小于等于7时则不会爆炸
  • - :  这段代码为一个跳转表,根据上一步得到的地址跳到相应的语句并执行相应的操作,将最终结果存储在%eax寄存器中
  • - :  将第一个参数和5进行比较,当其小于等于5时则不会爆炸,这里得出第一个数必须小于等于5
  • - :  将%eax(跳转表中的运算结果)与0x1c(%esp)在内存中的值进行比较,如果两个值相等则不会爆炸,这里要求第二个参数与运算结果相等。

综上分析,要输入两个整数参数,第一个参数要求小于等于5,第二个参数和跳转表中的运算结果相等。

③ 操作步骤
x/s 0x804a3cb            //查看0x804a3cb在内存中的内容
在这里插入图片描述
假设第一个参数为0,因此先跳转到0x804a1e0在内存中的值所对应的地址,如下所示
p/x (int *)(*0x804a1e0)            //查看0x804a1e0在内存中的值
在这里插入图片描述
可以看到下一个地址为0x8048c06,即跳转到了,根据代码得到%eax = 0x369 – 0x1b1 = 0x1b8,下一步跳转到,又得到%eax += 0x3cc,下一步跳转到,得到%eax -= 0x2c6,下一步跳转到,得到%eax += 0x2c6,下一步跳转到,得到%eax -= 0x2c6,下一步跳转到,得到%eax += 0x2c6,下一步跳转到,得到%eax -= 0x2c6。因此得到%eax的最终结果为0x2be,也就是十进制的704。这里我们得到phase_3的一个通关密码为0 702,同样地可以得到剩下的5组密码:
1 -171
2 262
3 -710
4 0
5 -710

四、 phase_4函数

① 反汇编代码
disassemble phase_4            //得到phase_4的反汇编代码

Dump of assembler code for function phase_4:
   0x08048cd7 <+0>:		sub    $0x2c,%esp						#栈帧准备
   0x08048cda <+3>:		lea    0x18(%esp),%eax
   0x08048cde <+7>:		mov    %eax,0xc(%esp)					#M[0xc(%esp)] = 0x18(%esp)
   0x08048ce2 <+11>:	lea    0x1c(%esp),%eax
   0x08048ce6 <+15>:	mov    %eax,0x8(%esp)					#M[0x8(%esp)] = 0x1c(%esp)
   0x08048cea <+19>:	movl   $0x804a3cb,0x4(%esp)				#M[0x4(%esp)] = 0x804a3cb  "%d %d"
   0x08048cf2 <+27>:	mov    0x30(%esp),%eax					
   0x08048cf6 <+31>:	mov    %eax,(%esp)						#M[(%esp)] = M[0x30(%esp)]
   0x08048cf9 <+34>:	call   0x8048870 <__isoc99_sscanf@plt>	#返回值为变量的个数,并存储在%eax中
   0x08048cfe <+39>:	cmp    $0x2,%eax						#比较%eax和0x2
   0x08048d01 <+42>:	jne    0x8048d11 <phase_4+58>			#如果%eax等于2则不会爆炸
   0x08048d03 <+44>:	mov    0x18(%esp),%eax					#%eax = M[0x18(%esp)]
   0x08048d07 <+48>:	cmp    $0x1,%eax						#比较%eax和0x1
   0x08048d0a <+51>:	jle    0x8048d11 <phase_4+58>			#如果%eax大于1则不会爆炸
   0x08048d0c <+53>:	cmp    $0x4,%eax						#比较%eax和0x4
   0x08048d0f <+56>:	jle    0x8048d16 <phase_4+63>			#如果%eax小于等于4则不会爆炸
   0x08048d11 <+58>:	call   0x80490f6 <explode_bomb>
   0x08048d16 <+63>:	mov    0x18(%esp),%eax					
   0x08048d1a <+67>:	mov    %eax,0x4(%esp)					#M[0x4(%esp)] = M[0x18(%esp)]
   0x08048d1e <+71>:	movl   $0x6,(%esp)						#M[(%esp)] = 0x6
   0x08048d25 <+78>:	call   0x8048c7a <func4>
   0x08048d2a <+83>:	cmp    0x1c(%esp),%eax					#比较%eax和M[0x1c(%esp)]
   0x08048d2e <+87>:	je     0x8048d35 <phase_4+94>			#如果相等则不爆炸
   0x08048d30 <+89>:	call   0x80490f6 <explode_bomb>
   0x08048d35 <+94>:	add    $0x2c,%esp
   0x08048d38 <+97>:	ret    
End of assembler dump.

disassemble func4 //得到func4的反汇编代码

Dump of assembler code for function func4:
   0x08048c7a <+0>:		sub    $0x1c,%esp
   0x08048c7d <+3>:		mov    %ebx,0x10(%esp)
   0x08048c81 <+7>:		mov    %esi,0x14(%esp)
   0x08048c85 <+11>:	mov    %edi,0x18(%esp)
   0x08048c89 <+15>:	mov    0x20(%esp),%esi
   0x08048c8d <+19>:	mov    0x24(%esp),%ebx
   0x08048c91 <+23>:	test   %esi,%esi						
   0x08048c93 <+25>:	jle    0x8048cc0 <func4+70>				#如果%esi小于等于0,跳转,结果返回0
   0x08048c95 <+27>:	cmp    $0x1,%esi
   0x08048c98 <+30>:	je     0x8048cc5 <func4+75>				#如果%esi等于1,跳转
   0x08048c9a <+32>:	mov    %ebx,0x4(%esp)					#M[0x4(%esp)] = %ebx
   0x08048c9e <+36>:	lea    -0x1(%esi),%eax
   0x08048ca1 <+39>:	mov    %eax,(%esp)						#M[(%esp)] = %esi - 1
   0x08048ca4 <+42>:	call   0x8048c7a <func4>
   0x08048ca9 <+47>:	lea    (%eax,%ebx,1),%edi
   0x08048cac <+50>:	mov    %ebx,0x4(%esp)
   0x08048cb0 <+54>:	sub    $0x2,%esi
   0x08048cb3 <+57>:	mov    %esi,(%esp)
   0x08048cb6 <+60>:	call   0x8048c7a <func4>
   0x08048cbb <+65>:	lea    (%edi,%eax,1),%ebx
   0x08048cbe <+68>:	jmp    0x8048cc5 <func4+75>
   0x08048cc0 <+70>:	mov    $0x0,%ebx
   0x08048cc5 <+75>:	mov    %ebx,%eax
   0x08048cc7 <+77>:	mov    0x10(%esp),%ebx
   0x08048ccb <+81>:	mov    0x14(%esp),%esi
   0x08048ccf <+85>:	mov    0x18(%esp),%edi
   0x08048cd3 <+89>:	add    $0x1c,%esp
   0x08048cd6 <+92>:	ret    
End of assembler dump.

② 函数分析
phase_4函数分析

  • :  进行函数栈帧准备
  • - :  分别将0xc(%esp)和0x8(%esp)在内存中的值存储在0x18(%esp)和0x1c(%esp)中
  • :  将0x804a3cb存储在0x4(%esp)中,使用x/s 0x804a3cb命令查看得到"%d %d",因此得出要输入2个整数参数
  • - :  将0x30(%esp)在内存中的值存储在(%esp)中
  • - :  调用__isoc99_sscanf@plt输入函数,将输入的参数个数存储在%eax中,并比较%eax和2,如果%eax等于2则不会爆炸,也就是要求输入的参数个数要等于2
  • - :  判断0x18(%esp)在内存中的值,如果该值大于1且小于等于4则不会爆炸,这里得出第二个数只能为2,3,4
  • - :  将0x18(%esp)在内存中的值(也就是第二个数)存储在0x4(%esp)中,将6存储在(%esp)中
  • :调用函数,  并将返回值存储在%eax寄存器中
  • - :  比较%eax的值与第一个参数的值,如果两者相等则不会爆炸

func4函数分析
根据func4的汇编代码,可以这是一个递归函数,下面给出通过汇编代码得到的C代码

int func4(int x, int y) {
	if (x <= 0){
		return 0;
	}
	if (x == 1){
		return y;
	}
	return func4(x-1, y) + func4(x-2, y) + y;
}

该函数的x初始值即为汇编代码中的6,y的初始值就是我们输入的第二个参数

综上分析,我们输入的第二个参数只能为2,3,4,当值为2时,调用func4(6, 2),得到结果为40;当值为3时,调用func4(6, 3),得到结果为60;当值为4时,调用func4(6, 4),得到结果为80。因此phase_4的 通关密码为(有3组):40 2,(或)60 3,(或)80 4

五、 phase_5函数

① 反汇编代码
disassemble phase_5           //得到phase_5的反汇编代码

Dump of assembler code for function phase_5:
   0x08048d39 <+0>:		sub    $0x2c,%esp
   0x08048d3c <+3>:		lea    0x1c(%esp),%eax					#第二个参数
   0x08048d40 <+7>:		mov    %eax,0xc(%esp)					#M[0xc(%esp)] = 0x1c(%esp)
   0x08048d44 <+11>:	lea    0x18(%esp),%eax					#第一个参数
   0x08048d48 <+15>:	mov    %eax,0x8(%esp)					#M[0x8(%esp)] = 0x18(%esp)
   0x08048d4c <+19>:	movl   $0x804a3cb,0x4(%esp)
   0x08048d54 <+27>:	mov    0x30(%esp),%eax
   0x08048d58 <+31>:	mov    %eax,(%esp)
   0x08048d5b <+34>:	call   0x8048870 <__isoc99_sscanf@plt>
   0x08048d60 <+39>:	cmp    $0x1,%eax
   0x08048d63 <+42>:	jg     0x8048d6a <phase_5+49>			#参数个数大于1则不会爆炸
   0x08048d65 <+44>:	call   0x80490f6 <explode_bomb>
   0x08048d6a <+49>:	mov    0x18(%esp),%eax
   0x08048d6e <+53>:	and    $0xf,%eax						#参数只保留低4位,其他位清0
   0x08048d71 <+56>:	mov    %eax,0x18(%esp)					#0x18(%esp)只保留了低40x08048d75 <+60>:	cmp    $0xf,%eax
   0x08048d78 <+63>:	je     0x8048da4 <phase_5+107>			#低4位不为全1则不会爆炸
   0x08048d7a <+65>:	mov    $0x0,%ecx						
   0x08048d7f <+70>:	mov    $0x0,%edx
   
   0x08048d84 <+75>:	add    $0x1,%edx
   0x08048d87 <+78>:	mov    0x804a200(,%eax,4),%eax
   0x08048d8e <+85>:	add    %eax,%ecx
   0x08048d90 <+87>:	cmp    $0xf,%eax
   0x08048d93 <+90>:	jne    0x8048d84 <phase_5+75>			#低4位不为全1则跳转
   
   0x08048d95 <+92>:	mov    %eax,0x18(%esp)
   0x08048d99 <+96>:	cmp    $0xf,%edx
   0x08048d9c <+99>:	jne    0x8048da4 <phase_5+107>			#%edx等于0xf则不会爆炸
   0x08048d9e <+101>:	cmp    0x1c(%esp),%ecx
   0x08048da2 <+105>:	je     0x8048da9 <phase_5+112>			#%ecx等于M[0x1c(%esp)]则不会爆炸
   0x08048da4 <+107>:	call   0x80490f6 <explode_bomb>
   0x08048da9 <+112>:	add    $0x2c,%esp
   0x08048dac <+115>:	ret    
End of assembler dump.

② 函数分析

  • :  进行函数栈帧准备
  • - :  分别将0xc(%esp)和0x8(%esp)在内存中的值存储在0x1c(%esp)和0x18(%esp)中
  • :  将0x804a3cb存储在0x4(%esp)中,使用x/s 0x804a3cb命令查看得到"%d %d",因此得出要输入2个整数参数
  • - :  将0x30(%esp)在内存中的值存储在(%esp)中
  • - :  调用__isoc99_sscanf@plt输入函数,将输入的参数个数存储在%eax中,并比较%eax和1,如果%eax大于1则不会爆炸,也就是要求输入的参数个数要大于1
  • - :  保留输入的第一个参数的二进制串的低四位,并将其存储在%eax寄存器,同时将值存储在0x18(%esp)中
  • - :  比较%eax的值和0xf,两者不相等则不会爆炸
  • - :  将%ecx和%edx寄存器清零
  • - :  这段代码相当于一个do-while循环,首相将%edx加1,然后取出(0x804a200+4*%eax)在内存中的值赋给%eax,并让%ecx加上%eax,最后判断%eax的值是否0xf,如果不是则继续循环
  • :  将%eax的值存储在0x18(%esp)中
  • - :  比较%edx和0xf,如果两者相等则不会爆炸,这里的出循环次数应达到15次
  • - :  比较%ecx和0x1c(%esp)在内存中的值,如果两者相等则不会爆炸,这里要求输入的第二个参数和上一步的求和结果相等

综上分析,该函数需要输入两个整数参数,输入要求满足第一个参数的低四位不全为1,循环执行次数为15次,且求和结果和输入的第二个参数相等

③ 操作步骤
x/s 0x804a3cb //查看0x804a3cb在内存中的内容在这里插入图片描述
假设输入的第一个数为0,下面执行循环操作(假设不会跳出循环)
首先得到%edx = 1,%eax = 0xa
在这里插入图片描述
继续循环得到%edx = 2,%eax = 0x1
在这里插入图片描述
继续循环得到%edx = 3,%eax = 0x2
在这里插入图片描述
继续循环得到%edx = 4,%eax = 0xe
在这里插入图片描述
继续循环得到%edx = 5,%eax = 0x6
在这里插入图片描述
继续循环得到%edx = 6,%eax = 0xf
在这里插入图片描述
继续循环得到%edx = 7,%eax = 0x5
在这里插入图片描述
继续循环得到%edx = 8,%eax = 0xc
在这里插入图片描述
继续循环得到%edx = 9,%eax = 0x3
在这里插入图片描述
继续循环得到%edx = 10,%eax = 0x7
在这里插入图片描述
继续循环得到%edx = 11,%eax = 0xb
在这里插入图片描述
继续循环得到%edx = 12,%eax = 0xd
在这里插入图片描述
继续循环得到%edx = 13,%eax = 0x9
在这里插入图片描述
继续循环得到%edx = 14,%eax = 0x4
在这里插入图片描述
继续循环得到%edx = 15,%eax = 0x8
在这里插入图片描述
继续循环得到%edx = 16,%eax = 0x0
在这里插入图片描述
继续循环得到%edx = 17,%eax = 0xa
在这里插入图片描述
这里可以看到,如果循环一直执行,得到的%eax的值也会循环的变化:
0xa->0x1->0x2->0xe->0x6->0xf->0x5->0xc->0x3->0x7->0xb->0xd->0x9->0x4->0x8->0x0->0xa

综上可得,因为最终的%edx的值要等于15才不会爆炸,所以%eax的初始值应该为5,也就是第一个参数的低四位的必须为5。而又因为第二个参数的值应该和最终的求和结果相同,因此第二个参数的值应该为:
0xc+0x3+0x7+0xb+0xd+0x9+0x4+0x8+0x0+0xa+0x1+0x2+0xe+0x6+0xf = 115

综上分析,phase_5的一个通关密码为:5 115

六、 phase_6函数

① 反汇编代码
disassemble phase_6            //得到phase_6的反汇编代码

Dump of assembler code for function phase_6:
   0x08048dad <+0>:		push   %esi
   0x08048dae <+1>:		push   %ebx
   0x08048daf <+2>:		sub    $0x44,%esp					#栈帧准备
   0x08048db2 <+5>:		lea    0x10(%esp),%eax
   0x08048db6 <+9>:		mov    %eax,0x4(%esp)				#M[0x4(%esp)] = 0x10(%esp)
   0x08048dba <+13>:	mov    0x50(%esp),%eax
   0x08048dbe <+17>:	mov    %eax,(%esp)					#M[(esp)] = M[0x50(%esp)]
   0x08048dc1 <+20>:	call   0x804922b <read_six_numbers>	#读取6个数字
   0x08048dc6 <+25>:	mov    $0x0,%esi					#%esi = 0
   
   0x08048dcb <+30>:	mov    0x10(%esp,%esi,4),%eax		
   0x08048dcf <+34>:	sub    $0x1,%eax
   0x08048dd2 <+37>:	cmp    $0x5,%eax
   0x08048dd5 <+40>:	jbe    0x8048ddc <phase_6+47>		#如果%eax小于等于0x5则不会爆炸
   0x08048dd7 <+42>:	call   0x80490f6 <explode_bomb>
   0x08048ddc <+47>:	add    $0x1,%esi					#%esi += 1
   0x08048ddf <+50>:	cmp    $0x6,%esi
   0x08048de2 <+53>:	je     0x8048e17 <phase_6+106>		#如果%esi等于6则跳转
   0x08048de4 <+55>:	mov    %esi,%ebx
   0x08048de6 <+57>:	mov    0x10(%esp,%ebx,4),%eax		#该循环的功能是判断后面的参数与前面的参数是否相等,不相等才不会爆炸
   0x08048dea <+61>:	cmp    %eax,0xc(%esp,%esi,4)
   0x08048dee <+65>:	jne    0x8048df5 <phase_6+72>		#后一个参数和前一个参数不相等则不会爆炸
   0x08048df0 <+67>:	call   0x80490f6 <explode_bomb>
   0x08048df5 <+72>:	add    $0x1,%ebx					
   0x08048df8 <+75>:	cmp    $0x5,%ebx
   0x08048dfb <+78>:	jle    0x8048de6 <phase_6+57>		#%ebx小于等于5则跳转
   0x08048dfd <+80>:	jmp    0x8048dcb <phase_6+30>		#无条件跳转
   #按照输入的顺序依次存储数据
   0x08048dff <+82>:	mov    0x8(%edx),%edx
   0x08048e02 <+85>:	add    $0x1,%eax
   0x08048e05 <+88>:	cmp    %ecx,%eax
   0x08048e07 <+90>:	jne    0x8048dff <phase_6+82>		#如果%eax不等于%ecx则跳转, a = %eax停止跳转
   0x08048e09 <+92>:	mov    %edx,0x28(%esp,%esi,4)
   0x08048e0d <+96>:	add    $0x1,%ebx
   0x08048e10 <+99>:	cmp    $0x6,%ebx
   0x08048e13 <+102>:	jne    0x8048e1c <phase_6+111>		#如果%ebx不等于0x6则跳转
   0x08048e15 <+104>:	jmp    0x8048e33 <phase_6+134>		#无条件跳转
   0x08048e17 <+106>:	mov    $0x0,%ebx
   0x08048e1c <+111>:	mov    %ebx,%esi
   0x08048e1e <+113>:	mov    0x10(%esp,%ebx,4),%ecx
   0x08048e22 <+117>:	mov    $0x1,%eax
   0x08048e27 <+122>:	mov    $0x804c13c,%edx				#%edx = 0x804c13c
   0x08048e2c <+127>:	cmp    $0x1,%ecx					
   0x08048e2f <+130>:	jg     0x8048dff <phase_6+82>		#如果%ecx大于0x1则跳转
   0x08048e31 <+132>:	jmp    0x8048e09 <phase_6+92>
   
   0x08048e33 <+134>:	mov    0x28(%esp),%ebx
   0x08048e37 <+138>:	mov    0x2c(%esp),%eax
   0x08048e3b <+142>:	mov    %eax,0x8(%ebx)
   0x08048e3e <+145>:	mov    0x30(%esp),%edx
   0x08048e42 <+149>:	mov    %edx,0x8(%eax)
   0x08048e45 <+152>:	mov    0x34(%esp),%eax
   0x08048e49 <+156>:	mov    %eax,0x8(%edx)
   0x08048e4c <+159>:	mov    0x38(%esp),%edx
   0x08048e50 <+163>:	mov    %edx,0x8(%eax)
   0x08048e53 <+166>:	mov    0x3c(%esp),%eax
   0x08048e57 <+170>:	mov    %eax,0x8(%edx)				#这里把所有结点结合构成链表
   0x08048e5a <+173>:	movl   $0x0,0x8(%eax)				#指向null
   
   0x08048e61 <+180>:	mov    $0x5,%esi
   0x08048e66 <+185>:	mov    0x8(%ebx),%eax
   0x08048e69 <+188>:	mov    (%eax),%edx
   0x08048e6b <+190>:	cmp    %edx,(%ebx)
   0x08048e6d <+192>:	jge    0x8048e74 <phase_6+199>		#如果M[(%ebx)]大于等于%edx则不会爆炸
   0x08048e6f <+194>:	call   0x80490f6 <explode_bomb>
   0x08048e74 <+199>:	mov    0x8(%ebx),%ebx
   0x08048e77 <+202>:	sub    $0x1,%esi
   0x08048e7a <+205>:	jne    0x8048e66 <phase_6+185>		#如果%esi不等于0x1则跳转
   
   0x08048e7c <+207>:	add    $0x44,%esp
   0x08048e7f <+210>:	pop    %ebx
   0x08048e80 <+211>:	pop    %esi
   0x08048e81 <+212>:	ret    
End of assembler dump.

② 函数分析

  • - :  进行函数栈帧准备,并读取6个数字,将%esi寄存器清零,而且可得到第一个数字的地址为0x10(%esp),进而得到6个数字的地址从0x10(%esp)到0x24(%esp)
  • - :  这段代码是一个循环,根据对代码的分析可以得出它的作用为:循环检测输入的每个数字是否小于等于6,如果不满足则会爆炸,然后再判断6个数字是否两两不相等,如果相等则会爆炸,因此,这里得到6个数字应该全都小于等于6,且两两不相等。
  • - :  根据对代码的分析可以得出,这段代码的作用为:内存中存有6个结构体,每个结构体包括3个部分(两个整数和一个地址),该段代码根据我们输入的6个数字的顺序对其进行相应的排序,同时将数据存储到从0x28(%esp)到0x3c(%esp)中(具体过程见“操作步骤”) - :这段代码将前一个结构体里的地址指向后一个结构体的首地址,最后一个结构体指向null,因此最终形成一个单向链表。
  • - :  这段代码对上述步骤中形成的链表进行判断,如果前面一个结构体里的其中一个数据大于等于后一个结构体里的数据则不会爆炸,因此这里得出我们应该按照这个数据递减的顺序进行输入

综上分析,我们输入的6个数字一定要小于等于6,且两两不相等,而且最终的输入顺序必须按照内存中的结构体里的某个数据的递减顺序来进行输入。

③ 操作步骤
注意到行出现了0x804c13c这个地址,因此可以知道这就是第一个结构体的地址,因此我们可以得到如下图所示内容
p/x *0x804c13c@3
深入了解计算机系统——实验三(Bomb Lab)(详解)_第1张图片
可以看到内存中有6个结构体,第二个数据全部小于等于6,可以知道就是我们应该输入的6个参数,最后一个就是一个地址常量,进而可以知道phase_6里的排序应该就是按照结构体里的第一个数据进行,因为要递减的顺序,因此顺序为:0x34e->0x320->27e->212->b4->71,因此与之相对应,我们的输入参数的顺序为:3 1 6 5 2 4,这也就是phase_6的通关密码。

七、 隐藏关卡

① 反汇编代码
disassemble phase_defused           //得到phase_defused的反汇编代码

Dump of assembler code for function phase_defused:
   0x0804927b <+0>:		sub    $0x8c,%esp						#栈帧准备
   0x08049281 <+6>:		mov    %gs:0x14,%eax					#用于验证堆栈没有爆炸或已被证实
   0x08049287 <+12>:	mov    %eax,0x7c(%esp)					
   0x0804928b <+16>:	xor    %eax,%eax						#%eax = 0
   0x0804928d <+18>:	cmpl   $0x6,0x804c3cc					#""
   0x08049294 <+25>:	jne    0x8049308 <phase_defused+141>	
   0x08049296 <+27>:	lea    0x2c(%esp),%eax
   0x0804929a <+31>:	mov    %eax,0x10(%esp)
   0x0804929e <+35>:	lea    0x28(%esp),%eax
   0x080492a2 <+39>:	mov    %eax,0xc(%esp)
   0x080492a6 <+43>:	lea    0x24(%esp),%eax
   0x080492aa <+47>:	mov    %eax,0x8(%esp)
   0x080492ae <+51>:	movl   $0x804a3d1,0x4(%esp)				#"%d %d %s"
   0x080492b6 <+59>:	movl   $0x804c4d0,(%esp)				#""
   0x080492bd <+66>:	call   0x8048870 <__isoc99_sscanf@plt>
   0x080492c2 <+71>:	cmp    $0x3,%eax
   0x080492c5 <+74>:	jne    0x80492fc <phase_defused+129>	#参数个数不等于3则跳转
   0x080492c7 <+76>:	movl   $0x804a3da,0x4(%esp)				#"DrEvil"
   0x080492cf <+84>:	lea    0x2c(%esp),%eax
   0x080492d3 <+88>:	mov    %eax,(%esp)
   0x080492d6 <+91>:	call   0x8048fe4 <strings_not_equal>	#比较两个字符串,返回值存储在%eax中
   0x080492db <+96>:	test   %eax,%eax
   0x080492dd <+98>:	jne    0x80492fc <phase_defused+129>	#如果不等于0则跳转
   0x080492df <+100>:	movl   $0x804a2a0,(%esp)				#"Curses, you've found the secret phase!"
   0x080492e6 <+107>:	call   0x8048800 <puts@plt>
   0x080492eb <+112>:	movl   $0x804a2c8,(%esp)				#"But finding it and solving it are quite different..."
   0x080492f2 <+119>:	call   0x8048800 <puts@plt>
   0x080492f7 <+124>:	call   0x8048ed3 <secret_phase>
   0x080492fc <+129>:	movl   $0x804a300,(%esp)				#"Congratulations! You've defused the bomb!"
   0x08049303 <+136>:	call   0x8048800 <puts@plt>
   0x08049308 <+141>:	mov    0x7c(%esp),%eax
   0x0804930c <+145>:	xor    %gs:0x14,%eax
   0x08049313 <+152>:	je     0x804931a <phase_defused+159>
   0x08049315 <+154>:	call   0x80487d0 <__stack_chk_fail@plt>
   0x0804931a <+159>:	add    $0x8c,%esp
   0x08049320 <+165>:	ret    
End of assembler dump.

disassemble secret_phase            //得到secret_phase的反汇编代码

Dump of assembler code for function secret_phase:
   0x08048ed3 <+0>:		push   %ebx
   0x08048ed4 <+1>:		sub    $0x18,%esp
   0x08048ed7 <+4>:		call   0x804911d <read_line>
   0x08048edc <+9>:		movl   $0xa,0x8(%esp)
   0x08048ee4 <+17>:	movl   $0x0,0x4(%esp)
   0x08048eec <+25>:	mov    %eax,(%esp)
   0x08048eef <+28>:	call   0x80488e0 <strtol@plt>
   0x08048ef4 <+33>:	mov    %eax,%ebx
   0x08048ef6 <+35>:	lea    -0x1(%eax),%eax
   0x08048ef9 <+38>:	cmp    $0x3e8,%eax
   0x08048efe <+43>:	jbe    0x8048f05 <secret_phase+50>	#%eax小于等于1001则不会爆炸
   0x08048f00 <+45>:	call   0x80490f6 <explode_bomb>
   0x08048f05 <+50>:	mov    %ebx,0x4(%esp)
   0x08048f09 <+54>:	movl   $0x804c088,(%esp)			
   0x08048f10 <+61>:	call   0x8048e82 <fun7>
   0x08048f15 <+66>:	test   %eax,%eax
   0x08048f17 <+68>:	je     0x8048f1e <secret_phase+75>	#%eax等于0则不会爆炸
   0x08048f19 <+70>:	call   0x80490f6 <explode_bomb>
   0x08048f1e <+75>:	movl   $0x804a240,(%esp)			#"Wow! You've defused the secret stage!"
   0x08048f25 <+82>:	call   0x8048800 <puts@plt>
   0x08048f2a <+87>:	call   0x804927b <phase_defused>
   0x08048f2f <+92>:	add    $0x18,%esp
   0x08048f32 <+95>:	pop    %ebx
   0x08048f33 <+96>:	ret    
End of assembler dump.

disassemble fun7           //得到fun7的反汇编代码

Dump of assembler code for function fun7:
   0x08048e82 <+0>:		push   %ebx
   0x08048e83 <+1>:		sub    $0x18,%esp			#栈帧准备
   0x08048e86 <+4>:		mov    0x20(%esp),%edx		#%edx = 0x804c088
   0x08048e8a <+8>:		mov    0x24(%esp),%ecx		#%ecx = 自己输入的数
   0x08048e8e <+12>:	test   %edx,%edx
   0x08048e90 <+14>:	je     0x8048ec9 <fun7+71>	#%edx等于0则跳转,返回0xffffffff
   0x08048e92 <+16>:	mov    (%edx),%ebx			#%ebx = 0x24
   0x08048e94 <+18>:	cmp    %ecx,%ebx			
   0x08048e96 <+20>:	jle    0x8048eab <fun7+41>	#%ebx小于等于%ecx则跳转
   0x08048e98 <+22>:	mov    %ecx,0x4(%esp)
   0x08048e9c <+26>:	mov    0x4(%edx),%eax
   0x08048e9f <+29>:	mov    %eax,(%esp)
   0x08048ea2 <+32>:	call   0x8048e82 <fun7>
   0x08048ea7 <+37>:	add    %eax,%eax
   0x08048ea9 <+39>:	jmp    0x8048ece <fun7+76>
   0x08048eab <+41>:	mov    $0x0,%eax			#%eax = 0
   0x08048eb0 <+46>:	cmp    %ecx,%ebx
   0x08048eb2 <+48>:	je     0x8048ece <fun7+76>	#%ebx小于等于%ecx则跳转,返回0
   0x08048eb4 <+50>:	mov    %ecx,0x4(%esp)
   0x08048eb8 <+54>:	mov    0x8(%edx),%eax
   0x08048ebb <+57>:	mov    %eax,(%esp)
   0x08048ebe <+60>:	call   0x8048e82 <fun7>
   0x08048ec3 <+65>:	lea    0x1(%eax,%eax,1),%eax
   0x08048ec7 <+69>:	jmp    0x8048ece <fun7+76>
   0x08048ec9 <+71>:	mov    $0xffffffff,%eax
   0x08048ece <+76>:	add    $0x18,%esp
   0x08048ed1 <+79>:	pop    %ebx
   0x08048ed2 <+80>:	ret    
End of assembler dump.

② 函数分析
phase_defused分析

  • :  进行函数栈帧准备
  • :  用于验证堆栈没有爆炸或以被证实
  • - :  判断是否已经通过前六关,如果没有则跳转
  • - :  通过gdb指令查看0x804a3d1在内存中的内容为"%d %d %s",因此得知秘密关卡的开启应该要输入两个数字和一个字符串。然后接下来判断参数个数是否为3,如果不相等则说明通过了前六关但是没有开启秘密关卡。
  • - :  通过gdb指令查看0x804a3da在内存中的内容为" DrEvil"。然后将输入的字符串与该字符串进行比较,如果不相等则直接跳转,说明秘密关卡开启失败。因此得到输入字符串" DrEvil"才会打开秘密关卡。
  • - :  输出两个字符串说明已经进入了秘密关卡
  • :  调用secret_phase函数
  • - :  输出字符串说明已经通过了所有关卡

secret_phase分析

  • - :  这里可以看到函数调用fun7函数,而且传进了两个参数,并将返回值存储在%eax寄存器
  • - :  对fun7的返回值进行判断,如果为0则不会爆炸,并进行最后的输出工作。因此这里得到调用fun7函数后返回值必须为0

fun7分析

  • < fun7> - < fun7+1>:  进行函数栈帧准备
  • < fun7+4> - < fun7+8>:  将0x804c088存入%edx寄存器,将输入的数存入%ecx寄存器
  • < fun7+12> - < fun7+14>:  判断%edx寄存器中的值是否为0,如果等于0则跳转,最终返回0xffffffff
  • < fun7+16>:  将0x804c088在内存中的值存入%ebx寄存器,查看0x804c088在内存中的值为0x24
  • < fun7+18> - < fun7+20>:  比较%ebx和%ecx的值,即比较0x24和输入的值,如果%ebx小于等于%ecx则跳转。

因为从对secret_phase函数的分析我们得知返回值必须为0,所以我们只需要查看fun7要怎么执行使其最终返回0即可。可以看到< fun7+41>行将0存储到%eax寄存器中,并且往下执行判断如果%ebx等于%ecx寄存器中的值,则会直接跳转最终返回0。因此只需使代码在执行时跳转到< fun7+41>行即可。往上查询代码可以看到< fun7+20>行可以跳转到该行。而< fun7+20>行的跳转条件为%ebx小于等于%ecx,即我们输入的数应该大于等于36。加之< fun7+48>行的跳转条件为%ebx等于%ecx。所以我们最终得到输入的数应该等于%ebx,也就是36。

综上我们得知,秘密关卡的开启方式为输入两个数字后面接着输入一个" DrEvil"字符串,因为我们直接输入前六关的正确通关密码时不会打开秘密关卡,可以推测秘密关卡的打开方式应该是在输入某一关的密码的同时进行输入,又因为要先输入两个数字,因此只有可能是3,4,5关中的某一个。而且打开秘密关卡后,分析得到秘密关卡的通关密码为36。

③ 操作步骤
phase_defused函数
x/s 0x804a3d1           //查看0x804a3d1在内存中的内容,得知秘密关卡的打开方式为输入两个数字和一个字符串
在这里插入图片描述
x/s 0x804a3da           //查看0x804a3da在内存中的内容,得知要输入的字符串为"DrEvil"
在这里插入图片描述
x/s 0x804a2a0           //查看0x804a2a0在内存中的内容
x/s 0x804a2c8           //查看0x804a2c8在内存中的内容
x/s 0x804a300           //查看0x804a300在内存中的内容
深入了解计算机系统——实验三(Bomb Lab)(详解)_第2张图片

secret_phase函数
x/s 0x804a240           //查看0x804a240在内存中的内容
在这里插入图片描述

因为3,4,5关都有可能是秘密关卡开启的地方,因此一个一个进行实验,发现只有第四关输入字符串才会开启秘密关卡。
第3关输入字符串:
深入了解计算机系统——实验三(Bomb Lab)(详解)_第3张图片
第4关输入字符串:
深入了解计算机系统——实验三(Bomb Lab)(详解)_第4张图片
第5关输入字符串:
深入了解计算机系统——实验三(Bomb Lab)(详解)_第5张图片

综上分析,秘密关卡的开启方式为:在第四关的两个参数输入后面输入"DrEvil"字符串,并且秘密关卡的通关秘密为36

最终运行结果

深入了解计算机系统——实验三(Bomb Lab)(详解)_第6张图片
运行过程如上所示,输入所有通关密码后成功通关所有关卡。
通关密码依次为:

Wow! Brazil is big.
1 2 4 8 16 32
0 7021 -1712 2623 -7104 05 -710)(不唯一)
40 2 DrEvil(60 3 DrEvil,80 4 DrEvil)(不唯一)
5 115
3 1 6 5 2 4
36

你可能感兴趣的:(计算机系统实验,c语言)