CSAPP:bomb实验

bomb实验是一个比较恶心的实验,因为你要被汇编玩死的。我刚开始做的时候对汇编还不怎么了解,可以说我的汇编知识全是从这个bomb实验学的。现在过去好久了,现在实验课要验收,写篇博客来巩固一下。希望能帮到有需要的人。实验文件如下:


实验介绍我简单说一下:就是一共有七个关卡(包含一个隐藏的关卡),每一关都有特定的通关密码,你需要找出这个通关密码,通关运行./bomb,输入你找到的密码,正确则到下一关,否则炸弹爆炸,讲真一开始我以为它会把我的电脑弄爆炸,吓死都有,原来就是输出个字符串。(小声比比)废话不多说,开始的破解。

1. Phase_1 

 原理设计:

1)通过反汇编指令得到汇编代码并导入到txt文档中:

Objdump -d bomb > 1.txt

     (2)分析通关条件:

首先要知道在整个过程中栈的变化,那么要了解栈的运行原理;其次要看下寄存器相关信息,因此需要了解相关gdb调试命令,比如查看某一地址的内容:x/s 0x80adce;要找到通关的条件,首先找到它要你输入的参数,再分析如何避开它调用引爆函数,就能找到通关条件。如何避开一个又一个炸弹呢?引爆炸弹必须要有条件,注意避开这些条件即可防止炸弹引爆了。

 实现与分析:

 汇编代码如下:

 08048f61 <phase_1>:
 8048f61:	55                   	push   %ebp
 8048f62:	89 e5                	mov    %esp,%ebp
 8048f64:	83 ec 18             		sub    $0x18,%esp  //这里对esp-24,开栈操作
 8048f67:	c7 44 24 04 5c a1 04 		movl   $0x804a15c,0x4(%esp) // 将这个地址的内容存放到esp+4的位置
 8048f6e:	08 
 8048f6f:	8b 45 08             		mov    0x8(%ebp),%eax //esp+8,从调用函数处取第一个参数放到eax寄存器中
 8048f72:	89 04 24             		mov    %eax,(%esp) //传送到esp寄存器
 8048f75:	e8 31 00 00 00       		call   8048fab <strings_not_equal> //入口函数, 判断字符串是否相等(判断esp+4和esp+8的字符串是否相等)
 8048f7a:	85 c0                	test   %eax,%eax //当两个字符串相等时返回eax=0
 8048f7c:	74 05                	je     8048f83 <phase_1+0x22>//是否引爆炸弹的条件(eax=0跳到leave,否则引爆炸弹,因此通关的条件是eax=0)
 8048f7e:	e8 4e 01 00 00       		call   80490d1 <explode_bomb>
 8048f83:	c9                   	leave  //程序结束
 8048f84:	c3                   	ret    
 8048f85:	90                   	nop
 8048f86:	90                   	nop
 8048f87:	90                   	nop
 8048f88:	90                   	nop
 8048f89:	90                   	nop
 8048f8a:	90                   	nop
 8048f8b:	90                   	nop
 8048f8c:	90                   	nop
 8048f8d:	90                   	nop
 8048f8e:	90                   	nop
 8048f8f:	90                   		nop

画一下那个栈帧的情况:


思路分析

根据以上分析我们可知,程序先将我们输入的参数存放到ebp+8的位置,接着传送到esp中,然后函数在0x804a15c这个地址取值,放到esp+8中。最后传送到eax进行比较,如果相等,返回eax=0,跳到leave结束,不相等则返回1,同时调用引爆炸弹的函数。因此我们只要查看0x804a15c这个地址的内容即可通关!GDB调试工具进行查看:

第一关通关密码为:

We have to stand with our North Korean allies.


通关成功!

2.Phase_2

原理设计

  先明确题目输入的参数类型,即输入整数还是字符串等等。确定输入的参数后,再分析它进行了哪些操作,查看关键地址内容,同时要画出栈帧的变化情况,那么需要用GDB工具查看寄存器的值;其次是分析通关的条件,也就是在输入参数后它进行了哪些操作,一步一步推下来找到通关密码。避开炸弹,只要引爆炸弹的条件不成立即可。

实现与分析:

汇编代码如下:

 08048d6a <phase_2>:
 8048d6a:	55                   	push   %ebp
 8048d6b:	89 e5                	mov    %esp,%ebp
 8048d6d:	56                   	push   %esi
 8048d6e:	53                   	push   %ebx  //esi和ebx为调用者保存寄存器,因为后面的循环用到了者两个寄存器,因此要压栈保存
 8048d6f:	83 ec 30             		sub    $0x30,%esp //esp-48开栈操作
 8048d72:	8d 45 e0             	lea    -0x20(%ebp),%eax
 8048d75:	89 44 24 04          		mov    %eax,0x4(%esp)//以上两句将ebp-32处的地址借助eax传给esp+4处
 8048d79:	8b 45 08             	mov    0x8(%ebp),%eax
 8048d7c:	89 04 24             		mov    %eax,(%esp)//以上两句将ebp+8处的地址传给esp
 8048d7f:	e8 87 03 00 00       		call   804910b <read_six_numbers>//输入6个数
 8048d84:	83 7d e0 00          		cmpl   $0x0,-0x20(%ebp)
 8048d88:	75 06                	jne    8048d90 <phase_2+0x26>//引爆炸弹的条件,不为0则引爆炸弹。因此ebp-32处要为0,从前面看,这是存放输入参数的位置
 8048d8a:	83 7d e4 01          		cmpl   $0x1,-0x1c(%ebp)
 8048d8e:	74 05                	je     8048d95 <phase_2+0x2b>//引爆炸弹的条件,ebp-28处要为1,否则引爆炸弹,因此第二个参数可知为1
 8048d90:	e8 3c 03 00 00       		call   80490d1 <explode_bomb>
 8048d95:	8d 5d e8             	lea    -0x18(%ebp),%ebx//ebp-24传给ebx(调用者帧保存)
 8048d98:	8d 75 f8             		lea    -0x8(%ebp),%esi//ebp-8传给esi(调用者帧保存)以上两句是数据的初始化,ebp-24是第三个数的地址,而ebp-8是第七个数(理论上没有第七个数),但这样设置是为了作判断(猜想是结束循环,因为就六个数最多到ebp-12,如果读到ebp-8了那肯定读完了,那么要结束循环。)
 8048d9b:	8b 43 fc             		mov    -0x4(%ebx),%eax//ebx-4传给eax
 8048d9e:	03 43 f8             		add    -0x8(%ebx),%eax//ebx-8+eax(也就是ebx前后两个数相加)
 8048da1:	39 03                	cmp    %eax,(%ebx)//此时的ebx应该是ebx-24,也就是第三个数,此时的eax是两个数相加后的值(第一次循环的时候)
 8048da3:	74 05                	je     8048daa <phase_2+0x40>//相等则跳过炸弹
 8048da5:	e8 27 03 00 00       		call   80490d1 <explode_bomb>
 8048daa:	83 c3 04             		add    $0x4,%ebx//ebx+=4,取值操作
 8048dad:	39 f3                	cmp    %esi,%ebx //此时的ebx是ebx-20了,第四个数
 8048daf:	75 ea                	jne    8048d9b <phase_2+0x31>//上数三句是作用是:如果我们的输入满足条件,那么继续循环上移动4位,也就是继续求两个数之和赋给第三个数。
 8048db1:	83 c4 30             		add    $0x30,%esp
 8048db4:	5b                   	pop    %ebx
 8048db5:	5e                   	pop    %esi
 8048db6:	5d                   	pop    %ebp
 8048db7:	c3                   	ret    

