Binary Bomb - 二进制炸弹通关解析

Binary Bomb,二进制炸弹。

偶然在网上看到这么一个玩意,逆向工程,一共有6关,每一关需要玩家输入一个字符串,如成功则进入下一关,失败则exit。

因为以前也学习过crack方面的东西,所以非常有亲切感,就下下来玩一玩。

没想到太久没搞,花了一个通宵才把6关通过。久违的兴奋感。。

这篇文章就不给出答案了,只是给一个足够任何人动手找到答案的指导。

通过这个逆向工程游戏,我们主要可以学到:

1. 使用objdump,gdb等工具进行程序分析的能力。

2. 了解编译器是如何调用函数,如何传入参数,保存返回值,如何实现高级语言中的循环等等,加深对堆栈的理解。

下面开始简略的分析。

当然要先分析main函数了。

 8048a68:	call   80490f0 <read_line> ;从read_line字面就可以看出,读取用户出入的字符串,把地址放入$eax中
 8048a6d:	mov    %eax,(%esp)         ;把字符串地址压栈,作为参数传给函数phase_1
 8048a70:	call   8048b1c <phase_1>   ;第一段

分析main函数的反汇编代码可以看出来基本上每一关在main中都是上面3个语句。

然后就可以进入phase_1进行分析了。

08048b1c <phase_1>:
 8048b1c:	55                   	push   %ebp
 8048b1d:	89 e5                	mov    %esp,%ebp
 8048b1f:	83 ec 10             	sub    $0x10,%esp
 8048b22:	68 78 96 04 08       	push   $0x8049678   ;传给strings_not_equal的第二个参数
 8048b27:	ff 75 08             	pushl  0x8(%ebp)    ;传给strings_not_equal的第一个参数
 8048b2a:	e8 00 04 00 00       	call   8048f2f <strings_not_equal>  ;函数声明应该是int strings_not_equal(char *s1, char *s2),相等则返回0
 8048b2f:	83 c4 10             	add    $0x10,%esp
 8048b32:	85 c0                	test   %eax,%eax    ;$eax中保存了strings_not_euqal的返回结果,测试是否为0
 8048b34:	74 05                	je     8048b3b <phase_1+0x1f>    ;如果相等则跳转到最后,成功返回
 8048b36:	e8 b1 08 00 00       	call   80493ec <explode_bomb>  ; 否则调用 explode_bomb,从名字就可以看出来 炸弹爆炸了。
 8048b3b:	c9                   	leave  
 8048b3c:	c3                   	ret    

注释解释的很清楚了。我们需要知道两点:

1:通常函数返回结果保存在$eax寄存器中。

2:进入函数保存并设置完ebp后,$ebp+4保存了返回地址,$ebp+8保存第一个参数,$ebp+12保存第二个参数,以此类推。

phase_1做的事情就是把用户输入的字符串和位于内存0x8049678起始的一个字符串进行比较,如果相同则通关。

所以我们可以用objdump查看0x8049678处的ascii值,可以用命令objdump --start-address=0x8049678 -s bomb | more 查看。

这个字符串就是第一关的答案。


phase_2:

