实验介绍

  • 使用课程知识拆除一个“Binary Bombs”来增强对程序的机器级表示、汇编语言、调试器和逆向工程等理解。
  • 一个“Binary Bombs”(二进制炸弹,简称炸弹)是一个Linux可执行C程序,包含phase1~phase6共6个阶段。
  • 炸弹运行各阶段要求输入一个字符串,若输入符合程序预期,该阶段炸弹被“拆除”,否则“爆炸” 。
  • 实验目标是你需要拆除尽可能多的炸弹。

实验步骤:

第一步:反汇编
将自己的炸弹文件复制到share目录下
解压炸弹文件,并 cd 到解压出的子目录
bomb进行反汇编并将汇编代码输出到asm.txt中:objdump –d bomb > asm.txt
查看生成的asm.txt文件,结合bomb.c中的代码框架和汇编代码,找出:函数之间的调用关系、传递的参数;
第二步:查看汇编源代码asm.txt文件
第三步:执行 gdb bomb,找到拆弹字符串。

一句话:通过阅读执行判断操作的汇编代码,得出正确输入。


实验内容:

<phase_1> :字符串比较
08048b33 <phase_1>:	#字符串比较
 8048b33:	83 ec 14             	sub    $0x14,%esp
 8048b36:	68 2c a1 04 08       	push    0x804a12c		#(gdb) x/s 0x804812c,便找到输入的字符串;
 8048b3b:ff 74 24 1c       		   	pushl  0x1c(%esp)
 8048b3f:	e8 7c 04 00 00       	call   8048fc0 <strings_not_equal>
 8048b44:	83 c4 10             	add    $0x10,%esp
 8048b47:	85 c0                	test   %eax,%eax		#判断两个字符串是否相等。
 8048b49:	74 05                	je     8048b50 <phase_1+0x1d>
 8048b4b:e8 ad 06 00 00     	  	call   80491fd <explode_bomb>
 8048b50:	83 c4 0c             	add    $0xc,%esp
 8048b53:	c3                   	ret    

gdb调试,找到判断时所对照的正确字符串,便是正确输入。
gdb bomb :进入bomb文件的调试界面;
x/s 0x804812c:找到该位置中存放的正确字符串。
找到的字符串为:When I get angry, Mr. Bigglesworth gets upset.


<phase_2>:循环
//例1:
08048b54 <phase_2>:	#循环
 8048b54:	56                   	push   %esi
 8048b55:	53                   	push   %ebx  	#没有修正ebp,还在main函数底部
 8048b56:	83 ec 2c             	sub    $0x2c,%esp
 8048b59:	65 a1 14 00 00 00    	mov    %gs:0x14,%eax	#gs段寄存器,保存段内地址
 8048b5f:	89 44 24 24          	mov    %eax,0x24(%esp)	 #检测栈帧有没有被破坏
 8048b63:	31 c0                	xor    %eax,%eax

 8048b65:	8d 44 24 0c          	lea    0xc(%esp),%eax
 8048b69:	50                   	push   %eax
 8048b6a:	ff 74 24 3c          	pushl  0x3c(%esp)			#修正到main函数栈帧中取参数,相当于ebp+8
 8048b6e:	e8 c7 06 00 00       	call   804923a <read_six_numbers> 	#从input分离出6个数字
 8048b73:	83 c4 10             	add    $0x10,%esp			
 8048b76:	83 7c 24 04 01       	cmpl   $0x1,0x4(%esp)		#判断第一个数是否为1
 8048b7b:74 05                		je     8048b82 <phase_2+0x2e>
 8048b7d:e8 7b 06 00 00       		call   80491fd <explode_bomb>
 8048b82:	8d 5c 24 04          	lea    0x4(%esp),%ebx		#现在ebx存的是数1所在位置
 8048b86:	8d 74 24 18          	lea    0x18(%esp),%esi		#设置esi为为ebx循环的终止位置
