计算机系统实验之bomblab

今天刚刚验收CSAPP实验3,趁着余温,记录一下这个实验,顺便回顾下CSAPP课程的相关知识。

实验目的

1.使用gdb工具反汇编出汇编代码,结合c语言文件找到每个关卡的入口函数。然后分析汇编代码,分析得到每一关的通关密码。
2.熟悉linux指令,学会如何使用gdb对程序进行调试

实验环境

个人电脑PC,32位ubuntu系统环境

实验内容及操作步骤

实验内容

“二进制炸弹”是Linux可执行C程序,包含六个“阶段”和一个秘密“阶段”。 每个阶段都要求输入特定的字符串在stdin上。 如果输入了预期的字符串,则该阶段通过,进入下一阶段。 否则,炸弹会通过打印“ BOOM !!!”而“爆炸”。

操作步骤

拆弹前准备

(1).进入Ubuntu系统,将自己的程序包复制到虚拟机中,并使用tar xvf bomb201808010515.tar进行解压。
在这里插入图片描述
(2).输入objdump –d bomb >my_bomb.txt,将反汇编代码输入到my_bomb.txt的文件中,便于拆弹时的查看和注释。
(3).打开反汇编文件,发现phase_i系列的函数,结合实验题目,可以知道这是每一个关卡对应的函数。

开始拆炸弹

PS:每个关卡汇编代码下面有相应的文字分析。

phase_1:确定输入字符串
  • 查看汇编代码
08048b50 <phase_1>:
 8048b50:	83 ec 1c             	sub    $0x1c,%esp
 8048b53:	c7 44 24 04 84 a1 04 	movl   $0x804a184,0x4(%esp)  //传入一个立即数
 8048b5a:	08 
 8048b5b:	8b 44 24 20          	mov    0x20(%esp),%eax
 8048b5f:	89 04 24             	mov    %eax,(%esp)
 8048b62:	e8 3d 04 00 00       	call   8048fa4 <strings_not_equal>  //输入字符串进入函数进行比较
 8048b67:	85 c0                	test   %eax,%eax
 8048b69:	74 05                	je     8048b70 <phase_1+0x20>       //返回值为0,结束,否则爆炸
 8048b6b:	e8 46 05 00 00       	call   80490b6 <explode_bomb>
 8048b70:	83 c4 1c             	add    $0x1c,%esp
 8048b73:	c3                   	ret    

  • 分析:
    (1).可以看到在%eax!=0的时候就会调用<explode_bomb>,所以在调用<strings_not_equal> 函数之后的返回值%eax必须为0。往前看,发现代码movl $0x804a184,0x4(%esp)有立即数,是将此处地址的值拿来用,输入gdb –q bomb进入调试状态,用x/s 0x804a184查看内容。
    在这里插入图片描述
    (2).下面一步 mov 0x20(%esp),%eax就是把我们输入的参数放进%eax中,然后放进(%esp) ,再调用函数<strings_not_equal>。所以猜测要输入的就是0x804a184中的字符。开始第一关的尝试,重新打开一个终端,用来测试。测试结果如下,输出"Phase 1 defused. How about the next one?",成功通关。
    在这里插入图片描述
phase_2: 确定一个有规律的数列
  • 分析:
08048b74 <phase_2>:
8048b74:	56                   	push   %esi
8048b75:	53                   	push   %ebx
8048b76:	83 ec 34             	sub    $0x34,%esp         //开辟栈帧
8048b79:	8d 44 24 18          	lea    0x18(%esp),%eax    
8048b7d:	89 44 24 04          	mov    %eax,0x4(%esp)   
8048b81:	8b 44 24 40          	mov    0x40(%esp),%eax
8048b85:	89 04 24             	mov    %eax,(%esp)       
8048b88:	e8 5e 06 00 00       	call   80491eb <read_six_numbers>
8048b8d:	83 7c 24 18 01       	cmpl   $0x1,0x18(%esp)    //esp+0x18-1
8048b92:	74 05                	je     8048b99 <phase_2+0x25> //esp+0x18==1
8048b94:	e8 1d 05 00 00       	call   80490b6 <explode_bomb> //esp+0x18!=1,爆炸
8048b99:	8d 5c 24 1c          	lea    0x1c(%esp),%ebx        //ebx=esp+0x1c
8048b9d:	8d 74 24 30          	lea    0x30(%esp),%esi        //esi=esp+0x30

(1). 首先注意到这个函数,这里应该是读取6个数字。接下来看到esp+0x18中存放的值和1进行比较,不相等则爆炸,由此猜测第1个数为1。连续的两条lea语句是将esp+0x1c,esp+0x30的值存放入对应寄存器。从esp+0x18到esp+0x30刚好存放6个int 类型的数据,故esp+0x30是最后一个数据,esp+0x1c是第2个数据。

8048ba1:	8b 43 fc             	mov    -0x4(%ebx),%eax        //eax=esp+0x1c-0x4=esp+0x18
8048ba4:	01 c0                	add    %eax,%eax              //eax=eax*2=2*(esp+0x18)
8048ba6:	39 03                	cmp    %eax,(%ebx)            //(ebx)-eax
8048ba8:	74 05                	je     8048baf <phase_2+0x3b> //(ebx)==2
8048baa:	e8 07 05 00 00       	call   80490b6 <explode_bomb> //(ebx)!=2,爆炸
8048baf:	83 c3 04             	add    $0x4,%ebx              //ebx=ebx+4=esp+0x20,地址+4,下一个数字
8048bb2:	39 f3                	cmp    %esi,%ebx              //ebx-esi=esp+0x20-(esp+0x30)
8048bb4:	75 eb                	jne    8048ba1 <phase_2+0x2d> //不相等,继续循环
8048bb6:	83 c4 34             	add    $0x34,%esp             //结束
8048bb9:	5b                   	pop    %ebx
8048bba:	5e                   	pop    %esi
8048bbb:	c3                   	ret 

