实验题目: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,%esp8048b53:   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,引爆炸弹,否则跳转到8048b708048b6b:   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   %ebx8048b75: 83 ec 38                sub    $0x38,%esp8048b78:   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)和08048b91: 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=i8048b9f: 03 44 9c 14             add    0x14(%esp,%ebx,4),%eax          //eax=*(esp+1*4+14)+eax,eax=a[i]+i8048ba3: 39 44 9c 18             cmp    %eax,0x18(%esp,%ebx,4)          //比较eax和a[i+1]8048ba7:  74 05                   je     8048bae <phase_2+0x3a>          //相等则跳转到8048bae8048ba9:   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和68048bb4:    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,%esp8048bbe:   8d 44 24 28             lea    0x28(%esp),%eax8048bc2:  89 44 24 10             mov    %eax,0x10(%esp)8048bc6:  8d 44 24 2f             lea    0x2f(%esp),%eax8048bca:  89 44 24 0c             mov    %eax,0xc(%esp)8048bce:   8d 44 24 24             lea    0x24(%esp),%eax8048bd2:  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),%eax8048be2:  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,所以输入参数个数必须大于28048bef:   e8 a2 05 00 00          call   8049196 <explode_bomb>             8048bf4:    83 7c 24 24 07          cmpl   $0x7,0x24(%esp)                   //第1个数必须小于0x78048bf9:  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=8048c0a8048c0a:  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),所以说函数将会输入一个整数,一个字符串一个整数。

 8048bde:    8b 44 24 40             mov    0x40(%esp),%eax8048be2:  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,所以输入参数个数必须大于28048bef:   e8 a2 05 00 00          call   8049196 <explode_bomb>             8048bf4:    83 7c 24 24 07          cmpl   $0x7,0x24(%esp)                   //第1个数必须小于0x78048bf9:  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=8048c0a8048c0a:  b8 63 00 00 00          mov    $0x63,%eax                        //eax=63 8048c0f: 81 7c 24 28 82 01 00    cmpl   $0x182,0x28(%esp)                 //比较第3个参数和0x182=3868048c16:   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,%eax8048d05:   3a 44 24 2f             cmp    0x2f(%esp),%al         //比较eax的低8位,0110 0011=c8048d09:   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,%esp8048d84:   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                    //输入个数等于28048dab:   75 0d                   jne    8048dba <phase_4+0x39>       8048dad: 8b 44 24 18             mov    0x18(%esp),%eax              //eax=参数18048db1:  85 c0                   test   %eax,%eax                    //参数18048db3:   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必须小于等于0xe8048dba:   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),返回值要为18048ddb:   83 f8 01                cmp    $0x1,%eax                    //将结果与1比较8048dde:   75 07                   jne    8048de7 <phase_4+0x66>       //如果结果不等于1那么爆炸,func4结果必须为18048de0:   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        //参数18048d23: 8b 44 24 24             mov    0x24(%esp),%eax        //参数28048d27: 8b 5c 24 28             mov    0x28(%esp),%ebx        //参数38048d2b: 89 d9                   mov    %ebx,%ecx              8048d2d:  29 c1                   sub    %eax,%ecx              //参数3-参数2->ecx8048d2f: 89 ce                   mov    %ecx,%esi              //ecx->esi           8048d31:  c1 ee 1f                shr    $0x1f,%esi             //esi>>31位,取到符号位8048d34: 01 f1                   add    %esi,%ecx              //将符号位加到ecx8048d36:   d1 f9                   sar    %ecx                   //sar $1 %ecx将ecx算数右移1位,以上三点整合起来就是 (ecx>>31 + ecx3) >> 18048d38: 01 c1                   add    %eax,%ecx              //ecx+=arg28048d3a: 39 d1                   cmp    %edx,%ecx              //比较arg2+(arg3>>31 + arg3) >> 1和参数18048d3c: 7e 17                   jle    8048d55 <func4+0x41>   //小于等于参数1,跳转,否则递归调用函数8048d3e:    83 e9 01                sub    $0x1,%ecx              //ecx-18048d41:   89 4c 24 08             mov    %ecx,0x8(%esp)         //arg38048d45:    89 44 24 04             mov    %eax,0x4(%esp)         //arg28048d49:    89 14 24                mov    %edx,(%esp)            //arg18048d4c:    e8 c3 ff ff ff          call   8048d14 <func4>        //func4(edx,eax,ecx),改变的是ecx,第三个参数8048d51:   01 c0                   add    %eax,%eax              //将结果*28048d53:   eb 20                   jmp    8048d75 <func4+0x61>   //函数结束   8048d55:  b8 00 00 00 00          mov    $0x0,%eax              //8048d3c跳转到这里,eax=08048d5a: 39 d1                   cmp    %edx,%ecx              //比较ecx-edx参数18048d5c:    7d 17                   jge    8048d75 <func4+0x61>   //如果ecx>=edx,返回8048d5e:    89 5c 24 08             mov    %ebx,0x8(%esp)         //否则,将参数3放回arg38048d62:    83 c1 01                add    $0x1,%ecx              //将ecx+1,作为arg28048d65:  89 4c 24 04             mov    %ecx,0x4(%esp)         8048d69:  89 14 24                mov    %edx,(%esp)            //edx参数一作为arg18048d6c:    e8 a3 ff ff ff          call   8048d14 <func4>        //递归调用函数8048d71:    8d 44 00 01             lea    0x1(%eax,%eax,1),%eax  //返回2*eax+18048d75:  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   %ebx8048df1: 83 ec 18                sub    $0x18,%esp8048df4:   8b 5c 24 20             mov    0x20(%esp),%ebx8048df8:  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,跳转到8048e0a8048e05: 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                 //比较68048e28:  75 ea                   jne    8048e14 <phase_5+0x24>8048e2a:    83 fa 2f                cmp    $0x2f,%edx                //比较0x2f和edx的值,要求最后等于0x2f8048e2d:   74 05                   je     8048e34 <phase_5+0x44>    //相等则成功8048e2f: e8 62 03 00 00          call   8049196 <explode_bomb>8048e34: 83 c4 18                add    $0x18,%esp8048e37:   5b                      pop    %ebx8048e38: 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_68048e39: 56                      push   %esi                   8048e3a:  53                      push   %ebx8048e3b: 83 ec 44                sub    $0x44,%esp8048e3e:   8d 44 24 10             lea    0x10(%esp),%eax8048e42:  89 44 24 04             mov    %eax,0x4(%esp)8048e46:   8b 44 24 50             mov    0x50(%esp),%eax8048e4a:  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-18048e5e:   83 f8 05                cmp    $0x5,%eax                    //x-1后与5比较8048e61:  76 05                   jbe    8048e68 <phase_6+0x2f>       //x-1小于等于5,也就是x<=68048e63:    e8 2e 03 00 00          call   8049196 <0>            8048e68:    83 c6 01                add    $0x1,%esi                    8048e6b:    83 fe 06                cmp    $0x6,%esi                    //判断下标esi8048e6e:   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,%ebx8048e84:    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,交换到ebx8048f15:  e8 7c 02 00 00          call   8049196 <explode_bomb>    //否则爆炸8048f1a:   8b 5b 08                mov    0x8(%ebx),%ebx           //将新的值放入ebx8048f1d: 83 ee 01                sub    $0x1,%esi8048f20:    75 ea                   jne    8048f0c <phase_6+0xd3>8048f22:    83 c4 44                add    $0x44,%esp8048f25:   5b                      pop    %ebx8048f26: 5e                      pop    %esi8048f27: 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 %s8049355:   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)第二个参数,DrEvil804936e:    08 804936f: 8d 44 24 2c             lea    0x2c(%esp),%eax8049373:  89 04 24                mov    %eax,(%esp)8049376:  e8 09 fd ff ff          call   8049084 <strings_not_equal>804937b:    85 c0                   test   %eax,%eax804937d:    75 1d                   jne    804939c <phase_defused+0x81>       //判断输入是否相等804937f: c7 04 24 78 a3 04 08    movl   $0x804a378,(%esp)                  //0x804a3788049386:   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,%ebx8048f9c:    8d 40 ff                lea    -0x1(%eax),%eax              //eax-18048f9f: 3d e8 03 00 00          cmp    $0x3e8,%eax                  //10008048fa4:  76 05                   jbe    8048fab <secret_phase+0x32>  //eax-1<=1000,也就是说eax<=10018048fa6:  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 为0x248048fb6:   e8 6d ff ff ff          call   8048f28 <fun7>8048fbb: 83 f8 07                cmp    $0x7,%eax                    //返回值必须要为0x78048fbe:    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,%esp8048fd9:   5b                      pop    %ebx