//循环部分
 8048b8a:	8b 03                	mov    (%ebx),%eax			#把ebx中存的数赋给eax
 8048b8c:	01 c0                	add    %eax,%eax			#eax+=eax;
 8048b8e:	39 43 04             	cmp    %eax,0x4(%ebx)		#看ebx+4位置上的数是否为eax;
 8048b91:	74 05                	je     8048b98 <phase_2+0x44>
 8048b93:	e8 65 06 00 00       	call   80491fd <explode_bomb>
 8048b98:	83 c3 04             	add    $0x4,%ebx			#找下一个位置上的数,ebx+=4;
 8048b9b:39 f3                		cmp    %esi,%ebx			#判断ebx是否已经到达终止位置,是否要继续循环
 8048b9d:75 eb                		jne    8048b8a <phase_2+0x36>	#继续的话,跳转到8048b8a;

 8048b9f:	8b 44 24 1c          	mov    0x1c(%esp),%eax
 8048ba3:	65 33 05 14 00 00 00 xor    %gs:0x14,%eax		#无影响,不管
 8048baa:	74 05                	je     8048bb1 <phase_2+0x5d>
 8048bac:	e8 df fb ff ff       	call   8048790 <__stack_chk_fail@plt>
 8048bb1:83 c4 24            	 	add    $0x24,%esp
 8048bb4:5b                 	  	pop    %ebx
 8048bb5:5e                   		pop    %esi
 8048bb6:c3                   		ret    

call 804923a <read_six_numbers> 可知,输入的字符串包括 6 个数。

8048b9d上的指令jne 8048b8a <phase_2+0x36>,又跳转回了上面的位置,可以知道这段是循环过程:
ebx 从第一个数所在的位置开始循环,esi 存放最后一个数所在的位置。
每次将 ebx 中的数赋值为到 eax 上,eax*=2,判断下一位置上的数是否和 eax 中的值相等。也就是说,后面一个数的位置一定是前面一个数的2倍。

而由 cmpl $0x1,0x4(%esp) 可知第一个数为1,那么最终的答案便为1 2 4 8 16 32

//例2:
08048b8c <phase2>:
 8048b8c:	55                   	push   %ebp
 8048b8d:89 e5                		mov    %esp,%ebp
 8048b8f:	56                   	push   %esi
 8048b90:	53                   	push   %ebx
 8048b91:	83 ec 28             	sub    $0x28,%esp
 8048b94:	8d 45 e0             	lea    -0x20(%ebp),%eax
 8048b97:	50                   	push   %eax
 8048b98:	ff 75 08             	pushl  0x8(%ebp)

 8048b9b:e8 ca 05 00 00      	 	call   804916a <readsixnumbers>
 8048ba0:	83 c4 10             	add    $0x10,%esp
 8048ba3:	83 7d e0 00          	cmpl   $0x0,-0x20(%ebp)	#判断第一个值是否为0
 8048ba7:	75 06                	jne    8048baf <phase2+0x23>
 8048ba9:	83 7d e4 01          	cmpl   $0x1,-0x1c(%ebp)	#判断第二个值是否为1
 8048bad:	74 05                	je     8048bb4 <phase2+0x28>
 8048baf:	e8 8e 05 00 00       	call   8049142 <explodebomb>
 8048bb4:8d 5d e0             		lea    -0x20(%ebp),%ebx	#将0的地址放入ebx
 8048bb7:8d 75 f0             		lea    -0x10(%ebp),%esi	#设置ebx的循环终止地址
 8048bba:	eb 07                	jmp    8048bc3 <phase2+0x37>	#跳过第一次循环的初始值判断部分,直接进行操作