(2). 接下来将ebx-0x4的中存放的值传给eax,执行add %eax,%eax操作,即eax中存放的数乘以2,再将这个值与第2个数据进行比较,如果不相等就爆炸,说明第2个数就是在第1个数的基础上乘以2。继续往下看,执行add $0x4,%ebx,将ebx中的地址加0x4,即下一个数字,接着的判断语句是和最后一个数字比较,不相等就跳转到对应位置继续循环。由此可知,这6个数字对应的递推关系为a[i+1]=2*a[i],其中a[0]=1,i=0,1,…,4。

(3). 切换到之前测试用的终端,输入1 2 4 8 16 32,终端显示“That’s number 2. Keep going!”,第二关顺利通过。结果如下图所示:
在这里插入图片描述

phase_3 :switch语句
  • 分析
08048bbc <phase_3>:
8048bbc:	83 ec 2c             	sub    $0x2c,%esp            //开辟栈帧
8048bbf:	8d 44 24 1c          	lea    0x1c(%esp),%eax
8048bc3:	89 44 24 0c          	mov    %eax,0xc(%esp)        //参数y   esp+0x1c
8048bc7:	8d 44 24 18          	lea    0x18(%esp),%eax
8048bcb:	89 44 24 08          	mov    %eax,0x8(%esp)        //参数x   esp+0x18
8048bcf:	c7 44 24 04 a3 a3 04 	movl   $0x804a3a3,0x4(%esp)  //查看汇编后知道需要输入两个数(%d %d)
8048bd6:	08 
8048bd7:	8b 44 24 30          	mov    0x30(%esp),%eax
8048bdb:	89 04 24             	mov    %eax,(%esp)
8048bde:	e8 8d fc ff ff       	call   8048870 <__isoc99_sscanf@plt>

(1).看到上面这段汇编代码,注意到函数_isoc99_sscanf@plt,说明这部分存在输入信息,往前看,发现movl $804a3a3,0x4(%esp)。进入GDB调试,设置断点 b phase_3,尝试使用x/s查看内容,发现是“%d %d”(如下图),说明需要输入两个数据。结合前面传入两个参数,这两个参数就是输入的数据,设为x,y。
在这里插入图片描述

 8048be3:	83 f8 01             	cmp    $0x1,%eax              //eax-1       
 8048be6:	7f 05                	jg     8048bed <phase_3+0x31> //eax>1
 8048be8:	e8 c9 04 00 00       	call   80490b6 <explode_bomb> //eax<1,爆炸
 8048bed:	83 7c 24 18 07       	cmpl   $0x7,0x18(%esp)        //x-7
 8048bf2:	77 3c                	ja     8048c30 <phase_3+0x74> //x>7或者x<0,引爆炸弹
 8048bf4:	8b 44 24 18          	mov    0x18(%esp),%eax        //eax=x
 8048bf8:	ff 24 85 e0 a1 04 08 	jmp    *0x804a1e0(,%eax,4)    //switch语句,gdb查看*0x804a1e0

(2). 接下来看到cmpl $0x7,%eax,进行无符号数比较,如果大于7或者小于0就爆炸,故可以知道eax中存放的值(参数1)的取值范围为0-7的整数。往下发现很多跳转指令,结合jmp *0x804a1e0(,%eax,4)知道,这是一个跳转表结构。

 8048bf8:	ff 24 85 e0 a1 04 08 	jmp    *0x804a1e0(,%eax,4)    //switch语句,gdb查看*0x804a1e0
 8048bff:	b8 cc 01 00 00       	mov    $0x1cc,%eax            //eax=0x1cc,x=0  对应查看*0x804a1e0
 8048c04:	eb 3b                	jmp    8048c41 <phase_3+0x85> 
 8048c06:	b8 b6 03 00 00       	mov    $0x3b6,%eax            //eax=0x3b6,x=2  对应查看*0x804a1e8
 8048c0b:	eb 34                	jmp    8048c41 <phase_3+0x85>
 8048c0d:	b8 03 03 00 00       	mov    $0x303,%eax            //eax=0x303,x=3
 8048c12:	eb 2d                	jmp    8048c41 <phase_3+0x85>
 8048c14:	b8 1d 01 00 00       	mov    $0x11d,%eax            //eax=0x11d,x=4
 8048c19:	eb 26                	jmp    8048c41 <phase_3+0x85>
 8048c1b:	b8 a0 01 00 00       	mov    $0x1a0,%eax            //eax=0x1a0,x=5
 8048c20:	eb 1f                	jmp    8048c41 <phase_3+0x85> 
 8048c22:	b8 60 03 00 00       	mov    $0x360,%eax            //eax=0x360,x=6
 8048c27:	eb 18                	jmp    8048c41 <phase_3+0x85>
 8048c29:	b8 6f 02 00 00       	mov    $0x26f,%eax            //eax=0x26f,x=7
 8048c2e:	eb 11                	jmp    8048c41 <phase_3+0x85>
 8048c30:	e8 81 04 00 00       	call   80490b6 <explode_bomb>  //爆炸
 8048c35:	b8 00 00 00 00       	mov    $0x0,%eax               //eax=0
 8048c3a:	eb 05                	jmp    8048c41 <phase_3+0x85>
 8048c3c:	b8 7a 00 00 00       	mov    $0x7a,%eax              //eax=0x7a,x=1  

(3).分析这个跳转表
假设x为0,则得到地址0x804a1e0+4*0=0x804a1e0,使用x/8bx 0x804a1e0查看信息,为0x8048bff,即case 0的入口地址。
在这里插入图片描述
根据case 0的入口地址0x8048bff找到执行执行mov $0x1cc,%eax。

