BOMBLAB

目录

实验目的

实验原理

实验准备

实验过程

一、phase_1

二、phase_2

三、phase_3

四、phase_4

五、phase_5

六、phase_6

七、secret_phase


实验目的

        理解汇编语言,学会使用调试器。

实验原理

        二进制炸弹是作为一个目标代码文件提供给学生们的程序,运行时,它提示用户输入6个不同的字符串。如果其中任何一个不正确,炸弹就会“爆炸”:打印出一条错误信息。学生通过反汇编和逆向工程来确定是哪六个字符串,从而解除他们各自炸弹的雷管。

实验准备

        找到自己对应的bomb文件并解压,得到可执行文件bomb和C语言文件bomb.c,通过VMtools里的共享文件夹传输到虚拟机里的ubantu系统里,就可以在ubantu系统里进行调试和运行了。

       使用objdump -d bomb > bomblab.txt指令将汇编代码输出到文本文件里方便查看,得到汇编代码后就可以进行拆弹了。        

实验过程

一、phase_1

phase_1的汇编代码如下:

08048b90 <phase_1>:
 8048b90:	83 ec 1c             	sub    $0x1c,%esp //初始化栈
 8048b93:	c7 44 24 04 44 a1 04 	movl   $0x804a144,0x4(%esp)
 8048b9a:	08 
 8048b9b:	8b 44 24 20          	mov    0x20(%esp),%eax
 8048b9f:	89 04 24             	mov    %eax,(%esp) //参数准备
 8048ba2:	e8 63 04 00 00       	call  804900a <strings_not_equal>//调用函数
 8048ba7:	85 c0                	test   %eax,%eax //检查结果
 8048ba9:	74 05                	je     8048bb0 <phase_1+0x20>
 8048bab:	e8 65 05 00 00       	call   8049115 <explode_bomb>
 8048bb0:	83 c4 1c             	add    $0x1c,%esp
 8048bb3:	c3                   	ret  

        可以看到在我们输入并且调用phase_1函数后,phase_1函数便开始准备参数并调用strings_not_equal函数,该函数的功能为判断两个字符串是否相等,相等则返回0,不相等则返回1;后面的test指令将两个操作数进行按位与,并根据结果设置各个标志位,test %eax,%eax就是判断eax是否为0,如果为0则标志位ZF会被设置为1,下面的je指令就会执行,从而跳过炸弹,就是说只有我们输入的字符串与程序保存的字符串相等才能不触发炸弹,至此我们可以判断:传进去的参数0x804a144即为题给字符串的首地址,下面我们调试程序来验证这一猜想:

可以看到,该位置确实存放着一个特殊的字符串,下面验证该字符串是否为phase_1的钥匙:

验证成功,该字符串确实是phase_1的钥匙,第一关通过。

二、phase_2

 phase_2的汇编代码如下:

08048bb4 <phase_2>:
 8048bb4:	53                   	push   %ebx
 8048bb5:	83 ec 38             	sub    $0x38,%esp
 8048bb8:	8d 44 24 18          	lea    0x18(%esp),%eax
 8048bbc:	89 44 24 04          	mov    %eax,0x4(%esp)
 8048bc0:	8b 44 24 40          	mov    0x40(%esp),%eax
 8048bc4:	89 04 24             	mov    %eax,(%esp)
 8048bc7:	e8 70 05 00 00     	call   804913c <read_six_numbers>//读入六个数
 8048bcc:	83 7c 24 18 00       	cmpl   $0x0,0x18(%esp) //与0相比
 8048bd1:	79 22                	jns    8048bf5 <phase_2+0x41> //小于0则跳转
 8048bd3:	e8 3d 05 00 00       	call   8049115 <explode_bomb> 
 8048bd8:	eb 1b                	jmp    8048bf5 <phase_2+0x41>
 8048bda:	89 d8                	mov    %ebx,%eax
 8048bdc:	03 44 9c 14          	add    0x14(%esp,%ebx,4),%eax
 8048be0:	39 44 9c 18          	cmp    %eax,0x18(%esp,%ebx,4)
 8048be4:	74 05                	je     8048beb <phase_2+0x37>
 8048be6:	e8 2a 05 00 00       	call   8049115 <explode_bomb>
 8048beb:	83 c3 01             	add    $0x1,%ebx
 8048bee:	83 fb 06             	cmp    $0x6,%ebx
 8048bf1:	75 e7                	jne    8048bda <phase_2+0x26>
 8048bf3:	eb 07                	jmp    8048bfc <phase_2+0x48>
 8048bf5:	bb 01 00 00 00       	mov    $0x1,%ebx
 8048bfa:	eb de                	jmp    8048bda <phase_2+0x26>
 8048bfc:	83 c4 38             	add    $0x38,%esp
 8048bff:	5b                   	pop    %ebx
 8048c00:	c3                   	ret    

        根据汇编我们可以看到phase_2在开头就调用了read_six_numbers来读入六个数,起初我找到了read_six_numbers的汇编代码,但发现其中还有许多其他的调用,很难能全部看懂,其实根据函数名就可以猜出来该函数的作用就是读入六个数,所以说并不用在意他的实现细节,同时这也体现了规范命名的重要性,如果read_six_numbers是别的乱取的名字那就另当别论了。

        在读入六个数之后,程序将$0和0x18(%esp)进行了对比,并设置了jns指令,意思是将0x18(%esp)减去0,如果结果的符号位不为1则跳转,而0x18(%esp)这个位置储存的是我们输入的第一个数,这就要求我们输入的第一个数必须大于等于0。

    接下来跳转到8048bf5,这是一个循环5次的循环,该循环的目的是检验0x14(%esp,%ebx,4)+%ebx和0x18(%esp,%ebx,4)是否相等,不相等则引爆炸弹,0x14(%esp,%ebx,4)和0x18(%esp,%ebx,4)在数组中是相邻的元素,而ebx储存的是循环次数,有了这些信息,我们就可以复现这个循环:

for(int i = 1; i < 6; i++){
    if(arr[i] + i != arr[i+1])
        bomb();
}

        从这里可以看出,题目的要求是让我们输入一个满足特定关系的数组,这个关系就包含在上面的循环中,假设输入的的第一个数是1,则第二个数为1+1=2,第三个数为2+2=4,第四个数为4+3=7,第五个数为7+4=11,第六个数为11+5=16,可以得出一个钥匙为:1 2 4 7 11 16,下面验证该钥匙是否正确:

         验证结果正确,前面提到过,输入的第一个数只要求大于等于0,所以本题应该不止一个答案,0 1 3 6 10 15应该也是一个正确的key,下面进行验证:

        验证结果正确,可以得到本题的通解: x+0 x+1 x+3 x+6 x+10 x+15,x为输入的第一个数,x>=0,当然,如果x大到超出表示范围也是不可以的。

第二关通过。

三、phase_3

phase_3的汇编代码如下:

08048c01 <phase_3>:
 8048c01:	83 ec 2c             	sub    $0x2c,%esp
 8048c04:	8d 44 24 1c          	lea    0x1c(%esp),%eax
 8048c08:	89 44 24 0c          	mov    %eax,0xc(%esp)
 8048c0c:	8d 44 24 18          	lea    0x18(%esp),%eax
 8048c10:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048c14:	c7 44 24 04 0f a3 04 	movl   $0x804a30f,0x4(%esp)
 8048c1b:	08 
 8048c1c:	8b 44 24 30          	mov    0x30(%esp),%eax
 8048c20:	89 04 24             	mov    %eax,(%esp)
 8048c23:	e8 38 fc ff ff       	call   8048860 <__isoc99_sscanf@plt>
 8048c28:	83 f8 01             	cmp    $0x1,%eax //参数不止一个
 8048c2b:	7f 05                	jg     8048c32 <phase_3+0x31>
 8048c2d:	e8 e3 04 00 00       	call   8049115 <explode_bomb>
 8048c32:	83 7c 24 18 07       	cmpl   $0x7,0x18(%esp)
 8048c37:	77 3c                	ja     8048c75 <phase_3+0x74> //大于7跳转
 8048c39:	8b 44 24 18          	mov    0x18(%esp),%eax
 8048c3d:	ff 24 85 a0 a1 04 08 	jmp    *0x804a1a0(,%eax,4) //跳转表
 8048c44:	b8 9e 01 00 00       	mov    $0x19e,%eax //0
 8048c49:	eb 3b                	jmp    8048c86 <phase_3+0x85>
 8048c4b:	b8 f6 01 00 00       	mov    $0x1f6,%eaxc //2
 8048c50:	eb 34                	jmp    8048c86 <phase_3+0x85>
 8048c52:	b8 de 00 00 00       	mov    $0xde,%eax //3
 8048c57:	eb 2d                	jmp    8048c86 <phase_3+0x85>
 8048c59:	b8 4d 00 00 00       	mov    $0x4d,%eax //4
 8048c5e:	eb 26                	jmp    8048c86 <phase_3+0x85>
 8048c60:	b8 e0 03 00 00       	mov    $0x3e0,%eax //5
 8048c65:	eb 1f                	jmp    8048c86 <phase_3+0x85>
 8048c67:	b8 48 01 00 00       	mov    $0x148,%eax //6
 8048c6c:	eb 18                	jmp    8048c86 <phase_3+0x85>
 8048c6e:	b8 8b 03 00 00       	mov    $0x38b,%eax //7
 8048c73:	eb 11                	jmp    8048c86 <phase_3+0x85>
 8048c75:	e8 9b 04 00 00       	call   8049115 <explode_bomb>
 8048c7a:	b8 00 00 00 00       	mov    $0x0,%eax
 8048c7f:	eb 05                	jmp    8048c86 <phase_3+0x85>
 8048c81:	b8 d6 00 00 00       	mov    $0xd6,%eax //1
 8048c86:	3b 44 24 1c          	cmp    0x1c(%esp),%eax
 8048c8a:	74 05                	je     8048c91 <phase_3+0x90>
 8048c8c:	e8 84 04 00 00       	call   8049115 <explode_bomb>
 8048c91:	83 c4 2c             	add    $0x2c,%esp
 8048c94:	c3                   	ret  

        从输入后的cmp  $0x1,%eax可以判断本题输入的参数不止一个,如果只有一个便会直接引爆炸弹,而下面将第一个数与7相比,并且在大于7的时候跳转到炸弹,说明输入的第一个数必须小于等于7。再下面出现了一个jmp  *0x804a1a0(,%eax,4)指令,次数的eax里的值经过上一个指令的处理变成了我们输入的第一个数,意思是将我们输入的第一个数作为偏移量,到相应的位置去取地址并跳转,此处的0x804a1a0其实是跳转表的首地址,下面我们调试查看跳转表的内容:

        可以看到该跳转表里储存着许多指令地址,当输入的第一个数为0时,对应的将要跳转到的地址是:0x8048c44,该指令将$0x19e放入寄存器eax,后面的操作是将我们输入的第二个数与eax储存的数进行对比,不相等则引爆炸弹,至此可以分析出题目要求:输入一个数,并且找出它在switch语句里对应的第二个数,当第一个数为0时,第二个数应该为0x19e,即十进制的414,下面验证该key是否正确:

        验证正确,根据题意可知本题不止一个答案,只需输入的两个数在switch语句中对应即可,第一个数为1时,对应跳转地址为0x8048c81,该地址处的数为0xd6,即十进制的214,下面验证该key是否正确: 

 验证正确,本题中其他答案对应的数已在汇编代码的注释中标明,第三关通过。

