《Hardware/Software Interface》实验二 是拆炸弹,也是实验里面比较有意思的一个实验,实验能帮助我们加深理解函数的调用过程,提升用GDB调试代码的能力,话不多说,把实验内容附上。
附实验环境:
64位Linux操作系统
实验总共有六个阶段,难度也是在逐步加大,每个阶段都要求你输入一些信息,只有提供恰当合适的正确信息,才会拆炸弹成功进入下一个阶段,那么如何获得正确的信息,就需要用GDB调试在汇编代码中获得线索。本文主要介绍每个阶段的正确输入的探索过程,对于GDB工具的使用大家可以另找资料来学习。
第一关的C代码如下,只有三行(其实每关也都只有三行),流程就是读入输入的数据然后数据处理,若是“正确”的输入,代码就会执行到 phase_defused();若不是“正确”的输入,炸弹就会爆炸,然后程序终止。好的,就让我们来找到本关的“正确”输入。
input = read_line(); /* Get input */
phase_1(input); /* Run the phase */
phase_defused(); /* Drat! They figured it out!
首先我们要用工具GDB调试可执行程序bomb, 之后在phase_1 函数这里加入breakpoint,运行bomb程序。
你会要求输入信息,第一次我们可以随便输入,比如:“1111122222”,之后会到断点phase_1,此时disas 查看该函数的汇编代码,汇编代码如下(截图效果好点):
第一行的命令让rsp栈顶指针减0x8,是给当前frame更多的栈空间来存储数据,phase_1要解决不用关注这行重点是后面的mov和callq 。
callq调用了方法strings_not_equal 显然是判断两个字符串是否相同,那么比较的两个参数是什么呢?先看一下现在的寄存器情况:
X86-64下有16个64位寄存器,其中%rdi、%rsi、%rdx,%rcx、%r8、%r9用作传递函数参数,分别对应第1个参数、第2个参数直到第6个参数(摘自网络)。那么说现在我们的输入,应该是作为调用函数时用到的参数保存在了寄存器%rdi中(如有不对请指出),查看对应的值确实也是如此:
另一个比较的值存到了%esi中(存储值得内存逻辑地址为0x401af8)
继续往后分析,string_not_equal 的返回结果会保存到eax寄存中,0表示相等,1表示不想等,汇编代码:test %eax,%eax 来设置计算eax与自己本身逻辑与之后的值,若为0则设置0标识位。 因此只有当string_not_equal 返回0 即两个字符串相等时程序才会跳过执行函数 explode_bomb, 本关才会通过。 那么现在只需要查看 地址0x401af8的存储内容就可以了。
这一关的考点是循环。跟其它关一样,也是要先输入信息,再判断是否引爆炸弹,直接进入汇编代码:
其中的read_six_numbers 的汇编代码如下:
其中<+51>,<+54>两行表明 读入的number数目必须大于5,否则就会引起炸弹;同时读入的数据会存到phase_2的frame栈中。
读取完前六个数字之后 我们回到phase_2的汇编代码中,该代码较长,但是我们只需要看其中的几个重点的指令行。<+32>行将rsp 与 rbp值统一,然后<+35> r13的值为一个地址,地址为rsp+0xc. <+65>将rbp的值加0x4,<+69>比较r13与rbp的值,若不相同就会跳回<+46>因此可以判断出这个循环要执行三遍。
<+49>和<+52>,比较了当前rbp与rbp+0xc 两个地址对应内容值,若不想等就会引发炸弹,因此得出结论6个number, 第一与第四个相等,第二与第五个相等,第三与第六个相等。
同时还看到r12d寄存器累加了前三个元素的值,若前三个相加为0 也会引起爆炸,这也要注意。
通过以上分析,我们可以得到本次该输入的为 1 2 3 1 2 3(类似即可)
本关的考点是switch语句。phase_3的汇编代码如下:
此关相对简单,首先需要输入两个数据,少于两个数据会引爆炸弹,其次是根据switch的number,第二输入数要和汇编代码中数相匹配,假设输入的第一个number为2,那么运行时会调到代码行<+64>处,0xd6 对应的10进制数为214. 此时就应当输入 2 214. 当然我们也可以选择其他的switch 分支。
本关的考点是递归。phase_4的汇编代码如下:
有了前几关的铺垫,这一关也变得非常简单。
<+24>的判断表明了这一关只需要输入一个数;<+29>表明我们输入的数必须大于0;<+45>调用了函数 func4, <+50>表明了函数fun4的返回值必须和0x37也就是55相等。满足上述情况即可拆炸弹成功。
那就看一下func4的汇编代码:
我们之前输入的数保存到了edi寄存器中,func4里面会判断edi与1 的大小,若大于1会将edi的值减1和减2后分别再调用func4函数,仔细观察可推出这就是求斐波那契数的函数:F(x) = F(x-1)+F(x-2),F(0)=0,F(1)=1. F(9)=55.因此我们要输入的数为 9.
本关的考点是数组和指针。phase_5的汇编代码如下:
<+29>的判断表明了我们输入的参数数量大于1;<+39~+53>表明我们输入的一个参数的二进制后四位不能为1111(15),也可以相称第一个参数的值要小于15.
<+65~+82>是一个循环,寄存器edx初值定为0,每次循环加1,根据<+88>行的cmp 0xc, %edx 可以得出,循环必须要走12遍;同时ecx寄存器不断的累加数,累加的数来源要看<+70>,每次把一个数的值存到eax寄存器中 并且作为下次取值的索引;<+93>表明我们最终ecx寄存器的累加值要和我们的第二个参数相同,若相同,即可拆除炸弹。
问题的关键在于循环,循环必须循环12次,同时循环终止的条件是eax寄存器的值为15.我们看一下地址0x401ba0存的是什么数据:
一个大小为15的数组,如下:
数组 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
value | 10 | 2 | 14 | 7 | 8 | 12 | 15 | 11 | 0 | 4 | 1 | 13 | 3 | 9 | 6 |
那么就是说数组index为6时对应元素为15,最后一次加了15,那么上一次就加了6,依此类推加12次的结果为: 15+6+14+2+1+10+0+8+4+9+13+11 =93. 第一次加的数为1,其下标为7.
因此本关的 输入为 7 93.