实验题目: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
1
一切准备就绪,开始逐个分析phase函数。
Phase_1:字符串比较
08048b50 :
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 //判断输入的字符串
8048b67: 85 c0 test %eax,%eax
8048b69: 74 05 je 8048b70 //如果结果为0,引爆炸弹,否则跳转到8048b70
8048b6b: e8 26 06 00 00 call 8049196
8048b70: 83 c4 1c add $0x1c,%esp //将esp+0x1c返回
8048b73: c3 ret
123456789101112
①查看Phase_1函数内容,可以看到函数将输入的字符串0x20(%esp)和一个立即数指向的字符串传入一个叫做的函数中,这个函数的功能是判断两个字符串是否相等。我启用gdb调试,输入指令:
Linux:> x/s 0x804a264
1

可以看到输出结果为When I get angry, Mr . Bigglesworth gets upset,我将结果输入验证,发现答案正确。
Phase_2:a[i+1]=a[i]+i序列
08048b74 :
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 //读入6个数据
8048b8c: 83 7c 24 18 00 cmpl $0x0,0x18(%esp) //比较0x18(%esp)和0
8048b91: 79 05 jns 8048b98 //Nonnegative 非负数,跳转到8048b98,所以第一个参数为非负数
8048b93: e8 fe 05 00 00 call 8049196 //
8048b98: bb 01 00 00 00 mov $0x1,%ebx //ebx=1初始化ebx=1
123456789101112
①首先我们看到这一段汇编,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+14+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 //相等则跳转到8048bae
8048ba9: e8 e8 05 00 00 call 8049196 //否则爆炸
8048bae: 83 c3 01 add $0x1,%ebx //ebx=i+1
8048bb1: 83 fb 06 cmp $0x6,%ebx //比较ebx和6
8048bb4: 75 e7 jne 8048b9d //如果ebx!=6则跳转到8048B9D,循环‬5次,逐一比较
/
12345678910
②这段代码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 :
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 //大于2跳转到8048bf4,所以输入参数个数必须大于2
8048bef: e8 a2 05 00 00 call 8049196
8048bf4: 83 7c 24 24 07 cmpl $0x7,0x24(%esp) //第1个数必须小于0x7
8048bf9: 0f 87 fc 00 00 00 ja 8048cfb //大等于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
12345678910111213141516171819202122
①看到这个函数,一开始是一个
isoc99_sscanf@plt函数,这一定是输入一些数据,但是这些数据的格式要看函数前的参数准备。看到一个立即数0x804a2ba,启用gdb调试,在函数phase_3处设置断点,执行指令
(gdb) x/s 0x804a2ba 发现这个字符串的结果是"%d %c %d"br/>1
结合前面的参数,我们可以知道__isoc99_sscanf@plt调用顺序为(0x24,0x2f,0x28),所以说函数将会输入一个整数,一个字符串一个整数。
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 //大于2跳转到8048bf4,所以输入参数个数必须大于2
8048bef: e8 a2 05 00 00 call 8049196
8048bf4: 83 7c 24 24 07 cmpl $0x7,0x24(%esp) //第1个数必须小于0x7
8048bf9: 0f 87 fc 00 00 00 ja 8048cfb //大等于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 //=182,跳转到8048d05
1234567891011121314
②这一段中cmpl $0x7,0x24(%esp),ja 8048cfb 大等于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
8048cfb: e8 96 04 00 00 call 8049196 //爆炸
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 //相等则通过
8048d0b: e8 86 04 00 00 call 8049196
123456
③第二个参数确定后跳转到8048d0,这个时候会将eax的低8位取出01100011,然后与0x2f(%esp)比较,这里存放的恰好是第2个参数,所以第二个参数ascii=0110 0011=c,因此三个参数可以确定为0 c 386,经验证,答案正确。但是由于这里的跳转表有7个所以答案不唯一。
Phase_4:过程递归调用
08048d81 :
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
8048dad: 8b 44 24 18 mov 0x18(%esp),%eax //eax=参数1
8048db1: 85 c0 test %eax,%eax //参数1
8048db3: 78 05 js 8048dba //参数1不能为负数
8048db5: 83 f8 0e cmp $0xe,%eax //比较参数1和0xe=14
8048db8: 7e 05 jle 8048dbf //参数1必须小于等于0xe
8048dba: e8 d7 03 00 00 call 8049196 //爆炸
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(arg1,0,14),返回值要为1
8048ddb: 83 f8 01 cmp $0x1,%eax //将结果与1比较
8048dde: 75 07 jne 8048de7 //如果结果不等于1那么爆炸,func4结果必须为1
8048de0: 83 7c 24 1c 01 cmpl $0x1,0x1c(%esp) //将参数2与1比较
8048de5: 74 05 je 8048dec //如果参数2=1,跳转,说明参数2必须等于1
123456789101112131415161718192021222324252627282930
①我们看到这里调用了__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 :
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 //小于等于参数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(edx,eax,ecx),改变的是ecx,第三个参数
8048d51: 01 c0 add %eax,%eax //将结果2
8048d53: eb 20 jmp 8048d75 //函数结束
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 //如果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 //递归调用函数
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
12345678910111213141516171819202122232425262728293031323334
②查看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;
}
12345678910
③最后我们回到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
123456789101112
以上为1的结果的第一个参数都能作为参数1解决phase4,我选用8 1这一个组合,经过验证答案正确。
Phase_5构造低四位和序列
08048df0 :
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 //读入字符串长度
8048e00: 83 f8 06 cmp $0x6,%eax //长度必须等于6,否则爆炸
8048e03: 74 05 je 8048e0a //等于6,跳转到8048e0a
8048e05: e8 8c 03 00 00 call 8049196
8048e0a: ba 00 00 00 00 mov $0x0,%edx
8048e0f: b8 00 00 00 00 mov $0x0,%eax
1234567891011
①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

