APP Bomb Lab

实验题目:CS:APP Bomb Lab

实验目的:

binary bomb is a Linux executable C program that consists of six
phases. Each phase expects the student to enter a particular string
on stdin. If the student enters the expected string, then that phase
is defused. Otherwise the bomb explodes by printing BOOM!!!.
The goal for the students is to defuse as many phases as possible.:

实验环境:ubuntu12.04 (32位)环境

实验内容及操作步骤

刚开始拿到题目的时候我试着用./bomb指令运行,但是它提示权限不够,在网上查了一些解决方法试着用sudo,但是还是不行,助教老师告诉我可以用chmod 777 bomb,提供权限,顺利解决。然后执行如下指令将可执行文件反汇编,将结果输出到文件中:

Linux:> Objdump -D bomb >test.txt

一切准备就绪,开始逐个分析phase函数。

Phase_1:字符串比较

08048b50 <phase_1>:
 8048b50:	83 ec 1c             	sub    $0x1c,%esp
 8048b53:	c7 44 24 04 64 a2 04 	movl   $0x804a264,0x4(%esp)               //字符串参数   When I get angry, Mr. Bigglesworth gets upset.
 8048b5a:	08                                                    
 8048b5b:	8b 44 24 20          	mov    0x20(%esp),%eax                    //原字符串
 8048b5f:	89 04 24             	mov    %eax,(%esp)
 8048b62:	e8 1d 05 00 00       	call   8049084 <strings_not_equal>        //判断输入的字符串
 8048b67:	85 c0                	test   %eax,%eax                   
 8048b69:	74 05                	je     8048b70 <phase_1+0x20>             //如果结果为0,引爆炸弹,否则跳转到8048b70
 8048b6b:	e8 26 06 00 00       	call   8049196 <explode_bomb>             
 8048b70:	83 c4 1c             	add    $0x1c,%esp                         //将esp+0x1c返回
 8048b73:	c3                   	ret    

①查看Phase_1函数内容,可以看到函数将输入的字符串0x20(%esp)和一个立即数指向的字符串传入一个叫做<strings_not_equal>的函数中,这个函数的功能是判断两个字符串是否相等。我启用gdb调试,输入指令:

Linux:> x/s 0x804a264

在这里插入图片描述

可以看到输出结果为When I get angry, Mr . Bigglesworth gets upset,我将结果输入验证,发现答案正确。

Phase_2:a[i+1]=a[i]+i序列

08048b74 <phase_2>:
 8048b74:	53                   	push   %ebx
 8048b75:	83 ec 38             	sub    $0x38,%esp
 8048b78:	8d 44 24 18          	lea    0x18(%esp),%eax                 //18(esp)为读入参数
 8048b7c:	89 44 24 04          	mov    %eax,0x4(%esp)                  //
 8048b80:	8b 44 24 40          	mov    0x40(%esp),%eax                 //
 8048b84:	89 04 24             	mov    %eax,(%esp)                     //read_six_numbers(0x40(%esp),0x18(%esp))
 8048b87:	e8 3f 07 00 00       	call   80492cb <read_six_numbers>      //读入6个数据
 8048b8c:	83 7c 24 18 00       	cmpl   $0x0,0x18(%esp)                 //比较0x18(%esp)和0
 8048b91:	79 05                	jns    8048b98 <phase_2+0x24>          //Nonnegative 非负数,跳转到8048b98,所以第一个参数为非负数
 8048b93:	e8 fe 05 00 00       	call   8049196 <explode_bomb>          //
 8048b98:	bb 01 00 00 00       	mov    $0x1,%ebx                       //ebx=1初始化ebx=1

①首先我们看到这一段汇编,read_six_numbers(0x40(%esp),0x18(%esp)) 读入6个数据,其中cmpl $0x0,0x18(%esp)指令比较序列第一个数和0的大小,如果Nonnegative 非负数,跳转到8048b98否则爆炸,所以第一个数必须为非负数,接着我们继续往下查看

/*循环部分
 8048b9d:	89 d8                	mov    %ebx,%eax                       //eax=i
 8048b9f:	03 44 9c 14          	add    0x14(%esp,%ebx,4),%eax          //eax=*(esp+1*4+14)+eax,eax=a[i]+i
 8048ba3:	39 44 9c 18          	cmp    %eax,0x18(%esp,%ebx,4)          //比较eax和a[i+1]
 8048ba7:	74 05                	je     8048bae <phase_2+0x3a>          //相等则跳转到8048bae
 8048ba9:	e8 e8 05 00 00       	call   8049196 <explode_bomb>          //否则爆炸
 8048bae:	83 c3 01             	add    $0x1,%ebx                       //ebx=i+1   
 8048bb1:	83 fb 06             	cmp    $0x6,%ebx                       //比较ebx和6
 8048bb4:	75 e7                	jne    8048b9d <phase_2+0x29>          //如果ebx!=6则跳转到8048B9D,循环‬5次,逐一比较
*/ 

②这段代码add 0x14(%esp,%ebx,4),%eax计算eax=a[i]+i,然后cmp %eax,0x18(%esp,%ebx,4) 比较eax和a[i+1] 相等则跳转到8048ba,否则将会爆炸,所以说想要避开炸弹就必须满足两个条件,第一a[0]为非负数,第二a[i+1]=a[i]+i,按照这个规则我构造序列:1 2 4 7 11 16 21,经验证结果正确。