08048b3d <phase_2>:
 8048b3d:	55                   	push   %ebp
 8048b3e:	89 e5                	mov    %esp,%ebp
 8048b40:	56                   	push   %esi
 8048b41:	53                   	push   %ebx
 8048b42:	83 ec 28             	sub    $0x28,%esp      ;分配栈空间
 8048b45:	8d 45 e0             	lea    -0x20(%ebp),%eax  ;这个地址属于phase_2的栈空间里
 8048b48:	50                   	push   %eax				;read_six_numbers的第二个参数压栈,这是个地址,后面知道是用来保存6个数字用的
 8048b49:	ff 75 08             	pushl  0x8(%ebp)        ;把用户输入的字符串地址压栈,作为调用read_six_numbers第一个参数
 8048b4c:	e8 84 03 00 00       	call   8048ed5 <read_six_numbers>  ;调用read_six_numbers
 8048b51:	83 c4 10             	add    $0x10,%esp          
 8048b54:	83 7d e0 01          	cmpl   $0x1,-0x20(%ebp)     ;$ebp-0x20保存了第一个数字,$ebp-0x20+4保存了第二个数字,以此类推。
															    ;这个cmpl语句检查$ebp-0x20中的int型整数是否等于1.
 8048b58:	74 05                	je     8048b5f <phase_2+0x22>   ;如果等于1则继续
 8048b5a:	e8 8d 08 00 00       	call   80493ec <explode_bomb>   ;否则炸弹爆炸
 8048b5f:	bb 02 00 00 00       	mov    $0x2,%ebx            ;$ebp初始化为2
 8048b64:	8d 75 e0             	lea    -0x20(%ebp),%esi     ;下面开始一段循环,这段循环用c伪代码解释比较清楚
 8048b67:	89 d8                	mov    %ebx,%eax
 8048b69:	0f af 44 9e f8       	imul   -0x8(%esi,%ebx,4),%eax
 8048b6e:	3b 44 9e fc          	cmp    -0x4(%esi,%ebx,4),%eax
 8048b72:	74 05                	je     8048b79 <phase_2+0x3c>
 8048b74:	e8 73 08 00 00       	call   80493ec <explode_bomb>
 8048b79:	43                   	inc    %ebx
 8048b7a:	83 fb 07             	cmp    $0x7,%ebx
 8048b7d:	75 e8                	jne    8048b67 <phase_2+0x2a>
 8048b7f:	8d 65 f8             	lea    -0x8(%ebp),%esp
 8048b82:	5b                   	pop    %ebx
 8048b83:	5e                   	pop    %esi
 8048b84:	c9                   	leave  
 8048b85:	c3                   	ret    


主要是通过read_six_numbers函数从用户输入的字符串中提取6个数字。read_six_numbers函数调用了sscanf。根据sscanf的功能,要提取6个数字,

所以我们输入的字符串必须是"num1 num2 num3 num4 num5 num6"。当然不是随便输入6个数字,在得到了6个数字后,要进行一段循环验证,用c伪代码解释一下:

int A[6];
if(A[0]!=1)
	call explode_bomb
int ebx = 2;
int i=1;
do{
	int eax = ebx;
	eax *= A[i-1];
	if(A[i]!= eax)
		call explode_bomb
	++i;
	++ebx;
}
while(ebx!=7)
return success
也就是6个数分别有一个乘法因子为factor[] = {1,2,3,4,5,6}。

必须满足A[i] = A[i-1]*factor[i]; A[0] = 1。 这样很容易推导出6个数字。这关也就解决了。下面是read_six_numbers的分析。

08048ed5 <read_six_numbers>:
 8048ed5:	55                   	push   %ebp
 8048ed6:	89 e5                	mov    %esp,%ebp
 8048ed8:	83 ec 08             	sub    $0x8,%esp
 8048edb:	8b 55 0c             	mov    0xc(%ebp),%edx    ;提出传入的第二个参数,存放6个数字的首地址。
 8048ede:	8d 42 14             	lea    0x14(%edx),%eax
 8048ee1:	50                   	push   %eax		;压入第8个参数  存放第6个数字的地址
 8048ee2:	8d 42 10             	lea    0x10(%edx),%eax
 8048ee5:	50                   	push   %eax		;压入第7个参数  存放第5个数字的地址
 8048ee6:	8d 42 0c             	lea    0xc(%edx),%eax
 8048ee9:	50                   	push   %eax 		;压入第6个参数  存放第4个数字的地址
 8048eea:	8d 42 08             	lea    0x8(%edx),%eax
 8048eed:	50                   	push   %eax                ;压入第5个参数  存放第3个数字的地址
 8048eee:	8d 42 04             	lea    0x4(%edx),%eax
 8048ef1:	50                   	push   %eax        ;压入第4个参数  存放第2个数字的地址
 8048ef2:	52                   	push   %edx       ;压入第3个参数(放置第一个数字的地址,见sscanf的声明)
 8048ef3:	68 5c 99 04 08       	push   $0x804995c ;压入第二个参数:通过objdump可以看到是"%d %d %d %d %d %d"。
 8048ef8:	ff 75 08             	pushl  0x8(%ebp)  ;压入第一个参数:我们输入的字符串。
 8048efb:	e8 78 f9 ff ff       	call   8048878 <sscanf@plt> ;调用sscanf, int sscanf(const char *buffer,const char *format,[argument ]...);
 8048f00:	83 c4 20             	add    $0x20,%esp
 8048f03:	83 f8 05             	cmp    $0x5,%eax
 8048f06:	7f 05                	jg     8048f0d <read_six_numbers+0x38>
 8048f08:	e8 df 04 00 00       	call   80493ec <explode_bomb>
 8048f0d:	c9                   	leave  
 8048f0e:	c3                   	ret    


