程序运行中有7个关卡(6个phase和1个隐藏关卡),每个phase需要用户在终端上输入特定的字符或者数字才能通关,否则会引爆炸弹!因此可以使用gdb工具反汇编出汇编代码,并结合c语言文件找到每个关卡的入口函数。然后查看汇编代码并进行分析,进而各个击破,得到每个函数的通关密码,最终完成整个实验,话不多说,直接开干(出错的地方请各位帮忙指出啦)!
① 反汇编代码
disassemble phase_1 //得到phase_1的反汇编代码
Dump of assembler code for function phase_1:
0x08048b50 <+0>: sub $0x1c,%esp #函数栈帧准备
0x08048b53 <+3>: movl $0x804a1c4,0x4(%esp) #将0x804a1c4存入0x4(%esp)中
0x08048b5b <+11>: mov 0x20(%esp),%eax
0x08048b5f <+15>: mov %eax,(%esp) #将0x20(%esp)在内存中的值存入(%esp)
0x08048b62 <+18>: call 0x8048fe4 <strings_not_equal> #调用<strings_not_equal>函数,将比较的结果存入%eax
0x08048b67 <+23>: test %eax,%eax
0x08048b69 <+25>: je 0x8048b70 <phase_1+32> #判断%eax的值,如果%eax为1则调用<explode_bomb>函数
(根据函数的名字可以得出就是爆炸函数),如果%eax为
0则直接返回。
0x08048b6b <+27>: call 0x80490f6 <explode_bomb>
0x08048b70 <+32>: add $0x1c,%esp
0x08048b73 <+35>: ret
End of assembler dump.
② 函数分析
综上,可以判断这个函数要输入一个字符串,而且要求输入的字符串与题目已经给定的某个字符串相等。而0x804a1c4是一个立即数,可以判断这是题目给定字符串的首地址,而另一个参数就是存储在0x20(%esp)中的我们输入的字符串。因此我们只需要通过0x804a1c4这个地址得到内存中存的字符串,而该字符串就是phase_1的密码。
③ 操作步骤
x/s 0x804a1c4 //查看内存中的字符串
所以phase_1的通关密码为:Wow! Brazil is big.
① 反汇编代码
disassemble phase_2 //得到phase_2的反汇编代码
Dump of assembler code for function phase_2:
0x08048b74 <+0>: push %esi
0x08048b75 <+1>: push %ebx
0x08048b76 <+2>: sub $0x34,%esp #栈帧准备
0x08048b79 <+5>: lea 0x18(%esp),%eax
0x08048b7d <+9>: mov %eax,0x4(%esp) #0x4(%esp) = 0x18(%esp)
0x08048b81 <+13>: mov 0x40(%esp),%eax
0x08048b85 <+17>: mov %eax,(%esp) #(%esp) = M[0x40(%esp)]
0x08048b88 <+20>: call 0x804922b <read_six_numbers> #读取6个数字
0x08048b8d <+25>: cmpl $0x1,0x18(%esp) #比较M[0x18(%esp)]和1
0x08048b92 <+30>: je 0x8048b99 <phase_2+37> #相等则不会爆炸
0x08048b94 <+32>: call 0x80490f6 <explode_bomb>
0x08048b99 <+37>: lea 0x1c(%esp),%ebx #%ebx = 0x1c(%esp)
0x08048b9d <+41>: lea 0x30(%esp),%esi #%esi = 0x30(%esp)
0x08048ba1 <+45>: mov -0x4(%ebx),%eax #%eax = M[-0x4(%ebx)]
0x08048ba4 <+48>: add %eax,%eax #%eax = 2 * %eax
0x08048ba6 <+50>: cmp %eax,(%ebx) #比较(%ebx)和%eax
0x08048ba8 <+52>: je 0x8048baf <phase_2+59> #相等则不会爆炸
0x08048baa <+54>: call 0x80490f6 <explode_bomb>
0x08048baf <+59>: add $0x4,%ebx #%ebx += 0x4
0x08048bb2 <+62>: cmp %esi,%ebx #比较%ebx和%esi
0x08048bb4 <+64>: jne 0x8048ba1 <phase_2+45> #不相等则跳转
0x08048bb6 <+66>: add $0x34,%esp #返回
0x08048bb9 <+69>: pop %ebx
0x08048bba <+70>: pop %esi
0x08048bbb <+71>: ret
End of assembler dump.
② 函数分析
综上分析,输入的6个数应该满足后一个数为前一个数的2倍,而因为第一个数只能为1,因此phase_2的通关密码为:1 2 4 8 16 32
① 反汇编代码
disassemble phase_3 //得到phase_3的反汇编代码
Dump of assembler code for function phase_3:
0x08048bbc <+0>: sub $0x2c,%esp #栈帧准备
0x08048bbf <+3>: lea 0x1c(%esp),%eax
0x08048bc3 <+7>: mov %eax,0xc(%esp) #M[0xc(%esp)] = 0x1c(%esp)
0x08048bc7 <+11>: lea 0x18(%esp),%eax
0x08048bcb <+15>: mov %eax,0x8(%esp) #M[0x8(%esp)] = 0x18(%esp)
0x08048bcf <+19>: movl $0x804a3cb,0x4(%esp) #M[0x4(%esp)] = 0x804a3cb "%d %d"
0x08048bd7 <+27>: mov 0x30(%esp),%eax #%eax = M[0x30(%esp)]
0x08048bdb <+31>: mov %eax,(%esp) #M[(%esp)] = M[0x30(%esp)]
0x08048bde <+34>: call 0x8048870 <__isoc99_sscanf@plt>
0x08048be3 <+39>: cmp $0x1,%eax #比较%eax和1
0x08048be6 <+42>: jg 0x8048bed <phase_3+49> #如果%eax大于1则不会爆炸
0x08048be8 <+44>: call 0x80490f6 <explode_bomb>
0x08048bed <+49>: cmpl $0x7,0x18(%esp) #比较M[0x18(%esp)]和0x7
0x08048bf2 <+54>: ja 0x8048c5a <phase_3+158> #如果M[0x18(%esp)]小于等于0x7则不会爆炸
0x08048bf4 <+56>: mov 0x18(%esp),%eax #%eax = M[0x18(%esp)]
0x08048bf8 <+60>: jmp *0x804a1e0(,%eax,4) #跳转
0x08048bff <+67>: mov $0x0,%eax #%eax = 0
0x08048c04 <+72>: jmp 0x8048c0b <phase_3+79> #跳转
0x08048c06 <+74>: mov $0x369,%eax
0x08048c0b <+79>: sub $0x1b1,%eax #%eax -= 0x1b1
0x08048c10 <+84>: jmp 0x8048c17 <phase_3+91> #跳转
0x08048c12 <+86>: mov $0x0,%eax
0x08048c17 <+91>: add $0x3cc,%eax #%eax += 0x3cc
0x08048c1c <+96>: jmp 0x8048c23 <phase_3+103> #跳转
0x08048c1e <+98>: mov $0x0,%eax
0x08048c23 <+103>: sub $0x2c6,%eax #%eax -= 0x2c6
0x08048c28 <+108>: jmp 0x8048c2f <phase_3+115> #跳转
0x08048c2a <+110>: mov $0x0,%eax
0x08048c2f <+115>: add $0x2c6,%eax #%eax += 0x2c6
0x08048c34 <+120>: jmp 0x8048c3b <phase_3+127> #跳转
0x08048c36 <+122>: mov $0x0,%eax
0x08048c3b <+127>: sub $0x2c6,%eax #%eax -= 0x2c6
0x08048c40 <+132>: jmp 0x8048c47 <phase_3+139> #跳转
0x08048c42 <+134>: mov $0x0,%eax
0x08048c47 <+139>: add $0x2c6,%eax #%eax += 0x2c6
0x08048c4c <+144>: jmp 0x8048c53 <phase_3+151> #跳转
0x08048c4e <+146>: mov $0x0,%eax
0x08048c53 <+151>: sub $0x2c6,%eax #%eax -= 0x2c6
0x08048c58 <+156>: jmp 0x8048c64 <phase_3+168> #跳转
0x08048c5a <+158>: call 0x80490f6 <explode_bomb>
0x08048c5f <+163>: mov $0x0,%eax
0x08048c64 <+168>: cmpl $0x5,0x18(%esp) #比较M[0x18(%esp)]和0x5
0x08048c69 <+173>: jg 0x8048c71 <phase_3+181> #如果小于等于5则不爆炸
0x08048c6b <+175>: cmp 0x1c(%esp),%eax #比较%eax和M[0x1c(%esp)]
0x08048c6f <+179>: je 0x8048c76 <phase_3+186> #如果相等则不爆炸
0x08048c71 <+181>: call 0x80490f6 <explode_bomb>
0x08048c76 <+186>: add $0x2c,%esp
0x08048c79 <+189>: ret
End of assembler dump.
② 函数分析
综上分析,要输入两个整数参数,第一个参数要求小于等于5,第二个参数和跳转表中的运算结果相等。
③ 操作步骤
x/s 0x804a3cb //查看0x804a3cb在内存中的内容
假设第一个参数为0,因此先跳转到0x804a1e0在内存中的值所对应的地址,如下所示
p/x (int *)(*0x804a1e0) //查看0x804a1e0在内存中的值
可以看到下一个地址为0x8048c06,即跳转到了
1 -171
2 262
3 -710
4 0
5 -710
① 反汇编代码
disassemble phase_4 //得到phase_4的反汇编代码
Dump of assembler code for function phase_4:
0x08048cd7 <+0>: sub $0x2c,%esp #栈帧准备
0x08048cda <+3>: lea 0x18(%esp),%eax
0x08048cde <+7>: mov %eax,0xc(%esp) #M[0xc(%esp)] = 0x18(%esp)
0x08048ce2 <+11>: lea 0x1c(%esp),%eax
0x08048ce6 <+15>: mov %eax,0x8(%esp) #M[0x8(%esp)] = 0x1c(%esp)
0x08048cea <+19>: movl $0x804a3cb,0x4(%esp) #M[0x4(%esp)] = 0x804a3cb "%d %d"
0x08048cf2 <+27>: mov 0x30(%esp),%eax
0x08048cf6 <+31>: mov %eax,(%esp) #M[(%esp)] = M[0x30(%esp)]
0x08048cf9 <+34>: call 0x8048870 <__isoc99_sscanf@plt> #返回值为变量的个数,并存储在%eax中
0x08048cfe <+39>: cmp $0x2,%eax #比较%eax和0x2
0x08048d01 <+42>: jne 0x8048d11 <phase_4+58> #如果%eax等于2则不会爆炸
0x08048d03 <+44>: mov 0x18(%esp),%eax #%eax = M[0x18(%esp)]
0x08048d07 <+48>: cmp $0x1,%eax #比较%eax和0x1
0x08048d0a <+51>: jle 0x8048d11 <phase_4+58> #如果%eax大于1则不会爆炸
0x08048d0c <+53>: cmp $0x4,%eax #比较%eax和0x4
0x08048d0f <+56>: jle 0x8048d16 <phase_4+63> #如果%eax小于等于4则不会爆炸
0x08048d11 <+58>: call 0x80490f6 <explode_bomb>
0x08048d16 <+63>: mov 0x18(%esp),%eax
0x08048d1a <+67>: mov %eax,0x4(%esp) #M[0x4(%esp)] = M[0x18(%esp)]
0x08048d1e <+71>: movl $0x6,(%esp) #M[(%esp)] = 0x6
0x08048d25 <+78>: call 0x8048c7a <func4>
0x08048d2a <+83>: cmp 0x1c(%esp),%eax #比较%eax和M[0x1c(%esp)]
0x08048d2e <+87>: je 0x8048d35 <phase_4+94> #如果相等则不爆炸
0x08048d30 <+89>: call 0x80490f6 <explode_bomb>
0x08048d35 <+94>: add $0x2c,%esp
0x08048d38 <+97>: ret
End of assembler dump.
disassemble func4 //得到func4的反汇编代码
Dump of assembler code for function func4:
0x08048c7a <+0>: sub $0x1c,%esp
0x08048c7d <+3>: mov %ebx,0x10(%esp)
0x08048c81 <+7>: mov %esi,0x14(%esp)
0x08048c85 <+11>: mov %edi,0x18(%esp)
0x08048c89 <+15>: mov 0x20(%esp),%esi
0x08048c8d <+19>: mov 0x24(%esp),%ebx
0x08048c91 <+23>: test %esi,%esi
0x08048c93 <+25>: jle 0x8048cc0 <func4+70> #如果%esi小于等于0,跳转,结果返回0
0x08048c95 <+27>: cmp $0x1,%esi
0x08048c98 <+30>: je 0x8048cc5 <func4+75> #如果%esi等于1,跳转
0x08048c9a <+32>: mov %ebx,0x4(%esp) #M[0x4(%esp)] = %ebx
0x08048c9e <+36>: lea -0x1(%esi),%eax
0x08048ca1 <+39>: mov %eax,(%esp) #M[(%esp)] = %esi - 1
0x08048ca4 <+42>: call 0x8048c7a <func4>
0x08048ca9 <+47>: lea (%eax,%ebx,1),%edi
0x08048cac <+50>: mov %ebx,0x4(%esp)
0x08048cb0 <+54>: sub $0x2,%esi
0x08048cb3 <+57>: mov %esi,(%esp)
0x08048cb6 <+60>: call 0x8048c7a <func4>
0x08048cbb <+65>: lea (%edi,%eax,1),%ebx
0x08048cbe <+68>: jmp 0x8048cc5 <func4+75>
0x08048cc0 <+70>: mov $0x0,%ebx
0x08048cc5 <+75>: mov %ebx,%eax
0x08048cc7 <+77>: mov 0x10(%esp),%ebx
0x08048ccb <+81>: mov 0x14(%esp),%esi
0x08048ccf <+85>: mov 0x18(%esp),%edi
0x08048cd3 <+89>: add $0x1c,%esp
0x08048cd6 <+92>: ret
End of assembler dump.
② 函数分析
phase_4函数分析:
func4函数分析:
根据func4的汇编代码,可以这是一个递归函数,下面给出通过汇编代码得到的C代码
int func4(int x, int y) {
if (x <= 0){
return 0;
}
if (x == 1){
return y;
}
return func4(x-1, y) + func4(x-2, y) + y;
}
该函数的x初始值即为汇编代码中的6,y的初始值就是我们输入的第二个参数
综上分析,我们输入的第二个参数只能为2,3,4,当值为2时,调用func4(6, 2),得到结果为40;当值为3时,调用func4(6, 3),得到结果为60;当值为4时,调用func4(6, 4),得到结果为80。因此phase_4的 通关密码为(有3组):40 2,(或)60 3,(或)80 4
① 反汇编代码
disassemble phase_5 //得到phase_5的反汇编代码
Dump of assembler code for function phase_5:
0x08048d39 <+0>: sub $0x2c,%esp
0x08048d3c <+3>: lea 0x1c(%esp),%eax #第二个参数
0x08048d40 <+7>: mov %eax,0xc(%esp) #M[0xc(%esp)] = 0x1c(%esp)
0x08048d44 <+11>: lea 0x18(%esp),%eax #第一个参数
0x08048d48 <+15>: mov %eax,0x8(%esp) #M[0x8(%esp)] = 0x18(%esp)
0x08048d4c <+19>: movl $0x804a3cb,0x4(%esp)
0x08048d54 <+27>: mov 0x30(%esp),%eax
0x08048d58 <+31>: mov %eax,(%esp)
0x08048d5b <+34>: call 0x8048870 <__isoc99_sscanf@plt>
0x08048d60 <+39>: cmp $0x1,%eax
0x08048d63 <+42>: jg 0x8048d6a <phase_5+49> #参数个数大于1则不会爆炸
0x08048d65 <+44>: call 0x80490f6 <explode_bomb>
0x08048d6a <+49>: mov 0x18(%esp),%eax
0x08048d6e <+53>: and $0xf,%eax #参数只保留低4位,其他位清0
0x08048d71 <+56>: mov %eax,0x18(%esp) #0x18(%esp)只保留了低4位
0x08048d75 <+60>: cmp $0xf,%eax
0x08048d78 <+63>: je 0x8048da4 <phase_5+107> #低4位不为全1则不会爆炸
0x08048d7a <+65>: mov $0x0,%ecx
0x08048d7f <+70>: mov $0x0,%edx
0x08048d84 <+75>: add $0x1,%edx
0x08048d87 <+78>: mov 0x804a200(,%eax,4),%eax
0x08048d8e <+85>: add %eax,%ecx
0x08048d90 <+87>: cmp $0xf,%eax
0x08048d93 <+90>: jne 0x8048d84 <phase_5+75> #低4位不为全1则跳转
0x08048d95 <+92>: mov %eax,0x18(%esp)
0x08048d99 <+96>: cmp $0xf,%edx
0x08048d9c <+99>: jne 0x8048da4 <phase_5+107> #%edx等于0xf则不会爆炸
0x08048d9e <+101>: cmp 0x1c(%esp),%ecx
0x08048da2 <+105>: je 0x8048da9 <phase_5+112> #%ecx等于M[0x1c(%esp)]则不会爆炸
0x08048da4 <+107>: call 0x80490f6 <explode_bomb>
0x08048da9 <+112>: add $0x2c,%esp
0x08048dac <+115>: ret
End of assembler dump.
② 函数分析
综上分析,该函数需要输入两个整数参数,输入要求满足第一个参数的低四位不全为1,循环执行次数为15次,且求和结果和输入的第二个参数相等
③ 操作步骤
x/s 0x804a3cb //查看0x804a3cb在内存中的内容
假设输入的第一个数为0,下面执行循环操作(假设不会跳出循环)
首先得到%edx = 1,%eax = 0xa
继续循环得到%edx = 2,%eax = 0x1
继续循环得到%edx = 3,%eax = 0x2
继续循环得到%edx = 4,%eax = 0xe
继续循环得到%edx = 5,%eax = 0x6
继续循环得到%edx = 6,%eax = 0xf
继续循环得到%edx = 7,%eax = 0x5
继续循环得到%edx = 8,%eax = 0xc
继续循环得到%edx = 9,%eax = 0x3
继续循环得到%edx = 10,%eax = 0x7
继续循环得到%edx = 11,%eax = 0xb
继续循环得到%edx = 12,%eax = 0xd
继续循环得到%edx = 13,%eax = 0x9
继续循环得到%edx = 14,%eax = 0x4
继续循环得到%edx = 15,%eax = 0x8
继续循环得到%edx = 16,%eax = 0x0
继续循环得到%edx = 17,%eax = 0xa
这里可以看到,如果循环一直执行,得到的%eax的值也会循环的变化:
0xa->0x1->0x2->0xe->0x6->0xf->0x5->0xc->0x3->0x7->0xb->0xd->0x9->0x4->0x8->0x0->0xa
综上可得,因为最终的%edx的值要等于15才不会爆炸,所以%eax的初始值应该为5,也就是第一个参数的低四位的必须为5。而又因为第二个参数的值应该和最终的求和结果相同,因此第二个参数的值应该为:
0xc+0x3+0x7+0xb+0xd+0x9+0x4+0x8+0x0+0xa+0x1+0x2+0xe+0x6+0xf = 115
综上分析,phase_5的一个通关密码为:5 115
① 反汇编代码
disassemble phase_6 //得到phase_6的反汇编代码
Dump of assembler code for function phase_6:
0x08048dad <+0>: push %esi
0x08048dae <+1>: push %ebx
0x08048daf <+2>: sub $0x44,%esp #栈帧准备
0x08048db2 <+5>: lea 0x10(%esp),%eax
0x08048db6 <+9>: mov %eax,0x4(%esp) #M[0x4(%esp)] = 0x10(%esp)
0x08048dba <+13>: mov 0x50(%esp),%eax
0x08048dbe <+17>: mov %eax,(%esp) #M[(esp)] = M[0x50(%esp)]
0x08048dc1 <+20>: call 0x804922b <read_six_numbers> #读取6个数字
0x08048dc6 <+25>: mov $0x0,%esi #%esi = 0
0x08048dcb <+30>: mov 0x10(%esp,%esi,4),%eax
0x08048dcf <+34>: sub $0x1,%eax
0x08048dd2 <+37>: cmp $0x5,%eax
0x08048dd5 <+40>: jbe 0x8048ddc <phase_6+47> #如果%eax小于等于0x5则不会爆炸
0x08048dd7 <+42>: call 0x80490f6 <explode_bomb>
0x08048ddc <+47>: add $0x1,%esi #%esi += 1
0x08048ddf <+50>: cmp $0x6,%esi
0x08048de2 <+53>: je 0x8048e17 <phase_6+106> #如果%esi等于6则跳转
0x08048de4 <+55>: mov %esi,%ebx
0x08048de6 <+57>: mov 0x10(%esp,%ebx,4),%eax #该循环的功能是判断后面的参数与前面的参数是否相等,不相等才不会爆炸
0x08048dea <+61>: cmp %eax,0xc(%esp,%esi,4)
0x08048dee <+65>: jne 0x8048df5 <phase_6+72> #后一个参数和前一个参数不相等则不会爆炸
0x08048df0 <+67>: call 0x80490f6 <explode_bomb>
0x08048df5 <+72>: add $0x1,%ebx
0x08048df8 <+75>: cmp $0x5,%ebx
0x08048dfb <+78>: jle 0x8048de6 <phase_6+57> #%ebx小于等于5则跳转
0x08048dfd <+80>: jmp 0x8048dcb <phase_6+30> #无条件跳转
#按照输入的顺序依次存储数据
0x08048dff <+82>: mov 0x8(%edx),%edx
0x08048e02 <+85>: add $0x1,%eax
0x08048e05 <+88>: cmp %ecx,%eax
0x08048e07 <+90>: jne 0x8048dff <phase_6+82> #如果%eax不等于%ecx则跳转, a = %eax停止跳转
0x08048e09 <+92>: mov %edx,0x28(%esp,%esi,4)
0x08048e0d <+96>: add $0x1,%ebx
0x08048e10 <+99>: cmp $0x6,%ebx
0x08048e13 <+102>: jne 0x8048e1c <phase_6+111> #如果%ebx不等于0x6则跳转
0x08048e15 <+104>: jmp 0x8048e33 <phase_6+134> #无条件跳转
0x08048e17 <+106>: mov $0x0,%ebx
0x08048e1c <+111>: mov %ebx,%esi
0x08048e1e <+113>: mov 0x10(%esp,%ebx,4),%ecx
0x08048e22 <+117>: mov $0x1,%eax
0x08048e27 <+122>: mov $0x804c13c,%edx #%edx = 0x804c13c
0x08048e2c <+127>: cmp $0x1,%ecx
0x08048e2f <+130>: jg 0x8048dff <phase_6+82> #如果%ecx大于0x1则跳转
0x08048e31 <+132>: jmp 0x8048e09 <phase_6+92>
0x08048e33 <+134>: mov 0x28(%esp),%ebx
0x08048e37 <+138>: mov 0x2c(%esp),%eax
0x08048e3b <+142>: mov %eax,0x8(%ebx)
0x08048e3e <+145>: mov 0x30(%esp),%edx
0x08048e42 <+149>: mov %edx,0x8(%eax)
0x08048e45 <+152>: mov 0x34(%esp),%eax
0x08048e49 <+156>: mov %eax,0x8(%edx)
0x08048e4c <+159>: mov 0x38(%esp),%edx
0x08048e50 <+163>: mov %edx,0x8(%eax)
0x08048e53 <+166>: mov 0x3c(%esp),%eax
0x08048e57 <+170>: mov %eax,0x8(%edx) #这里把所有结点结合构成链表
0x08048e5a <+173>: movl $0x0,0x8(%eax) #指向null
0x08048e61 <+180>: mov $0x5,%esi
0x08048e66 <+185>: mov 0x8(%ebx),%eax
0x08048e69 <+188>: mov (%eax),%edx
0x08048e6b <+190>: cmp %edx,(%ebx)
0x08048e6d <+192>: jge 0x8048e74 <phase_6+199> #如果M[(%ebx)]大于等于%edx则不会爆炸
0x08048e6f <+194>: call 0x80490f6 <explode_bomb>
0x08048e74 <+199>: mov 0x8(%ebx),%ebx
0x08048e77 <+202>: sub $0x1,%esi
0x08048e7a <+205>: jne 0x8048e66 <phase_6+185> #如果%esi不等于0x1则跳转
0x08048e7c <+207>: add $0x44,%esp
0x08048e7f <+210>: pop %ebx
0x08048e80 <+211>: pop %esi
0x08048e81 <+212>: ret
End of assembler dump.
② 函数分析
综上分析,我们输入的6个数字一定要小于等于6,且两两不相等,而且最终的输入顺序必须按照内存中的结构体里的某个数据的递减顺序来进行输入。
③ 操作步骤
注意到
p/x *0x804c13c@3
可以看到内存中有6个结构体,第二个数据全部小于等于6,可以知道就是我们应该输入的6个参数,最后一个就是一个地址常量,进而可以知道phase_6里的排序应该就是按照结构体里的第一个数据进行,因为要递减的顺序,因此顺序为:0x34e->0x320->27e->212->b4->71,因此与之相对应,我们的输入参数的顺序为:3 1 6 5 2 4,这也就是phase_6的通关密码。
① 反汇编代码
disassemble phase_defused //得到phase_defused的反汇编代码
Dump of assembler code for function phase_defused:
0x0804927b <+0>: sub $0x8c,%esp #栈帧准备
0x08049281 <+6>: mov %gs:0x14,%eax #用于验证堆栈没有爆炸或已被证实
0x08049287 <+12>: mov %eax,0x7c(%esp)
0x0804928b <+16>: xor %eax,%eax #%eax = 0
0x0804928d <+18>: cmpl $0x6,0x804c3cc #""
0x08049294 <+25>: jne 0x8049308 <phase_defused+141>
0x08049296 <+27>: lea 0x2c(%esp),%eax
0x0804929a <+31>: mov %eax,0x10(%esp)
0x0804929e <+35>: lea 0x28(%esp),%eax
0x080492a2 <+39>: mov %eax,0xc(%esp)
0x080492a6 <+43>: lea 0x24(%esp),%eax
0x080492aa <+47>: mov %eax,0x8(%esp)
0x080492ae <+51>: movl $0x804a3d1,0x4(%esp) #"%d %d %s"
0x080492b6 <+59>: movl $0x804c4d0,(%esp) #""
0x080492bd <+66>: call 0x8048870 <__isoc99_sscanf@plt>
0x080492c2 <+71>: cmp $0x3,%eax
0x080492c5 <+74>: jne 0x80492fc <phase_defused+129> #参数个数不等于3则跳转
0x080492c7 <+76>: movl $0x804a3da,0x4(%esp) #"DrEvil"
0x080492cf <+84>: lea 0x2c(%esp),%eax
0x080492d3 <+88>: mov %eax,(%esp)
0x080492d6 <+91>: call 0x8048fe4 <strings_not_equal> #比较两个字符串,返回值存储在%eax中
0x080492db <+96>: test %eax,%eax
0x080492dd <+98>: jne 0x80492fc <phase_defused+129> #如果不等于0则跳转
0x080492df <+100>: movl $0x804a2a0,(%esp) #"Curses, you've found the secret phase!"
0x080492e6 <+107>: call 0x8048800 <puts@plt>
0x080492eb <+112>: movl $0x804a2c8,(%esp) #"But finding it and solving it are quite different..."
0x080492f2 <+119>: call 0x8048800 <puts@plt>
0x080492f7 <+124>: call 0x8048ed3 <secret_phase>
0x080492fc <+129>: movl $0x804a300,(%esp) #"Congratulations! You've defused the bomb!"
0x08049303 <+136>: call 0x8048800 <puts@plt>
0x08049308 <+141>: mov 0x7c(%esp),%eax
0x0804930c <+145>: xor %gs:0x14,%eax
0x08049313 <+152>: je 0x804931a <phase_defused+159>
0x08049315 <+154>: call 0x80487d0 <__stack_chk_fail@plt>
0x0804931a <+159>: add $0x8c,%esp
0x08049320 <+165>: ret
End of assembler dump.
disassemble secret_phase //得到secret_phase的反汇编代码
Dump of assembler code for function secret_phase:
0x08048ed3 <+0>: push %ebx
0x08048ed4 <+1>: sub $0x18,%esp
0x08048ed7 <+4>: call 0x804911d <read_line>
0x08048edc <+9>: movl $0xa,0x8(%esp)
0x08048ee4 <+17>: movl $0x0,0x4(%esp)
0x08048eec <+25>: mov %eax,(%esp)
0x08048eef <+28>: call 0x80488e0 <strtol@plt>
0x08048ef4 <+33>: mov %eax,%ebx
0x08048ef6 <+35>: lea -0x1(%eax),%eax
0x08048ef9 <+38>: cmp $0x3e8,%eax
0x08048efe <+43>: jbe 0x8048f05 <secret_phase+50> #%eax小于等于1001则不会爆炸
0x08048f00 <+45>: call 0x80490f6 <explode_bomb>
0x08048f05 <+50>: mov %ebx,0x4(%esp)
0x08048f09 <+54>: movl $0x804c088,(%esp)
0x08048f10 <+61>: call 0x8048e82 <fun7>
0x08048f15 <+66>: test %eax,%eax
0x08048f17 <+68>: je 0x8048f1e <secret_phase+75> #%eax等于0则不会爆炸
0x08048f19 <+70>: call 0x80490f6 <explode_bomb>
0x08048f1e <+75>: movl $0x804a240,(%esp) #"Wow! You've defused the secret stage!"
0x08048f25 <+82>: call 0x8048800 <puts@plt>
0x08048f2a <+87>: call 0x804927b <phase_defused>
0x08048f2f <+92>: add $0x18,%esp
0x08048f32 <+95>: pop %ebx
0x08048f33 <+96>: ret
End of assembler dump.
disassemble fun7 //得到fun7的反汇编代码
Dump of assembler code for function fun7:
0x08048e82 <+0>: push %ebx
0x08048e83 <+1>: sub $0x18,%esp #栈帧准备
0x08048e86 <+4>: mov 0x20(%esp),%edx #%edx = 0x804c088
0x08048e8a <+8>: mov 0x24(%esp),%ecx #%ecx = 自己输入的数
0x08048e8e <+12>: test %edx,%edx
0x08048e90 <+14>: je 0x8048ec9 <fun7+71> #%edx等于0则跳转,返回0xffffffff
0x08048e92 <+16>: mov (%edx),%ebx #%ebx = 0x24
0x08048e94 <+18>: cmp %ecx,%ebx
0x08048e96 <+20>: jle 0x8048eab <fun7+41> #%ebx小于等于%ecx则跳转
0x08048e98 <+22>: mov %ecx,0x4(%esp)
0x08048e9c <+26>: mov 0x4(%edx),%eax
0x08048e9f <+29>: mov %eax,(%esp)
0x08048ea2 <+32>: call 0x8048e82 <fun7>
0x08048ea7 <+37>: add %eax,%eax
0x08048ea9 <+39>: jmp 0x8048ece <fun7+76>
0x08048eab <+41>: mov $0x0,%eax #%eax = 0
0x08048eb0 <+46>: cmp %ecx,%ebx
0x08048eb2 <+48>: je 0x8048ece <fun7+76> #%ebx小于等于%ecx则跳转,返回0
0x08048eb4 <+50>: mov %ecx,0x4(%esp)
0x08048eb8 <+54>: mov 0x8(%edx),%eax
0x08048ebb <+57>: mov %eax,(%esp)
0x08048ebe <+60>: call 0x8048e82 <fun7>
0x08048ec3 <+65>: lea 0x1(%eax,%eax,1),%eax
0x08048ec7 <+69>: jmp 0x8048ece <fun7+76>
0x08048ec9 <+71>: mov $0xffffffff,%eax
0x08048ece <+76>: add $0x18,%esp
0x08048ed1 <+79>: pop %ebx
0x08048ed2 <+80>: ret
End of assembler dump.
② 函数分析
phase_defused分析:
secret_phase分析:
fun7分析:
因为从对secret_phase函数的分析我们得知返回值必须为0,所以我们只需要查看fun7要怎么执行使其最终返回0即可。可以看到< fun7+41>行将0存储到%eax寄存器中,并且往下执行判断如果%ebx等于%ecx寄存器中的值,则会直接跳转最终返回0。因此只需使代码在执行时跳转到< fun7+41>行即可。往上查询代码可以看到< fun7+20>行可以跳转到该行。而< fun7+20>行的跳转条件为%ebx小于等于%ecx,即我们输入的数应该大于等于36。加之< fun7+48>行的跳转条件为%ebx等于%ecx。所以我们最终得到输入的数应该等于%ebx,也就是36。
综上我们得知,秘密关卡的开启方式为输入两个数字后面接着输入一个" DrEvil"字符串,因为我们直接输入前六关的正确通关密码时不会打开秘密关卡,可以推测秘密关卡的打开方式应该是在输入某一关的密码的同时进行输入,又因为要先输入两个数字,因此只有可能是3,4,5关中的某一个。而且打开秘密关卡后,分析得到秘密关卡的通关密码为36。
③ 操作步骤
phase_defused函数:
x/s 0x804a3d1 //查看0x804a3d1在内存中的内容,得知秘密关卡的打开方式为输入两个数字和一个字符串
x/s 0x804a3da //查看0x804a3da在内存中的内容,得知要输入的字符串为"DrEvil"
x/s 0x804a2a0 //查看0x804a2a0在内存中的内容
x/s 0x804a2c8 //查看0x804a2c8在内存中的内容
x/s 0x804a300 //查看0x804a300在内存中的内容
secret_phase函数:
x/s 0x804a240 //查看0x804a240在内存中的内容
因为3,4,5关都有可能是秘密关卡开启的地方,因此一个一个进行实验,发现只有第四关输入字符串才会开启秘密关卡。
第3关输入字符串:
第4关输入字符串:
第5关输入字符串:
综上分析,秘密关卡的开启方式为:在第四关的两个参数输入后面输入"DrEvil"字符串,并且秘密关卡的通关秘密为36
运行过程如上所示,输入所有通关密码后成功通关所有关卡。
通关密码依次为:
Wow! Brazil is big.
1 2 4 8 16 32
0 702(1 -171,2 262,3 -710,4 0,5 -710)(不唯一)
40 2 DrEvil(60 3 DrEvil,80 4 DrEvil)(不唯一)
5 115
3 1 6 5 2 4
36