假设x为1,则得到地址0x804a1e0+4*1=0x804a1e4,使用x/8bx 0x804a1e4查看信息,为0x804c3c,即case 1的入口地址。
在这里插入图片描述
根据case 1的入口地址0x8048c3c找到执行执行mov $0x7a,%eax。同理可以确定其他几组数据。

8048c41:	3b 44 24 1c          	cmp    0x1c(%esp),%eax         //eax-y
 8048c45:	74 05                	je     8048c4c <phase_3+0x90>  //eax==y
 8048c47:	e8 6a 04 00 00       	call   80490b6 <explode_bomb>  //eax!=y,爆炸
 8048c4c:	83 c4 2c             	add    $0x2c,%esp              //结束
 8048c4f:	c3                   	ret

(4).最后一部分将eax的值和输入的参数y进行比较,不相等则爆炸,说明输入的第2个数字y为case语句中传给eax的值。由此可以确定这个关卡有8组不同的通关答案,分别是0 460,1 122,2 950, 3 771, 4 285, 5 416,6 864, 7 623。下面对2组数据进行验证:
在这里插入图片描述
在这里插入图片描述

phase_4 :函数过程调用
  • 分析
08048cb9 <phase_4>:
 8048cb9:	83 ec 2c             	sub    $0x2c,%esp      //开辟栈帧
 8048cbc:	8d 44 24 1c          	lea    0x1c(%esp),%eax
 8048cc0:	89 44 24 0c          	mov    %eax,0xc(%esp)  //参数y
 8048cc4:	8d 44 24 18          	lea    0x18(%esp),%eax
 8048cc8:	89 44 24 08          	mov    %eax,0x8(%esp)  //参数x
 8048ccc:	c7 44 24 04 a3 a3 04 	movl   $0x804a3a3,0x4(%esp) 查看汇编后知道需要输入两个数(%d %d)
 8048cd3:	08 

(1).查看上面这一段代码,分析得出输入两个数字(和关卡3一样,用gdb调试看0x804a3a3中存放的内容)。结合前面传入两个参数,这两个参数就是输入的数据,设为x,y。

 8048cd4:	8b 44 24 30          	mov    0x30(%esp),%eax
 8048cd8:	89 04 24             	mov    %eax,(%esp)
 8048cdb:	e8 90 fb ff ff       	call   8048870 <__isoc99_sscanf@plt> //调用某个函数
 8048ce0:	83 f8 02             	cmp    $0x2,%eax                     //eax-2  判断传入参数的个数是否满足条件
 8048ce3:	75 0d                	jne    8048cf2 <phase_4+0x39>        //eax!=2 引爆
 8048ce5:	8b 44 24 18          	mov    0x18(%esp),%eax               //eax=x
 8048ce9:	85 c0                	test   %eax,%eax                    //判断x
 8048ceb:	78 05                	js     8048cf2 <phase_4+0x39>        //x<0  引爆
 8048ced:	83 f8 0e             	cmp    $0xe,%eax                     //x-0xe
 8048cf0:	7e 05                	jle    8048cf7 <phase_4+0x3e>        //x<=0xe
 8048cf2:	e8 bf 03 00 00       	call   80490b6 <explode_bomb>

(2).分析这一部分代码,test判断x是否小于0,cmp判断x是否大于0xe,故要是炸弹不爆炸,则参数x的取值范围为0-0xe的整数。

 8048cf7:	c7 44 24 08 0e 00 00 	movl   $0xe,0x8(%esp)                //esp+0x8=0xe
 8048cfe:	00 
 8048cff:	c7 44 24 04 00 00 00 	movl   $0x0,0x4(%esp)                //esp+0x4=0
 8048d06:	00  
 8048d07:	8b 44 24 18          	mov    0x18(%esp),%eax               //eax=x
 8048d0b:	89 04 24             	mov    %eax,(%esp)                   //esp=x 
 8048d0e:	e8 3d ff ff ff       	call   8048c50 <func4>               //调用func4

(3). 在调用func4之前将参数压入栈帧.

 8048d13:	83 f8 07             	cmp    $0x7,%eax                     //eax-7,eax里面存放的是func4的返回值
 8048d16:	75 07                	jne    8048d1f <phase_4+0x66>        //eax!=7,引爆
 8048d18:	83 7c 24 1c 07       	cmpl   $0x7,0x1c(%esp)               //y-7
 8048d1d:	74 05                	je     8048d24 <phase_4+0x6b>        //y==7,结束
 8048d1f:	e8 92 03 00 00       	call   80490b6 <explode_bomb>
 8048d24:	83 c4 2c             	add    $0x2c,%esp
 8048d27:	c3                   	ret 

(4). 继续分析发现下面对func4的返回值和参数y分别对7进行比较,如果不为7,则引爆炸弹,分析到此,可以知道对于x属于[0,0xe]使得func4(x,0,0xe)返回值为0x7的x即为所求。