四、phase_4

phase_4的汇编代码如下:

08048cf6 <phase_4>:
 8048cf6:	83 ec 2c             	sub    $0x2c,%esp
 8048cf9:	8d 44 24 1c          	lea    0x1c(%esp),%eax
 8048cfd:	89 44 24 0c          	mov    %eax,0xc(%esp)
 8048d01:	8d 44 24 18          	lea    0x18(%esp),%eax
 8048d05:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048d09:	c7 44 24 04 0f a3 04 	movl   $0x804a30f,0x4(%esp)
 8048d10:	08 
 8048d11:	8b 44 24 30          	mov    0x30(%esp),%eax
 8048d15:	89 04 24             	mov    %eax,(%esp)
 8048d18:	e8 43 fb ff ff       	call   8048860 <__isoc99_sscanf@plt>
 8048d1d:	83 f8 02             	cmp    $0x2,%eax //两个参数
 8048d20:	75 07                	jne    8048d29 <phase_4+0x33>
 8048d22:	83 7c 24 18 0e       	cmpl   $0xe,0x18(%esp)//对比第一个输入的数
 8048d27:	76 05                	jbe    8048d2e <phase_4+0x38>
 8048d29:	e8 e7 03 00 00       	call   8049115 <explode_bomb>
 8048d2e:	c7 44 24 08 0e 00 00 	movl   $0xe,0x8(%esp)//参数3
 8048d35:	00 
 8048d36:	c7 44 24 04 00 00 00 	movl   $0x0,0x4(%esp)//参数2
 8048d3d:	00 
 8048d3e:	8b 44 24 18          	mov    0x18(%esp),%eax//参数1
 8048d42:	89 04 24             	mov    %eax,(%esp)
 8048d45:	e8 4b ff ff ff       	call   8048c95 <func4>
 8048d4a:	83 f8 07             	cmp    $0x7,%eax //对比返回值
 8048d4d:	75 07                	jne    8048d56 <phase_4+0x60>
 8048d4f:	83 7c 24 1c 07       	cmpl   $0x7,0x1c(%esp)//对比第二个数
 8048d54:	74 05                	je     8048d5b <phase_4+0x65>
 8048d56:	e8 ba 03 00 00       	call   8049115 <explode_bomb>
 8048d5b:	83 c4 2c             	add    $0x2c,%esp
 8048d5e:	c3                   	ret  

phase_4在输入后检查了参数数量,要求输入两个参数,并且要求第一个数小于等于0xe,即十进制的14,否则便会引爆炸弹。接下来便是准备参数,调用func4,从汇编中可以看出,func4的第一个参数为我们输入的第一个数,第二个参数为0x0,即十进制的0,第三个参数为0xe,即十进制的14。再往下可以发现程序将func4的返回值与7进行了对比,且将输入的第二个数与7进行了对比,如果有一个不相等则引爆炸弹,所以输入的第二个数可以确定是7,输入的第一个数x要使func4(x,0,14)的返回值为7。

func4的汇编代码如下:

08048c95 <func4>:
 8048c95:	56                   	push   %esi
 8048c96:	53                   	push   %ebx
 8048c97:	83 ec 14             	sub    $0x14,%esp
 8048c9a:	8b 54 24 20          	mov    0x20(%esp),%edx //x
 8048c9e:	8b 44 24 24          	mov    0x24(%esp),%eax //y
 8048ca2:	8b 5c 24 28          	mov    0x28(%esp),%ebx //z
 8048ca6:	89 d9                	mov    %ebx,%ecx //ecx:z
 8048ca8:	29 c1                	sub    %eax,%ecx //ecx:z-y
 8048caa:	89 ce                	mov    %ecx,%esi //esi:z-y
 8048cac:	c1 ee 1f             	shr    $0x1f,%esi //右移31位
 8048caf:	01 f1                	add    %esi,%ecx //ecx:z-y+sign
 8048cb1:	d1 f9                	sar    %ecx //ecx:(z-y)/2
 8048cb3:	01 c1                	add    %eax,%ecx //ecx:y+(z-y)/2
 8048cb5:	39 d1                	cmp    %edx,%ecx //对比x与%ecx
 8048cb7:	7e 17                	jle    8048cd0 <func4+0x3b>
 8048cb9:	83 e9 01             	sub    $0x1,%ecx
 8048cbc:	89 4c 24 08          	mov    %ecx,0x8(%esp)
 8048cc0:	89 44 24 04          	mov    %eax,0x4(%esp)
 8048cc4:	89 14 24             	mov    %edx,(%esp) //准备参数
 8048cc7:	e8 c9 ff ff ff       	call   8048c95 <func4>
 8048ccc:	01 c0                	add    %eax,%eax //返回值*2
 8048cce:	eb 20                	jmp    8048cf0 <func4+0x5b>
 8048cd0:	b8 00 00 00 00       	mov    $0x0,%eax
 8048cd5:	39 d1                	cmp    %edx,%ecx
 8048cd7:	7d 17                	jge    8048cf0 <func4+0x5b>
 8048cd9:	89 5c 24 08          	mov    %ebx,0x8(%esp)
 8048cdd:	83 c1 01             	add    $0x1,%ecx
 8048ce0:	89 4c 24 04          	mov    %ecx,0x4(%esp)
 8048ce4:	89 14 24             	mov    %edx,(%esp) //准备参数
 8048ce7:	e8 a9 ff ff ff       	call   8048c95 <func4>
 8048cec:	8d 44 00 01          	lea    0x1(%eax,%eax,1),%eax //返回值*2+1
 8048cf0:	83 c4 14             	add    $0x14,%esp
 8048cf3:	5b                   	pop    %ebx
 8048cf4:	5e                   	pop    %esi
 8048cf5:	c3                   	ret

        如汇编代码所示,将三个参数分别记为x,y,z,程序首先对参数进行了一系列算术操作得到(z-y),然后又将其右移了31位,这个右移的目的是获得它的符号位,然后将该符号位sign与(z-y)相加再右移一位,不难猜出这个右移的目的是做除法操作除以2,但这个加上符号位又是干什么呢?起初我没有反应过来,后来想到在datalab中有一个类似的题目:用右移实现除法,这才想起来加上符号位是为了进行修正右移向负无穷大取整,而除法是向0取整,加上符号位就可以对负数除法进行修正,让其向0取整。说了这么多,这其实就是一个除法操作,得到(z-y)/2,然后进行一个加法操作得到y+(z-y)/2。

        再往下可以发现func4是一个递归函数当x<y+(z-y)/2时返回2*func(x,y,y+(z-y)/2-1);当x>y+(z-y)/2时返回2*func4(x,y+(z-y)/2+1,z)+1;当x=y+(z-y)/2时返回0,至此我们可以将func4用c代码表示出来了:

int cal(int y,int z){
    return y + (z-y)/2;
}
func4(int x, int y, int z){
    if(cal(y, z) < x)
        return 2 * func4(x, cal(y, z)+1, z) + 1;
    else if(cal(y, z) > x)
        return 2 * func4(x, y, cal(y, z)-1);
    else 
        return 0;
}

当然,源代码中并没有封装cal函数,该函数只是我为了方便观察而自己写的,它并不影响func4的功能,现在的任务就是func(x, 0, 14) = 7,需要求出x的值。

func4(x, 0, 14) = 2 * func4(x, 7+1, 14) + 1, func(x, 8, 14)需等于3

func4(x, 8, 14) = 2 * func4(x, 11+1, 14) + 1, func(x, 12, 14)需等于1

func4(x, 12 ,14) = 2 * func4(x, 13+1, 14) + 1, func4(x, 14, 14)需等于0

x = 14 + (14-14)/2 = 14

至此,推出第一个数为14,第二个数为7,下面验证该key是否正确:

        验证正确,解出结果的时候我比较疑惑为什么第二个数没有做任何操作,而只是简单的判断输入的是否是7,可能是我的这个func4相对较难?我看参考思路里面的func4是关于斐波那契数列的,所以我猜想我的也有可能是与一些知名的数学猜想或问题有关,也正是有了这个想法,当我把自己的func4函数原型写出来时,我都不太自信,因为这个函数很奇怪,我只能看出它最终是要达到让x等于y,z的中位数的效果,至于返回值有什么特殊的数学意义我还不太清楚,也有可能这就是一个有名的数学问题而我不了解罢了。

 第四关通过。

五、phase_5

phase_5的汇编代码如下:

08048d5f <phase_5>:
 8048d5f:	83 ec 2c             	sub    $0x2c,%esp
 8048d62:	8d 44 24 1c          	lea    0x1c(%esp),%eax
 8048d66:	89 44 24 0c          	mov    %eax,0xc(%esp)
 8048d6a:	8d 44 24 18          	lea    0x18(%esp),%eax
 8048d6e:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048d72:	c7 44 24 04 0f a3 04 	movl   $0x804a30f,0x4(%esp)
 8048d79:	08 
 8048d7a:	8b 44 24 30          	mov    0x30(%esp),%eax
 8048d7e:	89 04 24             	mov    %eax,(%esp)
 8048d81:	e8 da fa ff ff       	call   8048860 <__isoc99_sscanf@plt>
 8048d86:	83 f8 01             	cmp    $0x1,%eax //参数不止一个
 8048d89:	7f 05                	jg     8048d90 <phase_5+0x31>
 8048d8b:	e8 85 03 00 00       	call   8049115 <explode_bomb>
 8048d90:	8b 44 24 18          	mov    0x18(%esp),%eax
 8048d94:	83 e0 0f             	and    $0xf,%eax //保留第一个字节
 8048d97:	89 44 24 18          	mov    %eax,0x18(%esp)
 8048d9b:	83 f8 0f             	cmp    $0xf,%eax //检查是否等于15
 8048d9e:	74 2a                	je     8048dca <phase_5+0x6b>
 8048da0:	b9 00 00 00 00       	mov    $0x0,%ecx
 8048da5:	ba 00 00 00 00       	mov    $0x0,%edx //edx中为循环次数
 8048daa:	83 c2 01             	add    $0x1,%edx
 8048dad:	8b 04 85 c0 a1 04 08 	mov    0x804a1c0(,%eax,4),%eax 
 8048db4:	01 c1                	add    %eax,%ecx  //ecx保存每次eax值的累加
 8048db6:	83 f8 0f             	cmp    $0xf,%eax //循环条件
 8048db9:	75 ef                	jne    8048daa <phase_5+0x4b>
 8048dbb:	89 44 24 18          	mov    %eax,0x18(%esp)
 8048dbf:	83 fa 0f             	cmp    $0xf,%edx //判断循环次数
 8048dc2:	75 06                	jne    8048dca <phase_5+0x6b>
 8048dc4:	3b 4c 24 1c          	cmp    0x1c(%esp),%ecx
 8048dc8:	74 05                	je     8048dcf <phase_5+0x70>
 8048dca:	e8 46 03 00 00       	call   8049115 <explode_bomb>
 8048dcf:	83 c4 2c             	add    $0x2c,%esp
 8048dd2:	c3                   	ret 

        首先,从输入过后的cmp语句可以知道本题的参数不止一个,后面进行了一个AND操作保留了第一个数的第一个字节,还对第一个数进行了检验,如果等于15会直接引爆炸弹。然后就进入了一个15次的循环,其实说是15次的循环不够妥当,该循环的退出条件是eax的值为15,退出后会检查循环次数,只有循环次数为15次才不会引爆炸弹,所以15次是在正确情况下才会循环15次。

        观察循环体可知每次循环使用地址0x804a1c0(,%eax,4),%eax寻值,将寻到的值保存在eax中,直至eax的值为15,而每次循环会将eax的值累加到ecx中,最后会将输入的第二个数与ecx中的数比较,相等才不引爆炸弹,而第一个参数在循环开始之前就被放到eax中,作为第一次寻值时的地址偏移量,所以现在的目标就是通过eax最终的值为15这个信息反推,推出这15次循环中eax的值后,结果也就浮出水面了。由于寻址的基址为0x804a1c0,我们不妨通过调试将这个地址的信息打印出来,方便后续求解:

15保存在0x804a1c0 + 24,上一个eax的值是24/4 = 6

6 保存在0x804a1c0 + 56,上一个eax的值是56/4 = 14

14保存在0x804a1c0 + 8,上一个eax的值8/4 = 2

2 保存在0x804a1c0 + 4,上一个eax的值4/4 = 1

1 保存在0x804a1c0 + 40,上一个eax的值40/4 = 10

10保存在0x804a1c0 + 0,上一个eax的值0/4 = 0

0 保存在0x804a1c0 + 32,上一个eax的值32/4 = 8

8 保存在0x804a1c0 + 16,上一个eax的值16/4 = 4

4 保存在0x804a1c0 + 36,上一个eax的值36/4 = 9

9 保存在0x804a1c0 + 52,上一个eax的值52/4 = 13

13保存在0x804a1c0 + 44,上一个eax的值44/4 = 11

11保存在0x804a1c0 + 28,上一个eax的值28/4 = 7

7 保存在0x804a1c0 + 12,上一个eax的值12/4 = 3

3 保存在0x804a1c0 + 48,上一个eax的值48/4 = 12

至此可以得出第一个数为12,第二个数为eax的所有值的累加:0+1+2+…+15-5=115

现在验证该key是否正确:

这个key是错误的,为什么呢?前面已经提到过:而第一个参数在循环开始之前就被放到eax中,作为第一次寻值时的地址偏移量,所以说12应该是第一个数经过寻值后得到的数,也就是说要再往前反推一个数:12 保存在0x804a1c0 + 20,上一个eax的值20/4 = 5,

这个5才是真正的第一个数,下面验证这个新的key:

        验证正确。不难发现,最后得到的第一个数就是5,由于只用作寻值,所以并没有加到ecx中,在解这一题时,找到反推的这种思路并没有花很多时间,反倒是一些小错误浪费了许多时间,比如上面提到的直接将12作为第一个数,还有就是把13当成0xc,所以说还是要细心一点。另外,phase_5中前面只保留第一个数的第一个字节的操作是多余的,但是在参考思路中的那个例子理解这一操作却很重要,我猜测这是在自动分配关卡时将这些操作保留了下来,不过对解题并无影响。

第五关通过。

六、phase_6

phase_6的汇编代码如下:

Part1:
 8048dd3:	56                   	push   %esi
 8048dd4:	53                   	push   %ebx
 8048dd5:	83 ec 44             	sub    $0x44,%esp
 8048dd8:	8d 44 24 10          	lea    0x10(%esp),%eax
 8048ddc:	89 44 24 04          	mov    %eax,0x4(%esp)
 8048de0:	8b 44 24 50          	mov    0x50(%esp),%eax
 8048de4:	89 04 24             	mov    %eax,(%esp)
 8048de7:	e8 50 03 00 00       	call   804913c <read_six_numbers>
 8048dec:	be 00 00 00 00       	mov    $0x0,%esi
 8048df1:	8b 44 b4 10          	mov    0x10(%esp,%esi,4),%eax
 8048df5:	83 e8 01             	sub    $0x1,%eax
 8048df8:	83 f8 05             	cmp    $0x5,%eax //输入的数需小于等于6
 8048dfb:	76 05                	jbe    8048e02 <phase_6+0x2f>
 8048dfd:	e8 13 03 00 00       	call   8049115 <explode_bomb>
 8048e02:	83 c6 01             	add    $0x1,%esi
 8048e05:	83 fe 06             	cmp    $0x6,%esi
 8048e08:	75 07                	jne    8048e11 <phase_6+0x3e>
 8048e0a:	bb 00 00 00 00       	mov    $0x0,%ebx
 8048e0f:	eb 38                	jmp    8048e49 <phase_6+0x76>
 8048e11:	89 f3                	mov    %esi,%ebx
 8048e13:	8b 44 9c 10          	mov    0x10(%esp,%ebx,4),%eax
 8048e17:	39 44 b4 0c          	cmp    %eax,0xc(%esp,%esi,4)//查重
 8048e1b:	75 05                	jne    8048e22 <phase_6+0x4f>
 8048e1d:	e8 f3 02 00 00       	call   8049115 <explode_bomb>
 8048e22:	83 c3 01             	add    $0x1,%ebx
 8048e25:	83 fb 05             	cmp    $0x5,%ebx
 8048e28:	7e e9                	jle    8048e13 <phase_6+0x40>
 8048e2a:	eb c5                	jmp    8048df1 <phase_6+0x1e>