① Func7函数

08048f28 <fun7>:8048f28:   53                      push   %ebx8048f29: 83 ec 18                sub    $0x18,%esp8048f2c:   8b 54 24 20             mov    0x20(%esp),%edx        //arg18048f30:    8b 4c 24 24             mov    0x24(%esp),%ecx        //arg28048f34:    85 d2                   test   %edx,%edx           8048f36: 74 37                   je     8048f6f <fun7+0x47>    //arg1=null,返回0xffffffff8048f38:  8b 1a                   mov    (%edx),%ebx            //ebx=*(arg1)8048f3a:    39 cb                   cmp    %ecx,%ebx              //比较*(arg1)和arg28048f3c:  7e 13                   jle    8048f51 <fun7+0x29>    8048f3e:   89 4c 24 04             mov    %ecx,0x4(%esp)         //*(arg1)>arg28048f42: 8b 42 04                mov    0x4(%edx),%eax8048f45:   89 04 24                mov    %eax,(%esp)8048f48:  e8 db ff ff ff          call   8048f28 <fun7>         8048f4d:    01 c0                   add    %eax,%eax8048f4f:    eb 23                   jmp    8048f74 <fun7+0x4c>8048f51:   b8 00 00 00 00          mov    $0x0,%eax              //*(arg1)<=arg28048f56:   39 cb                   cmp    %ecx,%ebx8048f58:    74 1a                   je     8048f74 <fun7+0x4c>    //*(arg1)<arg28048f5a:  89 4c 24 04             mov    %ecx,0x4(%esp)8048f5e:   8b 42 08                mov    0x8(%edx),%eax8048f61:   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)+18048f6d:    eb 05                   jmp    8048f74 <fun7+0x4c>8048f6f:   b8 ff ff ff ff          mov    $0xffffffff,%eax8048f74: 83 c4 18                add    $0x18,%esp             //*(arg1)==arg28048f77: 5b                      pop    %ebx8048f78: 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输出的差别,学习到数据在内存中的存放方式采用哪种查看方式更为有效。