08048c50 <func4>:
 8048c50:	83 ec 1c             	sub    $0x1c,%esp             //开辟栈帧
 8048c53:	89 5c 24 14          	mov    %ebx,0x14(%esp)      
 8048c57:	89 74 24 18          	mov    %esi,0x18(%esp)
 8048c5b:	8b 44 24 20          	mov    0x20(%esp),%eax        //设esp+0x20存储x=>eax=x
 8048c5f:	8b 54 24 24          	mov    0x24(%esp),%edx        //设esp+0x24存储y=>edx=y
 8048c63:	8b 74 24 28          	mov    0x28(%esp),%esi        //设esp+0x28存储z=>esi=z
 8048c67:	89 f1                	mov    %esi,%ecx              //ecx=z
 8048c69:	29 d1                	sub    %edx,%ecx              //ecx=z-y
 8048c6b:	89 cb                	mov    %ecx,%ebx              //ebx=z-y
 8048c6d:	c1 eb 1f             	shr    $0x1f,%ebx             //逻辑右移,ebx=(z-y)>>31,得到符号位s
 8048c70:	01 d9                	add    %ebx,%ecx              //ecx=ebx+ecx=s+z-y
 8048c72:	d1 f9                	sar    %ecx                   //算术右移,ecx=(s+z-y)>>1
 8048c74:	8d 1c 11             	lea    (%ecx,%edx,1),%ebx     //ebx=edx*1+ecx=y+ecx=y+(s+z-y)>>1
 8048c77:	39 c3                	cmp    %eax,%ebx              //ebx-x,比较计算结果y+(s+z-y)>>1和x
 8048c79:	7e 17                	jle    8048c92 <func4+0x42>   //ebx<=eax
 8048c7b:	8d 4b ff             	lea    -0x1(%ebx),%ecx        //ecx=ebx-1
 8048c7e:	89 4c 24 08          	mov    %ecx,0x8(%esp)         //esp+0x8=ecx=ebx-1
 8048c82:	89 54 24 04          	mov    %edx,0x4(%esp)         //esp+0x4=edx=y
 8048c86:	89 04 24             	mov    %eax,(%esp)            //esp=x
 8048c89:	e8 c2 ff ff ff       	call   8048c50 <func4>        //递归调用函数func4
 8048c8e:	01 c3                	add    %eax,%ebx              //ebx=ebx+eax
 8048c90:	eb 19                	jmp    8048cab <func4+0x5b>
 8048c92:	39 c3                	cmp    %eax,%ebx              //ebx-eax         
 8048c94:	7d 15                	jge    8048cab <func4+0x5b>   //ebx>=eax
 8048c96:	89 74 24 08          	mov    %esi,0x8(%esp)         //esp+0x8=esi=z
 8048c9a:	8d 53 01             	lea    0x1(%ebx),%edx         //edx=ebx+1
 8048c9d:	89 54 24 04          	mov    %edx,0x4(%esp)         //esp+0x4=ebx+1
 8048ca1:	89 04 24             	mov    %eax,(%esp)            //esp=eax=x
 8048ca4:	e8 a7 ff ff ff       	call   8048c50 <func4>        //递归调用函数func4
 8048ca9:	01 c3                	add    %eax,%ebx              //ebx=eax+ebx
 8048cab:	89 d8                	mov    %ebx,%eax              //eax=ebx
 8048cad:	8b 5c 24 14          	mov    0x14(%esp),%ebx        
 8048cb1:	8b 74 24 18          	mov    0x18(%esp),%esi
 8048cb5:	83 c4 1c             	add    $0x1c,%esp
 8048cb8:	c3                   	ret  

(5).现在重点就是分析func4函数,看到汇编代码可以发现这是一个递归函数,关键在于找到递归的条件和退出函数的条件,以及递归之前函数的参数压栈准备。根据这段汇编代码写出C语言代码如下:

int func4(int x,int y,int z)
{
    int t=(((z-y)+((z-y)>>31))>>1)+y; 
    if(t<=x)
    {
        if(t==x)
        {
        	return t;
        }
        else return func4(x,t+1,z)+x;
    }
    else 
    {
       return func4(x,y,t-1)+x;
    }
} 

(6).编写测试函数如下

int main()
{
   int i=0;
   for(i=0;i<=14;i++)//每一种取值都进行测试
   {
         printf("func4(%d,0,14)=%d  ",i,func4(i,0,14));
         if((i+1)%5==0) printf("\n");
    }
    return 0;
}

运行代码结果如下,可以发现返回值为0x7对应的x只有0x7一种值,故第4关通关输入为7 7。
在这里插入图片描述
切换到之前测试用的终端,输入7 7,终端显示“So you got that one. Try this one.”,第四关顺利通过。
在这里插入图片描述

phase_5: 序列的构造和数组寻址
  • 分析
08048d28 <phase_5>:
 8048d28:	53                   	push   %ebx
 8048d29:	83 ec 18             	sub    $0x18,%esp                  //开辟栈帧
 8048d2c:	8b 5c 24 20          	mov    0x20(%esp),%ebx             //参数1 x存储在ebx
 8048d30:	89 1c 24             	mov    %ebx,(%esp) 
 8048d33:	e8 53 02 00 00       	call   8048f8b <string_length>
 8048d38:	83 f8 06             	cmp    $0x6,%eax                   //eax-6(猜测eax是字符串的长度)
 8048d3b:	74 05                	je     8048d42 <phase_5+0x1a>      //eax==6  
 8048d3d:	e8 74 03 00 00       	call   80490b6 <explode_bomb>      //eax不是6,引爆

(1).注意到<string_length>函数,结合后面eax和0x6比较,可知这一关输入的参数是一个长度为6的字符串,需要我们去匹配出这个字符串。

 8048d42:	ba 00 00 00 00       	mov    $0x0,%edx                   //edx=0
 8048d47:	b8 00 00 00 00       	mov    $0x0,%eax                   //eax=0