Phase_3:switch分支

08048bbb <phase_3>:
 8048bbb:	83 ec 3c             	sub    $0x3c,%esp
 8048bbe:	8d 44 24 28          	lea    0x28(%esp),%eax
 8048bc2:	89 44 24 10          	mov    %eax,0x10(%esp)
 8048bc6:	8d 44 24 2f          	lea    0x2f(%esp),%eax
 8048bca:	89 44 24 0c          	mov    %eax,0xc(%esp)
 8048bce:	8d 44 24 24          	lea    0x24(%esp),%eax
 8048bd2:	89 44 24 08          	mov    %eax,0x8(%esp)
 8048bd6:	c7 44 24 04 ba a2 04 	movl   $0x804a2ba,0x4(%esp)            //字符串参数
 8048bdd:	08 
 8048bde:	8b 44 24 40          	mov    0x40(%esp),%eax
 8048be2:	89 04 24             	mov    %eax,(%esp)
 8048be5:	e8 86 fc ff ff       	call   8048870 <__isoc99_sscanf@plt>     //输入"%d %c %d",顺序为(0x24,0x2f,0x28)
 8048bea:	83 f8 02             	cmp    $0x2,%eax                         //返回结果和2比较
 8048bed:	7f 05                	jg     8048bf4 <phase_3+0x39>            //大于2跳转到8048bf4,所以输入参数个数必须大于2
 8048bef:	e8 a2 05 00 00       	call   8049196 <explode_bomb>             
 8048bf4:	83 7c 24 24 07       	cmpl   $0x7,0x24(%esp)                   //第1个数必须小于0x7
 8048bf9:	0f 87 fc 00 00 00    	ja     8048cfb <phase_3+0x140>           //大等于7跳转到8048CFB‬,爆炸
 8048bff:	8b 44 24 24          	mov    0x24(%esp),%eax                   //将第一个数存入eax中
 8048c03:	ff 24 85 e0 a2 04 08 	jmp    *0x804a2e0(,%eax,4)               //switch跳转,假设第一个数为0,那么将会跳转到 *0x804a2e0=8048c0a
 8048c0a:	b8 63 00 00 00       	mov    $0x63,%eax                        //eax=63 
 8048c0f:	81 7c 24 28 82 01 00 	cmpl   $0x182,0x28(%esp)                 //比较第3个参数和0x182=386

①看到这个函数,一开始是一个__isoc99_sscanf@plt函数,这一定是输入一些数据,但是这些数据的格式要看函数前的参数准备。看到一个立即数0x804a2ba,启用gdb调试,在函数phase_3处设置断点,执行指令

(gdb) x/s 0x804a2ba                     发现这个字符串的结果是"%d %c %d"

结合前面的参数,我们可以知道__isoc99_sscanf@plt调用顺序为(0x24,0x2f,0x28),所以说函数将会输入一个整数,一个字符串一个整数。

扫描二维码关注公众号,回复: 11216563 查看本文章
 8048bde:	8b 44 24 40          	mov    0x40(%esp),%eax
 8048be2:	89 04 24             	mov    %eax,(%esp)
 8048be5:	e8 86 fc ff ff       	call   8048870 <__isoc99_sscanf@plt>     //输入"%d %c %d",顺序为(0x24,0x2f,0x28)
 8048bea:	83 f8 02             	cmp    $0x2,%eax                         //返回结果和2比较
 8048bed:	7f 05                	jg     8048bf4 <phase_3+0x39>            //大于2跳转到8048bf4,所以输入参数个数必须大于2
 8048bef:	e8 a2 05 00 00       	call   8049196 <explode_bomb>             
 8048bf4:	83 7c 24 24 07       	cmpl   $0x7,0x24(%esp)                   //第1个数必须小于0x7
 8048bf9:	0f 87 fc 00 00 00    	ja     8048cfb <phase_3+0x140>           //大等于7跳转到8048CFB‬,爆炸
 8048bff:	8b 44 24 24          	mov    0x24(%esp),%eax                   //将第一个数存入eax中
 8048c03:	ff 24 85 e0 a2 04 08 	jmp    *0x804a2e0(,%eax,4)               //switch跳转,假设第一个数为0,那么将会跳转到 *0x804a2e0=8048c0a
 8048c0a:	b8 63 00 00 00       	mov    $0x63,%eax                        //eax=63 
 8048c0f:	81 7c 24 28 82 01 00 	cmpl   $0x182,0x28(%esp)                 //比较第3个参数和0x182=386
 8048c16:	00 
 8048c17:	0f 84 e8 00 00 00    	je     8048d05 <phase_3+0x14a>           //=182,跳转到8048d05