Part2:
 8048e2c:	8b 52 08             	mov    0x8(%edx),%edx //找到对应地址
 8048e2f:	83 c0 01             	add    $0x1,%eax //找到对应地址
 8048e32:	39 c8                	cmp    %ecx,%eax
 8048e34:	75 f6                	jne    8048e2c <phase_6+0x59>
 8048e36:	eb 05                	jmp    8048e3d <phase_6+0x6a>
 8048e38:	ba 3c c1 04 08       	mov    $0x804c13c,%edx
 8048e3d:	89 54 b4 28          	mov    %edx,0x28(%esp,%esi,4) //储存地址
 8048e41:	83 c3 01             	add    $0x1,%ebx
 8048e44:	83 fb 06             	cmp    $0x6,%ebx
 8048e47:	74 17                	je     8048e60 <phase_6+0x8d>
 8048e49:	89 de                	mov    %ebx,%esi
 8048e4b:	8b 4c 9c 10          	mov    0x10(%esp,%ebx,4),%ecx //加载输入
 8048e4f:	83 f9 01             	cmp    $0x1,%ecx
 8048e52:	7e e4                	jle    8048e38 <phase_6+0x65>
 8048e54:	b8 01 00 00 00       	mov    $0x1,%eax
 8048e59:	ba 3c c1 04 08       	mov    $0x804c13c,%edx //链表首地址
 8048e5e:	eb cc                	jmp    8048e2c <phase_6+0x59>
Part3:
 8048e60:	8b 5c 24 28          	mov    0x28(%esp),%ebx//加载地址
 8048e64:	8d 44 24 2c          	lea    0x2c(%esp),%eax//加载地址
 8048e68:	8d 74 24 40          	lea    0x40(%esp),%esi
 8048e6c:	89 d9                	mov    %ebx,%ecx
 8048e6e:	8b 10                	mov    (%eax),%edx
 8048e70:	89 51 08             	mov    %edx,0x8(%ecx)
 8048e73:	83 c0 04             	add    $0x4,%eax
 8048e76:	39 f0                	cmp    %esi,%eax
 8048e78:	74 04                	je     8048e7e <phase_6+0xab>
 8048e7a:	89 d1                	mov    %edx,%ecx
 8048e7c:	eb f0                	jmp    8048e6e <phase_6+0x9b>
 8048e7e:	c7 42 08 00 00 00 00 	movl   $0x0,0x8(%edx)
 8048e85:	be 05 00 00 00       	mov    $0x5,%esi
 8048e8a:	8b 43 08             	mov    0x8(%ebx),%eax
 8048e8d:	8b 00                	mov    (%eax),%eax
 8048e8f:	39 03                	cmp    %eax,(%ebx) //对比值
 8048e91:	7e 05                	jle    8048e98 <phase_6+0xc5>
 8048e93:	e8 7d 02 00 00       	call   8049115 <explode_bomb>
 8048e98:	8b 5b 08             	mov    0x8(%ebx),%ebx
 8048e9b:	83 ee 01             	sub    $0x1,%esi
 8048e9e:	75 ea                	jne    8048e8a <phase_6+0xb7>
 8048ea0:	83 c4 44             	add    $0x44,%esp
 8048ea3:	5b                   	pop    %ebx
 8048ea4:	5e                   	pop    %esi
 8048ea5:	c3                   

        当我第一眼看到phase_6的代码时,我就注意到了密密麻麻的jump指令,可以说是没有调用,全是循环。可以看见前面还是调用了read_six_numbers,所以本关还是输入六个数字,其实phase_6可以从0x8048e2a处和0x8048e5e处分为三部分,第一部分为一个两重循环,目的是检测输入的各个数是否相等,而前面也有个cmpl语句,要求输入的数小于等于6。

        第二部分为处理输入部分,可以看到第二部分有一个特殊的立即数:$0x804c13c,由格式不难猜出这是一个地址,下面用gdb调试查看该地址储存的内容:

         可以看出这个数据结构是一个链表,第一个字节储存数据,第二个字节储存节点编号,而第三个字节存储指针,再接着看第二部分,0x10(%esp)处为输入数组的首地址,这样每次加载一个输入,令eax的初值为1,edx的初值为链表首地址,如果eax的值和加载的输入不相等则eax自增,并且edx+8,这样做的目的是使eax的值等于加载的输入时,edx中储存的正好是对应节点的地址,比如加载输入为3的话,最终edx中的值应该是节点3的首地址,而该地址最终会存入0x28(%esp,%esi,4),用于第三部分的检验。 现在第二部分的作用已经明了:将我们输入的数字对应的链表节点的首地址存入地址:0x28(%esp)。结合前面输入六个数小于等于6的要求我们不难猜出,本题是要输入特定的链表顺序。

        第三部分仍然是一个循环,用于检验结果的正确,根据第二部分加载的地址顺序,如果前一个节点的值小于等于后一个节点的值则跳过炸弹,否则引爆炸弹,到这里题目的意思就十分明确了:输入链表值由大到小的顺序。根据上面的图片可以退出当前的链表内容:

节点

1

2

3

4

5

6

505

461

105

923

240

847

根据顺序,key应该是:3 5 2 1 6 4,下面检验该key是否正确:

        验证正确。其实在做第六关时,我真的没什么头绪,因为这一大段汇编代码用到的跳转指令和相关变量实在是太多了,在看的时候我经常搞不清要往哪里跳转、寄存器储存的变量是什么,看了半天只知道这是一个链表数据结构,并且后面有一个类似排序的语句,然后我一想,排序?这一题不是正好输入六个数吗,会不会是链表由小到大的顺序?然后就将key输入了进去,结果真的通关了,再后来用gdb慢慢调试才慢慢看懂这一大段汇编。其实这一大段分成三部分的话还是没那么难理解的,但是初看谁又知道要分成三部分呢,而且第一部分感觉有点迷惑性,因为它好像对正确答案没什么影响。

第六关通过。

七、secret_phase

        由于隐藏关是需要自己触发的,所以这一关实际上有两个任务:一是触发隐藏关,而是找到隐藏关的key。为了知道如何触发隐藏关,在导出的bomblab.txt文件中搜索secret,找到了调用secret_phase的函数phase_defused:

08049286 <phase_defused>:
 8049286:	81 ec 8c 00 00 00    	sub    $0x8c,%esp
 804928c:	65 a1 14 00 00 00    	mov    %gs:0x14,%eax
 8049292:	89 44 24 7c          	mov    %eax,0x7c(%esp)
 8049296:	31 c0                	xor    %eax,%eax
 8049298:	83 3d c8 c3 04 08 06 	cmpl   $0x6,0x804c3c8
 804929f:	75 72                	jne    8049313 <phase_defused+0x8d>
 80492a1:	8d 44 24 2c          	lea    0x2c(%esp),%eax
 80492a5:	89 44 24 10          	mov    %eax,0x10(%esp)
 80492a9:	8d 44 24 28          	lea    0x28(%esp),%eax
 80492ad:	89 44 24 0c          	mov    %eax,0xc(%esp)
 80492b1:	8d 44 24 24          	lea    0x24(%esp),%eax
 80492b5:	89 44 24 08          	mov    %eax,0x8(%esp)
 80492b9:	c7 44 24 04 69 a3 04 	movl   $0x804a369,0x4(%esp)
 80492c0:	08 
 80492c1:	c7 04 24 d0 c4 04 08 	movl   $0x804c4d0,(%esp)
 80492c8:	e8 93 f5 ff ff       	call   8048860 <__isoc99_sscanf@plt>
 80492cd:	83 f8 03             	cmp    $0x3,%eax //三个参数触发隐藏关
 80492d0:	75 35                	jne    8049307 <phase_defused+0x81>
 80492d2:	c7 44 24 04 72 a3 04 	movl   $0x804a372,0x4(%esp)
 80492d9:	08 
 80492da:	8d 44 24 2c          	lea    0x2c(%esp),%eax
 80492de:	89 04 24             	mov    %eax,(%esp)
 80492e1:	e8 24 fd ff ff       	call   804900a <strings_not_equal>
 80492e6:	85 c0                	test   %eax,%eax
 80492e8:	75 1d                	jne    8049307 <phase_defused+0x81>
 80492ea:	c7 04 24 38 a2 04 08 	movl   $0x804a238,(%esp)
 80492f1:	e8 fa f4 ff ff       	call   80487f0 <puts@plt>
 80492f6:	c7 04 24 60 a2 04 08 	movl   $0x804a260,(%esp)
 80492fd:	e8 ee f4 ff ff       	call   80487f0 <puts@plt>
 8049302:	e8 f0 fb ff ff       	call   8048ef7 <secret_phase>
 8049307:	c7 04 24 98 a2 04 08 	movl   $0x804a298,(%esp)
 804930e:	e8 dd f4 ff ff       	call   80487f0 <puts@plt>
 8049313:	8b 44 24 7c          	mov    0x7c(%esp),%eax
 8049317:	65 33 05 14 00 00 00 	xor    %gs:0x14,%eax
 804931e:	74 05                	je     8049325 <phase_defused+0x9f>
 8049320:	e8 9b f4 ff ff       	call   80487c0 <__stack_chk_fail@plt>
 8049325:	81 c4 8c 00 00 00    	add    $0x8c,%esp
 804932b:	c3                   	ret    
 804932c:	66 90                	xchg   %ax,%ax
 804932e:	66 90                	xchg   %ax,%ax

        由于phase_defused每一关都会调用,所以有一些对隐藏关没用的信息,我们只需关注调用secret_phase的那一部分即可,可以调用语句之前的一些语句有很多地址格式的立即数,并且有字符串相等函数的调用,我们可以用gdb调试看看这些地址储存的字符串,也许会对解题有帮助:

        可以看到有三个特殊的字符串,前两个很明显是找到隐藏关之后的引导词,而第三个字符串很可能是通往隐藏关的钥匙,可是这个钥匙应该往哪放呢?

        再往上研究引出调用secret_phase的部分,又可以发现两个可疑的立即数,同样用gdb查看该地址是否有有用的字符串:

        到这一步可以说是豁然开朗了,14和7不正是我们在第四关输入的key吗,而上面的“%d %d %s”也提示我们第四关可以输入一个字符串,而后面的cmp $0x3,%eax更验证了这一点:如果第四关输入了三个参数则触发隐藏关,否则直接跳转至0x8049307,这里也有一个立即数,查看内容为: 

这正是不触发隐藏关时的结束语,下面我们验证上面的结论:

验证成功,触发隐藏关。

隐藏关的汇编代码如下:

08048ef7 <secret_phase>:
 8048ef7:	53                   	push   %ebx
 8048ef8:	83 ec 18             	sub    $0x18,%esp
 8048efb:	e8 8c 02 00 00       	call   804918c <read_line>
 8048f00:	c7 44 24 08 0a 00 00 	movl   $0xa,0x8(%esp)
 8048f07:	00 
 8048f08:	c7 44 24 04 00 00 00 	movl   $0x0,0x4(%esp)
 8048f0f:	00 
 8048f10:	89 04 24             	mov    %eax,(%esp)
 8048f13:	e8 b8 f9 ff ff       	call   80488d0 <strtol@plt>
 8048f18:	89 c3                	mov    %eax,%ebx
 8048f1a:	8d 40 ff             	lea    -0x1(%eax),%eax
 8048f1d:	3d e8 03 00 00       	cmp    $0x3e8,%eax //输入的数小于等于1001
 8048f22:	76 05                	jbe    8048f29 <secret_phase+0x32>
 8048f24:	e8 ec 01 00 00       	call   8049115 <explode_bomb>
 8048f29:	89 5c 24 04          	mov    %ebx,0x4(%esp) //参数2
 8048f2d:	c7 04 24 88 c0 04 08 	movl   $0x804c088,(%esp)//参数1
 8048f34:	e8 6d ff ff ff       	call   8048ea6 <fun7>
 8048f39:	83 f8 07             	cmp    $0x7,%eax //检查返回值
 8048f3c:	74 05                	je     8048f43 <secret_phase+0x4c>
 8048f3e:	e8 d2 01 00 00       	call   8049115 <explode_bomb>
 8048f43:	c7 04 24 70 a1 04 08 	movl   $0x804a170,(%esp)
 8048f4a:	e8 a1 f8 ff ff       	call   80487f0 <puts@plt>
 8048f4f:	e8 32 03 00 00       	call   8049286 <phase_defused>
 8048f54:	83 c4 18             	add    $0x18,%esp
 8048f57:	5b                   	pop    %ebx
 8048f58:	c3                   	ret    
 8048f59:	66 90                	xchg   %ax,%ax
 8048f5b:	66 90                	xchg   %ax,%ax
 8048f5d:	66 90                	xchg   %ax,%ax
 8048f5f:	90              

        在前面可以看到调用的strtol函数,该函数将输入的字符串转化成数字,再往下有cmp指令,要求输入的数字小于等于1001,再接下来便是准备参数的环节,准备调用fun7函数,可以看到在调用完fun7函数后,对fun7函数的返回值进行了检查,只有返回值等于7时才不会引爆炸弹,因此本题的关键在函数fun7上,只要fun7的返回值等于7即可