//movsbl指令负责拷贝一个字节,并用源操作数的最高位填充其目的操作数中的其余各位,这种扩展方式叫“符号扩展” 
 8048d4c:	0f be 0c 03          	movsbl (%ebx,%eax,1),%ecx          //ecx=(eax+ebx)  长度为6的字符串的第eax个字符
 8048d50:	83 e1 0f             	and    $0xf,%ecx                   //f=00001111     取ecx的低4
 8048d53:	03 14 8d 00 a2 04 08 	add    0x804a200(,%ecx,4),%edx     //edx=edx+4*ecx+0x804a200
 8048d5a:	83 c0 01             	add    $0x1,%eax                   //eax+1
 8048d5d:	83 f8 06             	cmp    $0x6,%eax                   //eax-6
 8048d60:	75 ea                	jne    8048d4c <phase_5+0x24>      //eax!=6
 8048d62:	83 fa 2b             	cmp    $0x2b,%edx                  //edx-0x2b
 8048d65:	74 05                	je     8048d6c <phase_5+0x44>      //相等
 8048d67:	e8 4a 03 00 00       	call   80490b6 <explode_bomb>      //爆炸
 8048d6c:	83 c4 18             	add    $0x18,%esp                  //结束
 8048d6f:	5b                   	pop    %ebx
 8048d70:	c3                   	ret  

(2).接着edx,eax寄存器的值初始化为0,movsbl (%ebx,%eax,1),%ecx,这一句是表示将长度为6的字符串的第eax个字符存入ecx中。取ecx中的低4位,利用基址比例变址寻址的方式,到数组中找值,并累和在寄存器edx中。往下看,可以看到cmp $0x2b,%edx,如果不相等则爆炸,说明累和得到的值必须为0x2b。
(3).分析到此,关键在于找到数组中的值,由汇编我们知道数组首地址为0x804a200,进入GDB调试,设置断点b phase_5,运行后查看其内容,从里边找出一组和为0x2b的数字,这里取出一组 {2,a,6,1,c,c}。
在这里插入图片描述
对应下标为0,1,2,3,4,4,去ascii表中找低四位为0,1,2,3,4,4的字符,故得到其中一组解为pabcdd。
在这里插入图片描述
(4). 切换到之前测试用的终端,输入pabcdd,终端显示“Good work! On to the next…!”,第五关顺利通过。
在这里插入图片描述

phase_6: 链表
  • 分析
08048d71 <phase_6>:
 8048d71:	56                   	push   %esi
 8048d72:	53                   	push   %ebx
 8048d73:	83 ec 44             	sub    $0x44,%esp              //开辟栈帧
 8048d76:	8d 44 24 10          	lea    0x10(%esp),%eax      
 8048d7a:	89 44 24 04          	mov    %eax,0x4(%esp)          //参数2 y
 8048d7e:	8b 44 24 50          	mov    0x50(%esp),%eax
 8048d82:	89 04 24             	mov    %eax,(%esp)             //参数1 x
 8048d85:	e8 61 04 00 00       	call   80491eb <read_six_numbers>

(1).首先看到函数,根据之前的经验,猜测这里也是读取6个数字。

 8048d8a:	be 00 00 00 00       	mov    $0x0,%esi               //esi=0   
 8048d8f:	8b 44 b4 10          	mov    0x10(%esp,%esi,4),%eax  //eax=(4*esi+y)
 8048d93:	83 e8 01             	sub    $0x1,%eax               //eax--
 8048d96:	83 f8 05             	cmp    $0x5,%eax               //eax-5
 8048d99:	76 05                	jbe    8048da0 <phase_6+0x2f>  //无符号数<=
 8048d9b:	e8 16 03 00 00       	call   80490b6 <explode_bomb>  //引爆
 8048da0:	83 c6 01             	add    $0x1,%esi                //esi+=1,,esi=1
 8048da3:	83 fe 06             	cmp    $0x6,%esi                //esi-6
 8048da6:	74 33                	je     8048ddb <phase_6+0x6a>   //等于跳转
                                                                    //以上代码是确定6个数字的范围。
 8048da8:	89 f3                	mov    %esi,%ebx                //ebx=esi
 8048daa:	8b 44 9c 10          	mov    0x10(%esp,%ebx,4),%eax   //eax=(4*ebx+y)
 8048dae:	39 44 b4 0c          	cmp    %eax,0xc(%esp,%esi,4)    //(4*esi+esp+0xc)-eax
 8048db2:	75 05                	jne    8048db9 <phase_6+0x48>   //不相等
 8048db4:	e8 fd 02 00 00       	call   80490b6 <explode_bomb>   //相等,引爆
 8048db9:	83 c3 01             	add    $0x1,%ebx                //ebx++
 8048dbc:	83 fb 05             	cmp    $0x5,%ebx                //ebx-5
 8048dbf:	7e e9                	jle    8048daa <phase_6+0x39>   //小于等于跳转
 8048dc1:	eb cc                	jmp    8048d8f <phase_6+0x1e>
 8048dc3:	8b 52 08             	mov    0x8(%edx),%edx           //edx=(edx+0x8)=(0x804c13c+0x8)
 8048dc6:	83 c0 01             	add    $0x1,%eax                //eax++
 8048dc9:	39 c8                	cmp    %ecx,%eax                //eax-ecx
 8048dcb:	75 f6                	jne    8048dc3 <phase_6+0x52>   //不相等,继续执行
 8048dcd:	89 54 b4 28          	mov    %edx,0x28(%esp,%esi,4)   //4*esi+esp+0x28=edx
 8048dd1:	83 c3 01             	add    $0x1,%ebx                //ebx++
 8048dd4:	83 fb 06             	cmp    $0x6,%ebx                //ebx-6
 8048dd7:	75 07                	jne    8048de0 <phase_6+0x6f>   //不相等
 8048dd9:	eb 1c                	jmp    8048df7 <phase_6+0x86> 
                                                                    //以上代码是判断数字之间是否互不相等。