//循环部分
 8048bbc:	83 c3 04             	add    $0x4,%ebx	#ebx+=4,地址更新
 8048bbf:	39 f3                	cmp    %esi,%ebx	#判断是否循环到终止位置了
 8048bc1:	74 11                	je     8048bd4 <phase2+0x48>	#如果是,退出循环
 8048bc3:	8b 43 04             	mov    0x4(%ebx),%eax	#把下一位置的值放到eax中
 8048bc6:	03 03                	add    (%ebx),%eax		#把当前位置的值累加到eax
 8048bc8:	39 43 08             	cmp    %eax,0x8(%ebx)	#判断下下位置是否是eax,即其前面两位置之和
 8048bcb:	74 ef                	je     8048bbc <phase2+0x30>	#如果是,继续循环;否则爆炸。

 8048bcd:	e8 70 05 00 00       	call   8049142 <explodebomb>
 8048bd2:eb e8                		jmp    8048bbc <phase2+0x30>
 8048bd4:8d 65 f8             		lea    -0x8(%ebp),%esp
 8048bd7:5b                   		pop    %ebx
 8048bd8:5e                   		pop    %esi
 8048bd9:5d                   		pop    %ebp
 8048bda:	c3                   	ret 

<phase_3>:跳转
08048bb7 <phase_3>:	#跳转
 8048bb7:83 ec 1c             	sub    $0x1c,%esp
 8048bba:	65 a1 14 00 00 00    mov    %gs:0x14,%eax
 8048bc0:	89 44 24 0c          	mov    %eax,0xc(%esp)
 8048bc4:	31 c0                	xor    %eax,%eax
 8048bc6:	8d 44 24 08          	lea    0x8(%esp),%eax
 8048bca:	50                   	push   %eax
 8048bcb:	8d 44 24 08          	lea    0x8(%esp),%eax
 8048bcf:	50                   	push   %eax
 8048bd0:68 8d a3 04 08       	push   $0x804a38d		#(gdb) x/s 0x804a38d,得到 "%d %d",可知输入的是数字,数字;
 8048bd5:ff 74 24 2c          	pushl  0x2c(%esp)

 8048bd9:e8 32 fc ff ff       	call   8048810 <__isoc99_sscanf@plt>
 8048bde:	83 c4 10             	add    $0x10,%esp
 8048be1:	83 f8 01             	cmp    $0x1,%eax		#判断输入数字的个数,一定要大于1;
 8048be4:	7f 05                	jg     8048beb <phase_3+0x34>
 8048be6:	e8 12 06 00 00       	call   80491fd <explode_bomb>
 8048beb:	83 7c 24 04 07       	cmpl   $0x7,0x4(%esp)	#判断输入的第一个值是否大于7,因为只有0~7八个分支;
 8048bf0:	77 3c                	ja     8048c2e <phase_3+0x77>
 8048bf2:	8b 44 24 04          	mov    0x4(%esp),%eax	
 8048bf6:	ff 24 85 8c a1 04 08 jmp    *0x804a18c(,%eax,4)		//#(gdb) p/x *0x804a18c,得到下一条指令的地址0x8048c3a;
# 因为一共8个跳转分支,所以需要得到其跳转位置。(gdb) x/8x 0x804a18c,得到其8个分支的位置
# 跳转表:
 8048bfd:	b8 e7 03 00 00       	mov    $0x3e7,%eax		#第1个,如果输入1的话,跳到此处;
 8048c02:	eb 3b                	jmp    8048c3f <phase_3+0x88>
 8048c04:	b8 83 03 00 00       	mov    $0x383,%eax		#第2个
 8048c09:	eb 34                	jmp    8048c3f <phase_3+0x88>
 8048c0b:	b8 9a 03 00 00       	mov    $0x39a,%eax		#第3个
 8048c10:	eb 2d                	jmp    8048c3f <phase_3+0x88>
 8048c12:	b8 f0 01 00 00       	mov    $0x1f0,%eax		#第4个
 8048c17:	eb 26                	jmp    8048c3f <phase_3+0x88>
 8048c19:	b8 31 01 00 00       	mov    $0x131,%eax		#第5个
 8048c1e:	eb 1f                	jmp    8048c3f <phase_3+0x88>
 8048c20:	b8 d2 03 00 00       	mov    $0x3d2,%eax		#第6个
 8048c25:	eb 18                	jmp    8048c3f <phase_3+0x88>
 8048c27:	b8 6b 01 00 00       	mov    $0x16b,%eax		#第7个
 8048c2c:	eb 11                	jmp    8048c3f <phase_3+0x88>
 8048c2e:	e8 ca 05 00 00       	call   80491fd <explode_bomb>
 8048c33:	b8 00 00 00 00       	mov    $0x0,%eax
 8048c38:	eb 05                	jmp    8048c3f <phase_3+0x88>
 8048c3a:	b8 ad 01 00 00       	mov    $0x1ad,%eax		#第0个 如果跳转到此处,eax赋值为(1ad)_16,即429;

 8048c3f:	3b 44 24 08          	cmp    0x8(%esp),%eax	#判断第二个数是否为eax。
 8048c43:	74 05                	je     8048c4a <phase_3+0x93>
 8048c45:	e8 b3 05 00 00       	call   80491fd <explode_bomb>
 8048c4a:	8b 44 24 0c          	mov    0xc(%esp),%eax
 8048c4e:	65 33 05 14 00 00 00 xor    %gs:0x14,%eax
 8048c55:	74 05                	je     8048c5c <phase_3+0xa5>
 8048c57:	e8 34 fb ff ff       	call   8048790 <__stack_chk_fail@plt>
 8048c5c:	83 c4 1c             	add    $0x1c,%esp
 8048c5f:	c3                   	ret    