②这一段中cmpl $0x7,0x24(%esp),ja 8048cfb <phase_3+0x140>大等于7跳转到8048CFB‬,爆炸,所以第1个数必须小于0x7;x804a2e0(,%eax,4)是典型的switch跳转,假设第一个数为0,那么将会跳转到 *0x804a2e0=8048c0a,
找到8048c0a 发现指令mov $0x63,%eax cmpl $0x182,0x28(%esp)比较了第3个参数和0x182=386,只有参数3等于386才能避免爆炸,因此第一个参数为0的时候参数3必须为386.

 8048cf9:	eb 0a                	jmp    8048d05 <phase_3+0x14a>
 8048cfb:	e8 96 04 00 00       	call   8049196 <explode_bomb> //爆炸
 8048d00:	b8 63 00 00 00       	mov    $0x63,%eax
 8048d05:	3a 44 24 2f          	cmp    0x2f(%esp),%al         //比较eax的低8位,0110 0011=c
 8048d09:	74 05                	je     8048d10 <phase_3+0x155>//相等则通过
 8048d0b:	e8 86 04 00 00       	call   8049196 <explode_bomb>

③第二个参数确定后跳转到8048d0,这个时候会将eax的低8位取出01100011,然后与0x2f(%esp)比较,这里存放的恰好是第2个参数,所以第二个参数ascii=0110 0011=c,因此三个参数可以确定为0 c 386,经验证,答案正确。但是由于这里的跳转表有7个所以答案不唯一。

Phase_4:过程递归调用

08048d81 <phase_4>:
 8048d81:	83 ec 2c             	sub    $0x2c,%esp
 8048d84:	8d 44 24 1c          	lea    0x1c(%esp),%eax              //参数2  0x1c(%esp)
 8048d88:	89 44 24 0c          	mov    %eax,0xc(%esp)            
 8048d8c:	8d 44 24 18          	lea    0x18(%esp),%eax              //参数1  0x18(%esp)
 8048d90:	89 44 24 08          	mov    %eax,0x8(%esp)               //存放在0x8(%esp)   
 8048d94:	c7 44 24 04 a3 a4 04 	movl   $0x804a4a3,0x4(%esp)         //字符串参数"%d %d"
 8048d9b:	08 
 8048d9c:	8b 44 24 30          	mov    0x30(%esp),%eax              
 8048da0:	89 04 24             	mov    %eax,(%esp)
 8048da3:	e8 c8 fa ff ff       	call   8048870 <__isoc99_sscanf@plt>
 8048da8:	83 f8 02             	cmp    $0x2,%eax                    //输入个数等于2
 8048dab:	75 0d                	jne    8048dba <phase_4+0x39>       
 8048dad:	8b 44 24 18          	mov    0x18(%esp),%eax              //eax=参数1
 8048db1:	85 c0                	test   %eax,%eax                    //参数1
 8048db3:	78 05                	js     8048dba <phase_4+0x39>       //参数1不能为负数
 8048db5:	83 f8 0e             	cmp    $0xe,%eax                    //比较参数1和0xe=14 
 8048db8:	7e 05                	jle    8048dbf <phase_4+0x3e>       //参数1必须小于等于0xe
 8048dba:	e8 d7 03 00 00       	call   8049196 <explode_bomb>       //爆炸
 8048dbf:	c7 44 24 08 0e 00 00 	movl   $0xe,0x8(%esp)               //0x8(%esp)=0xe,func4的第3个参数
 8048dc6:	00 
 8048dc7:	c7 44 24 04 00 00 00 	movl   $0x0,0x4(%esp)               //0x4(%esp)=0,func4的第2个参数
 8048dce:	00 
 8048dcf:	8b 44 24 18          	mov    0x18(%esp),%eax              //eax=参数1,func4的第一个参数
 8048dd3:	89 04 24             	mov    %eax,(%esp)                  
 8048dd6:	e8 39 ff ff ff       	call   8048d14 <func4>              //调用func4(arg1,0,14),返回值要为1
 8048ddb:	83 f8 01             	cmp    $0x1,%eax                    //将结果与1比较
 8048dde:	75 07                	jne    8048de7 <phase_4+0x66>       //如果结果不等于1那么爆炸,func4结果必须为1
 8048de0:	83 7c 24 1c 01       	cmpl   $0x1,0x1c(%esp)              //将参数2与1比较
 8048de5:	74 05                	je     8048dec <phase_4+0x6b>       //如果参数2=1,跳转,说明参数2必须等于1

①我们看到这里调用了__isoc99_sscanf@plt函数,在这之前有一个立即数0x804a4a3,在gdb调试的时候x/s 0x804a4a3查看字符串,结果为"%d %d",说明要传入两个整型参数。mov 0x18(%esp),%eax和test %eax,%eax对第一个参数做判断如果为负数则会爆炸,说明第一个参数必须不为负数。cmp $0xe,%eax和jle判断如果参数1小于等于0xe才能避免爆炸,所以说第一个参数还需要满足小于0xe的条件。然后进行函数调用,将两个参数传入func4。调用结束之后,cmp $0x1,%eax将结果与1比较jne 8048de7如果结果不等于1那么爆炸,func4结果必须为1.cmpl $0x1,0x1c(%esp)将参数2与1比较,如果参数2=1,跳转,说明参数2必须等于1

