二进制炸弹-山东大学计算机系统实验

@拆炸弹-反汇编 计算机系统(组成)实验)

二进制炸弹-山东大学计组实验

计算机系统实验花了两周终于搞完了,趁考试周的闲暇写写思路-
MIPS32位架构

phase1

作为实验的入门题,题目设计者给我们展示了一下如何拆这个炸弹,首先插一个断点(b *xxxx)
插断点试运行``
单步调试 si,利用x 或r i命令 查看寄存器内的值,发现 $a0存放的是输入的字符串
查看寄存器

发现应与 a 0 a0处的值相等才不会爆炸,查看 a0的值
答案
至此我们得到phase1的答案 Let’s begin now!

phase2

第二关考察的是一个简单的循环语句,disas查看phase2代码发现这是一个按逆序依次取学号最后一位的阶乘
我们查看phase_2处代码,所列代码为取学号的过程,学号首地址-32660(gp)+12个字节也就是学号的最后一位,放到 v 0 v0,与上一次比较过输入的数 a0相乘,将结果放入$a0,(注意mflo)然后与下一个输入的数作比较。

   0x00400e3c <+128>:   li      v1,12
   0x00400e40 <+132>:   lw      v0,24(s8)
   0x00400e44 <+136>:   nop
   0x00400e48 <+140>:   subu    v0,v1,v0
   0x00400e4c <+144>:   lw      v1,-32660(gp)
   0x00400e50 <+148>:   sll     v0,v0,0x2
   0x00400e54 <+152>:   addu    v0,v1,v0
   0x00400e58 <+156>:   lw      v0,0(v0)
   0x00400e5c <+160>:   nop
   0x00400e60 <+164>:   mult    a0,v0
   0x00400e64 <+168>:   mflo    a0
   0x00400e68 <+172>:   lw      v0,24(s8)
   0x00400e6c <+176>:   nop

就这么循环六次

phase3

第三关就是一个简单的switch结构,我们可以看到这一关的炸弹极多,进入scanf函数发现输入格式为"%d %c %d ",所以要输入两个数和一个字符。

   0x00400f5c <+136>:   sll     v1,v0,0x2
   0x00400f60 <+140>:   lui     v0,0x40
   0x00400f64 <+144>:   addiu   v0,v0,10124
   0x00400f68 <+148>:   addu    v0,v1,v0
   0x00400f6c <+152>:   lw      v0,0(v0)
   0x00400f70 <+156>:   nop
   0x00400f74 <+160>:   jr      v0

我们发现,程序是以输入的第一个数为基址进行switch跳转的,并且第一个数在[0,8)之间,我们以第一个数为4为例,第三个数要和学号最后一位相乘结果等于该炸弹处给的值,所以要根据学号最后一位还进行选关,最后一位为零的同学只能选择第三的炸弹,也就是第一个数字只能是2.第二个字符也是取决于所选的炸弹的,以输入的第二个数字是4为例,

   0x00401088 <+436>:   li      v0,111
   0x0040108c <+440>:   sb      v0,32(s8)
   0x00401090 <+444>:   lw      v0,-32660(gp)
   0x00401094 <+448>:   nop
   0x00401098 <+452>:   lw      v1,44(v0)
   0x0040109c <+456>:   lw      v0,36(s8)
   0x004010a0 <+460>:   nop
   0x004010a4 <+464>:   mult    v1,v0
   0x004010a8 <+468>:   mflo    v1
   0x004010ac <+472>:   li      v0,228
   0x004010b0 <+476>:   beq     v1,v0,0x4011dc <phase_3+776>
   0x004010b4 <+480>:   nop
   0x004010b8 <+484>:   jal     0x4021f0 <explode_bomb>
   0x004010bc <+488>:   nop
   0x004010c0 <+492>:   lw      gp,24(s8)
   0x004010c4 <+496>:   b       0x4011f8 <phase_3+804>

这里又出现了-32660(gp),不过这里是加44后取的是学号最后一位放到 v 1 v1中,然后与输入的第三个数 v0相乘与一个立即数作比较,这里我们就发现,由于 v 0 v0 v1都是整数,所以选择分支即输入第一个数要结合自己的学号综合分析。
接着往下看,

   0x004011f8 <+804>:   lb      v0,40(s8)
   0x004011fc <+808>:   lb      v1,32(s8)
   0x00401200 <+812>:   nop
   0x00401204 <+816>:   beq     v1,v0,0x401218 <phase_3+836>
   0x00401208 <+820>:   nop