栈图如下:

思路分析

到这里题目意思已经很明了,就是一个斐波那契数列的前六项。

结论

第二关的通关密码为:0 1 1 2 3 5


3.Phase_3

原理设计

先找到要输入的参数,通过GDB调试工具可知第三关要输入两个整数。要找到这两个数应该不难,画出栈帧的变化情况以及查看关键地址的内容。找到这两个整数即可找到通关条件,要避开炸弹只需让调用炸弹的条件不成立即可。

实现与分析:

汇编代码如下:

08048ea1 <phase_3>:

 8048ea1:	55                   	push   %ebp

 8048ea2:	89 e5                	mov    %esp,%ebp

 8048ea4:	83 ec 28             	sub    $0x28,%esp//esp-40

 8048ea7:	8d 45 f0             	lea    -0x10(%ebp),%eax//ebp-16赋给eax

 8048eaa:	89 44 24 0c          	mov    %eax,0xc(%esp)//将ebp-16赋给esp+12

 8048eae:	8d 45 f4             	lea    -0xc(%ebp),%eax//ebp-12赋给eax

 8048eb1:	89 44 24 08          	mov    %eax,0x8(%esp)//ebp-12赋给esp+8

 8048eb5:	c7 44 24 04 3e a2 04 movl   $0x804a23e,0x4(%esp)//GDB查看发现是两个%d%d,说明要输入的参数是两个整数。

 8048ebc:	08

 8048ebd:	8b 45 08             	mov    0x8(%ebp),%eax

 8048ec0:	89 04 24            	 	mov    %eax,(%esp)//ebp+8赋给esp

 8048ec3:	e8 78 f9 ff ff       	  	call   8048840 <__isoc99_sscanf@plt>//读入函数,将你输入的参数按指定格式读到相应的地址

 8048ec8:	83 f8 01             	cmp    $0x1,%eax //eax接受ssacnf函数的返回值,说明sscanf函数的返回值是参数个数,如果大于1跳过炸弹,否则引爆炸弹。

 8048ecb:	7f 05                	jg     8048ed2 <phase_3+0x31>//jg是大于

 8048ecd:	e8 ff 01 00 00       	call   80490d1 <explode_bomb>

 8048ed2:	83 7d f4 07          	cmpl   $0x7,-0xc(%ebp) 

 8048ed6:	77 6b                	ja     8048f43 <phase_3+0xa2>//以上两句说明ebp-12不能超过7,否则就引爆炸弹。

 8048ed8:	8b 45 f4             	mov    -0xc(%ebp),%eax //ebp-12赋给eax,观察下面的代码得知对ebp-12的值有不同的操作,而且very多jmp,猜想这是一个switch跳转表。

 8048edb:	ff 24 85 a0 a1 04 08 jmp    *0x804a1a0(,%eax,4) // 间接跳转,跳到*0x804a1a0这个地址,这是第三关的核心代码!

 8048ee2:	b8 00 00 00 00       	mov    $0x0,%eax  //eax清零

 8048ee7:	eb 53                	jmp    8048f3c <phase_3+0x9b>

 8048ee9:	b8 00 00 00 00       	mov    $0x0,%eax

 8048eee:	66 90                	xchg   %ax,%ax

 8048ef0:	eb 45                	jmp    8048f37 <phase_3+0x96>

 8048ef2:	b8 00 00 00 00       	mov    $0x0,%eax

 8048ef7:	eb 39                	jmp    8048f32 <phase_3+0x91>

 8048ef9:	b8 00 00 00 00       	mov    $0x0,%eax

 8048efe:	66 90                	xchg   %ax,%ax

 8048f00:	eb 2b                	jmp    8048f2d <phase_3+0x8c>

 8048f02:	b8 00 00 00 00       	mov    $0x0,%eax

 8048f07:	eb 1f                	jmp    8048f28 <phase_3+0x87>

 8048f09:	b8 00 00 00 00       	mov    $0x0,%eax

 8048f0e:	66 90                	xchg   %ax,%ax

 8048f10:	eb 11                	jmp    8048f23 <phase_3+0x82>//以上代码是switch的跳转表

 8048f12:	b8 14 03 00 00       	mov    $0x314,%eax //将这个地址的值取出赋给eax(跳转表的初始值,通过jmp到不同的地方计算出多组值),eax=788(十进制)

 8048f17:	eb 05                	jmp    8048f1e <phase_3+0x7d>

 8048f19:	b8 00 00 00 00       	mov    $0x0,%eax

 8048f1e:	2d 5a 03 00 00       	sub    $0x35a,%eax

 //计算到此处eax=788-0x35a=-70(第一次跳转的时候,也就是case 0 的情况)

 8048f23:	05 ef 02 00 00       	add    $0x2ef,%eax

 8048f28:	2d 16 02 00 00       	sub    $0x216,%eax

 8048f2d:	05 16 02 00 00       	add    $0x216,%eax

 8048f32:	2d 16 02 00 00       	sub    $0x216,%eax

 8048f37:	05 16 02 00 00       	add    $0x216,%eax

 8048f3c:	2d 16 02 00 00       	sub    $0x216,%eax

