山东大学计算机系统原理实验二进制炸弹拆除
要求根据反汇编指令分析程序运行需要的参数,即需要正确的输入,以拆除 炸弹。根据通过的关卡数目评判最终的实验得分。(1) 熟悉 MIPS 指令集;(2) 根据反汇编程序可以分析程序的功能和执行流程;(3) 熟悉 GDB 调试工具,帮助程序理解。
实验要求
要求根据反汇编指令分析程序运行需要的参数,即需要正确的输入,以拆除 炸弹。根据通过的关卡数目评判最终的实验得分。
实验目的
(1) 熟悉 MIPS 指令集;
(2) 根据反汇编程序可以分析程序的功能和执行流程;
(3) 熟悉 GDB 调试工具,帮助程序理解。
实验软件和硬件环境
用 GDB 调试工具
操作系统:Linux Ubuntu
实验原理和方法
通过阅读MIPS 指令集找到炸弹爆炸的炸点,并绕开这个炸点避免炸弹,最后成功拆除炸弹。运用GDB调试工具,分析汇编代码,了解寄存器的内容,避免程序跳转到“explode_bomb”程序段的地方。阅读bomb.s文件,对拆除炸弹也有帮助。
实验步骤
准备工作:
首先打开一个终端启动qemu-mips
打开另一个终端利用gdb-multiarch进行调试,并依次输入
(gdb) set arch mips
(gdb) set endian little
(gdb) target remote localhost:12345
这样就可以继续利用gdb调试
拆除 phase_1
jal 0x401cf8 <strings_not_equal>表示跳转到函数<strings_not_equal>,在这设断点。beqz v0,400da4 <phase_1+0x38>表示如果v0=0则跳转到炸弹2。
设置断点0x00400d90(其实在0x00400d8c),使用ni命令进行但不调试,使用 i r $a0 和 i r $a1 可以查看当前 a0 和a1 的寄存器状态。x /16c $a1查看从寄存器$a1开始的16个字节的内容,a0 里面保存着输入的字串。
由此可知应该输入字符串Let’s begin now!
拆除phase_2
说明要输入六个数,
要将v0与v1比较,在此处设置断点,用p $v1,p $v0查看,得v1是输入的数,要求v1和v0相等,所以第一个数是1。
此处造成了一个循环,
要想避免爆炸,要求a0和v0相等,在此处设断点,通过p $a0,p $v0查看,可得v0为输入的数,要求输入的数和a0的数相等,
所以输入为1 8 8 16 0 0
拆除phase_3
在bomb.s中找到炸弹3,应该输入整数、字符、整数。
slti v0,v0,3作用为,当v0小于3时v0为1,等于3时为0,如果想要跳过炸弹,不能让v0小于3,经过在此设置断点多次调试可知,v0为输入的数据的个数(炸点1)
跳转到phase_3+116
此时要求v0小于8,使v1为1,防止跳转到爆炸处。
查$s8+36~$s8+44存储的内容,为输入的三个数据,
可知v0处为第一个输入的值,66为输入的102的十六进制,所以第一个输入要小于8(炸点2)
jr v0表示跳转,跳转位置与第一个参数有关,通过x $v0查询
可知跳转到phase_3+632位置
读此处可知lw v0,36(s8)次数存的是输入的第三个参数,用p $v1查看得,v1为8,是学号的最后一位。相乘后v1=v1*v0。v0被赋值为824
同时要想避免爆炸,要使v1和v0相等,输入不同的第一个参数,v0为不同值,所以通过反复尝试,找到能整除学号最后一位的v0是824,824/8=103,第三个输入为103,调试得第一个输入为7。(炸点3)
跳到phase_3+800
读此处可知v0为第二个输入的字符,v1为s8+32的内容,由phase_3+632和phase_3+636可知,v1为98,ASCII码为98的字符是b,所以第二哥输入为b。(炸点四)。
拆除phase_4
由bomb.s可知要输入一个整数
这样就会跳过炸点1。
在phase_4+144设断点,通过调试可知v0为学号的最后一位
通过v0与0x1的按位与,如果最后一位为偶数则v0变为0,否则为1,最后一位为8,所以v0为0,需要跳转到phase_4+224
进入到递归函数
这个递归函数相当于一个计算斐波那契数列,计算第a0个斐波那契数列(从0计数),a0为输入的数。
最后一个要保证 v0和v1相等(炸点二),v0为13,v1为输入的数进入递归函数最后的返回值,即第a0个斐波那契数。
拆除phase_5
看bomb.s文件无法得出输入的类型,通过在0x004013fc处设置断点查看可知
S8+72处存的是输入的字符串。下面来到第一个炸点:
要求v1与v0相等,都为6。通过设断点调试可知,v1为输入字符串的长度,所以输入的字符串长度应为6。
接下来进入了一个循环
Sw zero,24(s8),表示初始化该位置数为0,由phase_5+200和phase_5+201可知,v0为6时循环结束,所以循环要进行6次。在phase_5+88处设置断点,看v1存的是什么。
此时是第二次循环,ASCII码98对应的是b也是输入的字符串的第二个字符。到phase_5+100执行完之后,v1先后和0xff,0xf按位与运算。得到的是该字符ASCII码的十六进制的后4位,即0010,十进制位2,v1为2。
在phase_5+160处有一次加载v1值,是经过了
0x0040147c <+148>: lui v0,0x41
0x00401480 <+152>: addiu v0,v0,12524
0x00401484 <+156>: addu v0,v1,v0
这三个步骤,通过设置断点调试
V1由2变成了114,转换成字符为r,v0存储的字符串为isrveawhobpnutfg,从0开始计数2所对应的字符正好为r。所以该循环的作用是将6个字符依次的ASII码的十六进制的后四位,转换成十进制后,在isrveawhobpnutfg找到对应的字符替代原来的字符。
由以上可知要使a1和a0相等,在phase_5+232处设置断点调试
由此可知a0为输入的字符经过循环转换后的字符串,a1里是存的字符串giants,要想避免第二个炸点,要使v0为0,即两者相同,通过查ascii码表可知应该输入opukma
拆除phase_6
由此处可知要输入6个数字,(输入的初始数为1 2 3 4 5 6)
以上是一个双重循环
在phase_6+108处有一个v0的比较,要想避免爆炸要使v0小于7(炸点一),在此处设置断点,看v0为何值
由调试结果可知v0为输入的第i个数(具体i由循环次数决定),当v0小于7,比较后v0为1,未发生跳转,避免爆炸。
在phase_6+148处有一个比较,在此处设置断点调试
此处v0为输入的第i个数(具体i由循环次数决定),当v0大于0时发生跳转,避开了炸点(炸点二)。
在从phase_6+176到phase_6+292的内部循环,相当于j从i+1开始循环到j等于6停止。在phase_6+264处有一个比较,要使v0不等于v1才能跳转避开炸点(炸点三),在此处设置断点调试
由此可知v1为第i个数,v0为输入的第j个数比较。
由这个双重循环可知输入的数要大于0小于7,同时输入的数不能和它后面输入的数相等。
这一部分又是一个双重循环。外部循环是i从0开始,为6时停止循环,内部循环是从1开始。循环停止条件在phase_6+444处有一个判断,在此处设置断点调试,
由此可知v0为内部的循环变量j,v1为输入的第i个数,也就是当j小于第i个输入的数时要继续循环。其中内部循环有一句
通过后面调试知道,这一句的作用为指向下一个节点。也知道了s8+32为第一个节点的位置,在phase_6+478处在内部循环结束后,读取了节点的值v1,在此处设置断点依次调试(由于我输入的数谁1 2 3 4 5 6,所以依次出现节点1 2 3 4 5 6)
由此可知每个节点都存了一个数字。这个双重循环的作用是,将输入的数对应位置节点的数取出,放到一个数组中。
此处是一个一重循环。
由此句猜测,此处可能涉及到节点的连接。
在phase_6+580和phase_6+612处设置断点调试
通过前两次循环可知,phase_6+580处循环中v1从第2个节点开始,v0为v1的前一个结点,也就是说
这里的连接是,将取出的结点依次连接,在phase_6+612处v0指向后一个结点为下一轮循环做准备。所以这个循环的作用就是将上一个循环从原链表取出数得到的数组,重新连接成一个链表。
为了查看此处取得值是什么,在次设置断点调试。
此处v0为学号的最后一位。
此处有一个比较若v0即学好最后为8,则v0变为0发生跳转
此处有一个v0和v1的比较,在phase_6+824处设置断点调试
由此可知v1为当前结点的值,v0为后一个节点,如果后一个结点小于前一个结点则,比较后v0为1会爆炸,所以使结点的值为升序(炸点四)。根据前面对每个结点的值的调试,找到正确的输入为5 1 3 6 2 4,就是对应的第5、1、3、6、2、4结点的值使升序。
拆除隐藏的炸弹
在bomb.s文件中查找phase会发现下图
于是查找phase_defused发现
这个隐藏炸弹,通过输入的比较发现要输入%d %s,而phase_4输入为%d所以猜测可能在phase_4输入时加一个字符串,
为保证进入隐藏炸弹,要保证,输入的字符串要和phase_defused里的相等,所以在此处设置断点调试
a0为输入的字符串,a1为phase_defused中的字符串,所以想进入要使a1和a0相同
成功找到隐藏的炸弹。
要输入一个数字
在此处调试可知v0为输入的数字,v0-1<1001才能避免爆炸(炸点1)
然后进入了一个递归函数fun7中
会发现fun7中有许多v1,v0的比较,输入123,通过调试,和读语句可知,
在fun7+32处a1为输入,a0和a1为将要比较的节点元素,fun7+72处v0为输入的数,v1为结点的元素。多次递归后发现,类似与一个二叉搜索树。如果大于则向右孩子,如果小于则向左孩子。通过读secret_phase
可知,要经历三次向右查询,使v0变成111即十进制的7,才能避免爆炸,
输入123可以找到结点36,50,107,1001。由上图可知,只要有向左孩子的,即小于节点元素的数,都会爆炸。所以再次调试输入1001。
第一个节点为36,输入1001,比36大
在fun7+172处查看v0发现v0向下一个节点即50
再下一个节点为107
最后一个节点1001,与输入相同
跳转
在跳转
次数就要进行递归中归的部分,统计v0值,因为在输入大于节点元素时在fun7+152处不跳转,为了查v0值,在fun7+200处设置断点调试。
在secret_phase中
在secret_phase+160处设置断点调试
成功拆除
提示
上面给大家演示了拆除炸弹 1 的过程,这也是一个最简单的炸弹。接下来的 5 个炸弹难度逐级递增,大家不要拘泥于代码,死读代码;要结合 GDB 调试器, 查看内存以及各个寄存器的值,这样对于绕过一些棘手的函数很有帮助,可以让 大家切中炸弹爆炸条件的要害进行分析。GDB 的具体使用也不止上面所说的几 个命令,其他的希望大家可以自行上网搜索,掌握 GDB 的其他命令。GDB 调试 器可以在拆除炸弹的过程中给予你很大的帮助,但是仍希望你将它当作一个辅助 的工具来使用,帮助你更好的了解与汇编代码有关的内容,对汇编代码的理解是 该实验的重点。
有时候不要纠结于代码,可以借助GDB调试器,查看内存以及各个寄存器的值,可以方便了解各种函数,也可以直接在跳转开炸点处设置断点调试,可以更直观的找到拆除的方法。善用GDB 汇编调试指令x查看内存地址信息,并且要记住/x按十六进制格式显示变量;/d按十进制格式显示变量;/c按字符格式显示变量;/s 按字符串格式显示变量。在刚开始不知道如何改变格式显示变量时,会多不少麻烦。查看寄存器的内容,更好理解汇编语言产生的作用。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)