我们在之前的代码可以看到,第一行就是向32(s8)内存入一个字符,它的ASCII码为111,也就是’o’,40(s8)为输入的第二个字符的地址,所以答案已经很明显了。
出题人在这里也留下了一个坑,就是不是所有的分支都能通过。

phase4

这一关是一个函数递归模型,phase3中有一个判定学号最后一位奇偶的分支,
0x00401340 <+132>: lw v0,-32660(gp) 0x00401344 <+136>: nop 0x00401348 <+140>: lw v0,44(v0) 0x0040134c <+144>: nop 0x00401350 <+148>: andi v0,v0,0x1 0x00401354 <+152>:c andi v0,v0,0xff 0x00401358 <+156>: beqz v0,0x40139c <phase_4+224>
我们可以看出,若$v0的值为0,也就是学号最后一位为偶数,跳转至偶数段,我们以偶数段为例分析

   0x0040139c <+224>:   lw      v0,24(s8)
   0x004013a0 <+228>:   nop
   0x004013a4 <+232>:   move    a0,v0
   0x004013a8 <+236>:   jal     0x401230 <func4>
   0x004013ac <+240>:   nop
   0x004013b0 <+244>:   lw      gp,16(s8)
   0x004013b4 <+248>:   move    v1,v0
   0x004013b8 <+252>:   li      v0,13
   0x004013bc <+256>:   beq     v1,v0,0x4013d0 <phase_4+276>
   0x004013c0 <+260>:   nop

24(s8)为我们输入的数,进入func4函数,我们得到的返回值要为13,这就很明确了,让我们来查看func4的内容

(gdb) disas func4
Dump of assembler code for function func4:
   0x00401230 <+0>:     addiu   sp,sp,-40
   0x00401234 <+4>:     sw      ra,36(sp)
   0x00401238 <+8>:     sw      s8,32(sp)
   0x0040123c <+12>:    sw      s0,28(sp)
   0x00401240 <+16>:    move    s8,sp
   0x00401244 <+20>:    sw      a0,40(s8)
   0x00401248 <+24>:    lw      v0,40(s8)
   0x0040124c <+28>:    nop
   0x00401250 <+32>:    slti    v0,v0,2
   0x00401254 <+36>:    bnez    v0,0x40129c <func4+108>
   0x00401258 <+40>:    nop
   0x0040125c <+44>:    lw      v0,40(s8)
   0x00401260 <+48>:    nop
   0x00401264 <+52>:    addiu   v0,v0,-1
   0x00401268 <+56>:    move    a0,v0
   0x0040126c <+60>:    jal     0x401230 <func4>
   0x00401270 <+64>:    nop
   0x00401274 <+68>:    move    s0,v0
   0x00401278 <+72>:    lw      v0,40(s8)
   0x0040127c <+76>:    nop
   0x00401280 <+80>:    addiu   v0,v0,-2
   0x00401284 <+84>:    move    a0,v0
   0x00401288 <+88>:    jal     0x401230 <func4>
   0x0040128c <+92>:    nop
   0x00401290 <+96>:    addu    v0,s0,v0
   0x00401294 <+100>:   b       0x4012a0 <func4+112>
   0x00401298 <+104>:   nop
   0x0040129c <+108>:   li      v0,1
   0x004012a0 <+112>:   move    sp,s8
   0x004012a4 <+116>:   lw      ra,36(sp)
   0x004012a8 <+120>:   lw      s8,32(sp)
   0x004012ac <+124>:   lw      s0,28(sp)
   0x004012b0 <+128>:   addiu   sp,sp,40
   0x004012b4 <+132>:   jr      ra
   0x004012b8 <+136>:   nop

不难看出这是一个斐波那契数列的递归函数,这里给出c++代码

int func4(int a)
{
	if(a<2)return 1;
	return func(a-1)+func(a-2);
}

若要返回值为13,则输入应为6。这里回过头来看为奇数的情况,这里要求返回值为8,哦,破案了。phase_4就是偶数输入6,奇数输入5.

phase5