//算到这里停止,此时eax=147(case 0)

 8048f41:	eb 0a                	jmp    8048f4d <phase_3+0xac>

 8048f43:	e8 89 01 00 00       	call   80490d1 <explode_bomb>

 8048f48:	b8 00 00 00 00       	mov    $0x0,%eax

 8048f4d:	83 7d f4 05          	cmpl   $0x5,-0xc(%ebp)//ebp-12要小于等于5

 8048f51:	7f 05                	jg     8048f58 <phase_3+0xb7>//ebp-12如果大于5就引爆炸弹,从前面看参数个数存放在ebp-12处(旧ebp-12的值为6),说明输入的参数个数最多为6组(下标从0开始)。所以推知第三关有六组答案。

 8048f53:	3b 45 f0             	cmp    -0x10(%ebp),%eax

 8048f56:	74 05                	je     8048f5d <phase_3+0xbc>

 8048f58:	e8 74 01 00 00       	call   80490d1 <explode_bomb>

 8048f5d:	c9                   	leave  

 8048f5e:	66 90                	xchg   %ax,%ax

 8048f60:	c3                   	ret

 栈帧数据记录:   


思路分析

第三关要输入两个%d%d,不妨设为参数A,Bebp-12处的值为参数A,存放在esp+8处。而ebp-20处的值为参数B,存放在esp+12处。接下来是查看switch跳转表,这也是最麻烦的地方。它出了一个地址指针*0x804a1a0,用GDB查看发现它指向0x8048f12。在源码找到这个地址,发现mov了一个$0x314。先分析参数A,刚开始的时候A<=6(下标从0开始数),但后面发现ebp-12必须要小于等5,所以参数A只能从0-5取值了。那么A=0的时候跳转到哪呢?当然就是上面说的那个啦!在源码备注中我给出了计算过程这里不再复述。当A=1的时候也是类似的计算过程,直到计算完5组答案,然而0 147已经可以通过了,剩下的算下去也没太大意义了。也就是说一共有六种case,每个case有一定的计算过程,前面分析到有个地方的值不可以大于7,因此第三关就是在0x314的基础上,根据不同的case计算出不同的值。我算了case0的,其它的也是类似计算。

实验结论

第三关通关密码:0 147

4.Phase_4

原理设计

老规矩先找到要输入的参数,用GDB调试工具发现要输入两个整数。第四关用到了函数递归,因此要了解递归函数的栈帧变化情况。通过输入的两个参数以及一系列操作推出通关条件是输入两个符合要求的整数。如何避免一个又一个炸弹呢?引爆炸弹的函数不成立即可。

 实现与分析

汇编代码如下:

 08048e2e <phase_4>:

 8048e2e:	55                   	push   %ebp

 8048e2f:	89 e5                	mov    %esp,%ebp

 8048e31:	83 ec 28             	sub    $0x28,%esp

 8048e34:	8d 45 f0             	lea    -0x10(%ebp),%eax

 8048e37:	89 44 24 0c          	mov    %eax,0xc(%esp)//将ebp-16处的地址赋给esp+12,记为参数2

 8048e3b:	8d 45 f4             	lea    -0xc(%ebp),%eax

 8048e3e:	89 44 24 08          	mov    %eax,0x8(%esp)//将ebp-12处的地址赋给esp+8,记为参数1

 8048e42:	c7 44 24 04 3e a2 04 movl   $0x804a23e,0x4(%esp)//GDB查看发现是%d%d,推知第四关也要输入两个整数。

 8048e49:	08

 8048e4a:	8b 45 08             	mov    0x8(%ebp),%eax

 8048e4d:	89 04 24             	mov    %eax,(%esp)//ebp+8的值赋给esp

 8048e50:	e8 eb f9 ff ff       	call   8048840 <__isoc99_sscanf@plt>//上一关已经说明,返回参数的个数

 8048e55:	83 f8 02             	cmp    $0x2,%eax//判断输入的参数是否为2个

 8048e58:	75 0c                	jne    8048e66 <phase_4+0x38>//不为2个则引爆炸弹

 8048e5a:	8b 45 f4             	mov    -0xc(%ebp),%eax//ebp-12赋给eax

 8048e5d:	85 c0                	test   %eax,%eax//判断ebp-12是否大于等于0

 8048e5f:	78 05                	js     8048e66 <phase_4+0x38>//若不大于等于0则引爆炸弹

 8048e61:	83 f8 0e             	cmp    $0xe,%eax//判断ebp-12是否小于14,若大于14引爆炸弹,由此推知参数1的范围是0-14。

 8048e64:	7e 05                	jle    8048e6b <phase_4+0x3d>

 8048e66:	e8 66 02 00 00       	call   80490d1 <explode_bomb>

 8048e6b:	c7 44 24 08 0e 00 00 movl   $0xe,0x8(%esp)//14赋给esp+8

 8048e72:	00

 8048e73:	c7 44 24 04 00 00 00 movl   $0x0,0x4(%esp)//0赋给esp+4

 8048e7a:	00

 8048e7b:	8b 45 f4             	mov    -0xc(%ebp),%eax

 8048e7e:	89 04 24             	mov    %eax,(%esp)//ebp-12也就是参数1赋给esp

 8048e81:	e8 da fc ff ff       	call   8048b60 <func4>//开始递归,传递了三个参数:14,0,参数1

 8048e86:	83 f8 01             	cmp    $0x1,%eax //eax是返回值且必须为1,否则引爆炸弹

 8048e89:	75 06                	jne    8048e91 <phase_4+0x63>

 8048e8b:	83 7d f0 01          	cmpl   $0x1,-0x10(%ebp)//判断ebp-16是否为1,也就是参数2必须为1。

 8048e8f:	74 0c                	je     8048e9d <phase_4+0x6f>参数2为1则结束,否则引爆炸弹。

 8048e91:	8d b4 26 00 00 00 00 lea    0x0(%esi,%eiz,1),%esi

 8048e98:	e8 34 02 00 00       	call   80490d1 <explode_bomb>

 8048e9d:	c9                   	leave  

 8048e9e:	66 90                	xchg   %ax,%ax

 8048ea0:	c3                   	ret    