phase_3:

注释已经解释的很清楚了。

要求输入的字符串格式是"num1 num2"。

08048b86 <phase_3>:
 8048b86:	55                   	push   %ebp
 8048b87:	89 e5                	mov    %esp,%ebp
 8048b89:	83 ec 18             	sub    $0x18,%esp
 8048b8c:	8d 45 f8             	lea    -0x8(%ebp),%eax
 8048b8f:	50                   	push   %eax             ;存放第二个数字地址  sscanf第四个参数
 8048b90:	8d 45 fc             	lea    -0x4(%ebp),%eax
 8048b93:	50                   	push   %eax             ;存放第一个数字地址  sscanf的第三个参数
 8048b94:	68 68 99 04 08       	push   $0x8049968       ;格式化字符串  sscanf 第二个参数  该地址的内容为"%d %d"
 8048b99:	ff 75 08             	pushl  0x8(%ebp)        ;输入的字符串  sscanf 第一个参数
 8048b9c:	e8 d7 fc ff ff       	call   8048878 <sscanf@plt>    ;从输入的字符串中读取两个数字
 8048ba1:	83 c4 10             	add    $0x10,%esp
 8048ba4:	83 f8 01             	cmp    $0x1,%eax        ;sscanf的返回结果,为成功读取的数字个数,如果<=1则肯定失败
 8048ba7:	7f 05                	jg     8048bae <phase_3+0x28>
 8048ba9:	e8 3e 08 00 00       	call   80493ec <explode_bomb>
 8048bae:	83 7d fc 07          	cmpl   $0x7,-0x4(%ebp)   
 8048bb2:	77 65                	ja     8048c19 <phase_3+0x93>  ;如果第一个数字>7,则跳转到后面的explode_bomb
 8048bb4:	8b 45 fc             	mov    -0x4(%ebp),%eax
 8048bb7:	ff 24 85 cc 96 04 08 	jmp    *0x80496cc(,%eax,4) ;这个间接跳转的意思是 跳转到 0x80496cc+4$eax 地址中的数字 所以用objdump查看0x80496cc中的内容,
																;因为$eax也就是第一个数字要>1所以有一些可能的跳转,很大的可能是有几个开放的答案,这里我就
																;选择了$eax=2来做,结果也是通过的。
 8048bbe:	b8 00 00 00 00       	mov    $0x0,%eax
 8048bc3:	eb 4d                	jmp    8048c12 <phase_3+0x8c>
 8048bc5:	b8 00 00 00 00       	mov    $0x0,%eax
 8048bca:	eb 41                	jmp    8048c0d <phase_3+0x87>
 8048bcc:	b8 00 00 00 00       	mov    $0x0,%eax
 8048bd1:	eb 35                	jmp    8048c08 <phase_3+0x82>
 8048bd3:	b8 00 00 00 00       	mov    $0x0,%eax
 8048bd8:	eb 29                	jmp    8048c03 <phase_3+0x7d>
 8048bda:	b8 00 00 00 00       	mov    $0x0,%eax
 8048bdf:	eb 1d                	jmp    8048bfe <phase_3+0x78>
 8048be1:	b8 00 00 00 00       	mov    $0x0,%eax
 8048be6:	eb 11                	jmp    8048bf9 <phase_3+0x73>
 8048be8:	b8 59 03 00 00       	mov    $0x359,%eax
 8048bed:	eb 05                	jmp    8048bf4 <phase_3+0x6e>
 8048bef:	b8 00 00 00 00       	mov    $0x0,%eax
 8048bf4:	2d df 01 00 00       	sub    $0x1df,%eax
 8048bf9:	05 bd 02 00 00       	add    $0x2bd,%eax
 8048bfe:	2d db 02 00 00       	sub    $0x2db,%eax
 8048c03:	05 f2 00 00 00       	add    $0xf2,%eax
 8048c08:	2d 86 00 00 00       	sub    $0x86,%eax
 8048c0d:	05 86 00 00 00       	add    $0x86,%eax
 8048c12:	2d 9b 01 00 00       	sub    $0x19b,%eax
 8048c17:	eb 0a                	jmp    8048c23 <phase_3+0x9d>
 8048c19:	e8 ce 07 00 00       	call   80493ec <explode_bomb>
 8048c1e:	b8 00 00 00 00       	mov    $0x0,%eax
 8048c23:	83 7d fc 05          	cmpl   $0x5,-0x4(%ebp)        ;要求第一个数字<=5
 8048c27:	7f 05                	jg     8048c2e <phase_3+0xa8>
 8048c29:	3b 45 f8             	cmp    -0x8(%ebp),%eax       ;经过一系列对eax的加加减减,最后要求我们输入的第二个数字要和eax相等。
																;我们根据之前选择的第一个数字,查看此时$eax的值作为第二个数字即可。
 8048c2c:	74 05                	je     8048c33 <phase_3+0xad>
 8048c2e:	e8 b9 07 00 00       	call   80493ec <explode_bomb>
 8048c33:	c9                   	leave  
 8048c34:	c3                   	ret   