08048d14 <func4>:                                                 
 8048d14:	83 ec 1c             	sub    $0x1c,%esp             //与1比较
 8048d17:	89 5c 24 14          	mov    %ebx,0x14(%esp)        
 8048d1b:	89 74 24 18          	mov    %esi,0x18(%esp)
 8048d1f:	8b 54 24 20          	mov    0x20(%esp),%edx        //参数1
 8048d23:	8b 44 24 24          	mov    0x24(%esp),%eax        //参数2
 8048d27:	8b 5c 24 28          	mov    0x28(%esp),%ebx        //参数3
 8048d2b:	89 d9                	mov    %ebx,%ecx              
 8048d2d:	29 c1                	sub    %eax,%ecx              //参数3-参数2->ecx
 8048d2f:	89 ce                	mov    %ecx,%esi              //ecx->esi           
 8048d31:	c1 ee 1f             	shr    $0x1f,%esi             //esi>>31位,取到符号位
 8048d34:	01 f1                	add    %esi,%ecx              //将符号位加到ecx
 8048d36:	d1 f9                	sar    %ecx                   //sar $1 %ecx将ecx算数右移1位,以上三点整合起来就是 (ecx>>31 + ecx3) >> 1
 8048d38:	01 c1                	add    %eax,%ecx              //ecx+=arg2
 8048d3a:	39 d1                	cmp    %edx,%ecx              //比较arg2+(arg3>>31 + arg3) >> 1和参数1
 8048d3c:	7e 17                	jle    8048d55 <func4+0x41>   //小于等于参数1,跳转,否则递归调用函数
 8048d3e:	83 e9 01             	sub    $0x1,%ecx              //ecx-1
 8048d41:	89 4c 24 08          	mov    %ecx,0x8(%esp)         //arg3
 8048d45:	89 44 24 04          	mov    %eax,0x4(%esp)         //arg2
 8048d49:	89 14 24             	mov    %edx,(%esp)            //arg1
 8048d4c:	e8 c3 ff ff ff       	call   8048d14 <func4>        //func4(edx,eax,ecx),改变的是ecx,第三个参数
 8048d51:	01 c0                	add    %eax,%eax              //将结果*2
 8048d53:	eb 20                	jmp    8048d75 <func4+0x61>   //函数结束   
 8048d55:	b8 00 00 00 00       	mov    $0x0,%eax              //8048d3c跳转到这里,eax=0
 8048d5a:	39 d1                	cmp    %edx,%ecx              //比较ecx-edx参数1
 8048d5c:	7d 17                	jge    8048d75 <func4+0x61>   //如果ecx>=edx,返回
 8048d5e:	89 5c 24 08          	mov    %ebx,0x8(%esp)         //否则,将参数3放回arg3
 8048d62:	83 c1 01             	add    $0x1,%ecx              //将ecx+1,作为arg2
 8048d65:	89 4c 24 04          	mov    %ecx,0x4(%esp)         
 8048d69:	89 14 24             	mov    %edx,(%esp)            //edx参数一作为arg1
 8048d6c:	e8 a3 ff ff ff       	call   8048d14 <func4>        //递归调用函数
 8048d71:	8d 44 00 01          	lea    0x1(%eax,%eax,1),%eax  //返回2*eax+1
 8048d75:	8b 5c 24 14          	mov    0x14(%esp),%ebx     //返回
 8048d79:	8b 74 24 18          	mov    0x18(%esp),%esi

②查看func4,一开始看到这个函数将参数3存放在ecx中,然后将ecx放入esi,接着shr $0x1f,%esi 将esi>>31位,取到符号位,add %esi,%ecx将符号位加到ecx,sar %ecx sar $1 %ecx将ecx算数右移1位。以上三点整合起来就是 (ecx>>31 + ecx) >> 1,最add %eax,%ecx则是将结果加上第二个参数存放在ecx中。接着cmp %edx,%ecx比较arg2+(arg3>>31 + arg3) >> 1和参数1的大小,如果小于等于,那么跳转到8048d55行,继续判断,如果相等直接返回0;如果小于那么递归调用func4,lea 0x1(%eax,%eax,1),%eax则是计算返回值func4(x,tmp+1,z)*2+1。如果小大于执行sub $0x1,%ecx 将ecx-1然后调用func4(x,z,tmp-1)*2。
③按照以上的分支条件以及调用规则,可以写出C代码:

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

③最后我们回到phase_4函数,要求func4的结果为1,因此我写了一个程序来生成结果为1的数据:

int main()
{
    int x;
    for(int i=0;i<14;i++)
    {
        printf("(%d,0,14)=%d\n",i,func4(i,0,14));
    }
    system("pause");
    return 0;
}
(0,0,14)=0   (1,0,14)=0   (2,0,14)=0   (3,0,14)=0   (4,0,14)=0   (5,0,14)=0   (6,0,14)=0   
(8,0,14)=1   (9,0,14)=1   (10,0,14)=1  (11,0,14)=1  (12,0,14)=3  (13,0,14)=3  (7,0,14)=0

以上为1的结果的第一个参数都能作为参数1解决phase4,我选用8 1这一个组合,经过验证答案正确。

Phase_5构造低四位和序列