(2).这里的汇编代码包括两个循环,用来给出数字的取值范围是属于1-6,并且数字之间彼此两两不相等。

                                                                       //链表初始化
 8048df7:	8b 5c 24 28          	mov    0x28(%esp),%ebx         /ebx=x1
 8048dfb:	8b 44 24 2c          	mov    0x2c(%esp),%eax         //eax=x2
 8048dff:	89 43 08             	mov    %eax,0x8(%ebx)          //x1->x2
 8048e02:	8b 54 24 30          	mov    0x30(%esp),%edx         //edx=x3
 8048e06:	89 50 08             	mov    %edx,0x8(%eax)          //x1->x2->x3
 8048e09:	8b 44 24 34          	mov    0x34(%esp),%eax         //eax=x4
 8048e0d:	89 42 08             	mov    %eax,0x8(%edx)          //x1->x2->x3->x4
 8048e10:	8b 54 24 38          	mov    0x38(%esp),%edx         //edx=x5
 8048e14:	89 50 08             	mov    %edx,0x8(%eax)          //x1->x2->x3->x4->x5
 8048e17:	8b 44 24 3c          	mov    0x3c(%esp),%eax         //eax=x6
 8048e1b:	89 42 08             	mov    %eax,0x8(%edx)          //x1->x2->x3->x4->x5->x6
 8048e1e:	c7 40 08 00 00 00 00 	movl   $0x0,0x8(%eax)          //链表尾

(3). 接下来对链表的结点进行了连接

(4).进入GDB调试,设置断点b phase_6,往下分析代码过程中看到一个立即数,查看其值,出现一个node1,猜测这是一个链表的数据结构。于是多查看几个值,发现了其中的规律,每三个数为一组,第1个是数据域中的值,第2个是下标,第3个比较大,类似于地址,是指针域,然后对猜想进行验证。调试后验证猜测正确。根据之前对链表进行连接,顺序为322->370->1c1->24b->0a1->192。
在这里插入图片描述

                                                                      //递增序列
 8048e25:	be 05 00 00 00       	mov    $0x5,%esi               //esi=5
 8048e2a:	8b 43 08             	mov    0x8(%ebx),%eax          //eax=x2的地址
 8048e2d:	8b 10                	mov    (%eax),%edx             //edx=x2
 8048e2f:	39 13                	cmp    %edx,(%ebx)             //x1-x2
 8048e31:	7e 05                	jle    8048e38 <phase_6+0xc7>  //x1<=x2
 8048e33:	e8 7e 02 00 00       	call   80490b6 <explode_bomb>
 8048e38:	8b 5b 08             	mov    0x8(%ebx),%ebx          //ebx=ebx+0x8,下一个数
 8048e3b:	83 ee 01             	sub    $0x1,%esi               //esi--
 8048e3e:	75 ea                	jne    8048e2a <phase_6+0xb9>
 8048e40:	83 c4 44             	add    $0x44,%esp
 8048e43:	5b                   	pop    %ebx
 8048e44:	5e                   	pop    %esi
 8048e45:	c3                   	ret    

(5).ebx+0x8存储的是第2个数字的地址,取出第2个数字x2,和edx存储的第1个数字x1进行比较,需要满足x1<=x2,接下来ebx地址加0x8,对下一个进行约束。由此可以看出这个序列是一个递增序列。
(6).结合之前输入的6个数字在1-6之间,且互不相同,这里应该是排序后递增链表序列的下标。故得到下标为5 6 3 4 1 2。切换到之前测试用的终端,输入5 6 3 4 1 2,终端显示“Congratulations! You’ve defused the bomb!”,第六关顺利通过。
在这里插入图片描述

secret_phase:二叉树

做完前6关,看到提示信息的那一刻,我以为就这样结束,但是听说还有秘密关卡,仔细一看汇编代码,果然有serect_phase的汇编代码片段。起初没有思路,不知道该从何入手,在网上参考了一些资料,结合bomb.c的代码,发现每个阶段函数结束后都调用了phase_defused()函数,猜测秘密关卡可能隐藏在某一关中。

  • 分析如何找到秘密关卡
    查看汇编代码如下
0804923b <phase_defused>:
 804923b:	81 ec 8c 00 00 00    	sub    $0x8c,%esp
 8049241:	65 a1 14 00 00 00    	mov    %gs:0x14,%eax
 8049247:	89 44 24 7c          	mov    %eax,0x7c(%esp)
 804924b:	31 c0                	xor    %eax,%eax
 804924d:	83 3d cc c3 04 08 06 	cmpl   $0x6,0x804c3cc
 8049254:	75 72                	jne    80492c8 <phase_defused+0x8d>
 8049256:	8d 44 24 2c          	lea    0x2c(%esp),%eax
 804925a:	89 44 24 10          	mov    %eax,0x10(%esp)
 804925e:	8d 44 24 28          	lea    0x28(%esp),%eax
 8049262:	89 44 24 0c          	mov    %eax,0xc(%esp)
 8049266:	8d 44 24 24          	lea    0x24(%esp),%eax
 804926a:	89 44 24 08          	mov    %eax,0x8(%esp)
 804926e:	c7 44 24 04 a9 a3 04 	movl   $0x804a3a9,0x4(%esp)
 8049275:	08 
 8049276:	c7 04 24 d0 c4 04 08 	movl   $0x804c4d0,(%esp)
 804927d:	e8 ee f5 ff ff       	call   8048870 <__isoc99_sscanf@plt>
 8049282:	83 f8 03             	cmp    $0x3,%eax
 8049285:	75 35                	jne    80492bc <phase_defused+0x81>
 8049287:	c7 44 24 04 b2 a3 04 	movl   $0x804a3b2,0x4(%esp)
 804928e:	08 
 804928f:	8d 44 24 2c          	lea    0x2c(%esp),%eax
 8049293:	89 04 24             	mov    %eax,(%esp)
 8049296:	e8 09 fd ff ff       	call   8048fa4 <strings_not_equal>
 804929b:	85 c0                	test   %eax,%eax
 804929d:	75 1d                	jne    80492bc <phase_defused+0x81>
 804929f:	c7 04 24 78 a2 04 08 	movl   $0x804a278,(%esp)
 80492a6:	e8 55 f5 ff ff       	call   8048800 <puts@plt>
 80492ab:	c7 04 24 a0 a2 04 08 	movl   $0x804a2a0,(%esp)
 80492b2:	e8 49 f5 ff ff       	call   8048800 <puts@plt>
 80492b7:	e8 db fb ff ff       	call   8048e97 <secret_phase>
 80492bc:	c7 04 24 d8 a2 04 08 	movl   $0x804a2d8,(%esp)
 80492c3:	e8 38 f5 ff ff       	call   8048800 <puts@plt>
 80492c8:	8b 44 24 7c          	mov    0x7c(%esp),%eax
 80492cc:	65 33 05 14 00 00 00 	xor    %gs:0x14,%eax
 80492d3:	74 05                	je     80492da <phase_defused+0x9f>
 80492d5:	e8 f6 f4 ff ff       	call   80487d0 <__stack_chk_fail@plt>
 80492da:	81 c4 8c 00 00 00    	add    $0x8c,%esp
 80492e0:	c3                   	ret    
 80492e1:	90                   	nop
 80492e2:	90                   	nop
 80492e3:	90                   	nop
 80492e4:	90                   	nop
 80492e5:	90                   	nop
 80492e6:	90                   	nop
 80492e7:	90                   	nop
 80492e8:	90                   	nop
 80492e9:	90                   	nop
 80492ea:	90                   	nop
 80492eb:	90                   	nop
 80492ec:	90                   	nop
 80492ed:	90                   	nop
 80492ee:	90                   	nop
 80492ef:	90                   	nop