phase_4

这一关感觉比较简单,我们输入一个数字当做参数调用func4,如果返回值等于0x90(144)则通关。

只要分析一下func4函数即可。

这个func4函数其实就是计算斐波纳契数列的函数。

08048c71 <phase_4>:
 8048c71:	55                   	push   %ebp
 8048c72:	89 e5                	mov    %esp,%ebp
 8048c74:	83 ec 1c             	sub    $0x1c,%esp
 8048c77:	8d 45 fc             	lea    -0x4(%ebp),%eax
 8048c7a:	50                   	push   %eax        ;存放数字的地址
 8048c7b:	68 6b 99 04 08       	push   $0x804996b  ;参数压栈 格式化字符串 ("%d")
 8048c80:	ff 75 08             	pushl  0x8(%ebp)   ;参数压栈  输入的字符串
 8048c83:	e8 f0 fb ff ff       	call   8048878 <sscanf@plt> ;经过sscanf 把输入的数字转换成int存入$ebp-4地址中。
 8048c88:	83 c4 10             	add    $0x10,%esp
 8048c8b:	83 f8 01             	cmp    $0x1,%eax
 8048c8e:	75 06                	jne    8048c96 <phase_4+0x25>
 8048c90:	83 7d fc 00          	cmpl   $0x0,-0x4(%ebp)   ;如果该数字<=0 则失败
 8048c94:	7f 05                	jg     8048c9b <phase_4+0x2a>
 8048c96:	e8 51 07 00 00       	call   80493ec <explode_bomb>
 8048c9b:	ff 75 fc             	pushl  -0x4(%ebp) ;参数压栈
 8048c9e:	e8 92 ff ff ff       	call   8048c35 <func4> ;调用func4(int i)函数
 8048ca3:	83 c4 04             	add    $0x4,%esp
 8048ca6:	3d 90 00 00 00       	cmp    $0x90,%eax  ;判断结果是否等于0x90
 8048cab:	74 05                	je     8048cb2 <phase_4+0x41>
 8048cad:	e8 3a 07 00 00       	call   80493ec <explode_bomb>
 8048cb2:	c9                   	leave  
 8048cb3:	c3                   	ret  