08048df0 <phase_5>:
 8048df0:	53                   	push   %ebx
 8048df1:	83 ec 18             	sub    $0x18,%esp
 8048df4:	8b 5c 24 20          	mov    0x20(%esp),%ebx
 8048df8:	89 1c 24             	mov    %ebx,(%esp)
 8048dfb:	e8 6b 02 00 00       	call   804906b <string_length>   //读入字符串长度
 8048e00:	83 f8 06             	cmp    $0x6,%eax                 //长度必须等于6,否则爆炸
 8048e03:	74 05                	je     8048e0a <phase_5+0x1a>    //等于6,跳转到8048e0a
 8048e05:	e8 8c 03 00 00       	call   8049196 <explode_bomb>    
 8048e0a:	ba 00 00 00 00       	mov    $0x0,%edx                 
 8048e0f:	b8 00 00 00 00       	mov    $0x0,%eax    

①Phase_5第一部分调用string_length函数,对返回值len进行判断cmp $0x6,%eax长度必须等于6,否则爆炸,所以说输入的字符必须只能是6个。

 8048e14:	0f be 0c 03          	movsbl (%ebx,%eax,1),%ecx        //计算(eax+ebx)->ecx,也就是长度为6的字符串的第eax个字符
 //movzbl指令负责拷贝一个字节,并用0填充其目的操作数中的其余各位,这种扩展方式叫“零扩展”
 //movsbl指令负责拷贝一个字节,并用源操作数的最高位填充其目的操作数中的其余各位,这种扩展方式叫“符号扩展” 
 8048e18:	83 e1 0f             	and    $0xf,%ecx                 //取ecx低4位
 8048e1b:	03 14 8d 00 a3 04 08 	add    0x804a300(,%ecx,4),%edx   //将 0x804a300(,%ecx,4)加到edx上  
 8048e22:	83 c0 01             	add    $0x1,%eax                 //eax计数
 8048e25:	83 f8 06             	cmp    $0x6,%eax                 //比较6
 8048e28:	75 ea                	jne    8048e14 <phase_5+0x24>

 8048e2a:	83 fa 2f             	cmp    $0x2f,%edx                //比较0x2f和edx的值,要求最后等于0x2f
 8048e2d:	74 05                	je     8048e34 <phase_5+0x44>    //相等则成功
 8048e2f:	e8 62 03 00 00       	call   8049196 <explode_bomb>
 8048e34:	83 c4 18             	add    $0x18,%esp
 8048e37:	5b                   	pop    %ebx
 8048e38:	c3                   	ret    

②Phase_5的第二部分,对首先将edx和eax进行了初始化,根据后面movsbl (%ebx,%eax,1),%ecx的寻址方式和跳转条件可以判断这是一个循环过程movzbl指令负责拷贝一个字节,并用0填充其目的操作数中的其余各位。and $0xf,%ecx则是取ecx低4位,add 0x804a300(,%ecx,4),%edx将 0x804a300(,%ecx,4)加到edx上,在循环执行结束后cmp $0x2f,%edx比较了0x2f和edx的值,要求最后等于0x2f,否则将会爆炸。所以说我们的任务是输入6个字符,取这个字符的低4位,按照0x804a300(,%ecx,4)找到0x804a300数组中的值,累加后结果必须为0x2f。

③启用gdb调试,用x/16wx查看0x804a300中的数值如下图,我们要在下表中可重复选择6个数据,和满足0x2f,我构造的一组值是10+10+10+10+6+1应的数组下标是1 1 1 1 2 3 在ascii码表中只要找到低4位的数据为0001/010/0011的三个值即可:
在这里插入图片描述
在这里插入图片描述
因此得到解结果为aaaabc,经验证答案正确

Phase_6:链表

08048e39 <phase_6>:                                                  //phase_6
 8048e39:	56                   	push   %esi                   
 8048e3a:	53                   	push   %ebx
 8048e3b:	83 ec 44             	sub    $0x44,%esp
 8048e3e:	8d 44 24 10          	lea    0x10(%esp),%eax
 8048e42:	89 44 24 04          	mov    %eax,0x4(%esp)
 8048e46:	8b 44 24 50          	mov    0x50(%esp),%eax
 8048e4a:	89 04 24             	mov    %eax,(%esp)
 8048e4d:	e8 79 04 00 00       	call   80492cb <read_six_numbers>   //读取6个数据,手动输入
 8048e52:	be 00 00 00 00       	mov    $0x0,%esi                    //执行循环
 8048e57:	8b 44 b4 10          	mov    0x10(%esp,%esi,4),%eax          
 8048e5b:	83 e8 01             	sub    $0x1,%eax                    //x-1
 8048e5e:	83 f8 05             	cmp    $0x5,%eax                    //x-1后与5比较
 8048e61:	76 05                	jbe    8048e68 <phase_6+0x2f>       //x-1小于等于5,也就是x<=6
 8048e63:	e8 2e 03 00 00       	call   8049196 <0>            
 8048e68:	83 c6 01             	add    $0x1,%esi                    
 8048e6b:	83 fe 06             	cmp    $0x6,%esi                    //判断下标esi
 8048e6e:	74 1b                	je     8048e8b <phase_6+0x52>       //esi=6,跳转到8048e8b,否则继续执行循环
 8048e70:	89 f3                	mov    %esi,%ebx                    //
 8048e72:	8b 44 9c 10          	mov    0x10(%esp,%ebx,4),%eax       //发现esi赋值给了ebx,后面执行了ebx+1,所以我断定这是一个2层循环
 8048e76:	39 44 b4 0c          	cmp    %eax,0xc(%esp,%esi,4)        //比较a[eip]和a[ebx]
 8048e7a:	75 05                	jne    8048e81 <phase_6+0x48>       //满足a[eip]不等于a[ebx]
 8048e7c:	e8 15 03 00 00       	call   8049196 <explode_bomb>       //
 8048e81:	83 c3 01             	add    $0x1,%ebx
 8048e84:	83 fb 05             	cmp    $0x5,%ebx                    //
 8048e87:	7e e9                	jle    8048e72 <phase_6+0x39>
 8048e89:	eb cc                	jmp    8048e57 <phase_6+0x1e>//for(eip for(ebx))