8048e2a: 83 fa 2f cmp $0x2f,%edx //比较0x2f和edx的值,要求最后等于0x2f
8048e2d: 74 05 je 8048e34 //相等则成功
8048e2f: e8 62 03 00 00 call 8049196
8048e34: 83 c4 18 add $0x18,%esp
8048e37: 5b pop %ebx
8048e38: c3 ret
123456789101112131415
②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
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 //读取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 //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 //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 //满足a[eip]不等于a[ebx]
8048e7c: e8 15 03 00 00 call 8049196 //
8048e81: 83 c3 01 add $0x1,%ebx
8048e84: 83 fb 05 cmp $0x5,%ebx //
8048e87: 7e e9 jle 8048e72
8048e89: eb cc jmp 8048e57 //for(eip for(ebx))
//得到两个条件就是a[i]<=6,a[i]!=a[j]
12345678910111213141516171819202122232425262728
①一开始我看到汇编代码是真的长,没有头绪,但是注意到这个函数,但是我往下看,可以发现整个过程中,前面一部分对输入的6个数据做了基础的检测,这两段代码cmp$0x5,%eax//x-1后与5比较,jbe8048e68 要求是x必须大于等于6,否则将会发生爆炸。cmp%eax,0xc(%esp,%esi,4) 比较a[eip]和a[ebx],jne 8048e81 满足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
123456
权重+下标+地址
④ 继续往后看,中间有一大堆汇编代码我直接跳过了,因为他们没有出现引爆函数
8049196 的调用,但是,很关键的一点来了,在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 //当(%ebx)>=%edx,交换到ebx
8048f15: e8 7c 02 00 00 call 8049196 //否则爆炸
8048f1a: 8b 5b 08 mov 0x8(%ebx),%ebx //将新的值放入ebx
8048f1d: 83 ee 01 sub $0x1,%esi
8048f20: 75 ea jne 8048f0c

8048f22: 83 c4 44 add $0x44,%esp
8048f25: 5b pop %ebx
8048f26: 5e pop %esi
8048f27: c3 ret
123456789101112131415
⑤所以我断定这个函数大概率按照每个节点的权重对节点进行了排序。因此我对这些节点按照权重递减的顺序进行排序,可以得到新的序列:
0x00000380 0x00000005 0x00000000
0x0000035d 0x00000001 0x0804c154
0x000002eb 0x00000002 0x0804c160
0x000002bb 0x00000003 0x0804c16c
0x000000eb 0x00000004 0x0804c178
0x0000009f 0x00000006 0x00000000
123456
⑥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
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
804937b: 85 c0 test %eax,%eax
804937d: 75 1d jne 804939c //判断输入是否相等
804937f: c7 04 24 78 a3 04 08 movl $0x804a378,(%esp) //0x804a378
8049386: e8 75 f4 ff ff call 8048800
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
8049397: e8 dd fb ff ff call 8048f79
123456789101112131415161718
①并没有发现什么突破性的东西,除了两段字符串DrEvil和一些提示消息,但是我发现想要输出提示消息(也就是进入秘密关卡)必须要满足两个条件,首先是cmpl $0x6,0x804c3cc这个立即数中的数字必须要等于6,其次在比较函数之前有一个参数准备的阶段,第2个参数就是我们上面得到的地址$0x804a4b2中的字符串DrEvil.第一个参数是什么?

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

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

④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
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 //eax-1<=1000,也就是说eax<=1001
8048fa6: e8 eb 01 00 00 call 8049196
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
8048fbb: 83 f8 07 cmp $0x7,%eax //返回值必须要为0x7
8048fbe: 74 05 je 8048fc5
8048fc0: e8 d1 01 00 00 call 8049196
8048fc5: c7 04 24 94 a2 04 08 movl $0x804a294,(%esp)
8048fcc: e8 2f f8 ff ff call 8048800
8048fd1: e8 45 03 00 00 call 804931b
8048fd6: 83 c4 18 add $0x18,%esp
8048fd9: 5b pop %ebx
12345678910111213141516171819
① Func7函数
08048f28 :
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 //arg1=null,返回0xffffffff
8048f38: 8b 1a mov (%edx),%ebx //ebx=(arg1)
8048f3a: 39 cb cmp %ecx,%ebx //比较
(arg1)和arg2
8048f3c: 7e 13 jle 8048f51
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
8048f4d: 01 c0 add %eax,%eax
8048f4f: eb 23 jmp 8048f74
8048f51: b8 00 00 00 00 mov $0x0,%eax //
(arg1)<=arg2
8048f56: 39 cb cmp %ecx,%ebx
8048f58: 74 1a je 8048f74 //*(arg1)8048f5a: 89 4c 24 04 mov %ecx,0x4(%esp)
8048f5e: 8b 42 08 mov 0x8(%edx),%eax