08048c35 <func4>:
 8048c35:	55                   	push   %ebp
 8048c36:	89 e5                	mov    %esp,%ebp
 8048c38:	56                   	push   %esi
 8048c39:	53                   	push   %ebx
 8048c3a:	8b 5d 08             	mov    0x8(%ebp),%ebx   ;$ebx为传入的参数i
 8048c3d:	83 fb 01             	cmp    $0x1,%ebx       ;如果ebx<=1 则返回1
 8048c40:	7f 07                	jg     8048c49 <func4+0x14>
 8048c42:	be 00 00 00 00       	mov    $0x0,%esi
 8048c47:	eb 1e                	jmp    8048c67 <func4+0x32>
 8048c49:	be 00 00 00 00       	mov    $0x0,%esi
 8048c4e:	83 ec 0c             	sub    $0xc,%esp
 8048c51:	8d 43 ff             	lea    -0x1(%ebx),%eax
 8048c54:	50                   	push   %eax  ;ebx-1压栈
 8048c55:	e8 db ff ff ff       	call   8048c35 <func4> ;调用func4(i-1)
 8048c5a:	83 eb 02             	sub    $0x2,%ebx ;ebx = ebx-2
 8048c5d:	01 c6                	add    %eax,%esi
 8048c5f:	83 c4 10             	add    $0x10,%esp
 8048c62:	83 fb 01             	cmp    $0x1,%ebx
 8048c65:	7f e7                	jg     8048c4e <func4+0x19>  ;调回继续计算func4(ebx-2)
 8048c67:	8d 46 01             	lea    0x1(%esi),%eax
 8048c6a:	8d 65 f8             	lea    -0x8(%ebp),%esp
 8048c6d:	5b                   	pop    %ebx
 8048c6e:	5e                   	pop    %esi
 8048c6f:	c9                   	leave  
 8048c70:	c3                   	ret    
func4(i) = func4(i-1)+func4(i-2)。

答案就是使得func4(i) = 144的i。


phase_5:

这一关需要输入长度为6的字符串。

08048cb4 <phase_5>:
 8048cb4:	55                   	push   %ebp
 8048cb5:	89 e5                	mov    %esp,%ebp
 8048cb7:	53                   	push   %ebx
 8048cb8:	83 ec 20             	sub    $0x20,%esp
 8048cbb:	8b 5d 08             	mov    0x8(%ebp),%ebx   ;$ebx中存放输入的字符串首地址
 8048cbe:	53                   	push   %ebx      ;参数压栈,字符串地址
 8048cbf:	e8 4b 02 00 00       	call   8048f0f <string_length>   ;计算字符串长度
 8048cc4:	83 c4 10             	add    $0x10,%esp
 8048cc7:	83 f8 06             	cmp    $0x6,%eax    ;比较是否长度为6,不为6则explode_bomb
 8048cca:	74 05                	je     8048cd1 <phase_5+0x1d>
 8048ccc:	e8 1b 07 00 00       	call   80493ec <explode_bomb>
 8048cd1:	ba 01 00 00 00       	mov    $0x1,%edx          ;下面一段是循环,0x8048cd9 到 0x8048cef ;$edx是计数器从1到6,循环体执行6次
 8048cd6:	8d 4d f5             	lea    -0xb(%ebp),%ecx
 8048cd9:	0f be 44 13 ff       	movsbl -0x1(%ebx,%edx,1),%eax    ;$eax赋值为 内存$ebx+$edx-1 中的内容 即输入的字符串某字节开始的4个字节(作为一个整型)
 8048cde:	83 e0 0f             	and    $0xf,%eax     ;$eax只保留低4位,所以此时$eax是对应的输入字符串中某字符的低4位。
 8048ce1:	8a 80 c0 a5 04 08    	mov    0x804a5c0(%eax),%al    ;把$al设置为0x804a5c0+$eax地址的那个字节,由此可见0x804a5c0地址是一个字符串的首地址,此时$eax取值范围;为0-15,因此0x804a5c0处应该存在一个长度为16的字符串,经过objdump查看可以发现正是如此。;这一语句实际上是以输入的字符串中每个字符的低4位为索引,找到0x804a5c0中对应的字符,将其存入al
 8048ce7:	88 44 0a ff          	mov    %al,-0x1(%edx,%ecx,1) ;此时把al存入最终栈空间 $ecx+$edx-1中,其中$ecx = $esp-0xb
 8048ceb:	42                   	inc    %edx       ;计数的同时可以让输入字符串后移一个字节。
 8048cec:	83 fa 07             	cmp    $0x7,%edx
 8048cef:	75 e8                	jne    8048cd9 <phase_5+0x25>
 8048cf1:	c6 45 fb 00          	movb   $0x0,-0x5(%ebp)   ;将6个字符存入$ecx为首的地址后,需要将第7个字符设置为NULL作为结束符。;可以分析得到$ecx到$ebp-5之间正好是7个字节(6个字符+1个结束符)
 8048cf5:	83 ec 08             	sub    $0x8,%esp
 8048cf8:	68 c2 96 04 08       	push   $0x80496c2    ;参数2,比较字符串。
 8048cfd:	51                   	push   %ecx         ;参数1 ,根据我们输入字符串为索引得到的字符串
 8048cfe:	e8 2c 02 00 00       	call   8048f2f <strings_not_equal> ;比较是否相同,相同则通关。
 8048d03:	83 c4 10             	add    $0x10,%esp
 8048d06:	85 c0                	test   %eax,%eax
 8048d08:	74 05                	je     8048d0f <phase_5+0x5b>
 8048d0a:	e8 dd 06 00 00       	call   80493ec <explode_bomb>
 8048d0f:	8b 5d fc             	mov    -0x4(%ebp),%ebx
 8048d12:	c9                   	leave  
 8048d13:	c3                   	ret    

