实验目的:
通过二进制炸弹实验,熟悉汇编语言,反汇编工具objdump以及gdb调试工具。
实验内容:
1、炸弹实验第5关。
2、炸弹实验第6关。
实验过程:
第五关:
1、根据前几关的经验,进入bomb文件的gdb调试命令下,直接查看第五关的汇编代码。
2、直接回车可以显示余下的phase_5函数的代码,浏览完一遍phase_5函数的汇编代码后,并不能直接发现此代码的精髓所在,因此,我开始逐条分析phase_5函数的代码。
首先是栈帧准备,开辟32字节的空间:
接着加载%ebp-0x10处的有效地址间接存储到%esp+0xc处,也就是参数2存放的地方,同理,加载%ebp-0xc处的有效地址间接存储到%esp+0x8处,也就是参数1存放的地方。
接着看见了一串似曾相似的代码,老规矩,查看此地址处存放的数据,发现我们仍是输入两个整数。
接着把%ebp+0x8处存储的值传送给%esp,然后调用__isoc99_sscanf@plt函数。
3、往下接着看,比较了%eax和1的大小,若%eax大于1,则跳转到
4、接着把参数1传给%eax,并对%eax与0xf作按位与运算,也就是取出参数1的低四位。
5、接着把取出来的参数1的低四位与0xf作比较,若相等,则跳转到爆炸函数,否则,执行下一步,由此可见,参数1的低四位不为1111。
6、接下来对%ecx,%edx赋值,初始化为0,%ebx则存储地址0x804a1c0。
7、接下来,执行%edx加1操作,而后执行操作得到eax=*(ebx+4eax),由于之前得到%ebx存储地址0x804a1c0,并且此处以4*eax为步长,猜测0x804a1c0是一个数组的首地址,接着把存储在地址*(ebx+4eax)处的值与%ecx相加存储在%ecx中。
8、接着把%eax存储的值(应该是数组的序号)与0xf也就是15作比较,若不等于,则跳到
9、分析到这里,大概可以理清了,输入参数1,调用循环,而后每次从数组中取出一个数累加到%ecx中,且16次循环的过程中直到最后一个才能取到15,由此,我们打印出数组连续的16位。
另外我试着多打印几位看看数组里存储的到底是什么:
发现,其实这个数组里存储的刚好就只有16个有效元素,由此,推导出下面这个表。
数组序号和数组值相对应的表:
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
10 |
2 |
14 |
7 |
8 |
12 |
15 |
11 |
0 |
4 |
1 |
13 |
3 |
9 |
6 |
5 |
然后,根据上述推断则可以逆推出在数组里取到的值包括参数1在内的序列为:
5 |
12 |
3 |
7 |
11 |
13 |
9 |
4 |
8 |
0 |
10 |
1 |
2 |
14 |
6 |
15 |
又因为参数2为在数组里取到的值的累加值,所以参数2为:
12+3+7+11+13+9+4+8+0+10+1+2+14+6+15=115
也可以通过查看寄存器的值来得到参数2,通过以上分析,可以知道参数2被保存在%ecx中,在下图中可以看到%ecx中存储的值为115,和我求出来的一致,同时,也可以看到%eax和%edx存储的值都为15,与上述分析的也一致:
接下来测试一下5 、115是不是正确的通关密码:
到这里,我不由得想起上次课程老师讲过的一点,在调用函数的之后,%eax和1作比较,得出的结论是,输入参数的个数必须大于1,那我是不是不用局限于两个,测试一下,随便输入第三个参数,看看会怎样,很惊讶,加了第三个参数,我随便输入了32,并没有影响通关密码的正确性,那么,就可以猜测,第五关的密码,只要前两个正确,后面应该随便组合都行吧,有无数种可能,这里,我就不一一去检验了:
紧接着,我想到了另外一点,前面有让%eax的低四位和1111作比较,得出的结论是参数1的二进制低四位不能为1111,而我求出来的答案是5,5的二进制表示为0101,于是,我有个大胆的猜想,是不是,只要输入的参数1的二进制形式的低四位不为1111,或者说刚好为0101的组合都符合通关密码的条件呢,我检验一下10101也就是21看看对不对,惊喜又出现了,真的可以,所以,答案应该是无数种,这里同样不一一检验了:
附上phase_5函数的栈帧图:
第五关成功通过。
第六关:
1、老规矩,查看phase_6的汇编代码。
2、在粗略浏览完全部代码后,真的是不知所云,但还是看见了那么两个熟悉的函数名read_six_numbers和explode_bomb函数,由此,只能慢慢开始分析汇编代码,老实说,在画完栈帧图,一条一条过完全部指令后,我发现,单条指令是什么我知道,但综合所有的汇编代码,我真的是懵的,所以,我接着又重新再分析代码,仔细咀嚼。
首先是一些堆栈准备以及内存空间的开辟,这里申请了92字节的内存空间:
下图是phase_6函数的栈帧图:
3、接着加载了%ebp-0x30存储的有效地址到%esp+0x4处,然后调用了read_six_numbers函数,查看此函数的汇编代码,结合前面第二关的经验,可以得到此函数是通过调用__isoc99_sscanf@plt函数来读取六个整数的:
4、在调用read_six_numbers函数读取完六个整数后,执行了如下操作,分析得到读取的六个整数存储在栈帧中的位置如下图所示,所以,当对%ebp-0x30处的数据进行操作时,即可知道是在对参数1进行操作,下图红色框内的语句可以得到参数1要小于等于6,通过蓝框和红框内的语句可以得到第二个数和第一个数不相同,结合这一部分的整体代码,可以推出读取到的六个数之间不相等且都小于等于6:
5、接着查看后续代码,分析以后可以得到,此段代码主要实现把一个单链表的节点信息根据输入参数的值提取出来存储在%ebp-0x48到%epb-0x34的地址处,也就是按照一定的顺序这里是降序(下图中蓝线部分,对新的链表的第一个节点的值和第二个结点的值作了比较,得出第一个节点的值大于等于第二个节点的值)把链表各个节点的地址存储在栈帧中:
得出降序排列的语句:
推导过程图:
执行完这部分代码的部分栈帧图,当然,下图中的节点1或者节点6指的是根据输入参数选取出的节点,所以这里的节点1指的就是链表中存储最大值的那个节点,其它同理:
6、接下来的汇编代码则是根据上一步得到的降序排序得到的节点地址信息把链表恢复的这么一个过程:
7、所以,只要能得到以地址0x804c0c4为首地址存储的这个链表存储的数据得出即可以推出输入的六个参数:
8、得到六个结点分别存储的值为:
1 |
2 |
3 |
4 |
5 |
6 |
0x1a7 |
0x06c |
0x155 |
0x187 |
0x3bd |
0x255 |
降序排列后得:
5 |
6 |
1 |
4 |
3 |
2 |
0x3bd |
0x255 |
0x1a7 |
0x187 |
0x155 |
0x06c |
9、根据前面的推导过程可以得出输入的六个参数为5、6、1、4、3、2,检验正确性:
第六关成功通过。
总结:
通过本次实验真的真的让我读汇编代码的能力提升了很多很多,虽然读汇编代码整体性的把控不是很好,但较以前真的大大提升了,也掌握了利用gdb调试的一些基本方法,颇有收获。