通过对主函数的观察发现里面有六个炸弹函数,先是获得输入input = read_line(); 接着运行 phase_1(input); 判断是否是正确答案,如果是正确答案,调用phase_defused(); 来获取接触炸弹的方式
输入"gdb -q bomb"进行调试
目录
前言
- 本实验是《深入理解计算机系统》一书中的附带实验——二进制炸弹实验。二进制炸弹是一个作为目标代码文件提供给学生们的程序。运行时,它提示用户输入6个不同的字符串。如果其中的任何一个不正确,炸弹就会“爆炸”,打印出一条错误信息,并且在一个分级服务器上记录事件日志。学生们必须通过对程序反汇编和逆向工程来测定应该是哪6个串,从而解除他们各自炸弹的雷管。
- 该实验教会学生理解汇编语言,并且强制他们学习怎么使用调试器。
- 本文用于记录之前做实验的一些信息,可能思路有些凌乱,谨慎参考!
一、phase_1
进入调试模式之后,首先输入"disas phase_1"查看phase_1函数的汇编代码
汇编代码及其说明
Dump of assembler code for function phase_1:
0x08048b50 <+0>: sub $0x1c,%esp //栈指针%esp-0x1c,即该操作是为了给当前帧开辟一个大小为0x1c字节的空间
0x08048b53 <+3>: movl $0x804a264,0x4(%esp) //将0x804a264(用于比较的字符串在内存中存放的地址)赋给*(0x4+%esp)
0x08048b5b <+11>: mov 0x20(%esp),%eax //将*(0x20+%esp)赋给寄存器%eax,其实就是输入的字符串 (字符串的理由:在后面的代码里,调用了一个strings_not_equal函数。我猜是字符串。用gdb读一下 果然是字符串。)
0x08048b5f <+15>: mov %eax,(%esp) //将寄存器%eax中的值赋给*(%esp)
0x08048b62 <+18>: call 0x8049084 <strings_not_equal> //比较两字符串是否相等,根据结果给%eax赋值,可以反汇编这个函数 具体看其中值的具体变化)
0x08048b67 <+23>: test %eax,%eax
0x08048b69 <+25>: je 0x8048b70 <phase_1+32> //如果eax=0,则进入phase2;否则爆炸
0x08048b6b <+27>: call 0x8049196 <explode_bomb>
0x08048b70 <+32>: add $0x1c,%esp //将%esp的值-0x1c,即栈指针往栈底方向移动0x1c字节,用于回收空间
0x08048b73 <+35>: ret //返回
End of assembler dump.
根据汇编代码可以推出,%eax用于保存的是输入的字符串,地址0x804a264处存放的是用于比较的字符串(即我们应该输入的正确字符串)
输入"x/s 0x804a264"查看此处的信息,得到字符串"When a problem comes along, you must zip it!"
输入"run"运行该程序,输入字符串"When a problem comes along, you must zip it!"测试是否成功拆弹?!
OK!成功拆掉第一个炸弹!!让我们继续下一阶段的拆弹吧!
二、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 //将地址0x18+%esp赋给寄存器%eax(可以看出%eax用于保存变量)
0x08048b7d <+9>: mov %eax,0x4(%esp)
0x08048b81 <+13>: mov 0x40(%esp),%eax
0x08048b85 <+17>: mov %eax,(%esp)
0x08048b88 <+20>: call 0x80492cb <read_six_numbers> //根据该函数名称可以推测答案是输入六个数
0x08048b8d <+25>: cmpl $0x1,0x18(%esp)
0x08048b92 <+30>: je 0x8048b99 <phase_2+37> //如果*(%esp+0x18)=0x1就跳转到phase+37,否则执行explode_bomb函数;所以可以推测*(%esp+0x18)=0x1
0x08048b94 <+32>: call 0x8049196 <explode_bomb>
0x08048b99 <+37>: lea 0x1c(%esp),%ebx //将地址%esp+0x1c赋给寄存器%ebx
0x08048b9d <+41>: lea 0x30(%esp),%esi //将地址%esp+0x30赋给寄存器%esi
0x08048ba1 <+45>: mov -0x4(%ebx),%eax //将*(%ebx-0x4)也就是*(%esp+0x18)也就是0x1赋给寄存器%eax
0x08048ba4 <+48>: add %eax,%eax //%eax中的值翻倍
0x08048ba6 <+50>: cmp %eax,(%ebx) //将%eax中的值与*(%ebx)也就是*(%esp+0x1c)作比较
0x08048ba8 <+52>: je 0x8048baf <phase_2+59> //如果相等就跳转至<phase_2+59>,可以推测出*(%esp+0x1c)为0x2;否则会执行<explode_bomb>
0x08048baa <+54>: call 0x8049196 <explode_bomb>
0x08048baf <+59>: add $0x4,%ebx //%ebx加0x4
0x08048bb2 <+62>: cmp %esi,%ebx //循环条件,判断%esi是否等于%ebx
0x08048bb4 <+64>: jne 0x8048ba1 <phase_2+45> 如果不等于就跳转至<phase_2+45>
0x08048bb6 <+66>: add $0x34,%esp //栈顶指针往栈底方向移动0x34个字节
0x08048bb9 <+69>: pop %ebx //恢复%ebx原先的数据
0x08048bba <+70>: pop %esi //弹出程序的入口地址
0x08048bbb <+71>: ret //结束
End of assembler dump.
根据第10行中的" cmpl $0x1,0x18(%esp) " 以及转移地址" je 0x8048b99 <phase_2+37>"得到地址为%esp+0x1中的值为0x1;根据第22行中的"jne 0x8048ba1 <phase_2+45>"得到程序进入了一个循环,循环终止的条件是第21行"%esi=%ebx",在循环中进行的是第16行%eax+=%eax后再将值保存到相应的地址单元中
故可以看出存储该六位输入数据的地址单元为%esp+0x18,%esp+0x1c,%esp+0x20,%esp+0x24,%esp+0x28,%esp+0x2c;存储的六位输入数据分别为0x1,0x2,0x4,0x8,0x10,0x20,对应十进制就是1,2,4,8,16,32
运行之后输入答案"1 2 4 8 16 32"之后没有爆炸说明拆弹成功!
三、phase_3
汇编代码及其说明
Dump of assembler code for function phase_3:
0x08048bbc <+0>: sub $0x3c,%esp
0x08048bbf <+3>: lea 0x28(%esp),%eax
0x08048bc3 <+7>: mov %eax,0x10(%esp)
0x08048bc7 <+11>: lea 0x2f(%esp),%eax
0x08048bcb <+15>: mov %eax,0xc(%esp)
0x08048bcf <+19>: lea 0x24(%esp),%eax
0x08048bd3 <+23>: mov %eax,0x8(%esp)
0x08048bd7 <+27>: movl $0x804a2ba,0x4(%esp)
0x08048bdf <+35>: mov 0x40(%esp),%eax
0x08048be3 <+39>: mov %eax,(%esp)
0x08048be6 <+42>: call 0x8048870 <__isoc99_sscanf@plt>
0x08048beb <+47>: cmp $0x2,%eax
0x08048bee <+50>: jg 0x8048bf5 <phase_3+57> //%eax>0x2,跳转至<phase_3+57>;否则跳转至<explode_bomb>,炸弹爆炸;因此推测输入中至少有一个数大于0x2
0x08048bf0 <+52>: call 0x8049196 <explode_bomb>
0x08048bf5 <+57>: cmpl $0x7,0x24(%esp)
0x08048bfa <+62>: ja 0x8048cf2 <phase_3+310> //*(0x24+esp)>0x7的话,跳转至<phase_3+310> 会出现炸弹爆炸,所以推测得到*(0x24+esp)<=0x7
0x08048c00 <+68>: mov 0x24(%esp),%eax
0x08048c04 <+72>: jmp *0x804a2e0(,%eax,4)
0x08048c0b <+79>: mov $0x78,%eax
0x08048c10 <+84>: cmpl $0x3b8,0x28(%esp)
0x08048c18 <+92>: je 0x8048cfc <phase_3+320> //由此推测出,*(0x28+esp)=0x3b8
0x08048c1e <+98>: call 0x8049196 <explode_bomb>
0x08048c23 <+103>: mov $0x78,%eax
0x08048c28 <+108>: jmp 0x8048cfc <phase_3+320>
0x08048c2d <+113>: mov $0x6d,%eax
0x08048c32 <+118>: cmpl $0x69,0x28(%esp)
0x08048c37 <+123>: je 0x8048cfc <phase_3+320> //由此推测出,*(0x28+esp)=0x69
0x08048c3d <+129>: call 0x8049196 <explode_bomb>
0x08048c42 <+134>: mov $0x6d,%eax
0x08048c47 <+139>: jmp 0x8048cfc <phase_3+320>
0x08048c4c <+144>: mov $0x78,%eax
0x08048c51 <+149>: cmpl $0x33e,0x28(%esp)
0x08048c59 <+157>: je 0x8048cfc <phase_3+320> //由此推测出,*(0x28+esp)=0x33e
0x08048c5f <+163>: call 0x8049196 <explode_bomb>
0x08048c64 <+168>: mov $0x78,%eax
0x08048c69 <+173>: jmp 0x8048cfc <phase_3+320>
0x08048c6e <+178>: mov $0x71,%eax
0x08048c73 <+183>: cmpl $0x345,0x28(%esp)
0x08048c7b <+191>: je 0x8048cfc <phase_3+320> //由此推测出,*(0x28+esp)=0x345
0x08048c7d <+193>: call 0x8049196 <explode_bomb>
0x08048c82 <+198>: mov $0x71,%eax
0x08048c87 <+203>: jmp 0x8048cfc <phase_3+320>
0x08048c89 <+205>: mov $0x70,%eax
0x08048c8e <+210>: cmpl $0xca,0x28(%esp) //由此推测出,*(0x28+esp)=0x70
0x08048c96 <+218>: je 0x8048cfc <phase_3+320>
0x08048c98 <+220>: call 0x8049196 <explode_bomb>
0x08048c9d <+225>: mov $0x70,%eax
0x08048ca2 <+230>: jmp 0x8048cfc <phase_3+320>
0x08048ca4 <+232>: mov $0x75,%eax
0x08048ca9 <+237>: cmpl $0x14f,0x28(%esp) //由此推测出,*(0x28+esp)=0x14f
0x08048cb1 <+245>: je 0x8048cfc <phase_3+320>
0x08048cb3 <+247>: call 0x8049196 <explode_bomb>
0x08048cb8 <+252>: mov $0x75,%eax
0x08048cbd <+257>: jmp 0x8048cfc <phase_3+320>
0x08048cbf <+259>: mov $0x69,%eax
0x08048cc4 <+264>: cmpl $0x189,0x28(%esp) //由此推测出,*(0x28+esp)=0x189
0x08048ccc <+272>: je 0x8048cfc <phase_3+320>
0x08048cce <+274>: call 0x8049196 <explode_bomb>
0x08048cd3 <+279>: mov $0x69,%eax
0x08048cd8 <+284>: jmp 0x8048cfc <phase_3+320>
0x08048cda <+286>: mov $0x62,%eax
0x08048cdf <+291>: cmpl $0x32,0x28(%esp) //由此推测出,*(0x28+esp)=0x32
0x08048ce4 <+296>: je 0x8048cfc <phase_3+320>
0x08048ce6 <+298>: call 0x8049196 <explode_bomb>
0x08048ceb <+303>: mov $0x62,%eax
0x08048cf0 <+308>: jmp 0x8048cfc <phase_3+320>
0x08048cf2 <+310>: call 0x8049196 <explode_bomb>
0x08048cf7 <+315>: mov $0x6c,%eax
0x08048cfc <+320>: cmp 0x2f(%esp),%al //由此推测出,*(0x2f+esp)=%al
0x08048d00 <+324>: je 0x8048d07 <phase_3+331>
0x08048d02 <+326>: call 0x8049196 <explode_bomb>
0x08048d07 <+331>: add $0x3c,%esp
0x08048d0a <+334>: ret
End of assembler dump.
根据第9行"movl $0x804a2ba,0x4(%esp)"以及第12行"call 0x8048870 <__isoc99_sscanf@plt>"中的scanf提示,使用"x/s 0x804a2ba"将地址单元为0x804a2ba中的信息按照字符串的形式打印出来得到最终的答案输入形式应该是“整型,字符,整型”
根据第3行到第8行可以得到地址(0x24+esp)保存到(0x8+esp)中,地址(0x28+esp)保存到(0x10+esp)中,地址(0x2f+esp)保存到(0xc+esp)中;可以得到*(0x24+esp)<=0x7
令输入的*(0x24+esp)=0x1,输入"x/s *(0x804a2e0+1*4)"查看最终的跳转地址0x8048c2d <phase_3+113>,因为*(0x24+esp)对应了八种情况,所以答案有八种情况;
根据"0x08048c2d <+113>: mov $0x6d,%eax
0x08048c32 <+118>: cmpl $0x69,0x28(%esp)
0x08048c37 <+123>: je 0x8048cfc <phase_3+320>"得到%eax=0x6d,*(0x28+esp)=0x69,紧接着跳转至
"0x08048cfc <+320>: cmp 0x2f(%esp),%al
0x08048d00 <+324>: je 0x8048d07 <phase_3+331>" 得到*(0x2f+esp)=%al=6d;
故最终三个输入的十六进制分别是0x1,0x6d,0x69,对应"%d %c %d"格式得到输入为1,m,105
让我们测试一下!Nice!又破解了一个!
四、phase_4
汇编代码如下:
Dump of assembler code for function phase_4:
0x08048d74 <+0>: sub $0x2c,%esp
0x08048d77 <+3>: lea 0x1c(%esp),%eax
0x08048d7b <+7>: mov %eax,0xc(%esp)
0x08048d7f <+11>: lea 0x18(%esp),%eax
0x08048d83 <+15>: mov %eax,0x8(%esp)
0x08048d87 <+19>: movl $0x804a4a3,0x4(%esp)
0x08048d8f <+27>: mov 0x30(%esp),%eax
0x08048d93 <+31>: mov %eax,(%esp)
0x08048d96 <+34>: call 0x8048870 <__isoc99_sscanf@plt> //根据scanf提示打印地址0x804a4a3中的值
0x08048d9b <+39>: cmp $0x2,%eax
0x08048d9e <+42>: jne 0x8048dad <phase_4+57> //根据跳转逻辑可以推测此时%eax=0x2
0x08048da0 <+44>: mov 0x18(%esp),%eax
0x08048da4 <+48>: test %eax,%eax
0x08048da6 <+50>: js 0x8048dad <phase_4+57> //根据跳转逻辑可以推测此时输入的第一个数是非负数
0x08048da8 <+52>: cmp $0xe,%eax
0x08048dab <+55>: jle 0x8048db2 <phase_4+62> //根据跳转逻辑可以推测此时%eax<=0xe,所以第一个输入是0-14之间某一个数
0x08048dad <+57>: call 0x8049196 <explode_bomb>
0x08048db2 <+62>: movl $0xe,0x8(%esp)
0x08048dba <+70>: movl $0x0,0x4(%esp)
0x08048dc2 <+78>: mov 0x18(%esp),%eax
0x08048dc6 <+82>: mov %eax,(%esp) //将%eax,0,14作为参数传给func4函数
0x08048dc9 <+85>: call 0x8048d0b <func4>
0x08048dce <+90>: cmp $0x13,%eax
0x08048dd1 <+93>: jne 0x8048dda <phase_4+102> //根据跳转逻辑可以推测此时函数func4的返回值%eax=0x13
0x08048dd3 <+95>: cmpl $0x13,0x1c(%esp)
0x08048dd8 <+100>: je 0x8048ddf <phase_4+107> //根据跳转逻辑可以推测此时*(0x1c+%esp)=0x13,即输入的第二个数是0x13
0x08048dda <+102>: call 0x8049196 <explode_bomb>
0x08048ddf <+107>: add $0x2c,%esp
0x08048de2 <+110>: ret
End of assembler dump.
根据提示输入"x/s 0x804a4a3"打印地址0x804a4a3中的值,可以查看到输入为两个整型
查看func4函数的汇编代码如下:
Dump of assembler code for function func4:
0x08048d0b <+0>: sub $0x1c,%esp
0x08048d0e <+3>: mov %ebx,0x14(%esp)
0x08048d12 <+7>: mov %esi,0x18(%esp)
0x08048d16 <+11>: mov 0x20(%esp),%eax
0x08048d1a <+15>: mov 0x24(%esp),%edx
0x08048d1e <+19>: mov 0x28(%esp),%esi //三个参数,对应第一个输入,0,14(第二个参数为low,第三个参数为high)
0x08048d22 <+23>: mov %esi,%ecx
0x08048d24 <+25>: sub %edx,%ecx //令high-low
0x08048d26 <+27>: mov %ecx,%ebx
0x08048d28 <+29>: shr $0x1f,%ebx
0x08048d2b <+32>: add %ebx,%ecx //将寄存器ebx中的值逻辑右移0x1f位,用于判断high-low是否为负数,如果为负数的话右移31位得到1,再加到high-low中去;
0x08048d2d <+34>: sar %ecx //默认算术右移一位,即求(high-low)/2
0x08048d2f <+36>: lea (%ecx,%edx,1),%ebx //ebx我们现在有的low + (high - low) / 2也被称为两个数字的平均值。
0x08048d32 <+39>: cmp %eax,%ebx
0x08048d34 <+41>: jle 0x8048d4d <func4+66>
0x08048d36 <+43>: lea -0x1(%ebx),%ecx
0x08048d39 <+46>: mov %ecx,0x8(%esp)
0x08048d3d <+50>: mov %edx,0x4(%esp)
0x08048d41 <+54>: mov %eax,(%esp)
0x08048d44 <+57>: call 0x8048d0b <func4>
0x08048d49 <+62>: add %eax,%ebx 如果x<average,返回average+func4(x, low, average-1)
0x08048d4b <+64>: jmp 0x8048d66 <func4+91>
0x08048d4d <+66>: cmp %eax,%ebx //如果x=average,返回的是average
0x08048d4f <+68>: jge 0x8048d66 <func4+91>
0x08048d51 <+70>: mov %esi,0x8(%esp)
0x08048d55 <+74>: lea 0x1(%ebx),%edx //
0x08048d58 <+77>: mov %edx,0x4(%esp)
0x08048d5c <+81>: mov %eax,(%esp)
0x08048d5f <+84>: call 0x8048d0b <func4>
0x08048d64 <+89>: add %eax,%ebx //如果x>average和计算则执行average+func4(x, average+1, high)
0x08048d66 <+91>: mov %ebx,%eax //可以根据phase_4得到函数返回值%eax=0x13
0x08048d68 <+93>: mov 0x14(%esp),%ebx
0x08048d6c <+97>: mov 0x18(%esp),%esi
0x08048d70 <+101>: add $0x1c,%esp
0x08048d73 <+104>: ret
End of assembler dump.
将函数func4中的寄存器看作变量将该汇编代码模拟转换成C语言代码如下:
#include<iostream>
using namespace std;
int func4(int x,int low,int high)
{
int aver= low+(high-low+((high-low)<0?1:0))/2;
if(x<aver)
{
return aver+func4(x,low,aver-1);
}
else if(x>aver)
{
return aver+func4(x,aver+1,high);
}
else return aver;
}
int main()
{
for(int i=0;i<=14;i++)
{
if(func4(i,0,14)==19) cout<<i<<" ";
}
return 0;
}
函数返回值通过寄存器%eax来保存,所以可以推测出函数func4返回值为0x13;通过C++模拟得到我们的第一个输入应该是4
第二个数可以直接根据phase_4的汇编代码推测为19(0x13)
测试一下!正确!
五、phase_5
汇编代码如下:
Dump of assembler code for function phase_5:
0x08048de3 <+0>: sub $0x2c,%esp
0x08048de6 <+3>: lea 0x1c(%esp),%eax
0x08048dea <+7>: mov %eax,0xc(%esp)
0x08048dee <+11>: lea 0x18(%esp),%eax
0x08048df2 <+15>: mov %eax,0x8(%esp)
0x08048df6 <+19>: movl $0x804a4a3,0x4(%esp) //根据提示0x804a4a3是"%d %d"在内存中的地址
0x08048dfe <+27>: mov 0x30(%esp),%eax
0x08048e02 <+31>: mov %eax,(%esp)
0x08048e05 <+34>: call 0x8048870 <__isoc99_sscanf@plt>
0x08048e0a <+39>: cmp $0x1,%eax
0x08048e0d <+42>: jg 0x8048e14 <phase_5+49> //根据跳转逻辑得到%eax=0x1
0x08048e0f <+44>: call 0x8049196 <explode_bomb>
0x08048e14 <+49>: mov 0x18(%esp),%eax
0x08048e18 <+53>: and $0xf,%eax
0x08048e1b <+56>: mov %eax,0x18(%esp) //将0x18(esp)中的内容与上0xf
0x08048e1f <+60>: cmp $0xf,%eax //根据跳转逻辑,%eax不等于0xf
0x08048e22 <+63>: je 0x8048e4e <phase_5+107>
0x08048e24 <+65>: mov $0x0,%ecx
0x08048e29 <+70>: mov $0x0,%edx
0x08048e2e <+75>: add $0x1,%edx
0x08048e31 <+78>: mov 0x804a300(,%eax,4),%eax //长度为十六的数组中的值{0xa, 0x2, 0xe, 0x7, 0x8, 0xc, 0xf, 0xb, 0x0, 0x4, 0x1, 0xd, 0x3, 0x9, 0x6, 0x5}
0x08048e38 <+85>: add %eax,%ecx
---Type <return> to continue, or q <return> to quit---
0x08048e3a <+87>: cmp $0xf,%eax
0x08048e3d <+90>: jne 0x8048e2e <phase_5+75>
0x08048e3f <+92>: mov %eax,0x18(%esp)
0x08048e43 <+96>: cmp $0xf,%edx //根据跳转逻辑得到%edx=0xf,说明循环了十五次
0x08048e46 <+99>: jne 0x8048e4e <phase_5+107>
0x08048e48 <+101>: cmp 0x1c(%esp),%ecx //根据跳转逻辑得到%ecx=*(0x1c+esp),即第二个输入为%ecx
0x08048e4c <+105>: je 0x8048e53 <phase_5+112>
0x08048e4e <+107>: call 0x8049196 <explode_bomb>
0x08048e53 <+112>: add $0x2c,%esp
0x08048e56 <+115>: ret
End of assembler dump.
分析得到%eax不能取到0xf,%edx作为循环变量,控制循环次数为0xf次,每次循环%ecx都加上%eax的值
输入"p/x *(int *)(0x804a300)@16"查看以0x804a300为基址的长度为十六的数组中的值{0xa, 0x2, 0xe, 0x7, 0x8, 0xc, 0xf, 0xb, 0x0, 0x4, 0x1, 0xd, 0x3, 0x9, 0x6, 0x5},即数组
0 1 2 3 4 5 6 7 8 9 a b c d e f(索引)
a 2 e 7 8 c f b 0 4 1 d 3 9 6 5(值)
反推,如果%eax=0xf,说明索引为6,即上一个eax=6,那么上一个eax的索引为e;故得到eax的逆推序列为f 6 e 2 1 a 0 8 4 9 d b 7 3 c 5
所以第一个输入等于最开始的%eax=5,第二个输入为%ecx=(c+…f)=115;
测试一下!下面只剩最后一个啦!!
六、phase_6
汇编代码如下:
Dump of assembler code for function phase_6:
0x08048e57 <+0>: push %esi
0x08048e58 <+1>: push %ebx
0x08048e59 <+2>: sub $0x44,%esp
0x08048e5c <+5>: lea 0x10(%esp),%eax
0x08048e60 <+9>: mov %eax,0x4(%esp)
0x08048e64 <+13>: mov 0x50(%esp),%eax
0x08048e68 <+17>: mov %eax,(%esp)
0x08048e6b <+20>: call 0x80492cb <read_six_numbers> //输入六个数字
0x08048e70 <+25>: mov $0x0,%esi
0x08048e75 <+30>: mov 0x10(%esp,%esi,4),%eax
0x08048e79 <+34>: sub $0x1,%eax
0x08048e7c <+37>: cmp $0x5,%eax
0x08048e7f <+40>: jbe 0x8048e86 <phase_6+47> //根据跳转逻辑eax<=0x5,故*(0x10+esp)<=0x6
0x08048e81 <+42>: call 0x8049196 <explode_bomb>
0x08048e86 <+47>: add $0x1,%esi
0x08048e89 <+50>: cmp $0x6,%esi
0x08048e8c <+53>: je 0x8048ec1 <phase_6+106>
0x08048e8e <+55>: mov %esi,%ebx
0x08048e90 <+57>: mov 0x10(%esp,%ebx,4),%eax
0x08048e94 <+61>: cmp %eax,0xc(%esp,%esi,4)
0x08048e98 <+65>: jne 0x8048e9f <phase_6+72>
0x08048e9a <+67>: call 0x8049196 <explode_bomb>
---Type <return> to continue, or q <return> to quit---
0x08048e9f <+72>: add $0x1,%ebx
0x08048ea2 <+75>: cmp $0x5,%ebx
0x08048ea5 <+78>: jle 0x8048e90 <phase_6+57>
0x08048ea7 <+80>: jmp 0x8048e75 <phase_6+30> //通过观察得到上面表示的是六个输入均小于等于0x6
0x08048ea9 <+82>: mov 0x8(%edx),%edx
0x08048eac <+85>: add $0x1,%eax
0x08048eaf <+88>: cmp %ecx,%eax
0x08048eb1 <+90>: jne 0x8048ea9 <phase_6+82> //查找结点
0x08048eb3 <+92>: mov %edx,0x28(%esp,%esi,4)
0x08048eb7 <+96>: add $0x1,%ebx
0x08048eba <+99>: cmp $0x6,%ebx
0x08048ebd <+102>: jne 0x8048ec6 <phase_6+111>
0x08048ebf <+104>: jmp 0x8048edd <phase_6+134>
0x08048ec1 <+106>: mov $0x0,%ebx
0x08048ec6 <+111>: mov %ebx,%esi
0x08048ec8 <+113>: mov 0x10(%esp,%ebx,4),%ecx
0x08048ecc <+117>: mov $0x1,%eax
0x08048ed1 <+122>: mov $0x804c13c,%edx //第一个节点的地址为0x804c13c
0x08048ed6 <+127>: cmp $0x1,%ecx
0x08048ed9 <+130>: jg 0x8048ea9 <phase_6+82>
0x08048edb <+132>: jmp 0x8048eb3 <phase_6+92>
0x08048edd <+134>: mov 0x28(%esp),%ebx
0x08048ee1 <+138>: mov 0x2c(%esp),%eax
---Type <return> to continue, or q <return> to quit---
0x08048ee5 <+142>: mov %eax,0x8(%ebx)
0x08048ee8 <+145>: mov 0x30(%esp),%edx
0x08048eec <+149>: mov %edx,0x8(%eax)
0x08048eef <+152>: mov 0x34(%esp),%eax
0x08048ef3 <+156>: mov %eax,0x8(%edx)
0x08048ef6 <+159>: mov 0x38(%esp),%edx
0x08048efa <+163>: mov %edx,0x8(%eax)
0x08048efd <+166>: mov 0x3c(%esp),%eax
0x08048f01 <+170>: mov %eax,0x8(%edx)
0x08048f04 <+173>: movl $0x0,0x8(%eax) //链表尾指针设为空
0x08048f0b <+180>: mov $0x5,%esi //按照降序重建链表
0x08048f10 <+185>: mov 0x8(%ebx),%eax
0x08048f13 <+188>: mov (%eax),%edx
0x08048f15 <+190>: cmp %edx,(%ebx) //判断第一个数是否大于第二个数
0x08048f17 <+192>: jge 0x8048f1e <phase_6+199>
0x08048f19 <+194>: call 0x8049196 <explode_bomb>
0x08048f1e <+199>: mov 0x8(%ebx),%ebx
0x08048f21 <+202>: sub $0x1,%esi
0x08048f24 <+205>: jne 0x8048f10 <phase_6+185>
0x08048f26 <+207>: add $0x44,%esp
0x08048f29 <+210>: pop %ebx
0x08048f2a <+211>: pop %esi
0x08048f2b <+212>: ret
---Type <return> to continue, or q <return> to quit---
End of assembler dump.
输入"p/x *(0x804c13c)@3)"打印出节点的数据,序号和写一个节点的地址,可以看到第六个节点的尾指针为0;
分析出是按照降序排列的之后按降序得到得节点顺序为"5 1 3 6 4 2"
测试一下!成功排雷!!
七、secret_phase
通过对phase_defused的汇编代码的查看,只有经过正确触发(且已经拆完前六个雷)才能打开隐藏炸弹,输入正确答案进行排雷
<phase_defused>的汇编代码如下:
Dump of assembler code for function phase_defused:
0x0804931b <+0>: sub $0x8c,%esp
0x08049321 <+6>: mov %gs:0x14,%eax
0x08049327 <+12>: mov %eax,0x7c(%esp)
0x0804932b <+16>: xor %eax,%eax
0x0804932d <+18>: cmpl $0x6,0x804c3cc //根据跳转逻辑,可以推测需要拆完六个雷才能打开隐藏函数
0x08049334 <+25>: jne 0x80493a8 <phase_defused+141>
0x08049336 <+27>: lea 0x2c(%esp),%eax
0x0804933a <+31>: mov %eax,0x10(%esp)
0x0804933e <+35>: lea 0x28(%esp),%eax
0x08049342 <+39>: mov %eax,0xc(%esp)
0x08049346 <+43>: lea 0x24(%esp),%eax
0x0804934a <+47>: mov %eax,0x8(%esp)
0x0804934e <+51>: movl $0x804a4a9,0x4(%esp) //按照x/s 0x804a4a9打印出来是%d %d %s
0x08049356 <+59>: movl $0x804c4d0,(%esp)
0x0804935d <+66>: call 0x8048870 <__isoc99_sscanf@plt>
0x08049362 <+71>: cmp $0x3,%eax //表明输入有三个参数,否则跳转到<+129>
0x08049365 <+74>: jne 0x804939c <phase_defused+129>
0x08049367 <+76>: movl $0x804a4b2,0x4(%esp) //打印出来是"DrEvil"
0x0804936f <+84>: lea 0x2c(%esp),%eax
0x08049373 <+88>: mov %eax,(%esp)
0x08049376 <+91>: call 0x8049084 <strings_not_equal>
0x0804937b <+96>: test %eax,%eax
---Type <return> to continue, or q <return> to quit---
0x0804937d <+98>: jne 0x804939c <phase_defused+129> //在这一段进行了比较,如果你输入的字符串不符合要求,就无法开启隐藏关卡
0x0804937f <+100>: movl $0x804a378,(%esp) //"Curses, you've found the secret phase!"
0x08049386 <+107>: call 0x8048800 <puts@plt>
0x0804938b <+112>: movl $0x804a3a0,(%esp) //"But finding it and solving it are quite different..."
0x08049392 <+119>: call 0x8048800 <puts@plt>
0x08049397 <+124>: call 0x8048f7d <secret_phase>
0x0804939c <+129>: movl $0x804a3d8,(%esp) 打印x/s 0x804a3d8得到的是 "Congratulations! You've defused the bomb!"
0x080493a3 <+136>: call 0x8048800 <puts@plt>
0x080493a8 <+141>: mov 0x7c(%esp),%eax
0x080493ac <+145>: xor %gs:0x14,%eax
0x080493b3 <+152>: je 0x80493ba <phase_defused+159>
0x080493b5 <+154>: call 0x80487d0 <__stack_chk_fail@plt>
0x080493ba <+159>: add $0x8c,%esp
0x080493c0 <+165>: ret
End of assembler dump.
需要查看几个地址的值
可以知道,第四关和第五关前两个都是输入整型,测试一下发现必须要在第四关(第五关不必要)输入的两个整形后面输入"DrEvil"才能成功触发
下面查看<secret_phase>的汇编代码:
Dump of assembler code for function secret_phase:
0x08048f7d <+0>: push %ebx
0x08048f7e <+1>: sub $0x18,%esp
0x08048f81 <+4>: call 0x80491bd <read_line> //先读入一行,返回值eax作为函数strtol@plt>的参数之一
0x08048f86 <+9>: movl $0xa,0x8(%esp) //a
0x08048f8e <+17>: movl $0x0,0x4(%esp) //4
0x08048f96 <+25>: mov %eax,(%esp)
0x08048f99 <+28>: call 0x80488e0 <strtol@plt> //将我们输入的数字当作字符串处理,然后转为十进制。简单地说就是输入什么十进制数参数就是那个十进制数本身
0x08048f9e <+33>: mov %eax,%ebx
0x08048fa0 <+35>: lea -0x1(%eax),%eax
0x08048fa3 <+38>: cmp $0x3e8,%eax
0x08048fa8 <+43>: jbe 0x8048faf <secret_phase+50> //根据跳转逻辑推测得到的输入的值小于等于0x3e8+1即小于等于1001
0x08048faa <+45>: call 0x8049196 <explode_bomb>
0x08048faf <+50>: mov %ebx,0x4(%esp) //a2
0x08048fb3 <+54>: movl $0x804c088,(%esp) //读出来的值为0x24,即36 第一个参数指针所指向的值为36
0x08048fba <+61>: call 0x8048f2c <fun7>
0x08048fbf <+66>: test %eax,%eax
0x08048fc1 <+68>: je 0x8048fc8 <secret_phase+75> //根据跳转逻辑推测fun7的返回值是0
0x08048fc3 <+70>: call 0x8049196 <explode_bomb>
0x08048fc8 <+75>: movl $0x804a294,(%esp) //"Wow! You've defused the secret stage!"
0x08048fcf <+82>: call 0x8048800 <puts@plt>
0x08048fd4 <+87>: call 0x804931b <phase_defused>
0x08048fd9 <+92>: add $0x18,%esp
---Type <return> to continue, or q <return> to quit---
0x08048fdc <+95>: pop %ebx
0x08048fdd <+96>: ret
End of assembler dump.
fun7的汇编代码如下:
Dump of assembler code for function fun7:
0x08048f2c <+0>: push %ebx
0x08048f2d <+1>: sub $0x18,%esp
0x08048f30 <+4>: mov 0x20(%esp),%edx //第一个参数,传的是一个地址0x804c088
0x08048f34 <+8>: mov 0x24(%esp),%ecx //第二个参数,我们所输入的那个数
0x08048f38 <+12>: test %edx,%edx
0x08048f3a <+14>: je 0x8048f73 <fun7+71> //如果传的地址所指向的值为0那么直接返回零
0x08048f3c <+16>: mov (%edx),%ebx
0x08048f3e <+18>: cmp %ecx,%ebx //如果*p<=n则跳转
0x08048f40 <+20>: jle 0x8048f55 <fun7+41>
0x08048f42 <+22>: mov %ecx,0x4(%esp)
0x08048f46 <+26>: mov 0x4(%edx),%eax
0x08048f49 <+29>: mov %eax,(%esp)
0x08048f4c <+32>: call 0x8048f2c <fun7>
0x08048f51 <+37>: add %eax,%eax //否则返回2*fun7(*(p+4),n)
0x08048f53 <+39>: jmp 0x8048f78 <fun7+76>
0x08048f55 <+41>: mov $0x0,%eax
0x08048f5a <+46>: cmp %ecx,%ebx
0x08048f5c <+48>: je 0x8048f78 <fun7+76> //*p<=n后跳转若*p=n的话则返回0
0x08048f5e <+50>: mov %ecx,0x4(%esp)
0x08048f62 <+54>: mov 0x8(%edx),%eax
0x08048f65 <+57>: mov %eax,(%esp)
0x08048f68 <+60>: call 0x8048f2c <fun7>
---Type <return> to continue, or q <return> to quit---
0x08048f6d <+65>: lea 0x1(%eax,%eax,1),%eax //如果*p<n的话,返回2*fun7(*(p+8),n)+1;
0x08048f71 <+69>: jmp 0x8048f78 <fun7+76>
0x08048f73 <+71>: mov $0xffffffff,%eax
0x08048f78 <+76>: add $0x18,%esp
0x08048f7b <+79>: pop %ebx
0x08048f7c <+80>: ret
End of assembler dump.
fun7对应的C++代码为:
int fun7(int *p,int n)
{
if(!p) return 0;
if(*p<n) return 2*fun7(*(p+8),n)+1;
else if(*p==n) return 0;
else return 2*fun7(*(p+4),n);
}
根据secret_phase的返回值为0得到输入的值与指针指向的值相等,即0x24,对应的十进制为36