<func4>汇编代码如下:

08048b60 <func4>:

 8048b60:	55                   	push   %ebp

 8048b61:	89 e5                	mov    %esp,%ebp

 8048b63:	83 ec 18             	sub    $0x18,%esp//esp-24,开栈操作

 8048b66:	89 5d f8             	mov    %ebx,-0x8(%ebp)//ebx赋给ebp-8

 8048b69:	89 75 fc             	mov    %esi,-0x4(%ebp)//esi赋给ebp-4,前面说了ebx和esi是调用保存寄存器

 8048b6c:	8b 55 08             	mov    0x8(%ebp),%edx//ebp+8赋给edx,记为参数A

 8048b6f:	8b 45 0c             	mov    0xc(%ebp),%eax//ebp+12赋给eax,记为参数B

 8048b72:	8b 5d 10             	mov    0x10(%ebp),%ebx//ebp+16赋给ebx,记为参数C

 8048b75:	89 d9                	mov    %ebx,%ecx//ebx赋给ecx

 8048b77:	29 c1                	sub    %eax,%ecx//C-B

 8048b79:	89 ce                	mov    %ecx,%esi//将ecx赋给esi

 8048b7b:	c1 ee 1f             	shr    $0x1f,%esi//对esi进行逻辑右移31位

 8048b7e:	8d 0c 0e             	lea    (%esi,%ecx,1),%ecx//按照这个寻址方式存放在ecx里面

 8048b81:	d1 f9                	sar    %ecx //算术右移,相当于 C/2,默认移动一位

 8048b83:	01 c1                	add    %eax,%ecx//将递归的返回值加倍

 8048b85:	39 d1                	cmp    %edx,%ecx//若C>A,则B=0,进入递归,否则跳到8048ba0继续执行。

 8048b87:	7e 17                	jle    8048ba0 <func4+0x40>//如果C<=A,跳到8048ba0继续执行,否则顺序执行

 8048b89:	83 e9 01             	sub    $0x1,%ecx//C=C-1

 8048b8c:	89 4c 24 08          	mov    %ecx,0x8(%esp)//将C-1放到%esp+8

 8048b90:	89 44 24 04          	mov   %eax,0x4(%esp)//将B放到%esp+4

 8048b94:	89 14 24            	 	mov    %edx,(%esp)//edx赋给esp

 8048b97:	e8 c4 ff ff ff       	call   8048b60 <func4>//进入递归

 8048b9c:	01 c0                	add    %eax,%eax//B=B+1

 8048b9e:	eb 20                	jmp    8048bc0 <func4+0x60>

 8048ba0:	b8 00 00 00 00       	mov    $0x0,%eax//将B重置为0

 8048ba5:	39 d1                	cmp    %edx,%ecx//将A,C进行比较,若A>=C跳到8048bc0继续执行,递归终止

 8048ba7:	7d 17                	jge    8048bc0 <func4+0x60>

 8048ba9:	89 5c 24 08          	mov    %ebx,0x8(%esp)//将C转存到esp

 8048bad:	83 c1 01            	 	add    $0x1,%ecx//C=C+1

 8048bb0:	89 4c 24 04          	mov    %ecx,0x4(%esp)

 8048bb4:	89 14 24            	 	mov    %edx,(%esp)

 8048bb7:	e8 a4 ff ff ff       	call   8048b60 <func4>//进入递归

 8048bbc:	8d 44 00 01          	lea    0x1(%eax,%eax,1),%eax//递归返回值加倍后再加1

 8048bc0:	8b 5d f8            	 	mov    -0x8(%ebp),%ebx//ebp-8赋给ebx

 8048bc3:	8b 75 fc             	mov    -0x4(%ebp),%esi

 8048bc6:	89 ec                	mov    %ebp,%esp

 8048bc8:	5d                   	pop    %ebp

 8048bc9:	c3                   	ret   

思路分析:

从上面可以推知第四关要输入两个数,第一个数的范围在0-14,第二个数确定为1。从fun4函数可知,第一次调用的时候是将140,参数1作为参数传入,fun4类似于一个二分操作,在0-14中,通过二分不断逼近参数1,经过分析发现一共三组答案:8 1111 1。也可以用比较笨的方法,就是直接输入0-14之间的数,没有引爆的都是符合的。

结论

第四关通关密码为:8 1 9 1 11 1 。输入三组中任意一组都可以。


5.Phase_5

原理设计

老规矩找到要输入的参数类型,用GDB调试工具查看发现要输入两个%d%d。接下来分析输入参数后进行的一系列操作一步一步推得通关条件。调用引爆函数前均有一个条件,不满足这些条件即可避开炸弹。

实现与分析

汇编代码如下:

08048db8 <phase_5>:

 8048db8:	55                   	push   %ebp

 8048db9:	89 e5                	mov    %esp,%ebp

 8048dbb:	56                   	push   %esi

 8048dbc:	53                   	push   %ebx//esi和ebx为调用者寄存器,接下来的循环用到了,故压栈保存。

 8048dbd:	83 ec 20             	sub    $0x20,%esp//esp-32

 8048dc0:	8d 45 f0             	lea    -0x10(%ebp),%ea

 8048dc3:	89 44 24 0c          	mov    %eax,0xc(%esp)//ebp-16赋给esp+12,记为参数2

 8048dc7:	8d 45 f4             	lea    -0xc(%ebp),%eax

 8048dca:	89 44 24 08          	mov    %eax,0x8(%esp)//ebp-12赋给esp+8,记为参数1

 8048dce:	c7 44 24 04 3e a2 04 movl   $0x804a23e,0x4(%esp)//跟前面关卡一样,还是两个%d%d

 8048dd5:	08

 8048dd6:	8b 45 08             mov    0x8(%ebp),%eax

 8048dd9:	89 04 24             	mov    %eax,(%esp)//ebp+8赋给esp

 8048ddc:	e8 5f fa ff ff       	call   8048840 <__isoc99_sscanf@plt>//前面说两遍了!

 8048de1:	83 f8 01             	cmp    $0x1,%eax//参数个数大于1,否则炸!

 8048de4:	7f 05               	jg     8048deb <phase_5+0x33>

 8048de6:	e8 e6 02 00 00       	call   80490d1 <explode_bomb>

 8048deb:	8b 45 f4             	mov    -0xc(%ebp),%eax//ebp-12(参数1)赋给eax

 8048dee:	83 e0 0f             	and    $0xf,%eax //把eax和低四位为1的数作位与操作,同时传回eax

 8048df1:	89 45 f4             	mov    %eax,-0xc(%ebp)//eax赋给ebp-12

 8048df4:	83 f8 0f             	cmp    $0xf,%eax 

 8048df7:	74 29               	je     8048e22 <phase_5+0x6a>//判断参数1是否等于15,若等于15则引爆炸弹,猜想输入的第一个参数要小于15。此时的eax是按位与后的eax,也就是00000000 00000000 00000000 00001111,说白了第一个参数不能大于等于15。

 8048df9:	b9 00 00 00 00       	mov    $0x0,%ecx//清零

 8048dfe:	ba 00 00 00 00       	mov    $0x0,%edx//清零

 8048e03:	bb c0 a1 04 08       	mov    $0x804a1c0,%ebx//传了个地址,用GDB查看发现是一个数组

 8048e08:	83 c2 01             	add    $0x1,%edx //edx+1(是记录循环次数的)

 8048e0b:	8b 04 83             mov    (%ebx,%eax,4),%eax//从数组读数,每次跳四个字节(一个int四字节),也就是一个数组位置。

 8048e0e:	01 c1                add    %eax,%ecx//ecx+=eax

 8048e10:	83 f8 0f             	cmp    $0xf,%eax //判断eax是否大于15

 8048e13:	75 f3                jne    8048e08 <phase_5+0x50>//不等于15则跳到8048e08,也就是继续循环,以上是循环部分。

 8048e15:	89 45 f4             	mov    %eax,-0xc(%ebp)//把完成循环后的eax传给ebp-12

 8048e18:	83 fa 0f             	cmp    $0xf,%edx//判断循环的次数是否够15次,加上exd=0那一次循环,共进行了16次循环。

 8048e1b:	75 05                jne    8048e22 <phase_5+0x6a>//不够15次引爆炸弹

 8048e1d:	39 4d f0             	cmp    %ecx,-0x10(%ebp)//把完成循环的ecx与参数2进行比较

 8048e20:	74 05                je     8048e27 <phase_5+0x6f>//若参数2与ecx不相等则顺序执行引爆炸弹,否则跳过炸弹。从而推知ecx是通过循环累加得到的。

 8048e22:	e8 aa 02 00 00       	call   80490d1 <explode_bomb>

 8048e27:	83 c4 20             	add    $0x20,%esp

 8048e2a:	5b                  pop    %ebx

 8048e2b:	5e                  pop    %esi

 8048e2c:	5d                  pop    %ebp

 8048e2d:	c3                  	ret    

思路分析:

 从上面的分析可知,第四关是对数组进行操作前一次取数组的值作为下一次取数的下标,并不断累加,因此我们要推出第一次取了数组那个值即可通关。用GDB查看发现是存储了16个元素的数组:[10,2,14,7,8,12,15,11,0,4,1,13,3,9,6,5]。从循环最开始前看,eax就是参数1的底四位且不能为1111,所以eax的取值只能是0-14。以上是循环开始前的条件,那么循环结束的条件呢?很明显是eax=15,那这个15究竟从何而来呢?当然是从数组里面读来的,按照mov (%ebx,%eax,4),%eax这个寻址方式得来的,15对应的上个值是6(就表示上一次数组取的值是6,然后下一次取a[6],也就是15,然后下一次取a[15])。依此类推,得到第一个取的值为5,数组累加得到的值为120,但进入循环前ecx已经为5了,故减去5,那么通关密码为 5 115


6.Phase_6

原理设计

老规矩先找到要输入的参数,发现要输入六个数。然后分析读入六个数后的操作,第六关用到了链表,要了解链表的特性(分为数据域和指针域),用GDB查看关键地址内容,一步一步即可推得通关条件,避开炸弹的方法就是让调用炸弹函数的条件不成立就好了。给点心里准备:第六关是一个链表,汇编代码又长又臭!