通过分析发现,需要索引的字符串为0x804a5c0起始的16个字符,比如是char B[16];

若输入字符串为char A[6];

则最终用于比较的字符串char C[6]满足:C[i] = B[ A[i]&0xF ];

若C = "abcdef" , B="qberasdfzxcvbgty"

我们需要的索引则为 4,1,10,6,2,7。要求我们输入的6个字符低四位分别为 4,1,10,6,2,7.

我们可以查ascii表,看看哪些可以输入的字符其低四位满足以上要求即可。


phase_6:

文档中说第六关很有挑战,但是我发现这一关很简单,加上整个程序有没有使用过的fun7函数和几个其它函数,我怀疑这个程序是否不完整。


这一关要求输入一个数字,会用strtol转换成long int,放入ebx寄存器。

然后调用了fun6函数,这个函数并没有用到我们的输入,而且其内容很复杂,有很多跳转。但这跟我们通关没什么关系。

直接看最后的比较,比较$ebx和($eax)的值,那我们就可以直接查看($eax)中的数值,这个就是我们要输入的答案了。

所以我怀疑最后一关原来是否是这个代码。

08048d72 <phase_6>:
 8048d72:	55                   	push   %ebp
 8048d73:	89 e5                	mov    %esp,%ebp
 8048d75:	53                   	push   %ebx
 8048d76:	83 ec 04             	sub    $0x4,%esp
 8048d79:	6a 00                	push   $0x0
 8048d7b:	6a 0a                	push   $0xa
 8048d7d:	6a 00                	push   $0x0
 8048d7f:	ff 75 08             	pushl  0x8(%ebp)
 8048d82:	e8 81 fa ff ff       	call   8048808 <__strtol_internal@plt> ;strtol函数是把字符串转换成long int。前面的参数压栈就不详述了。
 8048d87:	89 c3                	mov    %eax,%ebx       ;$ebx存放我们输入的字符串转换成long int后的值。
 8048d89:	68 30 a6 04 08       	push   $0x804a630   ;fun6参数压栈
 8048d8e:	e8 81 ff ff ff       	call   8048d14 <fun6> ;fun6只有一个参数,跟我们输入的字符串没有任何关系。
 8048d93:	ba 08 00 00 00       	mov    $0x8,%edx
 8048d98:	83 c4 14             	add    $0x14,%esp
 8048d9b:	8b 40 08             	mov    0x8(%eax),%eax
 8048d9e:	4a                   	dec    %edx
 8048d9f:	75 fa                	jne    8048d9b <phase_6+0x29>
 8048da1:	3b 18                	cmp    (%eax),%ebx      ;比较$ebx和($eax)的值,如果相同,就通关了!!!
 8048da3:	74 05                	je     8048daa <phase_6+0x38>
 8048da5:	e8 42 06 00 00       	call   80493ec <explode_bomb>
 8048daa:	8b 5d fc             	mov    -0x4(%ebp),%ebx
 8048dad:	c9                   	leave  
 8048dae:	c3                   	ret   





评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值