这一关是一个字符串转换算法,也不算算法. 先上炸弹段代码

   0x004014cc <+228>:   addiu   a1,v0,10160
   0x004014d0 <+232>:   jal     0x401cf8 <strings_not_equal>
   0x004014d4 <+236>:   nop
   0x004014d8 <+240>:   beqz    v0,0x4014e8 <phase_5+256>

这里和phase_1很类似,我按照相同的办法很轻松得知目标为"giants"但这次比较的不是我们输入的字符,接着来读程序,我们发现一个循环,在这个循环里依次取我们输入的字符,并且依次向$a0所在地址依次放字符,读到这些我们就能发现,这个作比较字符和我们输入的字符有某种对应关系,

   0x00401440 <+88>:    lb      v1,0(v1)
   0x00401444 <+92>:    nop
   0x00401448 <+96>:    andi    v1,v1,0xff
   0x0040144c <+100>:   andi    v1,v1,0xf
   0x00401450 <+104>:   sll     v0,v0,0x2
   0x00401454 <+108>:   addiu   a0,s8,24
   0x00401458 <+112>:   addu    v0,a0,v0
   0x0040145c <+116>:   sw      v1,12(v0)
   0x00401460 <+120>:   lw      a0,24(s8)
   0x00401464 <+124>:   lw      v0,24(s8)
   0x00401468 <+128>:   nop
   0x0040146c <+132>:   sll     v0,v0,0x2
   0x00401470 <+136>:   addiu   v1,s8,24
   0x00401474 <+140>:   addu    v0,v1,v0
   0x00401478 <+144>:   lw      v1,12(v0)
   0x0040147c <+148>:   lui     v0,0x41
   0x00401480 <+152>:   addiu   v0,v0,12524
=> 0x00401484 <+156>:   addu    v0,v1,v0
   0x00401488 <+160>:   lb      v1,0(v0)

我们可以看出<+96><+100>处的代码将输入的字符取后四位并存放在<+116>12(v0)处,并再<+156>以v1作为偏移量取值,想知道$v0基址内的具体内容,利用x/16c命令得到具体内容

(gdb) si
0x00401484 in phase_5 ()
(gdb) x/16c $v0
0x4130ec <array.3607>:  105 'i' 115 's' 114 'r' 118 'v' 101 'e' 97 'a'  119 'w' 104 'h'
0x4130f4 <array.3607+8>:        111 'o' 98 'b'  112 'p' 110 'n' 117 'u' 116 't' 102 'f' 103 'g'

这里符合我之前的猜想,我们得到了密码数组,c[16]={‘i’,s,r,v,e,a,w,h,o,b,p,n,u,t,f,g};我们利用这个密码可以推出输入字符的后四位大小依次为15 0 5 11 13 1.取ASCII码表中可以找到很多组答案,其中一组为opekma
哦~,答案貌似又出来了

phase6

链表

   0x00401500 <+0>:     addiu   sp,sp,-96
   0x00401504 <+4>:     sw      ra,92(sp)
   0x00401508 <+8>:     sw      s8,88(sp)
   0x0040150c <+12>:    move    s8,sp

函数的头就是那么吓人,整整开辟了96的栈空间,但是我们仍要带着“一切长代码都是纸老虎”的观点去啃它。
这个phase里面有很多的循环,先来看第一个循环,这里循环里还套着一个循环,

   0x0040156c <+108>:   slti    v0,v0,7
   0x00401570 <+112>:   beqz    v0,0x40159c <phase_6+156>
   0x00401594 <+148>:   bgtz    v0,0x4015a8 <phase_6+168>
	内循环:
   0x004015c0 <+192>:   lw      v0,28(s8)
   0x004015c4 <+196>:   nop
   0x004015c8 <+200>:   sll     v0,v0,0x2
   0x004015cc <+204>:   addiu   v1,s8,24
   0x004015d0 <+208>:   addu    v0,v1,v0
   0x004015d4 <+212>:   lw      v1,12(v0)
   0x004015d8 <+216>:   lw      v0,24(s8)
   0x004015dc <+220>:   nop
   0x004015e0 <+224>:   sll     v0,v0,0x2
   0x004015e4 <+228>:   addiu   a0,s8,24
   0x004015e8 <+232>:   addu    v0,a0,v0
   0x004015ec <+236>:   lw      v0,12(v0)
   0x004015f0 <+240>:   nop
   0x004015f4 <+244>:   bne     v1,v0,0x401608 <phase_6+264>
   0x004015f8 <+248>:   nop
   0x004015fc <+252>:   jal     0x4021f0 <explode_bomb>
   0x00401600 <+256>:   nop
   0x00401604 <+260>:   lw      gp,16(s8)
   0x00401608 <+264>:   lw      v0,24(s8)
   0x0040160c <+268>:   nop
   0x00401610 <+272>:   addiu   v0,v0,1
   0x00401614 <+276>:   sw      v0,24(s8)
   0x00401618 <+280>:   lw      v0,24(s8)

