HNU计算机系统实验之bombLab 2022级实战指南
实验题目
“二进制炸弹”是一个程序,以目标代码文件的形式提供给学生。运行时,它会提示用户键入 6 个不同的字符串。如果其中任何一个错误,炸弹就会“爆炸”,打印错误消息并将事件记录在打分服务器上。学生们必须通过反汇编和逆向程序,“拆除”他们自己独特的炸弹,以确定这 6 个字符串应该是什么。该实验教学生理解汇编语言,并迫使他们学习如何使用调试器(debugger)。
实验目的
通过 gdb 以及 objdump 等汇编工具,查看汇编代码,从而破解这个程序的每个 phase 的密码。
实验环境
实验内容及操作步骤
input = read_line(); /* Get input */
phase_1(input); /* Run the phase */
phase_defused(); /* Drat! They figured it out!
* Let me know how they did it. */
printf("Phase 1 defused. How about the next one?\n");
/* The second phase is harder. No one will ever figure out
* how to defuse this... */
input = read_line();
phase_2(input);
phase_defused();
printf("That's number 2. Keep going!\n");
/* I guess this is too easy so far. Some more complex code will
* confuse people. */
input = read_line();
phase_3(input);
phase_defused();
printf("Halfway there!\n");
/* Oh yeah? Well, how good is your math? Try on this saucy problem! */
input = read_line();
phase_4(input);
phase_defused();
printf("So you got that one. Try this one.\n");
/* Round and 'round in memory we go, where we stop, the bomb blows! */
input = read_line();
phase_5(input);
phase_defused();
printf("Good work! On to the next...\n");
/* This phase will never be used, since no one will get past the
* earlier ones. But just in case, make this one extra hard. */
input = read_line();
phase_6(input);
phase_defused();
bomb.c中共有6个函数phase_1~phase_6,每个函数都会从输入中读取input,即要输入的字符串。phase_defused函数用于判断炸弹是否被拆除。猜测phase函数中可能会有像exit(0)的终止方式,当输入的字符串不正确时,就会提前终止phase函数,导致炸弹拆除失败。
在后面你会看到,函数<explode_bomb>会调用exit,使程序提前终止。你要做的就是阅读汇编程序,然后推出一个**不会使控制到达<explode_bomb>**的字符串,这样的字符串就是答案。
为了更好的观察代码,我们需要把程序反汇编的代码放到一个文件中观察,这样的方法,我们同样放在答案输入上,这样可以大大节约时间,在调试的时候不用重复输入答案。
objdump -d bomb > bomb.asm
可以将答案存放到一个txt文件中,然后使用命令./bomb xxx.txt
(xxx为txt文件名),这样就可以保留前面phase的答案,不需要依次输入答案
phase_1
08048b50 <phase_1>:
8048b50: 83 ec 1c sub $0x1c,%esp
8048b53: c7 44 24 04 a4 a1 04 movl $0x804a1a4,0x4(%esp)
8048b5a: 08
8048b5b: 8b 44 24 20 mov 0x20(%esp),%eax
8048b5f: 89 04 24 mov %eax,(%esp)
8048b62: e8 5d 04 00 00 call 8048fc4 <strings_not_equal>
8048b67: 85 c0 test %eax,%eax
8048b69: 74 05 je 8048b70 <phase_1+0x20>
8048b6b: e8 66 05 00 00 call 80490d6 <explode_bomb>
8048b70: 83 c4 1c add $0x1c,%esp
8048b73: c3 ret
8048b6b: e8 66 05 00 00 call 80490d6 <explode_bomb>
这一步中调用的explode_bomb
为引爆炸弹函数,我们的目标就是阻止炸弹被引爆。所以我们要求上面的跳转指令执行成功:
8048b67: 85 c0 test %eax,%eax
8048b69: 74 05 je 8048b70 <phase_1+0x20>
je跳转指令代表相等就跳转,且根据上一行设置ZF位,说明跳转条件为%eax=0。于是继续往上找到使%eax=0的条件:
8048b53: c7 44 24 04 a4 a1 04 movl $0x804a1a4,0x4(%esp)
8048b5a: 08
8048b5b: 8b 44 24 20 mov 0x20(%esp),%eax
8048b5f: 89 04 24 mov %eax,(%esp)
8048b62: e8 5d 04 00 00 call 8048fc4 <strings_not_equal>
可以看到,程序首先准备了两个参数,接着调用了<strings_not_equal>
函数:
08048fc4 <strings_not_equal>:
8048fc4: 83 ec 10 sub $0x10,%esp
8048fc7: 89 5c 24 04 mov %ebx,0x4(%esp)
8048fcb: 89 74 24 08 mov %esi,0x8(%esp)
8048fcf: 89 7c 24 0c mov %edi,0xc(%esp)
8048fd3: 8b 5c 24 14 mov 0x14(%esp),%ebx
8048fd7: 8b 74 24 18 mov 0x18(%esp),%esi
8048fdb: 89 1c 24 mov %ebx,(%esp)
8048fde: e8 c8 ff ff ff call 8048fab <string_length>
8048fe3: 89 c7 mov %eax,%edi
8048fe5: 89 34 24 mov %esi,(%esp)
8048fe8: e8 be ff ff ff call 8048fab <string_length>
8048fed: ba 01 00 00 00 mov $0x1,%edx
8048ff2: 39 c7 cmp %eax,%edi
8048ff4: 75 33 jne 8049029 <strings_not_equal+0x65>
8048ff6: 0f b6 03 movzbl (%ebx),%eax
8048ff9: b2 00 mov $0x0,%dl
8048ffb: 84 c0 test %al,%al
8048ffd: 74 2a je 8049029 <strings_not_equal+0x65>
8048fff: b2 01 mov $0x1,%dl
8049001: 3a 06 cmp (%esi),%al
8049003: 75 24 jne 8049029 <strings_not_equal+0x65>
8049005: b8 00 00 00 00 mov $0x0,%eax
804900a: eb 08 jmp 8049014 <strings_not_equal+0x50>
804900c: 83 c0 01 add $0x1,%eax
804900f: 3a 14 06 cmp (%esi,%eax,1),%dl
8049012: 75 10 jne 8049024 <strings_not_equal+0x60>
8049014: 0f b6 54 03 01 movzbl 0x1(%ebx,%eax,1),%edx
8049019: 84 d2 test %dl,%dl
804901b: 75 ef jne 804900c <strings_not_equal+0x48>
804901d: ba 00 00 00 00 mov $0x0,%edx
8049022: eb 05 jmp 8049029 <strings_not_equal+0x65>
8049024: ba 01 00 00 00 mov $0x1,%edx
8049029: 89 d0 mov %edx,%eax
804902b: 8b 5c 24 04 mov 0x4(%esp),%ebx
804902f: 8b 74 24 08 mov 0x8(%esp),%esi
8049033: 8b 7c 24 0c mov 0xc(%esp),%edi
8049037: 83 c4 10 add $0x10,%esp
804903a: c3 ret
具体每一步的功能不进行赘述了,这个函数首先是检查两个字符串的长度,如果长度不等,两个字符串不同,直接会返回 1。所以两个字符串分别为我们输入的和存在首地址为0x804a1a4
的字符串。
使用GDB查看并验证:
所以There are rumors on the internets.
即为phase_1的答案。
void phase_1(string a,string b)
{
int answer=string_not_equal(a,b);
if(answer) explode_bomb();
}
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 7e 06 00 00 call 804920b <read_six_numbers>
8048b8d: 83 7c 24 18 00 cmpl $0x0,0x18(%esp)
8048b92: 75 07 jne 8048b9b <phase_2+0x27>
8048b94: 83 7c 24 1c 01 cmpl $0x1,0x1c(%esp)
8048b99: 74 05 je 8048ba0 <phase_2+0x2c>
8048b9b: e8 36 05 00 00 call 80490d6 <explode_bomb>
8048ba0: 8d 5c 24 20 lea 0x20(%esp),%ebx
8048ba4: 8d 74 24 30 lea 0x30(%esp),%esi
8048ba8: 8b 43 f8 mov -0x8(%ebx),%eax
8048bab: 03 43 fc add -0x4(%ebx),%eax
8048bae: 39 03 cmp %eax,(%ebx)
8048bb0: 74 05 je 8048bb7 <phase_2+0x43>
8048bb2: e8 1f 05 00 00 call 80490d6 <explode_bomb>
8048bb7: 83 c3 04 add $0x4,%ebx
8048bba: 39 f3 cmp %esi,%ebx
8048bbc: 75 ea jne 8048ba8 <phase_2+0x34>
8048bbe: 83 c4 34 add $0x34,%esp
8048bc1: 5b pop %ebx
8048bc2: 5e pop %esi
8048bc3: c3 ret
开头常规的开辟函数空间后,调用了 <read_six_numbers>
函数,传给它的参数就有我们输入的字符串。首先我们先看一下这个函数:
0804920b <read_six_numbers>:
804920b: 83 ec 2c sub $0x2c,%esp
804920e: 8b 44 24 34 mov 0x34(%esp),%eax
8049212: 8d 50 14 lea 0x14(%eax),%edx
8049215: 89 54 24 1c mov %edx,0x1c(%esp)
8049219: 8d 50 10 lea 0x10(%eax),%edx
804921c: 89 54 24 18 mov %edx,0x18(%esp)
8049220: 8d 50 0c lea 0xc(%eax),%edx
8049223: 89 54 24 14 mov %edx,0x14(%esp)
8049227: 8d 50 08 lea 0x8(%eax),%edx
804922a: 89 54 24 10 mov %edx,0x10(%esp)
804922e: 8d 50 04 lea 0x4(%eax),%edx
8049231: 89 54 24 0c mov %edx,0xc(%esp)
8049235: 89 44 24 08 mov %eax,0x8(%esp)
8049239: c7 44 24 04 b7 a3 04 movl $0x804a3b7,0x4(%esp)
8049240: 08
8049241: 8b 44 24 30 mov 0x30(%esp),%eax
8049245: 89 04 24 mov %eax,(%esp)
8049248: e8 23 f6 ff ff call 8048870 <__isoc99_sscanf@plt>
804924d: 83 f8 05 cmp $0x5,%eax
8049250: 7f 05 jg 8049257 <read_six_numbers+0x4c>
8049252: e8 7f fe ff ff call 80490d6 <explode_bomb>
8049257: 83 c4 2c add $0x2c,%esp
804925a: c3 ret
在第19行,它调用了sscanf进行扫描,所以之前的指令都是设置参数为其做准备,传入sscanf的参数的地址为15行的0x804a3b7
,查看这个地址的内容:
说明传入的参数为连续6个整形数,且每两个数之间有空格。并且之后的跳转指令:
804924d: 83 f8 05 cmp $0x5,%eax
8049250: 7f 05 jg 8049257 <read_six_numbers+0x4c>
8049252: e8 7f fe ff ff call 80490d6 <explode_bomb>
也代表满足要求的个数小于5就引爆炸弹!所以可以推断函数的作用是读取字符串中数字的个数,且大于5才能通过。我们再回过头来看原汇编:
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 7e 06 00 00 call 804920b <read_six_numbers>
将%esp+18
这个地址作为<read_six_numbers>
的参数传入,说明这个地址就是输入字符串的首地址。紧接着:
8048b8d: 83 7c 24 18 00 cmpl $0x0,0x18(%esp)
8048b92: 75 07 jne 8048b9b <phase_2+0x27>
8048b94: 83 7c 24 1c 01 cmpl $0x1,0x1c(%esp)
8048b99: 74 05 je 8048ba0 <phase_2+0x2c>
8048b9b: e8 36 05 00 00 call 80490d6 <explode_bomb>
把0与第一个数比较,若不等于则引爆炸弹,说明第一个数是0;把1与第二个数比较,相等则跳过炸弹,说明第二个数是1。紧接着:
8048ba0: 8d 5c 24 20 lea 0x20(%esp),%ebx
8048ba4: 8d 74 24 30 lea 0x30(%esp),%esi
8048ba8: 8b 43 f8 mov -0x8(%ebx),%eax
8048bab: 03 43 fc add -0x4(%ebx),%eax
8048bae: 39 03 cmp %eax,(%ebx)
8048bb0: 74 05 je 8048bb7 <phase_2+0x43>
8048bb2: e8 1f 05 00 00 call 80490d6 <explode_bomb>
第一行存入%ebx的就是第三个数的地址,三、四行将%ebx-8地址内的数(也就是第一个数),与%ebx-4地址内的数(也就是第二个数)相加,与第三个数比较,如果相等就跳过炸弹,说明第三个数是0+1=1。紧接着:
8048bb7: 83 c3 04 add $0x4,%ebx
8048bba: 39 f3 cmp %esi,%ebx
8048bbc: 75 ea jne 8048ba8 <phase_2+0x34>
8048bbe: 83 c4 34 add $0x34,%esp
继续寻找下一个数,也就是地址加4,与%esp+30
比较,若不等于,就跳转回上一步的操作,也就是说,每次判断是否超出了6位,未超出,则继续循环比较下一位是否等于前两位的和,全部满足才不会爆炸。
所以本位是前两位的和,即结果为:0 1 1 2 3 5。验证:
void phase_2(int a[6])
{
read_six_number(a);
if(a[0]!=0) explode_bomb();
if(a[1]!=1) explode_bomb();
for(int i=3;i<6;i++)
{
if(a[i]!=a[i-1]+a[i-2])
explode_bomb();
}
}
phase_3
08048bc4 <phase_3>:
8048bc4: 83 ec 2c sub $0x2c,%esp
8048bc7: 8d 44 24 1c lea 0x1c(%esp),%eax
8048bcb: 89 44 24 0c mov %eax,0xc(%esp)
8048bcf: 8d 44 24 18 lea 0x18(%esp),%eax
8048bd3: 89 44 24 08 mov %eax,0x8(%esp)
8048bd7: c7 44 24 04 c3 a3 04 movl $0x804a3c3,0x4(%esp)
8048bde: 08
8048bdf: 8b 44 24 30 mov 0x30(%esp),%eax
8048be3: 89 04 24 mov %eax,(%esp)
8048be6: e8 85 fc ff ff call 8048870 <__isoc99_sscanf@plt>
8048beb: 83 f8 01 cmp $0x1,%eax
8048bee: 7f 05 jg 8048bf5 <phase_3+0x31>
8048bf0: e8 e1 04 00 00 call 80490d6 <explode_bomb>
8048bf5: 83 7c 24 18 07 cmpl $0x7,0x18(%esp)
8048bfa: 77 66 ja 8048c62 <phase_3+0x9e>
8048bfc: 8b 44 24 18 mov 0x18(%esp),%eax
8048c00: ff 24 85 00 a2 04 08 jmp *0x804a200(,%eax,4)
8048c07: b8 00 00 00 00 mov $0x0,%eax
8048c0c: eb 05 jmp 8048c13 <phase_3+0x4f>
8048c0e: b8 5d 02 00 00 mov $0x25d,%eax
8048c13: 2d a4 00 00 00 sub $0xa4,%eax
8048c18: eb 05 jmp 8048c1f <phase_3+0x5b>
8048c1a: b8 00 00 00 00 mov $0x0,%eax
8048c1f: 05 36 02 00 00 add $0x236,%eax
8048c24: eb 05 jmp 8048c2b <phase_3+0x67>
8048c26: b8 00 00 00 00 mov $0x0,%eax
8048c2b: 2d 53 02 00 00 sub $0x253,%eax
8048c30: eb 05 jmp 8048c37 <phase_3+0x73>
8048c32: b8 00 00 00 00 mov $0x0,%eax
8048c37: 05 53 02 00 00 add $0x253,%eax
8048c3c: eb 05 jmp 8048c43 <phase_3+0x7f>
8048c3e: b8 00 00 00 00 mov $0x0,%eax
8048c43: 2d 53 02 00 00 sub $0x253,%eax
8048c48: eb 05 jmp 8048c4f <phase_3+0x8b>
8048c4a: b8 00 00 00 00 mov $0x0,%eax
8048c4f: 05 53 02 00 00 add $0x253,%eax
8048c54: eb 05 jmp 8048c5b <phase_3+0x97>
8048c56: b8 00 00 00 00 mov $0x0,%eax
8048c5b: 2d 53 02 00 00 sub $0x253,%eax
8048c60: eb 0a jmp 8048c6c <phase_3+0xa8>
8048c62: e8 6f 04 00 00 call 80490d6 <explode_bomb>
8048c67: b8 00 00 00 00 mov $0x0,%eax
8048c6c: 83 7c 24 18 05 cmpl $0x5,0x18(%esp)
8048c71: 7f 06 jg 8048c79 <phase_3+0xb5>
8048c73: 3b 44 24 1c cmp 0x1c(%esp),%eax
8048c77: 74 05 je 8048c7e <phase_3+0xba>
8048c79: e8 58 04 00 00 call 80490d6 <explode_bomb>
8048c7e: 83 c4 2c add $0x2c,%esp
8048c81: c3 ret
8048bd7: c7 44 24 04 c3 a3 04 movl $0x804a3c3,0x4(%esp)
8048bde: 08
8048bdf: 8b 44 24 30 mov 0x30(%esp),%eax
8048be3: 89 04 24 mov %eax,(%esp)
8048be6: e8 85 fc ff ff call 8048870 <__isoc99_sscanf@plt>
8048beb: 83 f8 01 cmp $0x1,%eax
8048bee: 7f 05 jg 8048bf5 <phase_3+0x31>
8048bf0: e8 e1 04 00 00 call 80490d6 <explode_bomb>
跟phase_2相同,将地址0x804a3c3
传参后调用sscanf函数,返回满足要求的个数,要求为:
函数返回值存放在%eax里,大于1才能跳过炸弹,即输入的是2个整形数。
8048bf5: 83 7c 24 18 07 cmpl $0x7,0x18(%esp)
8048bfa: 77 66 ja 8048c62 <phase_3+0x9e>
8048bfc: 8b 44 24 18 mov 0x18(%esp),%eax
8048c00: ff 24 85 00 a2 04 08 jmp *0x804a200(,%eax,4)
将%esp+18处的值与7比较,我们将其设为x,x必须小于7。第四行的汇编让我们很容易就想到了跳转表,事实证明确实如此:
存放的全是汇编指令的地址!根据GDB结果,每条地址分别是x=0,1,2,3,4,5,6,7,8的地址,先看x=0的情况,即地址为0x8048c0e,我们看它每一步的操作:
8048c0e: b8 5d 02 00 00 mov $0x25d,%eax
8048c13: 2d a4 00 00 00 sub $0xa4,%eax
8048c18: eb 05 jmp 8048c1f <phase_3+0x5b>
8048c1f: 05 36 02 00 00 add $0x236,%eax
8048c24: eb 05 jmp 8048c2b <phase_3+0x67>
8048c2b: 2d 53 02 00 00 sub $0x253,%eax
8048c30: eb 05 jmp 8048c37 <phase_3+0x73>
8048c37: 05 53 02 00 00 add $0x253,%eax
8048c3c: eb 05 jmp 8048c43 <phase_3+0x7f>
8048c43: 2d 53 02 00 00 sub $0x253,%eax
8048c48: eb 05 jmp 8048c4f <phase_3+0x8b>
8048c4f: 05 53 02 00 00 add $0x253,%eax
8048c54: eb 05 jmp 8048c5b <phase_3+0x97>
8048c5b: 2d 53 02 00 00 sub $0x253,%eax
8048c60: eb 0a jmp 8048c6c <phase_3+0xa8>
8048c6c: 83 7c 24 18 05 cmpl $0x5,0x18(%esp)
8048c71: 7f 06 jg 8048c79 <phase_3+0xb5>
8048c73: 3b 44 24 1c cmp 0x1c(%esp),%eax
8048c77: 74 05 je 8048c7e <phase_3+0xba>
8048c79: e8 58 04 00 00 call 80490d6 <explode_bomb>
8048c7e: 83 c4 2c add $0x2c,%esp
8048c81: c3 ret
首先经过一系列的计算,最后判断x是否小于5,说明有6种结果:
x=0:0x25d-0xa4+0x236-0x253+0x253-0x253+0x253-0x253=0x19c=412(10)
剩下的经过计算有:1 -193,2 -29,3 -595,4 0,5 -595。
void phase_2(int x,int number)
{
switch (x)
{
case(0): if(number!=412) explode_bomb();
case(1): if(number!=-193) explode_bomb();
case(2): if(number!=-29) explode_bomb();
case(3): if(number!=-595) explode_bomb();
case(4): if(number!=0) explode_bomb();
case(5): if(number!=-595) explode_bomb();
}
}
phase_4
08048cdf <phase_4>:
8048cdf: 83 ec 2c sub $0x2c,%esp
8048ce2: 8d 44 24 18 lea 0x18(%esp),%eax
8048ce6: 89 44 24 0c mov %eax,0xc(%esp)
8048cea: 8d 44 24 1c lea 0x1c(%esp),%eax
8048cee: 89 44 24 08 mov %eax,0x8(%esp)
8048cf2: c7 44 24 04 c3 a3 04 movl $0x804a3c3,0x4(%esp)
8048cf9: 08
8048cfa: 8b 44 24 30 mov 0x30(%esp),%eax
8048cfe: 89 04 24 mov %eax,(%esp)
8048d01: e8 6a fb ff ff call 8048870 <__isoc99_sscanf@plt>
8048d06: 83 f8 02 cmp $0x2,%eax
8048d09: 75 0e jne 8048d19 <phase_4+0x3a>
8048d0b: 8b 44 24 18 mov 0x18(%esp),%eax
8048d0f: 83 f8 01 cmp $0x1,%eax
8048d12: 7e 05 jle 8048d19 <phase_4+0x3a>
8048d14: 83 f8 04 cmp $0x4,%eax
8048d17: 7e 05 jle 8048d1e <phase_4+0x3f>
8048d19: e8 b8 03 00 00 call 80490d6 <explode_bomb>
8048d1e: 8b 44 24 18 mov 0x18(%esp),%eax
8048d22: 89 44 24 04 mov %eax,0x4(%esp)
8048d26: c7 04 24 05 00 00 00 movl $0x5,(%esp)
8048d2d: e8 50 ff ff ff call 8048c82 <func4>
8048d32: 3b 44 24 1c cmp 0x1c(%esp),%eax
8048d36: 74 05 je 8048d3d <phase_4+0x5e>
8048d38: e8 99 03 00 00 call 80490d6 <explode_bomb>
8048d3d: 83 c4 2c add $0x2c,%esp
8048d40: c3 ret
8048d01: e8 6a fb ff ff call 8048870 <__isoc99_sscanf@plt>
8048d06: 83 f8 02 cmp $0x2,%eax
8048d09: 75 0e jne 8048d19 <phase_4+0x3a>
我们又见到了熟悉的sscanf函数,查看地址,并且返回值不等于2就爆炸,所以基本可以断定输入的是两个数。
8048ce2: 8d 44 24 18 lea 0x18(%esp),%eax
8048ce6: 89 44 24 0c mov %eax,0xc(%esp)
8048cea: 8d 44 24 1c lea 0x1c(%esp),%eax
8048cee: 89 44 24 08 mov %eax,0x8(%esp)
这里我们可以设存入%esp+18的数为x,存入%esp+1c的数为y。
8048d0b: 8b 44 24 18 mov 0x18(%esp),%eax
8048d0f: 83 f8 01 cmp $0x1,%eax
8048d12: 7e 05 jle 8048d19 <phase_4+0x3a>
8048d14: 83 f8 04 cmp $0x4,%eax
8048d17: 7e 05 jle 8048d1e <phase_4+0x3f>
8048d19: e8 b8 03 00 00 call 80490d6 <explode_bomb>
这里将x与1比较,大于1则不爆炸;又与4比较,小于等于4则不爆炸,所以1<x<=4。
8048d1e: 8b 44 24 18 mov 0x18(%esp),%eax
8048d22: 89 44 24 04 mov %eax,0x4(%esp)
8048d26: c7 04 24 05 00 00 00 movl $0x5,(%esp)
8048d2d: e8 50 ff ff ff call 8048c82 <func4>
这里将x和5作为参数传入到了func4函数中,我们这就来看看这个函数:
08048c82 <func4>:
8048c82: 83 ec 1c sub $0x1c,%esp
8048c85: 89 5c 24 10 mov %ebx,0x10(%esp)
8048c89: 89 74 24 14 mov %esi,0x14(%esp)
8048c8d: 89 7c 24 18 mov %edi,0x18(%esp)
8048c91: 8b 74 24 20 mov 0x20(%esp),%esi
8048c95: 8b 5c 24 24 mov 0x24(%esp),%ebx
8048c99: 85 f6 test %esi,%esi
8048c9b: 7e 2b jle 8048cc8 <func4+0x46>
8048c9d: 83 fe 01 cmp $0x1,%esi
8048ca0: 74 2b je 8048ccd <func4+0x4b>
8048ca2: 89 5c 24 04 mov %ebx,0x4(%esp)
8048ca6: 8d 46 ff lea -0x1(%esi),%eax
8048ca9: 89 04 24 mov %eax,(%esp)
8048cac: e8 d1 ff ff ff call 8048c82 <func4>
8048cb1: 8d 3c 18 lea (%eax,%ebx,1),%edi
8048cb4: 89 5c 24 04 mov %ebx,0x4(%esp)
8048cb8: 83 ee 02 sub $0x2,%esi
8048cbb: 89 34 24 mov %esi,(%esp)
8048cbe: e8 bf ff ff ff call 8048c82 <func4>
8048cc3: 8d 1c 07 lea (%edi,%eax,1),%ebx
8048cc6: eb 05 jmp 8048ccd <func4+0x4b>
8048cc8: bb 00 00 00 00 mov $0x0,%ebx
8048ccd: 89 d8 mov %ebx,%eax
8048ccf: 8b 5c 24 10 mov 0x10(%esp),%ebx
8048cd3: 8b 74 24 14 mov 0x14(%esp),%esi
8048cd7: 8b 7c 24 18 mov 0x18(%esp),%edi
8048cdb: 83 c4 1c add $0x1c,%esp
8048cde: c3 ret
8048c82: 83 ec 1c sub $0x1c,%esp
8048c85: 89 5c 24 10 mov %ebx,0x10(%esp)
8048c89: 89 74 24 14 mov %esi,0x14(%esp)
8048c8d: 89 7c 24 18 mov %edi,0x18(%esp)
8048c91: 8b 74 24 20 mov 0x20(%esp),%esi
8048c95: 8b 5c 24 24 mov 0x24(%esp),%ebx
这里保存了旧的三个寄存器,防止被覆盖,并将x放在%esi中,将y放在%ebx中。
8048c99: 85 f6 test %esi,%esi
8048c9b: 7e 2b jle 8048cc8 <func4+0x46>
8048cc8: bb 00 00 00 00 mov $0x0,%ebx
8048ccd: 89 d8 mov %ebx,%eax
如果x<=0,则返回0。
8048c9d: 83 fe 01 cmp $0x1,%esi
8048ca0: 74 2b je 8048ccd <func4+0x4b>
8048ccd: 89 d8 mov %ebx,%eax
如果x=1,则返回y。
8048ca2: 89 5c 24 04 mov %ebx,0x4(%esp)
8048ca6: 8d 46 ff lea -0x1(%esi),%eax
8048ca9: 89 04 24 mov %eax,(%esp)
8048cac: e8 d1 ff ff ff call 8048c82 <func4>
8048cb1: 8d 3c 18 lea (%eax,%ebx,1),%edi
否则,将x-1与y作为参数,递归调用func4函数。将%eax+%ebx的值存入%edi中。
8048cb4: 89 5c 24 04 mov %ebx,0x4(%esp)
8048cb8: 83 ee 02 sub $0x2,%esi
8048cbb: 89 34 24 mov %esi,(%esp)
8048cbe: e8 bf ff ff ff call 8048c82 <func4>
8048cc3: 8d 1c 07 lea (%edi,%eax,1),%ebx
接着将x-2与y作为参数,递归调用func4函数。将%eax+%edi的值存入%ebx中。函数返回%ebx中的值。所以这个函数的功能可以这样表示:
8048d32: 3b 44 24 1c cmp 0x1c(%esp),%eax
8048d36: 74 05 je 8048d3d <phase_4+0x5e>
8048d38: e8 99 03 00 00 call 80490d6 <explode_bomb>
返回值与y比较,若相等则通过。
我们根据函数对函数的分析,求得以上结果。所以我们最后比较的是y=12x
。但不要忘记了上面的1<x<=4。所以结果有:24 2,36 3,48 4。
void phase_4(int y,int x)
{
if(x<=1||x>4) explode_bomb();
int answer=fun4(5,x);
if(answer!=y) explode_bomb();
}
int fun4(int n,int x)
{
if(n==0) return 0;
if(n==1) return 1;
else retrun fun4(n-1,x)+fun4(n-2,x)+x;
}
phase_5
08048d41 <phase_5>:
8048d41: 53 push %ebx
8048d42: 83 ec 18 sub $0x18,%esp
8048d45: 8b 5c 24 20 mov 0x20(%esp),%ebx
8048d49: 89 1c 24 mov %ebx,(%esp)
8048d4c: e8 5a 02 00 00 call 8048fab <string_length>
8048d51: 83 f8 06 cmp $0x6,%eax
8048d54: 74 05 je 8048d5b <phase_5+0x1a>
8048d56: e8 7b 03 00 00 call 80490d6 <explode_bomb>
8048d5b: ba 00 00 00 00 mov $0x0,%edx
8048d60: b8 00 00 00 00 mov $0x0,%eax
8048d65: 0f be 0c 03 movsbl (%ebx,%eax,1),%ecx
8048d69: 83 e1 0f and $0xf,%ecx
8048d6c: 03 14 8d 20 a2 04 08 add 0x804a220(,%ecx,4),%edx
8048d73: 83 c0 01 add $0x1,%eax
8048d76: 83 f8 06 cmp $0x6,%eax
8048d79: 75 ea jne 8048d65 <phase_5+0x24>
8048d7b: 83 fa 2e cmp $0x2e,%edx
8048d7e: 74 05 je 8048d85 <phase_5+0x44>
8048d80: e8 51 03 00 00 call 80490d6 <explode_bomb>
8048d85: 83 c4 18 add $0x18,%esp
8048d88: 5b pop %ebx
8048d89: c3 ret
8048d41: 53 push %ebx
8048d42: 83 ec 18 sub $0x18,%esp
8048d45: 8b 5c 24 20 mov 0x20(%esp),%ebx
8048d49: 89 1c 24 mov %ebx,(%esp)
8048d4c: e8 5a 02 00 00 call 8048fab <string_length>
8048d51: 83 f8 06 cmp $0x6,%eax
8048d54: 74 05 je 8048d5b <phase_5+0x1a>
8048d56: e8 7b 03 00 00 call 80490d6 <explode_bomb>
首先是常规的开辟空间,然后读取一个字符串,其长度等于6才能防止爆炸,说明输入的字符串长度为6。
8048d5b: ba 00 00 00 00 mov $0x0,%edx
8048d60: b8 00 00 00 00 mov $0x0,%eax
8048d65: 0f be 0c 03 movsbl (%ebx,%eax,1),%ecx
8048d69: 83 e1 0f and $0xf,%ecx
8048d6c: 03 14 8d 20 a2 04 08 add 0x804a220(,%ecx,4),%edx
8048d73: 83 c0 01 add $0x1,%eax
8048d76: 83 f8 06 cmp $0x6,%eax
8048d79: 75 ea jne 8048d65 <phase_5+0x24>
这里首先将%edx赋0,%eax(用来计数)也赋为0;接着用movsbl语句将字符串中的字母(从第一个开始)传入%ecx,接着做与运算,只留下了字母ASCII码的低四位,然后将这低四位(假设值为x)乘于4加到0x804a220上作为地址(也就是0x804a220偏移4*x个字节);接着将这个地址中的内容累加到%edx中,同时将%eax加1;此时如果%eax!=6则说明字符串中的六个字符还未处理完,就要回到movsbl语句进行下一个字符的同样的处理。
8048d7b: 83 fa 2e cmp $0x2e,%edx
8048d7e: 74 05 je 8048d85 <phase_5+0x44>
8048d80: e8 51 03 00 00 call 80490d6 <explode_bomb>
8048d85: 83 c4 18 add $0x18,%esp
8048d88: 5b pop %ebx
8048d89: c3 ret
最后的累加值应该等于0x2e,即46。
我们先看看0x804a220处连续16个整型空间的值(因为x为4位,最大为1111,即15):
所以我们可以得出能用到的ASCII码:0x02, 0x0a, 0x06, 0x01, 0x0c, 0x10, 0x09, 0x03, 0x04, 0x07, 0x0e, 0x05, 0x0b, 0x08, 0x0f, 0x0d。
它们的对应关系为:
02 | 0a | 06 | 01 | 0c | 10 | 09 | 03 | 04 | 07 | 0e | 05 | 0b | 08 | 0f | 0d |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2 | 10 | 6 | 1 | 12 | 16 | 9 | 3 | 4 | 7 | 14 | 5 | 11 | 8 | 15 | 13 |
我们的目标就是凑出相加等于46的情况,一种情况是两个2,一个6,三个12。它们对应的ASCII码的第四位分别为:0x0, 0x2, 0x4。根据ASCII表,可以找到字母:p(0x70), b(0x62), d(ox64)。
所以答案可以是:ppbddd。当然,答案不唯一。
void phase_5(string a,string b)
{
char ans[7];
for(int i=0;i<6;i++)
{
ans[i]=b[a[i]&0xf];
}
ans[6]='\0';
}
phase_6
08048d8a <phase_6>:
8048d8a: 56 push %esi
8048d8b: 53 push %ebx
8048d8c: 83 ec 44 sub $0x44,%esp
8048d8f: 8d 44 24 10 lea 0x10(%esp),%eax
8048d93: 89 44 24 04 mov %eax,0x4(%esp)
8048d97: 8b 44 24 50 mov 0x50(%esp),%eax
8048d9b: 89 04 24 mov %eax,(%esp)
8048d9e: e8 68 04 00 00 call 804920b <read_six_numbers>
8048da3: be 00 00 00 00 mov $0x0,%esi
8048da8: 8b 44 b4 10 mov 0x10(%esp,%esi,4),%eax
8048dac: 83 e8 01 sub $0x1,%eax
8048daf: 83 f8 05 cmp $0x5,%eax
8048db2: 76 05 jbe 8048db9 <phase_6+0x2f>
8048db4: e8 1d 03 00 00 call 80490d6 <explode_bomb>
8048db9: 83 c6 01 add $0x1,%esi
8048dbc: 83 fe 06 cmp $0x6,%esi
8048dbf: 74 33 je 8048df4 <phase_6+0x6a>
8048dc1: 89 f3 mov %esi,%ebx
8048dc3: 8b 44 9c 10 mov 0x10(%esp,%ebx,4),%eax
8048dc7: 39 44 b4 0c cmp %eax,0xc(%esp,%esi,4)
8048dcb: 75 05 jne 8048dd2 <phase_6+0x48>
8048dcd: e8 04 03 00 00 call 80490d6 <explode_bomb>
8048dd2: 83 c3 01 add $0x1,%ebx
8048dd5: 83 fb 05 cmp $0x5,%ebx
8048dd8: 7e e9 jle 8048dc3 <phase_6+0x39>
8048dda: eb cc jmp 8048da8 <phase_6+0x1e>
8048ddc: 8b 52 08 mov 0x8(%edx),%edx
8048ddf: 83 c0 01 add $0x1,%eax
8048de2: 39 c8 cmp %ecx,%eax
8048de4: 75 f6 jne 8048ddc <phase_6+0x52>
8048de6: 89 54 b4 28 mov %edx,0x28(%esp,%esi,4)
8048dea: 83 c3 01 add $0x1,%ebx
8048ded: 83 fb 06 cmp $0x6,%ebx
8048df0: 75 07 jne 8048df9 <phase_6+0x6f>
8048df2: eb 1c jmp 8048e10 <phase_6+0x86>
8048df4: bb 00 00 00 00 mov $0x0,%ebx
8048df9: 89 de mov %ebx,%esi
8048dfb: 8b 4c 9c 10 mov 0x10(%esp,%ebx,4),%ecx
8048dff: b8 01 00 00 00 mov $0x1,%eax
8048e04: ba 3c c1 04 08 mov $0x804c13c,%edx
8048e09: 83 f9 01 cmp $0x1,%ecx
8048e0c: 7f ce jg 8048ddc <phase_6+0x52>
8048e0e: eb d6 jmp 8048de6 <phase_6+0x5c>
8048e10: 8b 5c 24 28 mov 0x28(%esp),%ebx
8048e14: 8b 44 24 2c mov 0x2c(%esp),%eax
8048e18: 89 43 08 mov %eax,0x8(%ebx)
8048e1b: 8b 54 24 30 mov 0x30(%esp),%edx
8048e1f: 89 50 08 mov %edx,0x8(%eax)
8048e22: 8b 44 24 34 mov 0x34(%esp),%eax
8048e26: 89 42 08 mov %eax,0x8(%edx)
8048e29: 8b 54 24 38 mov 0x38(%esp),%edx
8048e2d: 89 50 08 mov %edx,0x8(%eax)
8048e30: 8b 44 24 3c mov 0x3c(%esp),%eax
8048e34: 89 42 08 mov %eax,0x8(%edx)
8048e37: c7 40 08 00 00 00 00 movl $0x0,0x8(%eax)
8048e3e: be 05 00 00 00 mov $0x5,%esi
8048e43: 8b 43 08 mov 0x8(%ebx),%eax
8048e46: 8b 10 mov (%eax),%edx
8048e48: 39 13 cmp %edx,(%ebx)
8048e4a: 7d 05 jge 8048e51 <phase_6+0xc7>
8048e4c: e8 85 02 00 00 call 80490d6 <explode_bomb>
8048e51: 8b 5b 08 mov 0x8(%ebx),%ebx
8048e54: 83 ee 01 sub $0x1,%esi
8048e57: 75 ea jne 8048e43 <phase_6+0xb9>
8048e59: 83 c4 44 add $0x44,%esp
8048e5c: 5b pop %ebx
8048e5d: 5e pop %esi
8048e5e: c3 ret
8048d8a: 56 push %esi
8048d8b: 53 push %ebx
8048d8c: 83 ec 44 sub $0x44,%esp
8048d8f: 8d 44 24 10 lea 0x10(%esp),%eax
8048d93: 89 44 24 04 mov %eax,0x4(%esp)
8048d97: 8b 44 24 50 mov 0x50(%esp),%eax
8048d9b: 89 04 24 mov %eax,(%esp)
8048d9e: e8 68 04 00 00 call 804920b <read_six_numbers>
这一段先读取了一个字符串,判断是否输入了6个数。
8048da3: be 00 00 00 00 mov $0x0,%esi
8048da8: 8b 44 b4 10 mov 0x10(%esp,%esi,4),%eax
8048dac: 83 e8 01 sub $0x1,%eax
8048daf: 83 f8 05 cmp $0x5,%eax
8048db2: 76 05 jbe 8048db9 <phase_6+0x2f>
8048db4: e8 1d 03 00 00 call 80490d6 <explode_bomb>
这里将字符串的元素取出,减一后与5比较,要求小于等于5,即每个元素(设为ai)满足1<=ai<=6。
8048db9: 83 c6 01 add $0x1,%esi
8048dbc: 83 fe 06 cmp $0x6,%esi
8048dbf: 74 33 je 8048df4 <phase_6+0x6a>
这里将%esi与6比较,保证6个元素,防止越界。
8048dc1: 89 f3 mov %esi,%ebx
8048dc3: 8b 44 9c 10 mov 0x10(%esp,%ebx,4),%eax
8048dc7: 39 44 b4 0c cmp %eax,0xc(%esp,%esi,4)
8048dcb: 75 05 jne 8048dd2 <phase_6+0x48>
8048dcd: e8 04 03 00 00 call 80490d6 <explode_bomb>
8048dd2: 83 c3 01 add $0x1,%ebx
8048dd5: 83 fb 05 cmp $0x5,%ebx
8048dd8: 7e e9 jle 8048dc3 <phase_6+0x39>
8048dda: eb cc jmp 8048da8 <phase_6+0x1e>
这里将4%esi+%esp+10的元素与4%esi+%esp+c的元素比较,即字符串中相邻两个数比较,要求不相等。当%ebx<=5时,循环。
以上的代码作用是输入6个数,并且逐步循环访问每一个数,比较判断是否∈[1,6],然后又开始不断地循环比较6个数中,每两个数都不能相等,一旦不满足这两个条件,则会爆炸。
下面一段代码也是一个循环,这里给出每行的解释:
8048ddc: 8b 52 08 mov 0x8(%edx),%edx //edx+8->edx
8048ddf: 83 c0 01 add $0x1,%eax //eax+1
8048de2: 39 c8 cmp %ecx,%eax //eax比较ecx
8048de4: 75 f6 jne 8048ddc <phase_6+0x52> //不相等就跳转回去
8048de6: 89 54 b4 28 mov %edx,0x28(%esp,%esi,4) //把当前编号对应的地址存到新的地方
8048dea: 83 c3 01 add $0x1,%ebx //ebx+1
8048ded: 83 fb 06 cmp $0x6,%ebx //与6比较,表示找完6个数
8048df0: 75 07 jne 8048df9 <phase_6+0x6f> //小于6就跳转
8048df2: eb 1c jmp 8048e10 <phase_6+0x86> //循环结束跳转
8048df4: bb 00 00 00 00 mov $0x0,%ebx //0->ebx
8048df9: 89 de mov %ebx,%esi //ebx->esi
8048dfb: 8b 4c 9c 10 mov 0x10(%esp,%ebx,4),%ecx //4*ebx+esp+10处的值存到ecx
8048dff: b8 01 00 00 00 mov $0x1,%eax //1->eax
8048e04: ba 3c c1 04 08 mov $0x804c13c,%edx //地址->edx
8048e09: 83 f9 01 cmp $0x1,%ecx //比较ecx与1
8048e0c: 7f ce jg 8048ddc <phase_6+0x52> //ecx>1跳转回开头
8048e0e: eb d6 jmp 8048de6 <phase_6+0x5c> //跳转
我们在理解的同时看到了这个地址0x804c13c
,查看可得:
看到这个我们可以断定这里存储的是链表,因为每一组都有一个标号,从1到6,那么自然标号前的数是权重,后面的数是下一结点的地址,并且每个地址之间相差12也印证了我们的猜想。所以这一段的代码作用就是把每一个结点的地址存到一个新的地方。
8048e10: 8b 5c 24 28 mov 0x28(%esp),%ebx
8048e14: 8b 44 24 2c mov 0x2c(%esp),%eax
8048e18: 89 43 08 mov %eax,0x8(%ebx)
8048e1b: 8b 54 24 30 mov 0x30(%esp),%edx
8048e1f: 89 50 08 mov %edx,0x8(%eax)
8048e22: 8b 44 24 34 mov 0x34(%esp),%eax
8048e26: 89 42 08 mov %eax,0x8(%edx)
8048e29: 8b 54 24 38 mov 0x38(%esp),%edx
8048e2d: 89 50 08 mov %edx,0x8(%eax)
8048e30: 8b 44 24 3c mov 0x3c(%esp),%eax
8048e34: 89 42 08 mov %eax,0x8(%edx)
8048e37: c7 40 08 00 00 00 00 movl $0x0,0x8(%eax)
这一段代码比较简单,即对链表进行排序,最后排序的结果为:p1->p2->p3->p4->p5->p6。
8048e3e: be 05 00 00 00 mov $0x5,%esi
8048e43: 8b 43 08 mov 0x8(%ebx),%eax
8048e46: 8b 10 mov (%eax),%edx
8048e48: 39 13 cmp %edx,(%ebx)
8048e4a: 7d 05 jge 8048e51 <phase_6+0xc7>
8048e4c: e8 85 02 00 00 call 80490d6 <explode_bomb>
这里将第一个节点的值与第二个节点的值比较,要求第一个节点的值大于等于第二个节点的值。
8048e51: 8b 5b 08 mov 0x8(%ebx),%ebx
8048e54: 83 ee 01 sub $0x1,%esi
8048e57: 75 ea jne 8048e43 <phase_6+0xb9>
8048e59: 83 c4 44 add $0x44,%esp
8048e5c: 5b pop %ebx
8048e5d: 5e pop %esi
8048e5e: c3 ret
这就是上一段代码的循环条件,比较每个节点元素的大小,要求链表是递减的。
所以根据我们之前查到的链表,根据权重排序序列号的:1 3 5 6 2 4。
int fun7(Node *addr,int x)
{
if(addr==0) return -1;
int v=addr->value;
if(v==x) return 0;
else if(v>x) retrun 2*fun7(addr->left,x);
else retrun 1+2*fun7(addr->right,x)
}
至此,我们拆除了6个炸弹!
但是你以为这就结束了吗?
secret_phase
怎么发现这个的呢?在看代码的时候,最后有一句:
/* Wow, they got it! But isn't something... missing? Perhaps
* something they overlooked? Mua ha ha ha ha! */
这就是提示啊!于是翻看汇编,找到了这个代码!
08048eb0 <secret_phase>:
8048eb0: 53 push %ebx
8048eb1: 83 ec 18 sub $0x18,%esp
8048eb4: e8 44 02 00 00 call 80490fd <read_line>
8048eb9: c7 44 24 08 0a 00 00 movl $0xa,0x8(%esp)
8048ec0: 00
8048ec1: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
8048ec8: 00
8048ec9: 89 04 24 mov %eax,(%esp)
8048ecc: e8 0f fa ff ff call 80488e0 <strtol@plt>
8048ed1: 89 c3 mov %eax,%ebx
8048ed3: 8d 40 ff lea -0x1(%eax),%eax
8048ed6: 3d e8 03 00 00 cmp $0x3e8,%eax
8048edb: 76 05 jbe 8048ee2 <secret_phase+0x32>
8048edd: e8 f4 01 00 00 call 80490d6 <explode_bomb>
8048ee2: 89 5c 24 04 mov %ebx,0x4(%esp)
8048ee6: c7 04 24 88 c0 04 08 movl $0x804c088,(%esp)
8048eed: e8 6d ff ff ff call 8048e5f <fun7>
8048ef2: 83 f8 02 cmp $0x2,%eax
8048ef5: 74 05 je 8048efc <secret_phase+0x4c>
8048ef7: e8 da 01 00 00 call 80490d6 <explode_bomb>
8048efc: c7 04 24 c8 a1 04 08 movl $0x804a1c8,(%esp)
8048f03: e8 f8 f8 ff ff call 8048800 <puts@plt>
8048f08: e8 4e 03 00 00 call 804925b <phase_defused>
8048f0d: 83 c4 18 add $0x18,%esp
8048f10: 5b pop %ebx
8048f11: c3 ret
但这个好像并不能发现什么,所以我们又找到了这个:
0804925b <phase_defused>:
804925b: 81 ec 8c 00 00 00 sub $0x8c,%esp
8049261: 65 a1 14 00 00 00 mov %gs:0x14,%eax
8049267: 89 44 24 7c mov %eax,0x7c(%esp)
804926b: 31 c0 xor %eax,%eax
804926d: 83 3d cc c3 04 08 06 cmpl $0x6,0x804c3cc
8049274: 75 72 jne 80492e8 <phase_defused+0x8d>
8049276: 8d 44 24 2c lea 0x2c(%esp),%eax
804927a: 89 44 24 10 mov %eax,0x10(%esp)
804927e: 8d 44 24 28 lea 0x28(%esp),%eax
8049282: 89 44 24 0c mov %eax,0xc(%esp)
8049286: 8d 44 24 24 lea 0x24(%esp),%eax
804928a: 89 44 24 08 mov %eax,0x8(%esp)
804928e: c7 44 24 04 c9 a3 04 movl $0x804a3c9,0x4(%esp)
8049295: 08
8049296: c7 04 24 d0 c4 04 08 movl $0x804c4d0,(%esp)
804929d: e8 ce f5 ff ff call 8048870 <__isoc99_sscanf@plt>
80492a2: 83 f8 03 cmp $0x3,%eax
80492a5: 75 35 jne 80492dc <phase_defused+0x81>
80492a7: c7 44 24 04 d2 a3 04 movl $0x804a3d2,0x4(%esp)
80492ae: 08
80492af: 8d 44 24 2c lea 0x2c(%esp),%eax
80492b3: 89 04 24 mov %eax,(%esp)
80492b6: e8 09 fd ff ff call 8048fc4 <strings_not_equal>
80492bb: 85 c0 test %eax,%eax
80492bd: 75 1d jne 80492dc <phase_defused+0x81>
80492bf: c7 04 24 98 a2 04 08 movl $0x804a298,(%esp)
80492c6: e8 35 f5 ff ff call 8048800 <puts@plt>
80492cb: c7 04 24 c0 a2 04 08 movl $0x804a2c0,(%esp)
80492d2: e8 29 f5 ff ff call 8048800 <puts@plt>
80492d7: e8 d4 fb ff ff call 8048eb0 <secret_phase>
80492dc: c7 04 24 f8 a2 04 08 movl $0x804a2f8,(%esp)
80492e3: e8 18 f5 ff ff call 8048800 <puts@plt>
80492e8: 8b 44 24 7c mov 0x7c(%esp),%eax
80492ec: 65 33 05 14 00 00 00 xor %gs:0x14,%eax
80492f3: 74 05 je 80492fa <phase_defused+0x9f>
80492f5: e8 d6 f4 ff ff call 80487d0 <__stack_chk_fail@plt>
80492fa: 81 c4 8c 00 00 00 add $0x8c,%esp
8049300: c3 ret
804926d: 83 3d cc c3 04 08 06 cmpl $0x6,0x804c3cc
8049274: 75 72 jne 80492e8 <phase_defused+0x8d>
这两句代表只有成功破解6个炸弹才能触发隐藏炸弹,0x804c3cc
这个地址里存放的是对我输入字符串的计数。
804928e: c7 44 24 04 c9 a3 04 movl $0x804a3c9,0x4(%esp)
8049295: 08
8049296: c7 04 24 d0 c4 04 08 movl $0x804c4d0,(%esp)
804929d: e8 ce f5 ff ff call 8048870 <__isoc99_sscanf@plt>
80492a2: 83 f8 03 cmp $0x3,%eax
80492a5: 75 35 jne 80492dc <phase_defused+0x81>
这里出现了两份地址,并作为参数调用sscanf函数,我们分别查看:
这里居然是空的!
说明开启条件是两个整数加一个字符串,但还不知道放在哪。再将返回结果与3比较,判断是否输入三个。
80492a7: c7 44 24 04 d2 a3 04 movl $0x804a3d2,0x4(%esp)
80492ae: 08
80492af: 8d 44 24 2c lea 0x2c(%esp),%eax
80492b3: 89 04 24 mov %eax,(%esp)
80492b6: e8 09 fd ff ff call 8048fc4 <strings_not_equal>
80492bb: 85 c0 test %eax,%eax
80492bd: 75 1d jne 80492dc <phase_defused+0x81>
接着传参,测试输入的字符串与要求的是否相等,若不相等就跳过隐藏炸弹:
表明上面两个数后面的字符串是DrEvil
。但现在还不知道这个字符串放在哪,我们尝试设置断点看一下:
这个就是我第四个炸弹的答案啊!我们尝试写入:
成功触发!接下来就看隐藏炸弹的汇编了。
8048eb0: 53 push %ebx
8048eb1: 83 ec 18 sub $0x18,%esp
8048eb4: e8 44 02 00 00 call 80490fd <read_line>
8048eb9: c7 44 24 08 0a 00 00 movl $0xa,0x8(%esp)
8048ec0: 00
8048ec1: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
8048ec8: 00
8048ec9: 89 04 24 mov %eax,(%esp)
8048ecc: e8 0f fa ff ff call 80488e0 <strtol@plt>
8048ed1: 89 c3 mov %eax,%ebx
8048ed3: 8d 40 ff lea -0x1(%eax),%eax
8048ed6: 3d e8 03 00 00 cmp $0x3e8,%eax
8048edb: 76 05 jbe 8048ee2 <secret_phase+0x32>
8048edd: e8 f4 01 00 00 call 80490d6 <explode_bomb>
首先它读取了输入的字符串,并调用strtol函数,将其转化为长整数(设为s)。且这个长整数-1的大小得<=0x3e8。
8048ee2: 89 5c 24 04 mov %ebx,0x4(%esp)
8048ee6: c7 04 24 88 c0 04 08 movl $0x804c088,(%esp)
8048eed: e8 6d ff ff ff call 8048e5f <fun7>
8048ef2: 83 f8 02 cmp $0x2,%eax
8048ef5: 74 05 je 8048efc <secret_phase+0x4c>
8048ef7: e8 da 01 00 00 call 80490d6 <explode_bomb>
然后就是调用函数fun7,返回值必须等于2。所以我们只要搞懂fun7就可以了。
08048e5f <fun7>:
8048e5f: 53 push %ebx
8048e60: 83 ec 18 sub $0x18,%esp
8048e63: 8b 54 24 20 mov 0x20(%esp),%edx
8048e67: 8b 4c 24 24 mov 0x24(%esp),%ecx
8048e6b: 85 d2 test %edx,%edx
8048e6d: 74 37 je 8048ea6 <fun7+0x47>
8048e6f: 8b 1a mov (%edx),%ebx
8048e71: 39 cb cmp %ecx,%ebx
8048e73: 7e 13 jle 8048e88 <fun7+0x29>
8048e75: 89 4c 24 04 mov %ecx,0x4(%esp)
8048e79: 8b 42 04 mov 0x4(%edx),%eax
8048e7c: 89 04 24 mov %eax,(%esp)
8048e7f: e8 db ff ff ff call 8048e5f <fun7>
8048e84: 01 c0 add %eax,%eax
8048e86: eb 23 jmp 8048eab <fun7+0x4c>
8048e88: b8 00 00 00 00 mov $0x0,%eax
8048e8d: 39 cb cmp %ecx,%ebx
8048e8f: 74 1a je 8048eab <fun7+0x4c>
8048e91: 89 4c 24 04 mov %ecx,0x4(%esp)
8048e95: 8b 42 08 mov 0x8(%edx),%eax
8048e98: 89 04 24 mov %eax,(%esp)
8048e9b: e8 bf ff ff ff call 8048e5f <fun7>
8048ea0: 8d 44 00 01 lea 0x1(%eax,%eax,1),%eax
8048ea4: eb 05 jmp 8048eab <fun7+0x4c>
8048ea6: b8 ff ff ff ff mov $0xffffffff,%eax
8048eab: 83 c4 18 add $0x18,%esp
8048eae: 5b pop %ebx
8048eaf: c3 ret
这是一个递归函数,我们根据汇编把这个函数的逻辑写出来:
而函数中的M[x]为0x24,即36:
依据函数逻辑推断这是一个二叉树,查看各节点:
我们可以画出这个树:
要想返回值为2,可以有:36>s , 指向8, 8<s,指向22。其中反递归的过程为:eax=0 ->2eax+1=1 ->2eax=2。所以答案是22。
至此,我们拆完了6个炸弹和一个隐藏炸弹:
在给的压缩包中,还有一个bomb-quiet可执行文件,反汇编后发现与bomb一样:
感悟
这个实验对于汇编语言的理解的要求很大,除了要学会用GDB查看各个内存地址存放的内容外,还要理解函数的目的究竟是什么,整个实验耗费了不少的时间,有的炸弹光要看懂汇编是在干什么就要耗费很长一段时间,理解的时候一直是在烧脑和惊喜之间反复横跳,但是解决之后成就感是值得花费大量的时间的,这次实验令我对反汇编破解有了一些深入的理解。
作者:往虚