//得到两个条件就是a[i]<=6,a[i]!=a[j]

①一开始我看到汇编代码是真的长,没有头绪,但是注意到<read_six_numbers>这个函数,但是我往下看,可以发现整个过程中,前面一部分对输入的6个数据做了基础的检测,这两段代码cmp$0x5,%eax//x-1后与5比较,jbe8048e68 <phase_6+0x2f>要求是x必须大于等于6,否则将会发生爆炸。cmp%eax,0xc(%esp,%esi,4) 比较a[eip]和a[ebx],jne 8048e81 <phase_6+0x48>满足a[eip]不等于a[ebx],因此我发现输入的数据必须要满足a[i]<=6,a[i]!=a[j]

②输入范围是数据必须小于等于6,并且互不相等,我确定下来为1,2,3,4,5,6这6个数据,并且用gdb进行调试,在phase_6设置断点.

③找到一个立即数,这十分关键,在gdb调试时,我输出这个立即数的连续数据,发现,居然是这样的结构体。而且比较有特点是的,这些数据三个三个为一组,第2个为一个顺序递增的下标,第三个则是后一个元素节点的地址,这显然是一个链表,因此我推测第一个元素应该是这个节点的信息值——他的权值。
在这里插入图片描述

整理后,得到的结构体数据:

0x0000035d	0x00000001	0x0804c148	
0x000002eb	0x00000002	0x0804c154	
0x000002bb	0x00000003	0x0804c160	
0x000000eb	0x00000004	0x0804c16c
0x00000380	0x00000005	0x0804c178	
0x0000009f	0x00000006	0x00000000

权重+下标+地址

④ 继续往后看,中间有一大堆汇编代码我直接跳过了,因为他们没有出现引爆函数
8049196 <explode_bomb>的调用,但是,很关键的一点来了,在phase_6将要结束之前的一段汇编代码中,遍历的整个链表!做了什么?首先从遍历了5个元素,esi存储下标,然后mov0x8(%ebx),%eax每次都将ebx对应的值存放在eax,这个基地址偏移的结果就是下一个node的权重!而且要满足cmp %edx,(%ebx) jge 因此这个序列必须是递减的才能不引爆炸弹!

//验证数据是否是递减顺序
 8048f07:	be 05 00 00 00       	mov    $0x5,%esi                 //循环遍历整个链表节点     
 8048f0c:	8b 43 08             	mov    0x8(%ebx),%eax            //将ebx的值也就是链表对应的下一个地址放在eax  
 8048f0f:	8b 10                	mov    (%eax),%edx               //取数(eax)->edx  
 8048f11:	39 13                	cmp    %edx,(%ebx)               //比较edx和ebx,edx记录前一个值
 8048f13:	7d 05                	jge    8048f1a <phase_6+0xe1>    //当(%ebx)>=%edx,交换到ebx
 8048f15:	e8 7c 02 00 00       	call   8049196 <explode_bomb>    //否则爆炸
 8048f1a:	8b 5b 08             	mov    0x8(%ebx),%ebx           //将新的值放入ebx
 8048f1d:	83 ee 01             	sub    $0x1,%esi
 8048f20:	75 ea                	jne    8048f0c <phase_6+0xd3>

 8048f22:	83 c4 44             	add    $0x44,%esp
 8048f25:	5b                   	pop    %ebx
 8048f26:	5e                   	pop    %esi
 8048f27:	c3                   	ret    

⑤所以我断定这个函数大概率按照每个节点的权重对节点进行了排序。因此我对这些节点按照权重递减的顺序进行排序,可以得到新的序列:

0x00000380	0x00000005	0x00000000
0x0000035d	0x00000001	0x0804c154
0x000002eb	0x00000002	0x0804c160
0x000002bb	0x00000003	0x0804c16c
0x000000eb	0x00000004	0x0804c178
0x0000009f	0x00000006	0x00000000

⑥5 1 2 3 4 6,验证发现还是引爆这炸弹!这是为什么呢?看来中间跳过的一段代码不能省略,继续往后看,我发现sub (%eax),%edx每个下标都用7减去了,一开始我不太理解后来才明白这是一个坑!我赶紧把序列换成7-x的版本:2 6 5 4 3 1经过验证,这是正确的!

在这里插入图片描述

Secret_pause