外循环判定的是输入的数要在(0,7)之间,而内循环判定的是当前的数与之后输入的数都不相同,所以第一个循环的意思就是输入的数只能是1 2 3 4 5 6这六个数的全排列。
再来看第二个循环,在这里出现了一个特殊的数据结构node,出题人给我们提示这是一个链表了

   0x004016b0 <+432>:   lw      v1,12(v0)
   0x004016b4 <+436>:   lw      v0,24(s8)
=> 0x004016b8 <+440>:   nop
   0x004016bc <+444>:   slt     v0,v0,v1
   0x004016c0 <+448>:   bnez    v0,0x401678 <phase_6+376>

v 1 v1是我们输入的数, v0为节点号,对应输入的六个数也为123456,初始状态也是按照123456的顺序排列的,将六个节点遍历得到的第一个不小于$v1的数,也就是相等的节点,存入36(v0)
下一个循环只有五次,因为连接六个节点只需要五次

   0x00401728 <+552>:   lw      v0,28(s8)
   0x0040172c <+556>:   nop
   0x00401730 <+560>:   sll     v0,v0,0x2
   0x00401734 <+564>:   addiu   v1,s8,24
   0x00401738 <+568>:   addu    v0,v1,v0
   0x0040173c <+572>:   lw      v1,36(v0)
   0x00401740 <+576>:   lw      v0,32(s8)
   0x00401744 <+580>:   nop
   0x00401748 <+584>:   sw      v1,8(v0)
   0x00401750 <+592>:   nop
   0x00401754 <+596>:   sll     v0,v0,0x2
   0x00401758 <+600>:   addiu   v1,s8,24
   0x0040175c <+604>:   addu    v0,v1,v0
   0x00401760 <+608>:   lw      v0,36(v0)
   0x00401764 <+612>:   nop
   0x00401768 <+616>:   sw      v0,32(s8)

此为链接过程,取出两节点地址 v 1 v1 v0,然后将 v 1 v1的地址放到 v0的next也就是8( v 0 ) , v0),然后更新 v0的节点,以便下一次链接这样就完成了一次连接过程。重复五次就得到了完整的链表。
再来看下一个循环,

   0x004017b4 <+692>:   lw      v0,-32660(gp)
   0x004017b8 <+696>:   nop
   0x004017bc <+700>:   lw      v0,44(v0)
   0x004017c0 <+704>:   nop
   0x004017c4 <+708>:   andi    v0,v0,0x1
   0x004017c8 <+712>:   andi    v0,v0,0xff
   0x004017cc <+716>:   beqz    v0,0x401818 <phase_6+792>

其中又出现了和phase_4中一样的代码,也是根据奇偶进行分支,我们这次以奇数为例,

   0x004017d4 <+724>:   lw      v0,32(s8)
   0x004017d8 <+728>:   nop
   0x004017dc <+732>:   lw      v1,0(v0)
   0x004017e0 <+736>:   lw      v0,32(s8)
   0x004017e4 <+740>:   nop
   0x004017e8 <+744>:   lw      v0,8(v0)
   0x004017ec <+748>:   nop
   0x004017f0 <+752>:   lw      v0,0(v0)
   0x004017f4 <+756>:   nop
   0x004017f8 <+760>:   slt     v0,v1,v0
   0x004017fc <+764>:   beqz    v0,0x401854 <phase_6+852>
   ......
   0x00401854 <+852>:   lw      v0,32(s8)
   0x00401858 <+856>:   nop
   0x0040185c <+860>:   lw      v0,8(v0)
   0x00401860 <+864>:   nop
   0x00401864 <+868>:   sw      v0,32(s8)