APP Bomb Lab相关推荐

  1. 《深入理解计算机系统》实验二Bomb Lab下载和官方文档机翻

    前言 <深入理解计算机系统>官网:http://csapp.cs.cmu.edu/3e/labs.html 该篇文章是实验二Bomb Lab的Writeup机翻. 原文:http://cs ...

  2. [精品]CSAPP Bomb Lab 解题报告(七)——隐藏关卡

    接上篇[精品]CSAPP Bomb Lab 解题报告(六) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  3. [精品]CSAPP Bomb Lab 解题报告(六)

    接上篇[精品]CSAPP Bomb Lab 解题报告(五) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  4. [精品]CSAPP Bomb Lab 解题报告(五)

    接上篇[精品]CSAPP Bomb Lab 解题报告(四) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  5. [精品]CSAPP Bomb Lab 解题报告(四)

    接上篇[精品]CSAPP Bomb Lab 解题报告(三) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  6. [精品]CSAPP Bomb Lab 解题报告(三)

    接上篇[精品]CSAPP Bomb Lab 解题报告(二) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  7. [精品]CSAPP Bomb Lab 解题报告(二)

    接上篇[精品]CSAPP Bomb Lab 解题报告(一) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  8. [精品]CSAPP Bomb Lab 解题报告(一)

    接上篇堆栈图解CSAPP Bomb Lab实验解析 gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 查看字 ...

  9. 堆栈图解CSAPP Bomb Lab实验解析

    CSAPP Bomb Lab 实验解析 Bomblab是csapp的第二个配套实验,该实验提供了一个bomb二进制文件和一个bomb.c源文件,我们的目标是运行bomb并按照提示一步步输入字符串,直到 ...

  10. CSAPP Bomb Lab记录

    记录关于CSAPP 二进制炸弹实验过程 (CSAPP配套教学网站Bomb Lab自学版本,实验地址:http://csapp.cs.cmu.edu/2e/labs.html) (个人体验:对x86汇编 ...

最新文章

  1. 清华《摸鱼学导论》开班啦!1000多学子在线摸鱼,无期末考试
  2. python编写程序-30分钟学会用Python编写简单程序
  3. Internet Explorer 9.0 正式版试用一点小总结
  4. 如何让你的大文件上传变得又稳又快?
  5. [跨平台系列三Docker篇]:ASP.NET Core应用
  6. oracle账号区分大小写吗,实战Oracle 11g用户密码不区分大小写
  7. jQuery循环滚动展示代码
  8. click 点击图片不起作用_JavaScript 练手小案例:基于SVG的图片切换效果
  9. 第二章:在HTML中使用JavaScript
  10. SparkStreaming 入门案例之wordcount
  11. java与数据库连接实验报告_数据库原理与应用java实验报告
  12. eclipse svn 没有 connector
  13. iframe 如何禁止视频自动播放
  14. LCP 03 机器人大冒险(分析-计算运动周期)
  15. 大数据风控必看,挖掘学历数据中暗藏的还款意愿及还款能力
  16. 机器学习中的偏差、方差以及泛化误差
  17. 经纬度5位数和6位数差多少_经纬度精度差别 - Rain - OSCHINA - 中文开源技术交流社区...
  18. 腾讯云 云点播 JAVASDK上传
  19. 技术文档的写作规范总结
  20. 《FORTRAN语法:章节篇》第1章 数据类型

热门文章

  1. java 去掉图片水印文字_Java实现图片水印工具类
  2. 哪款软件可以测试脉冲信号,脉冲测试
  3. 任务管理器显示命令行
  4. Annoying day
  5. 在北大国家发展研究院发言
  6. 绝对干货-国内值得关注的官方API集合,很全很强大(必须收藏)
  7. 【基础】格林尼治时间转化
  8. 如何使用启动盘启动计算机,电脑重装系统怎么设置用U盘启动盘引导?
  9. 16s测序 | 如何轻松发表文章
  10. ODC(Orthogonal Defect Classification)简介——正交缺陷分类法