什么?还有秘密关卡,一开始我是完全拒绝的。但是只能硬着头皮上了?找到Secret_pause
函数,发现其中调用了func7,但是Secret_pause秘密关卡怎么才能进入呢?我在phase_defused函数内部发现了它,但是有一些奇怪的立即数,我习惯性的用gdb x/s查看

 804934e:	c7 44 24 04 a9 a4 04 	movl   $0x804a4a9,0x4(%esp)               //字符串参数%d %d %s
 8049355:	08 
 8049356:	c7 04 24 d0 c4 04 08 	movl   $0x804c4d0,(%esp)                  //这是参数所在位置,"8 1"
 804935d:	e8 0e f5 ff ff       	call   8048870 <__isoc99_sscanf@plt>         
 8049362:	83 f8 03             	cmp    $0x3,%eax                             //输入数据等于3   
 8049365:	75 35                	jne    804939c <phase_defused+0x81>
 8049367:	c7 44 24 04 b2 a4 04 	movl   $0x804a4b2,0x4(%esp)                  //将0x804a4b2存入0x4(%esp)第二个参数,DrEvil
 804936e:	08 
 804936f:	8d 44 24 2c          	lea    0x2c(%esp),%eax
 8049373:	89 04 24             	mov    %eax,(%esp)
 8049376:	e8 09 fd ff ff       	call   8049084 <strings_not_equal>
 804937b:	85 c0                	test   %eax,%eax
 804937d:	75 1d                	jne    804939c <phase_defused+0x81>       //判断输入是否相等
 804937f:	c7 04 24 78 a3 04 08 	movl   $0x804a378,(%esp)                  //0x804a378
 8049386:	e8 75 f4 ff ff       	call   8048800 <puts@plt>
 804938b:	c7 04 24 a0 a3 04 08 	movl   $0x804a3a0,(%esp)                  //But finding it and solving it are quite different...
 8049392:	e8 69 f4 ff ff       	call   8048800 <puts@plt>
 8049397:	e8 dd fb ff ff       	call   8048f79 <secret_phase>

①并没有发现什么突破性的东西,除了两段字符串DrEvil和一些提示消息,但是我发现想要输出提示消息(也就是进入秘密关卡)必须要满足两个条件,首先是cmpl $0x6,0x804c3cc这个立即数中的数字必须要等于6,其次在比较<strings_not_equal>函数之前有一个参数准备的阶段,第2个参数就是我们上面得到的地址$0x804a4b2中的字符串DrEvil.第一个参数是什么?
在这里插入图片描述

②启用gdb调试,设置断点为b *0x8049356也就是立即数0x804c4d0所在行,用x/s 0x804c4d0查看地址上的内容,发现居然居然他就是在phase_4中输入的内容“8 1”,我猜测这必然和phase有着某种联系。

在这里插入图片描述

③注意到<strings_not_equal>函数的参数一个是DrEvil另一个是什么呢?难道有我们输入?他放在esp中,在此之前通过eax临时存放了,参数的值,我直接打印eax发现为空。是什么原因呢?为什么会为空,注意到8048870 __isoc99_sscanf@plt需要三个参数,而且是%d %d %s的格式,那么这个输入点只能是phase_4因为再无其他输入口,我果断在phase_4处输入8 1 hello World!然后gdb调试的时候打印<strings_not_equal>的第一个参数,p $eax,结果居然就是helloWorld!,只要<strings_not_equal>判断相等那么就能进入secret_phase,所以逻辑就很清晰了!

Pahse4输入%d %d %s------>s==DrEvil?----------> stringsEqual---->secretPhase

在这里插入图片描述

④Secret_pause顺利激活,接着看下去,我们看到读入先读入一行,返回值%eax作为函数strtol@plt的参数之一,另外两个参数分别是0xa和0x0由lea -0x1(%eax),%eax 和cmp $0x3e8,%eax 这两句知输入的十进制数要小于等于 1001。接着调用func7,函数的三个参数分别是p/x *0x804c088查看是0x24和读入的int数据。

 8048f91:	00 
 8048f92:	89 04 24             	mov    %eax,(%esp)                  //参数1,readline读入
 8048f95:	e8 46 f9 ff ff       	call   80488e0 <strtol@plt>         
 8048f9a:	89 c3                	mov    %eax,%ebx
 8048f9c:	8d 40 ff             	lea    -0x1(%eax),%eax              //eax-1
 8048f9f:	3d e8 03 00 00       	cmp    $0x3e8,%eax                  //1000
 8048fa4:	76 05                	jbe    8048fab <secret_phase+0x32>  //eax-1<=1000,也就是说eax<=1001
 8048fa6:	e8 eb 01 00 00       	call   8049196 <explode_bomb>
 8048fab:	89 5c 24 04          	mov    %ebx,0x4(%esp)
 8048faf:	c7 04 24 88 c0 04 08 	movl   $0x804c088,(%esp)            //参数1,p/x *0x804c088 为0x24
 8048fb6:	e8 6d ff ff ff       	call   8048f28 <fun7>
 8048fbb:	83 f8 07             	cmp    $0x7,%eax                    //返回值必须要为0x7
 8048fbe:	74 05                	je     8048fc5 <secret_phase+0x4c>
 8048fc0:	e8 d1 01 00 00       	call   8049196 <explode_bomb>
 8048fc5:	c7 04 24 94 a2 04 08 	movl   $0x804a294,(%esp)
 8048fcc:	e8 2f f8 ff ff       	call   8048800 <puts@plt>
 8048fd1:	e8 45 03 00 00       	call   804931b <phase_defused>
 8048fd6:	83 c4 18             	add    $0x18,%esp
 8048fd9:	5b                   	pop    %ebx

