当前位置:   article > 正文

山东大学计算机系统原理实验——拆除炸弹(反汇编)_拆弹实验如何查看寄存器

拆弹实验如何查看寄存器

<phase_1>:

不难发现,在

400d8c:   0c10073e   jal   401cf8 <strings_not_equal>

处进行了输入,先在 0x00400bb8 处设置一个断点,查看寄存器 $v0 的值,与输入的内容相同。说明v0保存了输入的字符

说明v0 中的值存到(s8+32)中去,并且从该内存中读取字符串保存到寄存器 a0中。

然后发现 

400d88:   2445276c   addiu      a1,v0,10092

说明a1 和 v0 即输入进行了比较,并且可以得知返回结果储存在v0中(0为不相等,1为相等,

400d94:      10400003 beqz v0,400da4 <phase_1+0x38>

在这里进行了v0值的判断,若v0为0(则继续,炸弹爆炸),不为 0 跳转到 <+38> 的位置,炸弹不爆炸。

然后使用ni命令看出a1处的内容为“Let’s begin now!”

当输入为“Let’s begin now!”时,炸弹不会触发

 

<phase_2>:

注意到,

400dfc:     10620004   beq v1,v0,400e10 <phase_2+0x54>

Beq指令对比v1,v0寄存器中的内容,相等时发生转移,发现转移的位置为

400e10: 24020001   li     v0,1,

跳过了指令:

400e04: 0c10087c jal 4021f0 <explode_bomb>,

避免了一次炸弹爆炸,说明首先v0和v1的内容要相等。

设置400dfc:     10620004   beq v1,v0,400e10 <phase_2+0x54>

为断点,试着输入 hello world!炸弹爆炸。

使用测试数据发现,输入有格式要求,发现

400de8:    0c1006ea   jal   401ba8 <read_six_numbers>

要求输入六个数字

试着输入 1 1 4 5 1 4 六个数字

发现v0,v1皆为1,合理怀疑其中一个为输入的字母,发现

  1. 400df8:    24020001   li     //v0,11加载到v0寄存器中
  2. 400e68: 8fc20018    lw   v0,24(s8) //从内存中加载一个字的内容

寻找该内存的其他信息

发现前六位为输入数据,后两位为无效数据,说明输入的第一个字符必须为1

继续向下,跳转到400e10:  24020001   li     v0,1,

通过查询指令可得知

  1. 400e10:   24020001   li     v0,1 //1加载到寄存器v0
  2. 400e14:   afc20018   sw  v0,24(s8) //把v0寄存器的内容存至内存中
  3. 400e18:   10000023   b    400ea8 <phase_2+0xec> //跳转至400ea8

将400ea8设置为断点

  1. 400eb0:   28420006   slti  v0,v0,6 //v0小于6,则v01,否则为0
  2. 400eb4:   1440ffda   bnez     v0,400e20 <phase_2+0x64> //v0不为0时发生跳转

跳转的位置是

400e20:   8fc20018    lw   v0,24(s8)

说明在e20和eb4之间进行了六次循环,推测是进行了六个数字的对比

发现400e84:     10820004   beq a0,v0,400e98 <phase_2+0xdc>

对a0和v0进行了对比,相同则跳转至e98,跳过了炸弹爆炸

对中间的指令进行查询:

  1. 400e20:   8fc20018   lw   v0,24(s8)            //循环开始(循环次数)
  2. 400e24:   00000000   nop
  3. 400e28:   2442ffff   addiu    v0,v0,-1      //把v0-1的结果存入v0
  4. 400e2c:   00021080   sll   v0,v0,0x2           //把v0左移两位,即乘四
  5. 400e30:   27c30018   addiu    v1,s8,24         //把s8+24的结果存入v1
  6. 400e34:   00621021   addu    v0,v1,v0          //把v0+v1的结果存入v0
  7. 400e38:   8c440004   lw   a0,4(v0)
  8. 400e3c:   2403000c   li     v1,12              //12加载到寄存器v1
  9. 400e40:   8fc20018   lw   v0,24(s8)            //把内存中的内容加载至v0(循环次数)
  10. 400e44:   00000000   nop
  11. 400e48:   00621023   subu     v0,v1,v0       //把v1-v0的值存进v0
  12. 400e4c:  8f83806c   lw   v1,-32660(gp)      //把内存加载至v1
  13. 400e50:   00021080   sll   v0,v0,0x2           //v0左移两位,即乘4
  14. 400e54:   00621021   addu    v0,v1,v0        //把v1+v0的值存进v0
  15. 400e588c420000   lw   v0,0(v0)
  16. 400e5c:   00000000   nop
  17. 400e60:   00820018   mult  a0,v0               //将a0与v0相乘,储存在HI,LO寄存器中
  18. 400e64:   00002012   mflo     a0               //将HI,LO寄存器中的内容存放至a0
  19. 400e68:   8fc20018   lw   v0,24(s8)            //把内存中的内容加载至v0(循环次数
  20. 400e6c:   00000000   nop
  21. 400e70:   00021080   sll   v0,v0,0x2          //把v0左移两位,即乘四
  22. 400e74:   27c30018   addiu    v1,s8,24       //把s8+24的结果存入v1
  23. 400e78:   00621021   addu    v0,v1,v0       //把v0+v1的结果存入v0
  24. 400e7c:   8c420004   lw   v0,4(v0)            //把v0+4的内容存入v0
  25. 400e80:   00000000   nop
  26. 400e84:   10820004   beq a0,v0,400e98 <phase_2+0xdc>

发现,24(s8)处储存循环次数,-32660(gp)加十二个字是输入学号的最后一位,即为a0的初始值。

每循环一次,a0就与前一位相乘。然后将a0与输入的数字进行比较,相等炸弹就不会爆炸

我的学号为:202200130021,应当输入1 1 2 0 0 0时炸弹不爆炸

<phase_3>:

炸弹三代码量很大,但总的来看是一个switch结构,通过输入的第一个数字来判断进入的case;

输入的结构是int char int;

  1. 400f2c:    8fdc0018   lw   gp,24(s8)
  2. 400f30:    28420003   slti  v0,v0,3
  3. 400f34:    10400004   beqz     v0,400f48 <phase_3+0x74>

通过断点调试,gp这里加载的是输入的个数,即输入个数大于等于3时才发生跳转,即跳过炸弹。

输入的第一个数的范围是0到7

  1. 400f50:    2c430008   sltiu    v1,v0,8
  2. 400f54:    1060008e   beqz     v1,401190 <phase_3+0x2bc>

V0的内容即为输入的第一个数,若第一个数大于等于8就会跳转,发现跳转后炸弹爆炸。

接下来,对于不同的v0的值有8种情况:

当v0 = 0:

  1. 400f7c:    24020071   li     v0,113     //设置 v0 寄存器为 113
  2. 400f80:    a3c20020   sb   v0,32(s8)    //将v0的值存储到 (s8 + 32) 的地址
  3. 400f84:    8f82806c   lw   v0,-32660(gp)  //从全局变量加载 v0 寄存器的值,经确认,为学号
  4. 400f88:    00000000   nop
  5. 400f8c:    8c43002c   lw   v1,44(v0//加载学号的最后一位为v1
  6. 400f90:    8fc20024   lw   v0,36(s8//此处加载的为最后一个输入
  7. 400f94:    00000000   nop
  8. 400f98:    00620018   mult     v1,v0  //计算 v1 * v0,并将结果存储在 v1
  9. 400f9c:    00001812   mflo     v1    //将乘积的低 32 位存储在 v1
  10. 400fa0:    24020309   li     v0,777    //设置 v0 寄存器为 777
  11. 400fa4:    10620081   beq v1,v0,4011ac   //如果 v1 等于 v0,则跳转到 4011ac,炸弹不会爆炸
  12. 4011ac:   00000000   nop
  13. 4011b0:   10000011   b    4011f8 <phase_3+0x324>跳转到 4011ac
  14. 4011f8:    83c20028   lb   v0,40(s8//此处为第二个输入,即字母的ascll码
  15. 4011fc:    83c30020   lb   v1,32(s8// v1即为113
  16. 401200:   00000000   nop
  17. 401204:   10620004   beq v1,v0,401218 <phase_3+0x344> 若字母ascll码等于113,即输入‘q’,发生跳转,炸弹不会爆炸。

以400f98为断点,查看v0寄存器以及v1寄存器的值,

发现v1为学号的最后一位,v0为最后一个输入

综上所述,当输入的第一个数为0,要确保输入的第二个数字与学号的最后一位的乘积为777,且输入的字母为q

当v0 = 1:

相似的,当输入的第一个数为1,要确保输入的第二个数字与学号的最后一位的乘积为214,且输入的字母为b

当v0 = 2:

相似的,当输入的第一个数为2,要确保输入的第二个数字与学号的最后一位的乘积为755,且输入的字母为b

当v0 = 3:

相似的,当输入的第一个数为3,要确保输入的第二个数字与学号的最后一位的乘积为0,且输入的字母为k,若学号的最后一位为0,也只能用这种解法

当v0 = 4:

相似的,当输入的第一个数为4,要确保输入的第二个数字与学号的最后一位的乘积为228,且输入的字母为o

当v0 = 5:

相似的,当输入的第一个数为5,要确保输入的第二个数字与学号的最后一位的乘积为513,且输入的字母为t

当v0 = 6:

此种情况不同,前面判断完第二个数字与最后一位的乘积为780后,虽然跳过了炸弹,但是跳转到了v0 = 7的情况里

  1. 40112c:   00620018   mult     v1,v0
  2. 401130:   00001812   mflo     v1
  3. 401134:   2402030c   li     v0,780
  4. 401138:   10620004   beq v1,v0,40114c <phase_3+0x278> //40114c并没有跳转到最后,而是在v0 = 7

而v0 = 7里对于乘积的要求与v0 = 6的并不一样(悲,怎么可能有一个数字既等于780,又等于824)

所以当输入的第一个数字是6的时候,已经注定了炸弹爆炸的命运。

当v0 = 7:

相似的,当输入的第一个数为7,要确保输入的第二个数字与学号的最后一位的乘积为824,且输入的字母为b

<phase_4>:

顺着phase_4 发现401328: 1c400005   bgtz     v0,401340 <phase_4+0x84>

判断v0是否大于0,大于0则发生跳转,跳过了炸弹

设置401328为断点,查看v0寄存器的值

发现与输入的数字相同,所以输入要大于0。继续探索

  1. 401340:   8f82806c   lw   v0,-32660(gp)
  2. 401344:   00000000   nop
  3. 401348:   8c42002c   lw   v0,44(v0)

已经多次见到,v0的内容是学号的最后一位

  1. 401350:   30420001   andi      v0,v0,0x1
  2. 401354:   304200ff   andi      v0,v0,0xff
  3. 401358:   10400010   beqz     v0,40139c <phase_4+0xe0> 

将v0寄存器先与0x1进行按位与操作,再与0xff进行按位与操作

如果v0为奇数,结果为1,不跳转,如果为偶数,跳转到40139c

如果v0为奇数: 

  1. 401360:     8fc20018   lw   v0,24(s8)  //加载 v0 寄存器的值。通过经验以及断点调试,此处为输入的数字
  2. 401364:     00000000   nop
  3. 401368:     00402021   move    a0,v0  //将 v0 的值赋给 a0
  4. 40136c:     0c10048c   jal   401230 <func4>  //调用函数 func4
  5. 401370:     00000000   nop
  6. 401374:     8fdc0010   lw   gp,16(s8//从栈上加载 gp的值
  7. 401378:     00401821   move    v1,v0  //将返回值保存到 v1 寄存器
  8. 40137c:     24020008   li     v0,8    //设置 v0 寄存器为 8
  9. 401380:     10620013   beq v1,v0,4013d0 <phase_4+0x114>  //如果 v1的值等于 8,跳转到 4013d0 跳过炸弹
  10. 401384:     00000000   nop
  11. 401388:     0c10087c   jal   4021f0 <explode_bomb> 
  12. 40138c:     00000000   nop
  13. 401390:     8fdc0010   lw   gp,16(s8)  
  14. 401394:     1000000e   b    4013d0 <phase_4+0x114>  //跳转到 4013d0

即把输入的数字作为func4的参数,然后判断返回值是否是8,如果是炸弹就不会爆炸。接下来探索func4,注意到

  1. 401250:     28420002   slti  v0,v0,2      //判断 v0 是否小于 2
  2. 401254:     14400011   bnez     v0,40129c <func4+0x6c> //如果 v0 不为零,跳转到 40129c
  3. 401258:     00000000   nop
  4. 40125c:     8fc20028   lw   v0,40(s8)    //加载v0的值,即为当前的循环
  5. 401260:     00000000   nop
  6. 401264:     2442ffff   addiu    v0,v0,-1     // 将 v01
  7. 401268:     00402021   move    a0,v0     //将 v0 的值赋给 a0
  8. 40126c:     0c10048c   jal   401230 <func4>  //递归调用函数 func4
  9. 401270:     00000000   nop
  10. 401274:     00408021   move    s0,v0    //将返回值保存到 s0 寄存器
  11. 401278:     8fc20028   lw   v0,40(s8)    //从栈上加载 v0 寄存器的值
  12. 40127c:     00000000   nop
  13. 401280:     2442fffe   addiu    v0,v0,-2  //将 v02
  14. 401284:     00402021   move    a0,v0     //将 v0 的值赋给 a0
  15. 401288:     0c10048c   jal   401230 <func4>  //递归调用函数 func4
  16. 40128c:     00000000   nop
  17. 401290:     02021021   addu    v0,s0,v0    //将 s0 和返回值相加
  18. 401294:     10000002   b    4012a0 <func4+0x70>  //跳转到 4012a0
  19. 401298:     00000000   nop
  20. 40129c:     24020001   li     v0,1           //设置 v01
  21. 4012a0:     03c0e821   move    sp,s8        //设置 sp 寄存器为 s8 的值

发现func4实为求斐波那契数列的函数,即func4(v0) = func4(v0-1)+ func4(v0-2),返回值为f(v0),即斐波那契数列第v0位的值,储存在寄存器上。

所以对于学号最后一位为奇数的情况,要想炸弹不爆炸,需要输入8在斐波那契数列中的位置,即5。

接下来,应对学号最后一位为偶数的情况:

  1. 4013b0:     8fdc0010   lw   gp,16(s8)
  2. 4013b4:     00401821   move    v1,v0
  3. 4013b8:     2402000d   li     v0,13
  4. 4013bc:     10620004   beq v1,v0,4013d0 <phase_4+0x114>

相似地,对于学号最后一位为偶数的情况,要想炸弹不爆炸,需要输入13在斐波那契数列中的位置,即6。

<phase_5>:

两条判断是否爆炸的语句,分别为:

401418: 0c10087c   jal   4021f0 <explode_bomb>与

4014e0: 0c10087c   jal   4021f0 <explode_bomb>

第一条前有401410: 10620003   beq v1,v0,401420 <phase_5+0x38>

比较了v0 与 v1的值,v0的为6,设置401410为断点

可知v1 的值为输入字段的长度

说明输入必须为6个字符

继续向下看,

发现(s8 + 72)处储存的就是输入的字符

  1. 401420: afc00018 sw zero,24(s8)  //将零值存储在(s8 + 24
  2. . . . . . .
  3. 401498: 8fc20018 lw v0,24(s8//从(s8 + 24)处加载值到寄存器 v0
  4. 40149c: 00000000 nop
  5. 4014a0: 24420001 addiu v0,v0,1  //将寄存器 v0 的值加 1
  6. 4014a4: afc20018 sw v0,24(s8)   //将v0的值存储在(s8 + 24
  7. 4014a8: 8fc20018 lw v0,24(s8)    //从(s8 + 24)加载值到寄存器 v0
  8. 4014ac: 00000000 nop
  9. 4014b0: 28420006 slti v0,v0,6    //将v0 的值与 6 比较,v06小则为1
  10. 4014b4: 1440ffdd bnez v0,40142c <phase_5+0x44> //如果 v0 不为零,则跳转到地址

这些代码实现了一个次数为6的循环,接下来让我们看看循环内的内容

  1. 401430: 8fc30018 lw v1,24(s8)   //从(s8 + 24)加载值到v1(循环次数)
  2. 401434: 8fc40048 lw a0,72(s8)   //从(s8 + 72)加载a0 (输入字符)
  3. 401438: 00000000 nop
  4. 40143c: 00831821 addu v1,a0,v1  //计算 v1 = a0 + v1
  5. 401440: 80630000 lb v1,0(v1)   //从内存中加载一个字节到寄存器 v1
  6. 401444: 00000000 nop
  7. 401448: 306300ff andi v1,v1,0xff   //将 v1 按位与 0xff,保留低 8
  8. 40144c: 3063000f andi v1,v1,0xf    //将 v1 按位与 0xf,保留低 4
  9. 401450: 00021080 sll v0,v0,0x2     //将寄存器 v0 左移 2
  10. 401454: 27c40018 addiu a0,s8,24  //将(s8 + 24)存储在a0 中(循环次数)
  11. 401458: 00821021 addu v0,a0,v0  //计算 v0 = a0 + v0
  12. 40145c: ac43000c sw v1,12(v0)   //将v1 中的值存储在内存地址 v0 + 12

在第n次循环中,处理第n位输入,保存此字符的ascll码后四位

  1. 401460: 8fc40018 lw a0,24(s8)   //从(s8 + 24)加载参数 a0(循环次数)
  2. 401464: 8fc20018 lw v0,24(s8)    //从(s8 + 24)加载值到v0(循环次数)
  3. 401468: 00000000 nop
  4. 40146c: 00021080 sll v0,v0,0x2     //将寄存器 v0 左移 2
  5. 401470: 27c30018 addiu v1,s8,24  //将当(s8 + 24)存储在v1 中(循环次数)
  6. 401474: 00621021 addu v0,v1,v0  //计算 v0 = v1 + v0
  7. 401478: 8c43000c lw v1,12(v0) //从内存地址 v0 + 12 处加载值到寄存器 v1
  8. 40147c: 3c020041 lui v0,0x41  //将常数 0x41 存储在寄存器 v0 的高 16
  9. 401480: 244230ec addiu v0,v0,12524  //将常数12524存储在v0 的低 16
  10. 401484: 00621021 addu v0,v1,v0  //计算 v0 = v1 + v0
  11. 401488: 80430000 lb v1,0(v0)   //从内存地址 v0 处加载一个字节到v1
  12. 40148c: 27c20018 addiu v0,s8,24 //将(s8 + 24)存储在v0 中(循环次数)
  13. 401490: 00441021 addu v0,v0,a0  //计算 v0 = v0 + a0
  14. 401494: a0430004 sb v1,4(v0)   //将v1 中的值存储在内存地址 v0 + 4

401488这里访问了内存地址v0,将401488设置为断点,查看v0处的内容

是一个字符串

  1. 401470: 27c30018 addiu v1,s8,24  //将当(s8 + 24)存储在v1 中(循环次数)
  2. 401474: 00621021 addu v0,v1,v0  //计算 v0 = v1 + v0
  3. 401478: 8c43000c lw v1,12(v0)  //从内存地址 v0 + 12 处加载值到寄存器 v1
  4. 40147c: 3c020041 lui v0,0x41  //将常数 0x41 存储在寄存器 v0 的高 16
  5. 401480: 244230ec addiu v0,v0,12524   //将常数12524存储在v0 的低 16
  6. 401484: 00621021 addu v0,v1,v0  //计算 v0 = v1 + v0

这里v1处的值,即为第n位输入的ascll码的后四位,v0为0x00410000加上12524,计算过后为0x004130ec,发现正是内存v0地址的前一位

即为完整的16位字符表

所以输入的字符,将会转换为字符表里的某一位,即从0x004130ec开始,加上输入的字符ascll码的后四位,第几个即为转换过的字符。

如输入b,后四位为0010,则为2,对应字符表里是r。

  1. 4014bc: a3c00022 sb zero,34(s8)  //将零值存储在当前栈帧的偏移 34
  2. 4014c0: 27c2001c addiu v0,s8,28  //计算 v0 = s8 + 28
  3. 4014c4: 00402021 move a0,v0     //将 v0 的值存储在 a0
  4. 4014c8: 3c020040 lui v0,0x40      //0x40 存储在v0 的高 16
  5. 4014cc: 244527b0 addiu a1,v0,10160  //将v0 + 10160的值存储在a1
  6. 4014d0: 0c10073e jal 401cf8 <strings_not_equal>  //调用对比函数
  7. 4014d4: 00000000 nop
  8. 4014d8: 10400003 beqz v0,4014e8 <phase_5+0x100> //如果 v0 为零,则跳转到地址

接下来将转换后的字符串与a1处对比,若相等则v0为0,同时跳过炸弹

设置4014d0断点查看a1的值

 

说明要让转换后的字符串为giants

通过计算,输入为opekma时,转换为giants,答案不唯一

<phase_6>:

  1. 401500:   27bdffa0   addiu    sp,sp,-96    //分配 96 字节的栈空间
  2. 401504:   afbf005c   sw  ra,92(sp)        //保存返回地址
  3. 401508:   afbe0058   sw  s8,88(sp)         //保存调用者的栈帧指针
  4. 40150c:   03a0f021   move    s8,sp      //设置当前栈帧指针
  5. 401510:   3c1c0042   lui   gp,0x42   //设置全局指针 gp 的高 16 位为 0x42
  6. 401514:   279cb190   addiu    gp,gp,-20080 //设置指针gp的低16位为-20080
  7. 401518:   afbc0010   sw  gp,16(sp)  //将全局指针 gp 存储在(sp + 16)处
  8. 40151c:   afc40060   sw  a0,96(s8//将参数 a0 存储在(sp + 96) 处
  9. 401520:   3c020041   lui   v0,0x41   //设置寄存器 v0 的高 16 位为 0x41
  10. 401524:   24423130   addiu    v0,v0,12592  //设置v0 的低 16 位为 12592
  11. 401528:   afc20020   sw v0,32(s8)    //将v0 存储在(sp + 32)处
  12. 40152c:   27c20024   addiu    v0,s8,36  //设置v0 为sp + 36
  13. 401530:   8fc40060   lw   a0,96(s8)    //从当(sp + 96)处加载参数 a0
  14. 401534:   00402821   move    a1,v0    //将 v0 的值存储在 a1
  15. 401538:   0c1006ea   jal   401ba8 <read_six_numbers>//调用函数读入六个数字

以上指令分配了96字节栈空间,读取了六个数,若不是六个数字炸弹将会爆炸

以下是一个循环从401550到40163c,循环次数为6,让我们看看循环内的指令

  1. 401540:   8fdc0010   lw   gp,16(s8//从(s8 + 16)处加载全局指针 gp
  2. 401544:   afc0001c   sw  zero,28(s8) //将零值存储在(s8 + 28)处(循环次数)
  3. 401548:   1000003c   b    40163c <phase_6+0x13c>//跳转到地址 40163c
  4. 40154c:   00000000   nop
  5. 401550:   8fc2001c   lw   v0,28(s8) //从(s8 + 28)处加载值到v0(循环次数)
  6. …………
  7. …………
  8. 40163c:   8fc2001c   lw   v0,28(s8) //从(s8 + 28)处加载值到v0(循环次数)
  9. 401640:   00000000   nop
  10. 401644:   28420006   slti  v0,v0,6  //将v06比较,相等就令v00
  11. 401648:   1440ffc1   bnez     v0,401550 <phase_6+0x50> //如果v0不等于零,则跳转到401550
  12. 401550:   8fc2001c   lw   v0,28(s8) //从(s8 + 28)处加载值到v0(循环次数)
  13. 401554:   00000000   nop
  14. 401558:   00021080   sll   v0,v0,0x2  //将v0左移2
  15. 40155c:   27c30018   addiu    v1,s8,24  //设置v1 为(s8 + 24
  16. 401560:   00621021   addu    v0,v1,v0  //计算 v0 = v1 + v0
  17. 401564:   8c42000c   lw   v0,12(v0)    //从内存(v0 + 12)处加载值到v0
  18. 401568:   00000000   nop
  19. 40156c:   28420007   slti  v0,v0,7   //将v07比较,v0大于7就令v00
  20. 401570:   1040000a   beqz     v0,40159c <phase_6+0x9c> //如果v00,则跳转到地址 40159c,炸弹爆炸
  21. 401574:   00000000   nop
  22. 401578:   8fc2001c   lw   v0,28(s8)   //从(s8  + 28)处加载值到v0(循环次数)
  23. 40157c:   00000000   nop
  24. 401580:   00021080   sll   v0,v0,0x2   //将v0左移 2
  25. 401584:   27c30018   addiu    v1,s8,24  //设置v1 为(s8 + 24
  26. 401588:   00621021   addu    v0,v1,v0  //计算 v0 = v1 + v0
  27. 40158c:   8c42000c   lw   v0,12(v0)    //从内存(v0 + 12)处加载值到v0
  28. 401590:   00000000   nop
  29. 401594:   1c400004   bgtz     v0,4015a8 <phase_6+0xa8>  //如果 v0 大于零,则跳转到地址 4015a8,跳过炸弹爆炸
  30. 401598:   00000000   nop
  31. 40159c:   0c10087c   jal   4021f0 <explode_bomb> //如果v0不大于零,则炸弹爆炸

以上两段代码对内存中的一个值进行了比较,这个值必须小于7大于0,所以只能在1到6中,猜测是输入的第n个数,设置断点为401564,进行查看

发现内存v0处储存的即为输入的数字,第n个循环对比的数字为输入的第n个数字。

  1. 4015a4:   8fdc0010   lw   gp,16(s8)   //从(s8 + 16)处加载全局指针 gp
  2. 4015a8:   8fc2001c   lw   v0,28(s8)   //从(s8 + 28)处加载到v0(循环次数)
  3. 4015ac:   00000000   nop
  4. 4015b0:   24420001   addiu    v0,v0,1    //将v0的值加 1
  5. 4015b4:   afc20018   sw  v0,24(s8)      //将v0的值存储在(s8 + 24)处(循环次数+1
  6. 4015b8:   10000017   b    401618 <phase_6+0x118>//无条件跳转到地址 401618
  7. 4015bc:   00000000   nop
  8. 4015c0:   8fc2001c   lw   v0,28(s8)   //从(s8 + 28)处加载到v0(循环次数)
  9. 4015c4:   00000000   nop
  10. 4015c8:   00021080   sll   v0,v0,0x2     //将v0 左移 2
  11. 4015cc:   27c30018   addiu    v1,s8,24  //设置v1 为(s8 + 24)(循环次数+1
  12. 4015d0:   00621021   addu    v0,v1,v0  //计算 v0 = v1 + v0
  13. 4015d4:   8c43000c   lw   v1,12(v0)    //从v0+12处加载到v1(输入的循环次数+1个数)
  14. 4015d8:   8fc20018   lw   v0,24(s8) //从(s8 + 24)处加载到v0(循环次数+1
  15. 4015dc:   00000000   nop
  16. 4015e0:   00021080   sll   v0,v0,0x2    //将寄存器 v0 左移 2
  17. 4015e4:   27c40018   addiu    a0,s8,24  //设置a0为(s8 + 24)(循环次数+1
  18. 4015e8:   00821021   addu    v0,a0,v0      //计算 v0 = a0 + v0
  19. 4015ec:   8c42000c   lw   v0,12(v0)        //从v0+12处加载值到寄存器 v0
  20. 4015f0:   00000000   nop
  21. 4015f4:   14620004   bne v1,v0,401608 <phase_6+0x108> // 如果v1不等于v0,则跳转到401608
  22. 4015f8:   00000000   nop
  23. 4015fc:   0c10087c   jal   4021f0 <explode_bomb>//如果 v1 等于 v0,则炸弹爆炸
  24. 401600:   00000000   nop                     
  25. 401604:   8fdc0010   lw   gp,16(s8)   //从(s8 + 16)处加载全局指针 gp
  26. 401608:   8fc20018   lw   v0,24(s8)    //从(s8 + 24)处加载值到寄存器 v0
  27. 40160c:   00000000   nop
  28. 401610:   24420001   addiu    v0,v0,1  //将v0 的值加 1
  29. 401614:   afc20018   sw  v0,24(s8)     //将v0 的值存储在(s8 + 24)处
  30. 401618:   8fc20018   lw   v0,24(s8)    //从(s8 + 24)处加载值到v0(循环次数+1
  31. 40161c:   00000000   nop
  32. 401620:   28420006   slti  v0,v0,6      //将v06比较,相等就令v00
  33. 401624:   1440ffe6   bnez     v0,4015c0 <phase_6+0xc0> //如果v0不等于零,则跳转到地址4015c0

又是一个4015c0到401618的循环,这个循环意在比较两个数的值是否相等,循环的次数由外部循环第几次决定,即从第n个数,n+1,n+2到6。循环内部比较的两个数,从上文可以得出,是第n个数与当前内部循环的数,也就是将第n个数与后面的数都比较一遍,若有相等的情况则炸弹爆炸,说明输入的6个数不能重复。到这里六个循环就结束了,让我们看看循环后的指令。

  1. 40164c:   00000000   nop
  2. 401650:   afc0001c   sw  zero,28(s8)   //将零存储在(s8 + 28)处(循环次数)
  3. 401654:   10000028   b    4016f8 <phase_6+0x1f8> //无条件跳转到地址 4016f8
  4. …………
  5. …………
  6. 4016f8:   8fc2001c   lw   v0,28(s8) //从(s8 + 28)处加载值到v0(循环次数)
  7. 4016fc:   00000000   nop
  8. 401700:   28420006   slti  v0,v0,6  //将v06 比较,小于61,否则为0
  9. 401704:   1440ffd5   bnez     v0,40165c <phase_6+0x15c>  //如果 v0 不等于零,则跳转到地址 40165c

这里又是一个从401654 到4016f8,次数为6的循环,让我们看看循环里的指令

  1. 40165c:   3c020041   lui   v0,0x41    //设置v0 的高 16 位为 0x41
  2. 401660:   24423130   addiu    v0,v0,12592  //设置v0 的低 16 位为 12592
  3. 401664:   afc20020   sw  v0,32(s8)   //将v0 存储在(s8 + 32)处(0x413130)
  4. 401668:   24020001   li     v0,1       //将v0 的值设置为 1
  5. 40166c:   afc20018   sw  v0,24(s8//将v0 存储在(s8 + 24)处(内层循环)

这里的(s8 + 24)处储存着的是内层的循环变量,初始化为1

  1. 401670:   1000000a   b    40169c <phase_6+0x19c> //无条件跳转到40169c
  2. 401674:   00000000   nop
  3. 401678:   8fc20020   lw   v0,32(s8//从(s8 + 32)处加载值到v0
  4. 40167c:   00000000   nop
  5. 401680:   8c420008   lw   v0,8(v0)   //从内存地址 v0 + 8 处加载值到v0
  6. 401684:   00000000   nop
  7. 401688:   afc20020   sw  v0,32(s8//将v0的值存储在(s8 + 32)处
  8. 40168c:   8fc20018   lw   v0,24(s8//从(s8 + 24)加载到 v0(内层循环)
  9. 401690:   00000000   nop
  10. 401694:   24420001   addiu    v0,v0,1   //将 v0 的值加 1
  11. 401698:   afc20018   sw  v0,24(s8//将 v0 存储在(s8 + 24)处(内层循环)
  12. 40169c:   8fc2001c   lw   v0,28(s8//从(s8 + 28)加载到v0(循环次数)

这里的v0原先是一个指针,为0x413130+12592,v0还会增加内层循环次数,每次循环v0取的都是(v0 + 8),而v1为(v0 + 12)

  1. 4016a0:   00000000   nop
  2. 4016a4:   00021080   sll   v0,v0,0x2  //将寄存器 v0 左移 2
  3. 4016a8:   27c30018   addiu    v1,s8,24  //设置 v1 为(s8 + 24)(内层循环)
  4. 4016ac:   00621021   addu    v0,v1,v0  //计算 v0 = v1 + v0
  5. 4016b0:   8c43000c   lw   v1,12(v0)    //从内存地址 v0 + 12 处加载值到 v1

这里的(v0 + 12)是输入的第(s8 + 24)个数

  1. 4016b4:   8fc20018   lw   v0,24(s8//从(s8 + 24)处加载到 v0(内层循环)
  2. 4016b8:   00000000   nop
  3. 4016bc:   0043102a   slt   v0,v0,v1     //将v0 与v1 比较,设置条件码
  4. 4016c0:   1440ffed   bnez     v0,401678 <phase_6+0x178> //如果v0不等于0,则跳转到 401678

发现内层循环的循环次数为(第外层循环个数个输入-1),即v0跳转到(v0 + 8)所指的位置n次,n为内层循环的次数,并且把值存储到(s8 + 32)处

  1. 4016c8:   8fc2001c   lw   v0,28(s8)   //从(s8 + 28)处加载到 v0(循环次数)
  2. 4016cc:   00000000   nop
  3. 4016d0:   00021080   sll   v0,v0,0x2  //将寄存器 v0 左移 2
  4. 4016d4:   27c30018   addiu  v1,s8,24   //设置v1 为(s8 + 24)(内层循环)
  5. 4016d8:   00621021   addu    v0,v1,v0   //计算 v0 = v1 + v0
  6. 4016dc:   8fc30020   lw   v1,32(s8)      //从(s8 + 32)处加载值到v1
  7. 4016e0:   00000000   nop
  8. 4016e4:   ac430024   sw  v1,36(v0)   //将v1 的值存储在内存 v0 + 24
  9. 4016e8:   8fc2001c   lw   v0,28(s8)    //从(s8 + 28)处加载到 v0(循环次数)
  10. 4016ec:   00000000   nop
  11. 4016f0:   24420001   addiu    v0,v0,1   //将v0 的值加 1
  12. 4016f4:   afc2001c   sw  v0,28(s8)      //将 v0 存储在(s8 + 28)(循环次数)

这里把之前v0循环的值从(s8 + 32)处取出,拷贝了一份又放到(v0 + 24)处

然后按照惯例增加循环次数,直到六次循环完成。让我们继续往下看

  1. 40170c:   8fc2003c   lw   v0,60(s8//从(s8 + 60)处加载值到v0
  2. 401710:   00000000   nop
  3. 401714:   afc20020   sw  v0,32(s8//将v0 存储在(s8 + 32) 处
  4. 401718:   24020001   li     v0,1      //将v0 设置为 1
  5. 40171c:   afc2001c   sw  v0,28(s8//将v0 存储在(s8 + 28)处(循环次数)
  6. 401720:   10000016   b    40177c <phase_6+0x27c> //跳转到地址 40177c
  7. …………
  8. …………
  9. 40176c:   8fc2001c   lw   v0,28(s8//从(s8 + 28)加载到v0(循环次数)
  10. 401770:   00000000   nop         
  11. 401774:   24420001   addiu    v0,v0,1   //将v0 中的值加1
  12. 401778:   afc2001c   sw  v0,28(s8)   //将v0 中的值存储到(s8 + 28)处
  13. 40177c:   8fc2001c   lw   v0,28(s8)   //从(s8 + 28)处加载值到v0
  14. 401780:   00000000   nop
  15. 401784:   28420006   slti  v0,v0,6     //如果v0小于6,将v0设为1,否则0
  16. 401788:   1440ffe7   bnez   v0,401728 <phase_6+0x228> //如果v0不为零,跳转到地址401728

这里又是一个从401720 到40177c,次数为6的循环,循环次数储存在(s8 + 28)中让我们看看循环里的指令

  1. 401728:   8fc2001c   lw   v0,28(s8//从(s8 + 28)处加载到v0(循环次数)
  2. 40172c:   00000000   nop
  3. 401730:   00021080   sll   v0,v0,0x2  //将寄存器 v0 左移 2
  4. 401734:   27c30018   addiu    v1,s8,24    //设置 v1 为s8 + 24
  5. 401738:   00621021   addu    v0,v1,v0    //计算 v0 = v1 + v0
  6. 40173c:   8c430024   lw   v1,36(v0)   //从内存地址v0 + 36 处加载值到v1
  7. 401740:   8fc20020   lw   v0,32(s8)   //从(s8 + 32)处加载值到 v0

此处v0为上一个循环中确定的值,设置401740为断点,查看v0与v1的值,发现v1为下一个v0的地址

  1. 401744:   00000000   nop
  2. 401748:   ac430008   sw  v1,8(v0)    //将v1 的值存储在内存 v0 + 8
  3. 40174c:   8fc2001c   lw   v0,28(s8)   //从(s8 + 28)处加载到 v0(循环次数)
  4. 401750:   00000000   nop
  5. 401754:   00021080   sll   v0,v0,0x2   //将寄存器 v0 左移 2
  6. 401758:   27c30018   addiu    v1,s8,24  //设置 v1 为s8 + 24
  7. 40175c:   00621021   addu    v0,v1,v0    //计算 v0 = v1 + v0
  8. 401760:   8c420024   lw   v0,36(v0)   //从内存 v0 + 36 处加载到寄存器 v0
  9. 401764:   00000000   nop
  10. 401768:   afc20020   sw  v0,32(s8)   //将寄存器 v0 的值存储在(s8 + 32) 处

通过上个循环确定的首个节点,以及两节点地址v 1 ,v0,然后将v1的地址放到(v0 + 8),即v0的下一个节点的地址,然后更新v0的节点,以便下一次链接,五次循环以后构成一个新链表。让我们继续往下看

  1. 40178c:   00000000   nop                
  2. 401790:   8fc20020   lw   v0,32(s8)   //从(s8 + 32)处加载值到v0
  3. 401794:   00000000   nop                
  4. 401798:   ac400008   sw  zero,8(v0//将零存储到内存(v0 + 8
  5. 40179c:   8fc2003c   lw   v0,60(s8)   //从(s8 + 60)处加载值到v0
  6. 4017a0:   00000000   nop                
  7. 4017a4:   afc20020   sw  v0,32(s8)   //将v0中的值存储到(s8 + 32)中
  8. 4017a8:   afc0001c   sw  zero,28(s8)  //将零存储到(s8 + 28)中
  9. 4017ac:   10000032   b    401878 <phase_6+0x378> ;//跳转到地址401878
  10. …………
  11. …………
  12.  
  13. 401878:   8fc2001c   lw   v0,28(s8)   //从(s8 + 28)处加载值到v0
  14. 40187c:   00000000   nop                
  15. 401880:   28420005   slti  v0,v0,5  //如果v0小于5,将v0设为1,否则为0
  16. 401884:   1440ffcb   bnez     v0,4017b4 <phase_6+0x2b4> ; 如果v0不为零,跳转到地址4017b4

这里又是一个从4017b4到401878,次数为5的循环,循环次数储存在(s8 + 28)中让我们看看循环里的指令

  1. 4017b4:   8f82806c   lw   v0,-32660(gp) //从全局指针(gp - 32660)加载到v0
  2. 4017b8:   00000000   nop                
  3. 4017bc:   8c42002c   lw   v0,44(v0)   //从(v0 + 44)处加载值到v0

通过经验得知,(v0 + 44)中的内容为学号的最后一位

  1. 4017c0:   00000000   nop                
  2. 4017c4:   30420001   andi     v0,v0,0x1  //将v0中的值与1按位与运算
  3. 4017c8:   304200ff   andi     v0,v0,0xff  //将v0中的值与0xff按位与运算
  4. 4017cc:   10400012   beqz     v0,401818 <phase_6+0x318> ; 如果v0为零,跳转到地址401818

这里v0与1按位与以后,若v0是奇数,则继续,若v0是偶数,则发生跳转。

  1. 4017d0:   00000000   nop                
  2. 4017d4:   8fc20020   lw   v0,32(s8)  //从(s8 + 32)处加载值到v0
  3. 4017d8:   00000000   nop                
  4. 4017dc:   8c430000   lw   v1,0(v0)   //从内存 v0 处加载值到v1

从(s8 + 32)处获取上一节点地址,加载上一节点值到v1

  1. 4017e0:   8fc20020   lw   v0,32(s8)   //从(s8 + 32)处加载值到v0
  2. 4017e4:   00000000   nop                
  3. 4017e8:   8c420008   lw   v0,8(v0)    //从(v0 + 8)处加载值到v0
  4. 4017ec:   00000000   nop                
  5. 4017f0:   8c420000   lw   v0,0(v0)    //从内存 v0 处加载值到v0

从(s8 + 32)处获取当前节点地址,加载当前节点值到v0

  1. 4017f4:   00000000   nop                
  2. 4017f8:   0062102a   slt   v0,v1,v0    //如果v1<v0,将v0寄存器设置为1,否则设置为0
  3. 4017fc:   10400015   beqz   v0,401854 <phase_6+0x354> //如果v0为零,跳转到地址401854
  4. 401800:   00000000   nop                
  5. 401804:   0c10087c   jal 4021f0 <explode_bomb> //调用函数,炸弹爆炸
  6. 401808:   00000000   nop                
  7. 40180c:   8fdc0010   lw   gp,16(s8)   //从(s8 + 16)加载一个值到gp寄存器
  8. 401810:   10000010   b    401854 <phase_6+0x354> //无条件跳转到地址401854
  9. 401814:   00000000   nop

发现4017d0至401814的指令与401818至401850的指令基本相同,区别在于slt判断指令,学号最后一位偶数时判断为

40183c:   0043102a   slt   v0,v0,v1  //如果v0<v1,将v0设置为1,否则为0

整个部分意在判断上一节点与当前节点存储值的大小关系,学号最后一位为偶数时要确保链表保存值为降序,学号最后一位为奇数时要确保链表保存值为升序。

  1. 401854:   8fc20020   lw   v0,32(s8)   //从(s8 + 32)处加载一个值到v0
  2. 401858:   00000000   nop                
  3. 40185c:   8c420008   lw   v0,8(v0)    //从v0+8处加载一个值到v0
  4. 401860:   00000000   nop                
  5. 401864:   afc20020   sw  v0,32(s8)   //将v0的值存储到(s8 + 28)处
  6. 401868:   8fc2001c   lw   v0,28(s8)   //从(s8 + 28)处加载一个值到v0
  7. 40186c:   00000000   nop                
  8. 401870:   24420001   addiu    v0,v0,1   //将v0的值加1
  9. 401874:   afc2001c   sw  v0,28(s8)   //将v0的值存储到(s8 + 28)处

正常的把当前节点的地址保存在(s8 + 32)中,然后增加(s8 + 28)中保存的循环次数。

综上所述,实验六通过输入一个1到6的排列,确定一个链表中节点的顺序,如果学号最后一位为奇数,需要保证从头遍历链表时,节点里存储的值为升序,若学号最后一位为偶数,则保证为降序。节点一共有六个,通过指针来确定各个节点的值的大小。

通过断点调试,可知每个节点储存的值:

node1  

0xfd
node2 0x2d5
node3 0x12d
node4  0x3e5
node5 0xd4
node60x1b0

比较大小,可知

学号最后一位为奇数,输入:4 2 6 3 1 5

学号最后一位为偶数,输入:5 1 3 6 2 4

<secret_phase>:

这里发现,如果按照正常输入,前面六个炸弹都输入完成后,程序就结束了,并没有进入隐藏炸弹,所以需要输入其他东西触发隐藏炸弹

观察bomb.s文件,发现上图中的语句,可以得知当输入格式为 %d %s 时进入隐藏炸弹。回想以上炸弹,输入为整数的有不少,但是只有炸弹4里没有检测输入整数个数的指令,所以要进入炸弹4,应该在炸弹4输入数字后再输入一个字符串,让我们看看隐藏炸弹。

  1. 4019ac:    0c1007fb   jal  401fec <read_line>  // 调用read_line函数
  2. 4019b0:    00000000   nop                 
  3. 4019b4:    8fdc0010   lw   gp,16(s8)    // 从栈上加载gp的值
  4. 4019b8:    afc2001c   sw  v0,28(s8)    // 将返回值保存在(s8 + 28
  5. 4019bc:    8fc2001c   lw   v0,28(s8)     // 加载read_line的返回值到v0
  6. …………
  7. …………
  8. 4019e8:    afc20018   sw  v0,24(s8)    // 将返回值保存在(s8 + 24
  9. 4019ec:    8fc20018   lw   v0,24(s8)    // 从栈上加载返回值
  10. 4019f0: 00000000   nop                // 无操作
  11. 4019f4: 2442ffff   addiu    v0,v0,-1    // 将v0的值减1
  12. 4019f8: 2c4203e9   sltiu v0,v0,1001//若v0小于1001,将v0设为1否则为0
  13. 4019fc: 14400004   bnez     v0,401a10 <secret_phase+0x80>  // 如果v0不为零,则跳转到401a10
  14. 401a00:    00000000   nop              
  15. 401a04:    0c10087c   jal   4021f0 <explode_bomb>  //调用函数,炸弹爆炸
  16. 401a08:    00000000   nop       

以上指令,确保进入隐藏炸弹后输入的数小于1002

  1. 401a1c:     0c100629   jal   4018a4 <fun7>   // 调用fun7函数
  2. 401a20:     00000000   nop                 
  3. 401a24:     8fdc0010   lw   gp,16(s8)    // 从栈上加载gp的值
  4. 401a28:     00401821   move    v1,v0  // 将fun7的返回值传递给v1
  5. 401a2c:     24020007   li     v0,7      // 将常数7传递给v0寄存器
  6. 401a30:     10620004   beq v1,v0,401a44 <secret_phase+0xb4>  // 如果v1等于v0,则跳转到401a44
  7. 401a34:     00000000   nop             
  8. 401a38:     0c10087c   jal   4021f0 <explode_bomb> //调用函数,炸弹爆炸
  9. 401a3c:     00000000   nop       

这里将fun7函数的返回值与7进行比较,若返回值为7则炸弹不爆炸,让我们看看fun7里的内容

  1. 4018b4:     afc40020   sw  a0,32(s8)    //将a0参数保存在栈上
  2. 4018b8:     afc50024   sw  a1,36(s8)    //将a1参数保存在栈上
  3. 4018bc:     8fc20020   lw   v0,32(s8)    //从栈上加载a0参数
  4. 4018c0:     00000000   nop                
  5. 4018c4:     14400004   bnez   v0,4018d8 <fun7+0x34>  //如果a0不为零,则跳转到4018d8
  6. 4018c8:     00000000   nop             
  7. 4018cc: 2402ffff   li     v0,-1       //将v0设置为-1
  8. 4018d0:     10000029   b    401978 <fun7+0xd4>  //跳转到函数结尾
  9. 4018d4:     00000000   nop             
  10. 4018d8:     8fc20020   lw   v0,32(s8//从(s8 + 32)上加载参数到v0
  11. 4018dc:     00000000   nop                  
  12. 4018e0:     8c430000   lw   v1,0(v0)   //从a0指向的地址加载一个值到v1
  13. 4018e4:     8fc20024   lw   v0,36(s8//从栈上加载a1参数到v0
  14. 4018e8:     00000000   nop                 
  15. 4018ec:     0043102a   slt   v0,v0,v1   //将v0设置为a1与 v1的比较结果
  16. 4018f0: 1040000c   beqz     v0,401924 <fun7+0x80>  //如果 a1 > v1,则跳转到401924

根据以上内容可确定,fun7内传入的参数包括a0指针,保存在(s8 + 32)上。还有通过转换变为整形的字符串,保存在(s8 + 36)上。

首先输入的整数会与根节点进行比较,若输入的不小于当前节点的值,则会发生跳转,以下是跳转后的指令:

  1. 401924:     8fc20020   lw   v0,32(s8//从(s8 + 32)上加载参数到v0
  2. 401928:     00000000   nop           
  3. 40192c:     8c430000   lw   v1,0(v0)   //从a0指向的地址加载一个值到v1
  4. 401930:     8fc20024   lw   v0,36(s8//加载输入的整形到v0
  5. 401934:     00000000   nop                 
  6. 401938:     0062102a   slt   v0,v1,v0  //将v0设置为 v1 与 v0 的比较结果
  7. 40193c:     1040000d   beqz     v0,401974 <fun7+0xd0>  如果 v1 = v0 ,则跳转到401974,函数结束
  8. 401944:     8fc20020   lw   v0,32(s8//从(s8 + 32)上加载参数到v0
  9. 401948:     00000000   nop                
  10. 40194c:     8c420008   lw   v0,8(v0)   //从(a0 + 8)指向的地址加载值到v0
  11. 401950:     00000000   nop               
  12. 401954:     00402021   move  a0,v0  //将v0的值传递给a0,完成a0的更新
  13. 401958:     8fc50024   lw   a1,36(s8)   //加载输入的整形到a1
  14. 40195c:     0c100629   jal   4018a4 <fun7>  //递归调用fun7函数
  15. 401964:     00021040   sll   v0,v0,0x1   //将v0左移1
  16. 401968:     24420001   addiu    v0,v0,1     //将v01,v0 = v0 * 2 + 1
  17. 40196c:     10000002   b    401978 <fun7+0xd4>  //跳转到函数结尾

进行当前节点,与输入的数的比较,若比较结果为输入的数不大于当前节点的值,则发生跳转。不过之前已经确定输入的数不大于当前节点的值,说明当前值与输入的相等时跳转。又发现跳转的位置相较于其他位置多增加了一条指令

401974: 00001021   move    v0,zero      //将v0设置为零

说明可能有妙用,我们接着看之前的代码,进行了根节点,即a0的自增,指向了新的节点(a0 + 8),然后以新的a0作为参数递归调用fun7,调用结束后,对v0寄存器内存储的值进行运算,v0 = v0 * 2 + 1

接下来我们回到第一次判断后小于的情况:

  1. 4018f8: 8fc20020   lw   v0,32(s8)   //从(s8 + 32)上加载参数到v0
  2. 4018fc: 00000000   nop                  
  3. 401900:     8c420004   lw   v0,4(v0)   //从(v0 + 4)指向的地址加载值到v0
  4. 401904:     00000000   nop                  
  5. 401908:     00402021   move    a0,v0  //将v0的值传递给a0寄存器
  6. 40190c:     8fc50024   lw   a1,36(s8//加载输入的整形到a1
  7. 401910:     0c100629   jal   4018a4 <fun7>  //递归调用fun7函数
  8. 401918:     00021040   sll   v0,v0,0x1   //将v0左移1位,v0 = v0 * 2
  9. 40191c:     10000016   b    401978 <fun7+0xd4> //跳转到函数结尾

进行了根节点,即a0的自增,指向了新的节点(a0 + 4),然后以新的a0作为参数递归调用fun7,调用结束后,对v0寄存器内存储的值进行运算,v0=v0 * 2

根据以上指令不难发现,fun7为一个二叉搜索树的搜索操作,进行输入值,以及各个节点的值的比较。若输入值小于当前节点,则把当前节点a0的下一个节点(a0 + 4)作为当前节点,递归调用fun7,若大于,则以(a0 + 8)作为当前节点,说明(a0 + 4)为左孩子,(a0 + 8)为右孩子。调用后会对v0进行操作,最后当前节点与输入值相等,或该值不存在,函数返回v0。

结合secrete_phase里的内容,函数返回值应该为7,否则炸弹将会爆炸,说明v0应该为7。经过倒推可知:

7 = 2 * 3 + 1

3 = 2 * 1 + 1

1 = 2 * 0 + 1

每次进行的操作为v0 = v0 * 2 +1,并且要求v0的初始值为0,想到之前判断相等条件后进行跳转到401974,说明不仅要进行三次大于的判断,还得让输入的值与二叉搜索树的最右叶节点相等。

回到secret_phase,

401a64:     0c100899   jal   402264 <phase_defused>//调用phase_defused

接下来看看phase_defused里的内容

  1. 402274:     3c1c0042   lui   gp,0x42          //0x42左移16位并存入gp寄存器
  2. 402278:     279cb190   addiu    gp,gp,-20080  //gp = gp - 20080
  3. 40227c:     afbc0010   sw  gp,16(sp)       //将gp的值保存在栈上
  4. 402280:     3c020041   lui   v0,0x41          //0x41左移16位并存入v0寄存器
  5. 402284:     8c433240   lw   v1,12864(v0)      //从地址0x41 + 12864加载一个值到v1寄存器
  6. 402288:     24020006   li     v0,6            //将v0寄存器设置为6
  7. 40228c:     14620039   bne v1,v0,402374 <phase_defused+0x110> //如果v1不等于6,则跳转到402374,不进入secrete_phase

这里将地址0x41 + 12864与6进行比较,经过断点调试可知这里储存的是当前的第几个炸弹,所以隐藏炸弹会在六个炸弹都排查完毕后才进入

  1. 4022e4:     27c20018   addiu(v0,s8,24//将s8 + 24的值存入v0寄存器
  2. 4022e8:     00402021   move(a0,v0)    //将v0的值传递给a0寄存器
  3. 4022ec:     3c020040   lui(v0,0x40)     //0x40左移16位并存入v0
  4. 4022f0: 244528b0   addiu(a1,v0,10416//将v0 + 10416的值存入a1
  5. 4022f4: 0c10073e   jal   401cf8 <strings_not_equal>//调用strings_not_equal函数
  6. 4022f8: 00000000   nop                  
  7. 4022fc: 8fdc0010   lw   gp,16(s8)       //从栈上加载gp的值
  8. 402300:     14400014   bnez(v0,402354 <phase_defused+0xf0>) //如果v0不为零,则跳转到402354,不进入secrete_phase

这里判断了a1 和 a0两个寄存器内的内容是否相同,若不相同,则不进入secrete_phase。通过设置断点,可知会将输入的字符串与austinpowers进行比较

说明要进入隐藏炸弹需要在phase_4 后输入austinpowers

然后查看fun7里节点的值

设置断点查看每个根节点的地址,输入的是1000,是范围内较大的值,方便找到最右叶节点

可以发现,v0存储的即为输入的数字1000的十六进制,v1即为最右叶节点的值的十六进制,1001。

说明进入secret_phase后输入1001。

同样方法可查看所有节点的值

综上所述,拆解secret_phase需要在phase_4后输入austinpowers,然后拆除六个炸弹后进入隐藏炸弹,输入1001即可拆除成功。

完结撒花!!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/642184
推荐阅读
相关标签
  

闽ICP备14008679号