实现与分析

  汇编代码按如下:

 08048c89 <phase_6>:

 8048c89:	55                   	push   %ebp

 8048c8a:	89 e5                	mov    %esp,%ebp

 8048c8c:	57                   	push   %edi

 8048c8d:	56                   	push   %esi

 8048c8e:	53                   	push   %ebx //以上三个为调用者保存寄存器,后面的循环用到,故压栈保存(寄存器数量有限,要将原来的状态保存,因为要复原)

 8048c8f:	83 ec 5c             	sub    $0x5c,%esp//esp-92

 8048c92:	8d 45 d0             	lea    -0x30(%ebp),%eax

 8048c95:	89 44 24 04          	mov    %eax,0x4(%esp) //ebp-48赋给esp+4

 8048c99:	8b 45 08             	mov    0x8(%ebp),%eax

 8048c9c:	89 04 24             	mov    %eax,(%esp)//ebp+8赋给esp

 8048c9f:	e8 67 04 00 00       	call   804910b <read_six_numbers>//读入6个数

 8048ca4:	be 00 00 00 00       	mov    $0x0,%esi//清零

 8048ca9:	8d 7d d0             	lea    -0x30(%ebp),%edi//ebp-48赋给edi

 8048cac:	8b 04 b7             	mov    (%edi,%esi,4),%eax//edi+4esi赋给eax,当前esi=0,亦即把ebp-48赋给eax,对于不同的esi,就是从ebp-48向上找第esi个数赋给eax。

 8048caf:	83 e8 01             	sub    $0x1,%eax//eax-=1

 8048cb2:	83 f8 05             	cmp    $0x5,%eax

 8048cb5:	76 05                	jbe    8048cbc <phase_6+0x33>//将eax-1与5比较,如果小于等于则继续,否则引爆炸弹,jbe是无符号数操作,故推知eax的范围为1-6。

 8048cb7:	e8 15 04 00 00       	call   80490d1 <explode_bomb>

 8048cbc:	83 c6 01             	add    $0x1,%esi//esi+=1(取第二个数)

 8048cbf:	83 fe 06             	cmp    $0x6,%esi

 8048cc2:	74 22                	je     8048ce6 <phase_6+0x5d>//判断取的数是否超过6个,若大于6个引爆炸弹。从而推知循环结束的条件是esi=6。

 8048cc4:	8d 1c b7             	lea    (%edi,%esi,4),%ebx//edi+4esi赋给ebx,当前esi=1,edi=ebp-48,故本次将ebp-44赋给ebx(下一次应该是ebp-40)到这里就知道这是个线性的结构

 8048cc7:	89 75 b4             	mov    %esi,-0x4c(%ebp)//esi赋给ebp-76,此时esi=1

 8048cca:	8b 44 b7 fc          	mov    -0x4(%edi,%esi,4),%eax//将edi+4esi-4赋给eax,综合上面两句来看,应该是将ebx的下一个地址赋给eax

 8048cce:	3b 03                	cmp    (%ebx),%eax

 8048cd0:	75 05                	jne    8048cd7 <phase_6+0x4e>

 8048cd2:	e8 fa 03 00 00       	call   80490d1 <explode_bomb>//eax和ebx的值相等则引爆炸弹,证明ebx和它的下一个值不相等。

 8048cd7:	83 45 b4 01          	addl   $0x1,-0x4c(%ebp)//ebp-76+1,此时变为2

 8048cdb:	83 c3 04             	add    $0x4,%ebx//ebx+=4(读取下一个地址的内容)

 8048cde:	83 7d b4 05          	cmpl   $0x5,-0x4c(%ebp)

 8048ce2:	7e e6                	jle    8048cca <phase_6+0x41>

 8048ce4:	eb c6                	jmp    8048cac <phase_6+0x23>//ebp-76的值小于等于5跳到8048cca,大于5则跳到8048cac,由此分析这是一个嵌套循环。(思路分析处有详细分析)

 8048ce6:	bb 00 00 00 00       	mov    $0x0,%ebx//清零,ebx=0

 8048ceb:	8d 7d d0             	lea    -0x30(%ebp),%edi//ebp-48赋给edi

 8048cee:	eb 16                	jmp    8048d06 <phase_6+0x7d>//直接跳到8048d06号地址

 8048cf0:	8b 52 08             	mov    0x8(%edx),%edx//edx+8赋给edx

 8048cf3:	83 c0 01             	add    $0x1,%eax //eax+=1

 8048cf6:	39 c8                	cmp    %ecx,%eax

 8048cf8:	75 f6                	jne    8048cf0 <phase_6+0x67>//若ecx不等于eax,跳回8048cf0,直到两者相等才继续。意思就是匹配节点

 8048cfa:	89 54 b5 b8          	mov    %edx,-0x48(%ebp,%esi,4)//edx赋给ebp+4esi-48,也就是第esi个参数

 8048cfe:	83 c3 01             	add    $0x1,%ebx//ebx+=1,记录取的结点个数

 8048d01:	83 fb 06             	cmp    $0x6,%ebx//是否取完6个结点的内容

 8048d04:	74 16                	je     8048d1c <phase_6+0x93>//等于6则跳到8048d1,否则顺序执行

 8048d06:	89 de                	mov    %ebx,%esi //ebx赋给esi

 8048d08:	8b 0c 9f             	mov    (%edi,%ebx,4),%ecx//寻址操作

 8048d0b:	ba c4 c0 04 08       	mov    $0x804c0c4,%edx//用GDB查看发现它是链表表头的首地址,推知第六关是对链表进行操作

 8048d10:	b8 01 00 00 00       	mov    $0x1,%eax //eax=1

 8048d15:	83 f9 01             	cmp    $0x1,%ecx

 8048d18:	7f d6                	jg     8048cf0 <phase_6+0x67>

 8048d1a:	eb de                	jmp    8048cfa <phase_6+0x71>//ecx大于1则跳到8048cf0,否则跳到8048cfa

//下面的内容画重点~

 8048d1c:	8b 5d b8             	mov    -0x48(%ebp),%ebx //ebp-72的值传给ebx

 8048d1f:	8b 45 bc             	mov    -0x44(%ebp),%eax //ebp-68的值传给eax

 8048d22:	89 43 08             	mov    %eax,0x8(%ebx) // eax传给ebx+8,此时ebx=ebp-72,故eax传给ebp-64

 8048d25:	8b 55 c0             	mov    -0x40(%ebp),%edx //ebp-64传给edx

 8048d28:	89 50 08             	mov    %edx,0x8(%eax)//edx传给eax+8,即ebp-60

 8048d2b:	8b 45 c4             	mov    -0x3c(%ebp),%eax //ebp-60传给eax

 8048d2e:	89 42 08             	mov    %eax,0x8(%edx)//eax传给edx+8,即ebp-56

 8048d31:	8b 55 c8             	mov    -0x38(%ebp),%edx //ebp-56传给edx

 8048d34:	89 50 08             	mov    %edx,0x8(%eax) //edx传给eax+8,即ebp-52

 8048d37:	8b 45 cc             	mov    -0x34(%ebp),%eax //ebp-52传给eax

 8048d3a:	89 42 08             	mov    %eax,0x8(%edx)//eax传给edx+8,即ebp-48

 8048d3d:	c7 40 08 00 00 00 00 movl   $0x0,0x8(%eax) //eax+8,即ebp-44为0

 8048d44:	be 00 00 00 00       	mov    $0x0,%esi //esi=0

 8048d49:	8b 43 08             	mov    0x8(%ebx),%eax //ebx+8传给eax,即ebp-64传给eax

 8048d4c:	8b 13                	mov    (%ebx),%edx

 8048d4e:	3b 10                	cmp    (%eax),%edx

 8048d50:	7d 05                	jge    8048d57 <phase_6+0xce>//eax必须大于edx,结合上面推知第五关是对链表进行排序操作。

 8048d52:	e8 7a 03 00 00       	call   80490d1 <explode_bomb>

 8048d57:	8b 5b 08             	mov    0x8(%ebx),%ebx//ebx+8传给ebx

 8048d5a:	83 c6 01             	add    $0x1,%esi//esi+1

 8048d5d:	83 fe 05             	cmp    $0x5,%esi

 8048d60:	75 e7                	jne    8048d49 <phase_6+0xc0>//若esi不等于5,跳回循环中。

 8048d62:	83 c4 5c             	add    $0x5c,%esp

 8048d65:	5b                   	pop    %ebx

 8048d66:	5e                   	pop    %esi

 8048d67:	5f                   	pop    %edi

 8048d68:	5d                   	pop    %ebp

 8048d69:	c3                   	ret    