(1).结合前面关卡,发现这里有2个立即数,使用gdb调试查看其内容
在这里插入图片描述
发现提示输入两个数字和一个字符串,以及第4关的输入。于是猜测秘密关卡藏在第4关,而这个字符串就是打开秘密关卡的“钥匙”。
在这里插入图片描述
(2). 可以看到这里将0x804a3b2中存放的内容传入函数strings_not_equal中进行比较,如果返回值不为0,就跳转到0x80492bc,输出0x804a2d8中的内容,否则就一次往下输出0x804a278,0x804a2a0的信息,最后输出0x804a2d8的信息。猜测:0x804a3b2就是进入秘密关卡的“钥匙”,当“钥匙”输入错误就直接结束。
在这里插入图片描述
在这里插入图片描述
添加断点,用gdb调试查看上述提到的4个地址中的内容,验证了猜测的正确性。
在这里插入图片描述

  • 分析如何通关
08048e97 <secret_phase>:
 8048e97:	53                   	push   %ebx
 8048e98:	83 ec 18             	sub    $0x18,%esp               //开辟栈帧
 8048e9b:	e8 3d 02 00 00       	call   80490dd <read_line>      
 8048ea0:	c7 44 24 08 0a 00 00 	movl   $0xa,0x8(%esp)           //参数3 z=a
 8048ea7:	00 
 8048ea8:	c7 44 24 04 00 00 00 	movl   $0x0,0x4(%esp)           //参数2 y=0
 8048eaf:	00 
 8048eb0:	89 04 24             	mov    %eax,(%esp)              //参数1 x
 8048eb3:	e8 28 fa ff ff       	call   80488e0 <strtol@plt>
 8048eb8:	89 c3                	mov    %eax,%ebx                //ebx=x
 8048eba:	8d 40 ff             	lea    -0x1(%eax),%eax          //eax=x-1
 8048ebd:	3d e8 03 00 00       	cmp    $0x3e8,%eax              //eax-0x3e8
 8048ec2:	76 05                	jbe    8048ec9 <secret_phase+0x32> 无符号数比较,小于等于
 8048ec4:	e8 ed 01 00 00       	call   80490b6 <explode_bomb>

(1).这段代码首先将三个参数放入栈帧中,接下来调用了strtol函数。mov %eax,%ebx;lea -0x1(%eax),%eax;cmp $0x3e8,%eax;jbe 8048ec9 <secret_phase+0x32>;这4句对参数x的范围进行了限定,x的取值是1-0x3e9。

 8048ec9:	89 5c 24 04          	mov    %ebx,0x4(%esp)           //fun7参数2 n=x
 8048ecd:	c7 04 24 88 c0 04 08 	movl   $0x804c088,(%esp)        //fun7参数1 m=0x804c088
 8048ed4:	e8 6d ff ff ff       	call   8048e46 <fun7>           //调用fun7        
 8048ed9:	85 c0                	test   %eax,%eax                //eax and eax
 8048edb:	74 05                	je     8048ee2 <secret_phase+0x4b> //等于0,不是0就爆炸
 8048edd:	e8 d4 01 00 00       	call   80490b6 <explode_bomb>  
 8048ee2:	c7 04 24 b8 a1 04 08 	movl   $0x804a1b8,(%esp)         
 8048ee9:	e8 12 f9 ff ff       	call   8048800 <puts@plt>
 8048eee:	e8 48 03 00 00       	call   804923b <phase_defused>
 8048ef3:	83 c4 18             	add    $0x18,%esp
 8048ef6:	5b                   	pop    %ebx
 8048ef7:	c3                   	ret    
 8048ef8:	90                   	nop
 8048ef9:	90                   	nop
 8048efa:	90                   	nop
 8048efb:	90                   	nop
 8048efc:	90                   	nop
 8048efd:	90                   	nop
 8048efe:	90                   	nop
 8048eff:	90                   	nop