给出了一个地址 push $0x804a38d,那么gdb调试 #(gdb) x/s 0x804a38d,得到 "%d %d",可知输入的是数字,数字,两个数。
cmpl $0x7,0x4(%esp)可以知道,输入的第一个数最大不超过7,说明只有0~7八个分支。
通过gdb调试指令:x/8x 0x804a18c,便能找到所有分支的地址。
在这里插入图片描述
可见,第0个分支的地址为 0x8048c3a,跳转到该位置,eax 赋值为(1ad)_16,即十进制的 429;所以最终的答案可以为:0 429
同理,可以找到第1分支,第2分支…上的数,答案不唯一。


<phase_4>:递归调用
08048c60 <func4>:
 8048c60:	57                   	push   %edi
 8048c61:	56                   	push   %esi
 8048c62:	53                   	push   %ebx
 8048c63:	8b 5c 24 10          	mov    0x10(%esp),%ebx	#x = 7
 8048c67:	8b 7c 24 14          	mov    0x14(%esp),%edi	#y = d2
 8048c6b:	85 db                	test   %ebx,%ebx
 8048c6d:	7e 2b                	jle    8048c9a <func4+0x3a>	#如果x<=0的话,返回0
 8048c6f:	89 f8                	mov    %edi,%eax
 8048c71:	83 fb 01             	cmp    $0x1,%ebx 		#否则,若x=1,返回y
 8048c74:	74 29                	je     8048c9f <func4+0x3f>	
 8048c76:	83 ec 08             	sub    $0x8,%esp
 8048c79:	57                   	push   %edi
 8048c7a:	8d 43 ff             	lea    -0x1(%ebx),%eax
 8048c7d:	50                   	push   %eax
 8048c7e:	e8 dd ff ff ff       	call   8048c60 <func4> 	#以x-1作为形参继续调用func(x-1,y);
 8048c83:	83 c4 08             	add    $0x8,%esp
 8048c86:	8d 34 07             	lea    (%edi,%eax,1),%esi	#esi = y + sub_8048C60(x - 1, y);
 8048c89:	57                   	push   %edi
 8048c8a:	83 eb 02             	sub    $0x2,%ebx
 8048c8d:	53                   	push   %ebx
 8048c8e:	e8 cd ff ff ff       	call   8048c60 <func4>	#以x-2作为形参继续调用func(x-2,y);
 8048c93:	83 c4 10             	add    $0x10,%esp
 8048c96:	01 f0                	add    %esi,%eax		#否则 x!=1, 返回 esi + func(x-2,y),即y + func(x-1,y) + func(x-2,y)。
 8048c98:	eb 05                	jmp    8048c9f <func4+0x3f>
 8048c9a:	b8 00 00 00 00       	mov    $0x0,%eax
 8048c9f:	5b                   	pop    %ebx
 8048ca0:	5e                   	pop    %esi
 8048ca1:	5f                   	pop    %edi
 8048ca2:	c3                   	ret    