取出链表首节点 v 1 v1和下一个节点 v0,要让 v 1 > = v1>= v0才能跳过炸弹,之后更新23(s8)的值为下一个节点,这个循环的意思就是判断链表是不是非递增的,偶数情况是判断是否为非递减。现在目标很明确了,只需要知道各个节点里面存的element是多少就行了,在循环中分别查看得

node
node1 0xfd
node2 0x2d5
node3 0x12d
node4 0x3e5
node5 0xd4
node6 0x1b0

所以偶数答案为513624,奇数为426315

secret_phase

要想进入sercet需要花上好会儿功夫,可以看到main函数下,每个phase后面都有phase_defused,我们进入phase_defused查看,发现 jal 401990<secret_phase>

   0x00402284 <+32>:    lw      v1,12864(v0)
   0x00402288 <+36>:    li      v0,6
   0x0040228c <+40>:    bne     v1,v0,0x402374 <phase_defused+272>

其中$v1为闯过的关数,就是说只有在完成第六关才会真正进入,接着往下看又发现ssanf函数,其输入格式为” %d %s“,要想获得这个格式的字符串只能在phase_4处构造,因为那里已经有了一个%d.现在只需要找到这个%s就可以进入serect了。函数又一次进入了<string_not_equal>,和phase_1类似,得到字符串应为"austinpowers",在第四关输入顺利进入secret.
现在进入serect,其sscanf格式为%d,只需要输入一个整数,

   0x004019f4 <+100>:   addiu   v0,v0,-1
   0x004019f8 <+104>:   sltiu   v0,v0,1001
   0x004019fc <+108>:   bnez    v0,0x401a10 <secret_phase+128>
   0x00401a00 <+112>:   nop
   0x00401a04 <+116>:   jal     0x4021f0 <explode_bomb>
   ...
   0x00401a18 <+136>:   lw      a1,24(s8)
   0x00401a1c <+140>:   jal     0x4018a4 <fun7>
   0x00401a20 <+144>:   nop
   0x00401a24 <+148>:   lw      gp,16(s8)
   0x00401a28 <+152>:   move    v1,v0
   0x00401a2c <+156>:   li      v0,7
   0x00401a30 <+160>:   beq     v1,v0,0x401a44 <secret_phase+180>

并且该整数要小于1002,后来将该整数传入fun7,得到的返回值为7,这里就将问题引到了fun7,我们进入fun7,这是类似二叉树搜索的过程,给出c++实现代码

node currentNode =root;
int v0;
void fun7(int a)
{
	if(a==0){v0=-1;return ;}
	else if(a<currentNode.element)//左
	{
		currentNode=currentNode.leftChild;
		fun7(a);
		v0*=2;
	}
	else if(a>currentNode.element)//右
	{
		currentNode=currentNode.leftChild;
		fun7(a);
		v0=v0*2+1;
	}
	else {v0=0;return}
}

我们可以逆向推出v0的得到过程,因为7为奇数,只能来自右,倒推v0=3,仍为奇数,再倒推v0=1,再倒推v0=0;所以a应该是比较了三次右子树才得到了相等的element。按照这样思路,只需要看三层右子树就能得到答案;

(gdb) i r $a0   //根节点
a0: 0x413184
(gdb) x 0x413184+8  //右子树
0x41318c <n1+8>:        0x0041316c
(gdb) x 0x0041316c+8  //右子树
0x413174 <n22+8>:       0x0041313c
(gdb) x 0x0041313c+8  //右子树
0x413144 <n34+8>:       0x0041308c
(gdb) x 0x0041308c   //查看element
0x41308c <n48>: 0x000003e9  //0x3e9=1001

输入10001,就可通过,至此拆完所以炸弹

总结

刚拿到这个实验的时候觉得这是不可能完成的任务,那么多炸弹,汇编代码晦涩难懂,看着实验没有任何头绪,网上的相关资料也很少,每周也只有一次进实验室上机的机会,还是一个小组一个机器。ddl是第一生产力果然不错,后来在宁dalao的帮助下在自己电脑上搭好了环境(再次感谢宁哥),硬着头皮往上怼,熬了三四个晚上,终于在验收前一周肝完了,现在回想起来还有几分欣慰自豪。

发布了20 篇原创文章 · 获赞 3 · 访问量 465

猜你喜欢

转载自blog.csdn.net/qq_44893580/article/details/103544781