① Func7函数

08048f28 <fun7>:
 8048f28:	53                   	push   %ebx
 8048f29:	83 ec 18             	sub    $0x18,%esp
 8048f2c:	8b 54 24 20          	mov    0x20(%esp),%edx        //arg1
 8048f30:	8b 4c 24 24          	mov    0x24(%esp),%ecx        //arg2
 8048f34:	85 d2                	test   %edx,%edx           
 8048f36:	74 37                	je     8048f6f <fun7+0x47>    //arg1=null,返回0xffffffff
 8048f38:	8b 1a                	mov    (%edx),%ebx            //ebx=*(arg1)
 8048f3a:	39 cb                	cmp    %ecx,%ebx              //比较*(arg1)和arg2
 8048f3c:	7e 13                	jle    8048f51 <fun7+0x29>    
 8048f3e:	89 4c 24 04          	mov    %ecx,0x4(%esp)         //*(arg1)>arg2
 8048f42:	8b 42 04             	mov    0x4(%edx),%eax
 8048f45:	89 04 24             	mov    %eax,(%esp)
 8048f48:	e8 db ff ff ff       	call   8048f28 <fun7>         
 8048f4d:	01 c0                	add    %eax,%eax
 8048f4f:	eb 23                	jmp    8048f74 <fun7+0x4c>
 8048f51:	b8 00 00 00 00       	mov    $0x0,%eax              //*(arg1)<=arg2
 8048f56:	39 cb                	cmp    %ecx,%ebx
 8048f58:	74 1a                	je     8048f74 <fun7+0x4c>    //*(arg1)<arg2
 8048f5a:	89 4c 24 04          	mov    %ecx,0x4(%esp)
 8048f5e:	8b 42 08             	mov    0x8(%edx),%eax
 8048f61:	89 04 24             	mov    %eax,(%esp)
 8048f64:	e8 bf ff ff ff       	call   8048f28 <fun7>
 8048f69:	8d 44 00 01          	lea    0x1(%eax,%eax,1),%eax  //2*func7(arg1+8,arg2)+1
 8048f6d:	eb 05                	jmp    8048f74 <fun7+0x4c>
 8048f6f:	b8 ff ff ff ff       	mov    $0xffffffff,%eax
 8048f74:	83 c4 18             	add    $0x18,%esp             //*(arg1)==arg2
 8048f77:	5b                   	pop    %ebx
 8048f78:	c3                   	ret    

这个函数的功能是(假设参数为a,b),首先判断a是否为空指针如果是那么就返回-1,否则判断a和b的大小,如果a==b 那么返回0,如果a<b那么将a向后偏移8个字节后递归处理这个函数,得到结果2+1;如果a>b那么将a向后偏移4个字节后递归处理这个函数,得到结果*2;这个指针一开始就是0x804c088,我们输出这个地址表显示地址值:实际上我发现,这也是一个链表的结构,每个节点包括接节点权值和两个地址,经过验证这就是一个二叉树结构:

可以将func7函数还原如下:

int func7(int* a,int b)
{
    if(a==null) return 0xffffffff;
    if(*a<=b)
    {
        if(*a==b) return 0;
        return 2*func7(a+8,b)+1;
    }
    return 2*func7(a+4,b);
}

在这里插入图片描述

由于func7(a,b)的结果要为0x7才能顺利过关,我们必须要构造一个 结果为7的解,函数边界一定返回值是0,那么我们可以这样构造:从起始节点24开始向右子节点访问32,然后访问32的右子节点6b,然后再访问其右子节点3e9在此处返回,这样得到结果是0----1------21+1=3------3*2+1=7,得到结果为7,因此可以选用3e9也就是十进制1001,经过验证答案正确。

实验结果及分析:

① 最后得到的答案是:2,3,4,5,6答案不唯一

When I get angry, Mr . Bigglesworth gets upset.
1  2  4  7  11  16  21
0 c 386
8 1
aaaabc
2 6 5 4 3 1
1001

② 经程序验证,答案正确

收获与体会:

① 通过本次实验对gdb调试的使用方法有了进一步了解,调试能力得到了锻炼
② 对movzbl和movsbl指令有了新的理解:movzbl负责拷贝一个字节,并用0填充其目的操作数中的其余各位,这种扩展方式叫零扩展,movsbl指令负责拷贝一个字节,并用源操作数的最高位填充其目的操作数中的其余各位,这种扩展方式叫符号扩展
③ 对过程调用和参数准备工作理解得到了加深
④ 进一步理解了x/输出和p输出的差别,学习到数据在内存中的存放方式采用哪种查看方式更为有效。

原创文章 236 获赞 430 访问量 7万+

猜你喜欢

转载自blog.csdn.net/weixin_44307065/article/details/105932303
今日推荐