(2).这段代码中主要调用了函数fun7,调用之前可以分析出参数,即fun7(地址,n),第1个参数为某个数组或者链表的首地址,第2个参数为1-1001之间的整数。通关的条件是fun7()的返回值为0。分析fun7中代码,并结合返回值为0,故可以知道保证传入fun7中两个参数相等即可。

08048e46 <fun7>:
 8048e46:	53                   	push   %ebx
 8048e47:	83 ec 18             	sub    $0x18,%esp           //开辟栈帧
 8048e4a:	8b 54 24 20          	mov    0x20(%esp),%edx      //参数1,edx=&x
 8048e4e:	8b 4c 24 24          	mov    0x24(%esp),%ecx      //参数2,ecx=y
 8048e52:	85 d2                	test   %edx,%edx            //判断edx是否为0
 8048e54:	74 37                	je     8048e8d <fun7+0x47>  //等于0
 8048e56:	8b 1a                	mov    (%edx),%ebx          //取出地址中的值,给ebx,即ebx=x
 8048e58:	39 cb                	cmp    %ecx,%ebx            //x-y
 8048e5a:	7e 13                	jle    8048e6f <fun7+0x29>  //x<=y
 8048e5c:	89 4c 24 04          	mov    %ecx,0x4(%esp)       //esp+0x4=y
 8048e60:	8b 42 04             	mov    0x4(%edx),%eax       
 8048e63:	89 04 24             	mov    %eax,(%esp)          //esp=edx+0x4
 8048e66:	e8 db ff ff ff       	call   8048e46 <fun7>       //调用函数fun7
 8048e6b:	01 c0                	add    %eax,%eax            //eax=eax*2
 8048e6d:	eb 23                	jmp    8048e92 <fun7+0x4c>
 8048e6f:	b8 00 00 00 00       	mov    $0x0,%eax            //eax=0
 8048e74:	39 cb                	cmp    %ecx,%ebx            //x-y
 8048e76:	74 1a                	je     8048e92 <fun7+0x4c>  //如果x==y
 8048e78:	89 4c 24 04          	mov    %ecx,0x4(%esp)       //esp+0x4=y
 8048e7c:	8b 42 08             	mov    0x8(%edx),%eax  
 8048e7f:	89 04 24             	mov    %eax,(%esp)          //esp=edx+0x8
 8048e82:	e8 bf ff ff ff       	call   8048e46 <fun7>       //调用函数fun7
 8048e87:	8d 44 00 01          	lea    0x1(%eax,%eax,1),%eax  eax=2*eax+1
 8048e8b:	eb 05                	jmp    8048e92 <fun7+0x4c>
 8048e8d:	b8 ff ff ff ff       	mov    $0xffffffff,%eax
 8048e92:	83 c4 18             	add    $0x18,%esp
 8048e95:	5b                   	pop    %ebx
 8048e96:	c3                   	ret 

(3).根据fun7的汇编代码写出对应的C语言代码

int fun7(int *x,int y)
{
    if(x==0)
        return 0xffffffff;
    if(*x<=y)
    {
        if(*x==y) return 0;
        x+=8;
       	return 2*fun7(x,y)+1;
    }
    else
    {
       x+=4;
       return 2*fun7(x,y);
    } 
} 

(4).根据前面的分析,要使炸弹不爆炸需要fun7返回值为0,从函数中可以看出,返回值为0,只需*x=y即可,于是查看传入fun7中x中的内容即可。

 8048ecd:	c7 04 24 88 c0 04 08 	movl   $0x804c088,(%esp)        //fun7参数1 m=0x804c088
 8048ed4:	e8 6d ff ff ff       	call   8048e46 <fun7>           //调用fun7 

在这里插入图片描述
(5).切换到之前测试用的终端,运行./bomb,从第一关开始输入,在第四关时,加上字符串“DrEvil”,最终输入秘密关卡的36,终端显示“Wow! You’ve defused the secret stage!Congratulations! You’ve defused the bomb!”。
在这里插入图片描述
(6).秘密关卡到这里就结束了,而对于这个关卡涉及到的结构可以进行进一步探索。查看0x804c088中内容发现有3个值,第1个是数值,第2,3个值比较大,是两个地址,联系所学,这两个地址就是左,右子结点的地址。将整个结构用图表示如下,可以看出这是一个典型的二叉树结构。
在这里插入图片描述

实验结果及分析

在这里插入图片描述

  • 所有关卡通关密码均已找出,执行结果如上。
  • 对每一关进行简单分析:第1关是确定输入字符串;第2关是确定一个有规律的数列;第3关是switch语句的考察;第4关是函数过程调用的检测;第5关是序列的构造和数组寻址方式;第6关是考察链表的相关知识;秘密关卡的寻找考察了考查Linux Canary绕过技术及ROP(Return-Oriented-Programming)攻击负载的构造(如何找到秘密和关卡的入口),秘密关卡则考察了二叉树。

收获与体会

1.本次实验进一步熟悉了GDB调试工具,调试能力得到了一定的提高。
2.熟悉了linux中一些之前没有见过的指令,如movsbl指令movsbl指令负责拷贝一个字节,并用源操作数的最高位填充其目的操作数中的其余各位,这种扩展方式叫符号扩展,对linux指令更加熟悉。
3.对过程调用和参数准备工作得到了进一步的理解。
4.学到了一些更为复杂的数据表示,比如链表,二叉树的在计算机中的具体存储,以及在底层的使用。
5.实验中每个人的实验的可执行文件都是不同,实验过程中,也帮其他同学看过一些代码,对于不同的反汇编代码更加熟悉,也间接复习了之前计算机系统关于汇编语言的相关知识。
6.结合之前缓冲区溢出攻击的学习,在实验做完后,尝试了用hexedit去直接修改可执行文件的某些执行,使得无论输入什么,都不会跳转到爆炸函数处,进一步理解了缓冲区溢出攻击的原理。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值