08048ca3 <phase_4>:
 8048ca3:	83 ec 1c             	sub    $0x1c,%esp
 8048ca6:	65 a1 14 00 00 00    mov    %gs:0x14,%eax
 8048cac:	89 44 24 0c          	mov    %eax,0xc(%esp)
 8048cb0:	31 c0                	xor    %eax,%eax
 8048cb2:	8d 44 24 04          	lea    0x4(%esp),%eax
 8048cb6:	50                   	push   %eax
 8048cb7:	8d 44 24 0c          	lea    0xc(%esp),%eax
 8048cbb:	50                   	push   %eax
 8048cbc:	68 8d a3 04 08       	push   $0x804a38d
 8048cc1:	ff 74 24 2c          	pushl  0x2c(%esp)
 8048cc5:	e8 46 fb ff ff       	call   8048810 <__isoc99_sscanf@plt>
 8048cca:	83 c4 10             	add    $0x10,%esp
 8048ccd:	83 f8 02             	cmp    $0x2,%eax	#判断,如果输入数的个数小于等于2的话,爆炸
 8048cd0:	75 0c                	jne    8048cde <phase_4+0x3b>
 8048cd2:	8b 44 24 04          	mov    0x4(%esp),%eax
 8048cd6:	83 e8 02             	sub    $0x2,%eax
 8048cd9:	83 f8 02             	cmp    $0x2,%eax
 8048cdc:	76 05                	jbe    8048ce3 <phase_4+0x40>	#判断,第二个数是否大于等于2,小于4 
 8048cde:	e8 1a 05 00 00       	call   80491fd <explode_bomb>
 8048ce3:	83 ec 08             	sub    $0x8,%esp
 8048ce6:	ff 74 24 0c          	pushl  0xc(%esp)
 8048cea:	6a 07                	push   $0x7
 8048cec:	e8 6f ff ff ff       	call   8048c60 <func4 > 	#func(7,d2)
 8048cf1:	83 c4 10             	add    $0x10,%esp
 8048cf4:	3b 44 24 08          	cmp    0x8(%esp),%eax	#判断第一个数是否为函数返回值
 8048cf8:	74 05                	je     8048cff <phase_4+0x5c>
 8048cfa:	e8 fe 04 00 00       	call   80491fd <explode_bomb>
 8048cff:	8b 44 24 0c          	mov    0xc(%esp),%eax
 8048d03:	65 33 05 14 00 00 00 xor    %gs:0x14,%eax
 8048d0a:	74 05                	je     8048d11 <phase_4+0x6e>
 8048d0c:	e8 7f fa ff ff       	call   8048790 <__stack_chk_fail@plt>
 8048d11:	83 c4 1c             	add    $0x1c,%esp
 8048d14:	c3                   	ret    

根据func4函数的汇编代码,还原出其C++程序为:

int __cdecl sub_8048C60(int a1, int a2)
{
  int result; // eax
  int v3; // esi

  if ( a1 <= 0 )
    return 0;
  result = a2;
  if ( a1 != 1 )
  {
    v3 = a2 + sub_8048C60(a1 - 1, a2);
    result = v3 + sub_8048C60(a1 - 2, a2);
  }
  return result;
}

最初状态,a1为定值7,a2为第二个输入的数。
所以,如果第二个输入2的话,函数递归过程为:func(7,2)调用func(6,2)和func(5,2),func(6,2)再调用func(5,2)和func(4,2),func(5,2)再调用func(4,2)和func(3,2)…
所以,为了计算func(7,2),则需依次计算出func(0,2), func(1,2), func(2,2), func(3,2), func(4,2), func(5,2), func(6,2).
结果依次为:2 4 8 14 24 40,那么最终答案为2 + 24 + 40 = 66。
所以,一种答案可为66 2

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