思路分析

 这个函数真是又长又臭!真的最难的一个了!幸好查看了0x804c0c4这个地址的内容,发现它显示node,网上查了一下表示链表。然后分析上面变来变去的寄存器发现这是一个交换结点的过程,后来推知这是对链表进行降序排序。起初我猜想链表是存储了两个信息的,一个是数据大小,另一个是指向下一个节点的地址,后来查看发现还存放了节点的序号,从输入参数限制小于6知第六关的通关密码为排好序后的节点的下标顺序!通过GDB查看六个节点的值分别为:

      

下一个节点的地址在上一个节点已经给出。

每个结点存储的值依次为:1a7, 6c ,155,187,3bd,255。从大到校排序后:3bd2551a71871556c。那么节点顺序就是:561432。(因为输入的六个数不能大于6,所以不可能输入结点存储的值。)

结论

第六关的通关密码为:561432

7.Secret_phase

原理设计

首先是找到入口函数,那么先找到哪里调用了这个secre_phase函数,查看发现在phase_defused调用了这个函数。接着分析发现secre_phase还调用了一个fun7函数,经过一系列分析发现开启隐藏关卡的条件是在输入两个%d%d的关卡后再输入一个%s,通过第六关就会提示你成功开启了隐藏关卡。然后分析通关条件,发现是让你输入的是一个十进制数,然后作判断,相等则通关,否则引爆炸弹。避开炸弹的方法是让引爆炸弹的条件不成立即可。

实现与分析

由于秘密关卡关联的函数有点多,分析较难,故引用日志并加以修改。

 08049014 <phase_defused>:

 8049014:	55                   	push   %ebp

 8049015:	89 e5                	mov    %esp,%ebp

 8049017:	81 ec 88 00 00 00    	sub    $0x88,%esp

 804901d:	65 a1 14 00 00 00    	mov    %gs:0x14,%eax

 8049023:	89 45 f4             	mov    %eax,-0xc(%ebp)

 8049026:	31 c0                	xor    %eax,%eax

 8049028:	83 3d d0 c3 04 08 06 cmpl   $0x6,0x804c3d0

 804902f:	0f 85 86 00 00 00    	jne    80490bb <phase_defused+0xa7>

 8049035:	8d 45 a4             	lea    -0x5c(%ebp),%eax

//先是做一个判断,如果你前面六个关卡已经通关那么开启隐藏关卡,否则跳到80490bb结束,接下来发现到调用密码关卡函数前movl了一连串的立即数:

 

 8049038:	89 44 24 10          	mov    %eax,0x10(%esp)

 804903c:	8d 45 9c             	lea    -0x64(%ebp),%eax

 804903f:	89 44 24 0c          	mov    %eax,0xc(%esp)

 8049043:	8d 45 a0             	lea    -0x60(%ebp),%eax

 8049046:	89 44 24 08          	mov    %eax,0x8(%esp)

 804904a:	c7 44 24 04 00 a2 04 movl   $0x804a200,0x4(%esp)

 8049051:	08

 8049052:	c7 04 24 d0 c4 04 08 movl   $0x804c4d0,(%esp)

 8049059:	e8 e2 f7 ff ff       	call   8048840 <__isoc99_sscanf@plt>

 804905e:	83 f8 03             	cmp    $0x3,%eax

 8049061:	75 44                	jne    80490a7 <phase_defused+0x93>

 8049063:	c7 44 24 04 09 a2 04 movl   $0x804a209,0x4(%esp)

 804906a:	08

 804906b:	8d 45 a4             	lea    -0x5c(%ebp),%eax

 804906e:	89 04 24             	mov    %eax,(%esp)

 8049071:	e8 35 ff ff ff       	call   8048fab <strings_not_equal>

 8049076:	85 c0                	test   %eax,%eax

 8049078:	75 2d                	jne    80490a7 <phase_defused+0x93>

 804907a:	c7 44 24 04 dc a2 04 movl   $0x804a2dc,0x4(%esp)

 8049081:	08

 8049082:	c7 04 24 01 00 00 00 movl   $0x1,(%esp)

 8049089:	e8 e2 f7 ff ff       	call   8048870 <__printf_chk@plt>

 804908e:	c7 44 24 04 04 a3 04 movl   $0x804a304,0x4(%esp)

 8049095:	08

 8049096:	c7 04 24 01 00 00 00 movl   $0x1,(%esp)

 804909d:	e8 ce f7 ff ff       	call   8048870 <__printf_chk@plt>

 80490a2:	e8 74 fb ff ff       	call   8048c1b <secret_phase>

 80490a7:	c7 44 24 04 3c a3 04 movl   $0x804a33c,0x4(%esp)

 80490ae:	08

 80490af:	c7 04 24 01 00 00 00 movl   $0x1,(%esp)

 80490b6:	e8 b5 f7 ff ff       	call   8048870 <__printf_chk@plt>

 80490bb:	8b 45 f4             	mov    -0xc(%ebp),%eax

 80490be:	65 33 05 14 00 00 00 xor    %gs:0x14,%eax

 80490c5:	74 05                	je     80490cc <phase_defused+0xb8>

 80490c7:	e8 e4 f6 ff ff       	call   80487b0 <__stack_chk_fail@plt>

 80490cc:	c9                   	leave  

 80490cd:	8d 76 00             	lea    0x0(%esi),%esi

 80490d0:	c3                   	ret    

GBD查看这些地址存储的内容:

 

先输入两个整数再输入字符串,就能开启密码关卡,而前面要输入两个整数的关卡只有345。所以应该是要在这三关中任意一关输入DrEvil就能激活秘密关卡。下面测试一下:(我测试过了只能在第三关输入才能激活密码关卡)

 

接下来回去看密码关卡的代码:

08048c1b <secret_phase>:

 8048c1b:	55                   	push   %ebp

 8048c1c:	89 e5                	mov    %esp,%ebp

 8048c1e:	53                   	push   %ebx

 8048c1f:	83 ec 14             	sub    $0x14,%esp

 8048c22:	e8 df 05 00 00       	call   8049206 <read_line>

 8048c27:	c7 44 24 08 0a 00 00 movl   $0xa,0x8(%esp)

 8048c2e:	00

 8048c2f:	c7 44 24 04 00 00 00 movl   $0x0,0x4(%esp)

 8048c36:	00

 8048c37:	89 04 24             	mov    %eax,(%esp)

 8048c3a:	e8 71 fc ff ff       	call   80488b0 <strtol@plt>

 8048c3f:	89 c3                	mov    %eax,%ebx

 8048c41:	8d 40 ff             	lea    -0x1(%eax),%eax

 8048c44:	3d e8 03 00 00       	cmp    $0x3e8,%eax

 8048c49:	76 05                	jbe    8048c50 <secret_phase+0x35>

 8048c4b:	e8 81 04 00 00       	call   80490d1 <explode_bomb>

 8048c50:	89 5c 24 04          	mov    %ebx,0x4(%esp)

 8048c54:	c7 04 24 78 c1 04 08 movl   $0x804c178,(%esp)

 8048c5b:	e8 6a ff ff ff       	call   8048bca <fun7>

 8048c60:	83 f8 05             	cmp    $0x5,%eax

 8048c63:	74 05                	je     8048c6a <secret_phase+0x4f>

 8048c65:	e8 67 04 00 00       	call   80490d1 <explode_bomb>

 8048c6a:	c7 44 24 04 34 a1 04 movl   $0x804a134,0x4(%esp)

 8048c71:	08

 8048c72:	c7 04 24 01 00 00 00 movl   $0x1,(%esp)

 8048c79:	e8 f2 fb ff ff       	call   8048870 <__printf_chk@plt>

 8048c7e:	e8 91 03 00 00       	call   8049014 <phase_defused>

 8048c83:	83 c4 14             	add    $0x14,%esp

 8048c86:	5b                   	pop    %ebx

 8048c87:	5d                   	pop    %ebp

 8048c88:	c3                   	ret    

 call  8049206 <read_line>,表明程序先读入一行,随后返回值%eax作为函数<strtol@plt>的参数之一,另外两个参数分别

是0xa和0x0,也就是我们需要输入一个十进制数。由lea -0x1(%eax),%eax 和cmp $0x3e8,%eax 这两句知输入的十进制数要小于等

于1001。随后将所输入的数作为<fun7> 的参数之一。另外一个参数来自 0x804c178,查看为0x24。

接下来发现调用了fun7函数。找到它的代码:

 08048bca <fun7>:

 8048bca:	55                   	push   %ebp

 8048bcb:	89 e5                	mov    %esp,%ebp

 8048bcd:	53                   	push   %ebx

 8048bce:	83 ec 14             	sub    $0x14,%esp

 8048bd1:	8b 55 08             	mov    0x8(%ebp),%edx

 8048bd4:	8b 4d 0c             	mov    0xc(%ebp),%ecx

 8048bd7:	b8 ff ff ff ff       	mov    $0xffffffff,%eax

 8048bdc:	85 d2                	test   %edx,%edx

 8048bde:	74 35                	je     8048c15 <fun7+0x4b>

 8048be0:	8b 1a                	mov    (%edx),%ebx

 8048be2:	39 cb                	cmp    %ecx,%ebx

 8048be4:	7e 13                	jle    8048bf9 <fun7+0x2f>

 8048be6:	89 4c 24 04          	mov    %ecx,0x4(%esp)

 8048bea:	8b 42 04             	mov    0x4(%edx),%eax

 8048bed:	89 04 24             	mov    %eax,(%esp)

 8048bf0:	e8 d5 ff ff ff       	call   8048bca <fun7>

 8048bf5:	01 c0                	add    %eax,%eax

 8048bf7:	eb 1c                	jmp    8048c15 <fun7+0x4b>

 8048bf9:	b8 00 00 00 00       	mov    $0x0,%eax

 8048bfe:	39 cb                	cmp    %ecx,%ebx

 8048c00:	74 13                	je     8048c15 <fun7+0x4b>

 8048c02:	89 4c 24 04          	mov    %ecx,0x4(%esp)

 8048c06:	8b 42 08             	mov    0x8(%edx),%eax

 8048c09:	89 04 24             	mov    %eax,(%esp)

 8048c0c:	e8 b9 ff ff ff       	call   8048bca <fun7>

 8048c11:	8d 44 00 01          	lea    0x1(%eax,%eax,1),%eax

 8048c15:	83 c4 14             	add    $0x14,%esp

 8048c18:	5b                   	pop    %ebx

 8048c19:	5d                   	pop    %ebp

 8048c1a:	c3                   	ret    

思路分析:

首先看mov 0x8(%ebp),%edx,mov 0xc(%ebp),%ecx。不妨设为参数A,BB是自己输入的参数)。这步操作是传参操作,test %edx%edx是递归终止的时候,返回%edx=0。接下来是进入递归:这里有两个判断,如果A<B,那么将地址A+8作为地址进入递归。如A>B,以地址A+4作为地址进入递归。代码add %eax %eax将递归返回值加倍。代码add $0x14,%esp是将返回值再加一。在调用<fun7>之后,紧跟着cmp $0x5,%eax,即返回值必须为5。<fun7>分析如上,为递归函数,与第四题十分相似。递归最深处的返回值肯定为0,最外层返回值为5,可得出如下反递归过程:

A*2+1=5 -->A=2    即有*A<B

A*2=2  -->A=1    有*A>B

A*2+1=1 -->A=0    即有*A<B

也就是说在这三次递归中两次执行了“若A<B将(A+8)作为地址进入递归”系列代码,一次执行了“若A>B,将(A+4)作为地址进入递归”系列代码。使用GDB查询储存值:


最后一次得到的值是0x2f,化为十进制是32+15=47。下面来验证一下:



秘密关卡调用的函数太多了,分析起来很麻烦,我也是参考了下别人的做法,写得不是太好,若有问题欢迎留言指出,转载务必注明出处谢谢。

完成于2018.6.29



猜你喜欢

转载自blog.csdn.net/weixin_42294984/article/details/80839294
今日推荐