fun7的汇编代码如下:

08048ea6 <fun7>:
 8048ea6:	53                   	push   %ebx
 8048ea7:	83 ec 18             	sub    $0x18,%esp
 8048eaa:	8b 54 24 20          	mov    0x20(%esp),%edx
 8048eae:	8b 4c 24 24          	mov    0x24(%esp),%ecx
 8048eb2:	85 d2                	test   %edx,%edx
 8048eb4:	74 37                	je     8048eed <fun7+0x47>
 8048eb6:	8b 1a                	mov    (%edx),%ebx
 8048eb8:	39 cb                	cmp    %ecx,%ebx
 8048eba:	7e 13                	jle    8048ecf <fun7+0x29>
 8048ebc:	89 4c 24 04          	mov    %ecx,0x4(%esp)
 8048ec0:	8b 42 04             	mov    0x4(%edx),%eax
 8048ec3:	89 04 24             	mov    %eax,(%esp)
 8048ec6:	e8 db ff ff ff       	call   8048ea6 <fun7>
 8048ecb:	01 c0                	add    %eax,%eax
 8048ecd:	eb 23                	jmp    8048ef2 <fun7+0x4c>
 8048ecf:	b8 00 00 00 00       	mov    $0x0,%eax
 8048ed4:	39 cb                	cmp    %ecx,%ebx
 8048ed6:	74 1a                	je     8048ef2 <fun7+0x4c>
 8048ed8:	89 4c 24 04          	mov    %ecx,0x4(%esp)
 8048edc:	8b 42 08             	mov    0x8(%edx),%eax
 8048edf:	89 04 24             	mov    %eax,(%esp)
 8048ee2:	e8 bf ff ff ff       	call   8048ea6 <fun7>
 8048ee7:	8d 44 00 01          	lea    0x1(%eax,%eax,1),%eax
 8048eeb:	eb 05                	jmp    8048ef2 <fun7+0x4c>
 8048eed:	b8 ff ff ff ff       	mov    $0xffffffff,%eax
 8048ef2:	83 c4 18             	add    $0x18,%esp
 8048ef5:	5b                   	pop    %ebx
 8048ef6:	c3                   	ret 

        不难看出fun7是一个递归函数,secret_phase在调用fun7时传入的两个初始参数是0x804c088和我们输入的那个数,下面用gdb调试查看参数1储存的内容:

        可以看出该地址是一种特殊数据结构的首地址,该数据结构第一个字节存放数据,第二和第三个字节存放两个指针,到这里已经可以猜出这个数据结构是什么了,就是二叉树,并且通过变量的命名也能验证这一结论,n1应该是根节点,n21是第二层第一个节点,以此类推…

        接下来看汇编的内容就比较好理解了,edx储存的是当前节点的首地址,edx+8储存的是它的右子节点的首地址,edx+4储存的是它的左子节点的首地址,如果当前节点数据域等于我们输入的数则返回0,大于则返回2*fun7(左子节点首地址,y),小于则返回2*fun7(右子节点首地址,y)+1,下面给出函数原型:

int fun7(int *btree, int y){
	if(btree->data == y)
		return 0;
	else if(btree->data < y)
		return 2 * fun7(btree->rchild, y) + 1;
	else
		return 2 * fun7(btree->lchild, y);
}

y的值即为我们要输入的内容,接下来反推y的值:

fun7(0x804c088, y) = 7 = 2 * fun7(0x804c0a0, y) + 1,fun7(0x804c0a0, y)应等于3

fun7(0x804c0a0, y) = 3 = 2 * fun7(0x804c0d0, y) + 1,fun7(0x804c0d0, y)应等于1

fun7(0x804c0d0, y) = 7 = 2 * fun7(0x804c130, y) + 1,fun7(0x804c130, y)应等于0

        注意此处传递参数不是简单的将地址加8或加4,而是要拿加8或加4处储存的地址作为参数。0x804c130处的节点储存的数据为0x3e9,即十进制的1001,其实fun7(0x804c130, y)也可以等于2 * fun7(*(0x804c134), y)),让fun7(*(0x804c134), y))等于0,但是由于0x804c130处的节点无子节点,所以此路不通,这一切都是题目设计好的,最后,得出secret_phase的key为1001,下面验证该key:

验证成功,隐藏关通过。

至此,全部七关已全部通过。

 实验结论

本次实验的参考答案为:

And they have no disregard for human life.

1 2 4 7 11 16

0 414

14 7 DrEvil

5 115

3 5 2 1 6 4

1001

其中关卡2、3的答案不唯一,其他答案已在解题过程中指出。

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值