怎么用免费的网站空间,天津住房城乡建设网站,营销策划点子公司,军事信息化建设网站写在前面
第一次拿到这个实验还是有点慌#xff01;之前没见过#xff0c;不过还是慢慢做过来了。
这是个需要耐心的过程#xff0c;请一定静下心来哦#xff01;
环境及配置
环境#xff1a;Ubuntu 20.04 GDB 调试工具 可参考配置#xff1a;GDB调试工具配置#…写在前面
第一次拿到这个实验还是有点慌之前没见过不过还是慢慢做过来了。
这是个需要耐心的过程请一定静下心来哦
环境及配置
环境Ubuntu 20.04 GDB 调试工具 可参考配置GDB调试工具配置须先自行下载 Ubuntu 哈
参考资料
MIPS 常用指令MIPS 常用指令 MIPS寄存器说明MIPS寄存器说明 供参考的 C 语言代码Brief C Programs of the Bombs 队友 scy 巨巨的博客占个坑太奆了
phase_1
先看主函数
...
//先看主函数
0x00400900main:0x00400ba4 676: lw gp,16(s8)0x00400ba8 680: jal 0x401fec read_line0x00400bac 684: nop0x00400bb0 688: lw gp,16(s8)0x00400bb4 692: sw v0,32(s8)0x00400bb8 696: lw a0,32(s8)0x00400bbc 700: jal 0x400d6c phase_1
...首先是主函数的部分这一部分主要是为了给 phase_1 传递我们所输入的字符串。看下面的例子首先我在 phase_1 时输入 “Helloworld!”“Hello\ world!”“Hello world!”共 12 个字符此时使用 GDB 进行调试 先在 0x00400bb8 处设置一个断点查看寄存器 $v0 的值 可以看到从 v0v0v0 中向后读取 12 个字符就是我们所输入的 Helloworld!Hello\ world!Hello world!。 因此这部分的思路就是 1. 先调用 read_line 函数去读取我们输入的字符串并将它存入专门保存函数返回值的寄存器 v0v0v0 中 2. 接着将 v0v0v0 中的值存到内存 (s832)(s832)(s832) 中去从该内存中读取字符串保存到寄存器 a0a0a0 中为了在 phase_1 中使用 3. 最后跳转到 phase_1 中进行炸弹 1 的拆解。
...0x00400ba4 676: lw gp,16(s8)0x00400ba8 680: jal 0x401fec read_line //调用函数读取字符串0x00400bac 684: nop0x00400bb0 688: lw gp,16(s8)0x00400bb4 692: sw v0,32(s8) //m[$s8 32] v0将 $v0 中的值即输入的字符串保存到内存中0x00400bb8 696: lw a0,32(s8) //a0 m[$s8 32]在从内存中读出这个字符串到 $a0 中为了在 phase_1 中使用0x00400bbc 700: jal 0x400d6c phase_1 //跳转至 phase_1 中
...知道输入的参数的传递了之后接着看 phase_1 0x00400d6c 0: addiu sp,sp,-320x00400d70 4: sw ra,28(sp)0x00400d74 8: sw s8,24(sp)0x00400d78 12: move s8,sp0x00400d7c 16: sw a0,32(s8)0x00400d80 20: lw a0,32(s8)0x00400d84 24: lui v0,0x400x00400d88 28: addiu a1,v0,100920x00400d8c 32: jal 0x401cf8 strings_not_equal0x00400d90 36: nop0x00400d94 40: beqz v0,0x400da4 phase_1560x00400d98 44: nop0x00400d9c 48: jal 0x4021f0 explode_bomb0x00400da0 52: nop0x00400da4 56: move sp,s80x00400da8 60: lw ra,28(sp)0x00400dac 64: lw s8,24(sp)0x00400db0 68: addiu sp,sp,320x00400db4 72: jr ra0x00400db8 76: nop
首先前四行 这是为了在栈中开辟空间即为 phase_1 开辟足够的空间以便函数运行 接下来可以看见一个 string_not_equal 函数用于对字符串进行比较。那么比较的是哪两个字符串呢我们可以使用 GDB 调试来查看相关寄存器的值
在函数 string_not_equal 调用之前涉及到了 a0a0a0 和 a1a1a1 寄存器的处理我们先看 a0a0a0 再往后面输出 12 个字符 刚好是我们输入的字符串 Helloworld!Hello\ world!Hello world!那么可以确定 a0a0a0 中存的就是输入的字符串
接着在 0x00400d8c 设置断点之后看 a1a1a1 发现我们输出 17 个字符之后第 17 个字符就是 ‘\000’ 了看前面 16 个字符刚好组成Let′sbeginnow!Lets\ begin\ now!Let′s begin now!就是内部存储的要比较的字符串啦~
结合 string_not_equal 可知phase_1 比较的就是我们输入的字符串和 Let′sbeginnow!Lets\ begin\ now!Let′s begin now! 是否相等并将返回值存入了专门存放返回值的寄存器 v0v0v0相等为 1不相等为 0。接下来就是一个比较过程根据注释食用哈~ 0x00400d6c 0: addiu sp,sp,-320x00400d70 4: sw ra,28(sp)0x00400d74 8: sw s8,24(sp)0x00400d78 12: move s8,sp //为程序段开辟栈空间0x00400d7c 16: sw a0,32(s8)0x00400d80 20: lw a0,32(s8) //重新使输入的字符串入栈0x00400d84 24: lui v0,0x400x00400d88 28: addiu a1,v0,10092 //a1 m[0x40 10092]到内存中找到我们要比较的字符串0x00400d8c 32: jal 0x401cf8 strings_not_equal //进入函数比较字符串是否相等相等则返回1不相等返回0这个返回存入了寄存器 $v0 中0x00400d90 36: nop0x00400d94 40: beqz v0,0x400da4 phase_156 //$v0 为 0 则继续炸弹爆炸不为 0 跳转到 56 的位置可以看到跳转后便跳过了炸弹第一个炸弹 avoided0x00400d98 44: nop0x00400d9c 48: jal 0x4021f0 explode_bomb0x00400da0 52: nop0x00400da4 56: move sp,s8 //这后面就是归还栈空间并跳回 $ra 寄存器中的地址即return返回主函数了0x00400da8 60: lw ra,28(sp)0x00400dac 64: lw s8,24(sp)0x00400db0 68: addiu sp,sp,320x00400db4 72: jr ra0x00400db8 76: nop
炸弹一总结
爆炸点1 比较输入字符串是否与 Let′sbeginnow!Lets\ begin\ now!Let′s begin now! 是否相等不相等就 BOOM!BOOM!BOOM!。
phase_2 0x00400dbc 0: addiu sp,sp,-640x00400dc0 4: sw ra,60(sp)0x00400dc4 8: sw s8,56(sp)0x00400dc8 12: move s8,sp0x00400dcc 16: lui gp,0x420x00400dd0 20: addiu gp,gp,-200800x00400dd4 24: sw gp,16(sp)0x00400dd8 28: sw a0,64(s8)0x00400ddc 32: addiu v0,s8,280x00400de0 36: lw a0,64(s8)0x00400de4 40: move a1,v00x00400de8 44: jal 0x401ba8 read_six_numbers0x00400dec 48: nop0x00400df0 52: lw gp,16(s8)0x00400df4 56: lw v1,28(s8)0x00400df8 60: li v0,10x00400dfc 64: beq v1,v0,0x400e10 phase_2840x00400e00 68: nop0x00400e04 72: jal 0x4021f0 explode_bomb
首先我们找到这道题的切入点 0x00400de8 44: jal 0x401ba8 read_six_numbersread_six_numbers()很明显这是在告诉我们需要输入 6 个数那我们随便输入 6 个数2 4 7 9 15 20。注意若输入的不是 6 个数会直接爆炸的这是我偶然试错试出来的 做个记录炸弹1输入的数字个数必须是 6 个否则爆炸
接着往下 0x00400dfc 64: beq v1,v0,0x400e10 phase_284将寄存器 v0v0v0 与寄存器 v1v1v1 进行了比较那么我们可以使用 p $v0 和 p $v1 来查看一下两个寄存器中的值 发现 v0v0v0 中存正好是我们输入的第一个 2这会不会是巧合呢我们向上查找有这么一句 0x00400df4 56: lw v1,28(s8) //m[$s828] v1v1v1v1 中的值是从内存中来的如果我们的猜想正确的话继续往后读 7 个数 前 6 个是我们输入的数第 7 个数由于我们没有输入因此是无效数据这就说明我们的猜想是正确的内存 m[m[m[s828]$ 中存着我们输入的第一个数并使用了 load 访存将它放入了寄存器 v1v1v1 中。
结合着这句话 0x00400dfc 64: beq v1,v0,0x400e10 phase_284意识到是要将我们输入的第一个数与 1 作比较相等则跳转到 phase_284 行去也就避开了炸弹咯~否则炸弹就爆炸了
做个记录炸弹2第一个数必须是1否则爆炸
接下来循环开始大家根据注释食用哈~ 0x00400e08 76: nop0x00400e0c 80: lw gp,16(s8)0x00400e10 84: li v0,1 //循环变量相当于 for 循环中的 i10x00400e14 88: sw v0,24(s8) //m[$s824] $v0将该循环变量入栈0x00400e18 92: b 0x400ea8 phase_2236 //循环开始跳转到 phase_2236 的位置进行条件判断0x00400e1c 96: nop0x00400e20 100: lw v0,24(s8) //$v0 m[$s824]取出栈中之前存好的 i即v010x00400e24 104: nop0x00400e28 108: addiu v0,v0,-1 //$v0--0x00400e2c 112: sll v0,v0,0x2 //左移操作相当于 $v0 $v0 * 4把它变为int型变量的长度0x00400e30 116: addiu v1,s8,24 //$s824 相当于到达了之前存循环变量 i 的地址0x00400e34 120: addu v0,v1,v0 //$s8 24 $v0 * 4到达要取的数之前 4 个字节的位置这一步看着有点奇怪为什么不一步到位呢我也是根据下一句才推出来的0x00400e38 124: lw a0,4(v0) //a0 m[$v04]将我们输入的第 i 个数存入 $a0 中0x00400e3c 128: li v1,12 //根据后面的0x00400e40 132: lw v0,24(s8) //$v0 m[$s824]将之前的循环变量 i 读入0x00400e44 136: nop0x00400e48 140: subu v0,v1,v0 //$v0 $v1(12) - $v0(i)0x00400e4c 144: lw v1,-32660(gp) //读取输入的学号0x00400e50 148: sll v0,v0,0x2 //左移操作将 $v0 变为int型变量的长度 0x00400e54 152: addu v0,v1,v0 //$v0 $v1 $v0到达了输入学号的倒数第 i 位的位置具体可以使用 x $v0 进行查看0x00400e58 156: lw v0,0(v0) //$v0 m[$v0]将学号的倒数第 i 为存入 $v00x00400e5c 160: nop0x00400e60 164: mult a0,v00x00400e64 168: mflo a0 //上一句将 $a0 中的值与 $v0 中的值相乘此句意为将结果存入 $a0 中0x00400e68 172: lw v0,24(s8) //$v0 m[$s824]取出之前的循环变量 i0x00400e6c 176: nop0x00400e70 180: sll v0,v0,0x2 //将 i 变为int型变量的长度0x00400e74 184: addiu v1,s8,240x00400e78 188: addu v0,v1,v0 //$s8 24 $v0 * 40x00400e7c 192: lw v0,4(v0) //这里与116处类似但要注意的是上面的 $v0 是自减之后那时得到的是输入的第 i 个数在这里$v0 i没有自减因此取得的是第 i 1 个数!//192有点难以想到~0x00400e80 196: nop0x00400e84 200: beq a0,v0,0x400e98 phase_2220 //比较乘积项 a0 与 输入的第 i1 个数 v0 是否相等//是则跳转到 phase_2220 处咯避开了炸弹否则就爆炸啦0x00400e88 204: nop0x00400e8c 208: jal 0x4021f0 explode_bomb0x00400e90 212: nop0x00400e94 216: lw gp,16(s8) //末尾循环体0x00400e98 220: lw v0,24(s8) //取得之前的循环变量 i0x00400e9c 224: nop0x00400ea0 228: addiu v0,v0,1 //相当于 i 啦0x00400ea4 232: sw v0,24(s8) //m[$s824] v0存入之前放置循环变量的位置0x00400ea8 236: lw v0,24(s8) //第一次条件判断是从 phase_292 跳转下来的哈~0x00400eac 240: nop0x00400eb0 244: slti v0,v0,6 //循环体条件判断i 6 ?0x00400eb4 248: bnez v0,0x400e20 phase_2100 //为1则跳转到 phase_2100 处咯继续循环体否则就往下执行了~0x00400eb8 252: nop //下面就是归还空间跳回主函数啦~//Congratulations你坚持了下来0x00400ebc 256: move sp,s80x00400ec0 260: lw ra,60(sp)0x00400ec4 264: lw s8,56(sp)0x00400ec8 268: addiu sp,sp,640x00400ecc 272: jr ra0x00400ed0 276: nop
做个记录炸弹3第 i 个输入数据与学号的倒数第 i 位相乘结果若不与第 i 1 个输入数据相同则爆炸
炸弹二总结
爆炸点1 输入的数字个数必须是 6 个否则爆炸 爆炸点2 第一个数必须是1否则爆炸 爆炸点3 第 i 个输入数据与学号的倒数第 i 位相乘结果若不与第 i 1 个输入数据相同则爆炸
phase_3
首先从 bomb.s 文件中 phase_3 前面的 $LC13 中可以看到输入的格式为 %d%s%d\%d\ \%s\ \%d%d %s %d即整数字符整数的格式。
接着往下 0x00400ed4 0: addiu sp,sp,-560x00400ed8 4: sw ra,52(sp)0x00400edc 8: sw s8,48(sp)0x00400ee0 12: move s8,sp //为程序留出空间0x00400ee4 16: lui gp,0x420x00400ee8 20: addiu gp,gp,-200800x00400eec 24: sw gp,24(sp)0x00400ef0 28: sw a0,56(s8)0x00400ef4 32: lw a0,56(s8)0x00400ef8 36: lui v0,0x400x00400efc 40: addiu a1,v0,101120x00400f00 44: addiu v1,s8,440x00400f04 48: addiu v0,s8,400x00400f08 52: addiu a2,s8,360x00400f0c 56: sw a2,16(sp)0x00400f10 60: move a2,v10x00400f14 64: move a3,v00x00400f18 68: lw v0,-32636(gp) //从 bomb.s 中可以得到这里调用了 scanf() 函数0x00400f1c 72: nop0x00400f20 76: move t9,v00x00400f24 80: jalr t9 //这一步干了啥
根据注释可以看出以上的汇编主要是为了留出空间调用 scanf() 输入以及一个不知所踪的跳转以至于在 GDB 中单步调试无法访存根据后面盲猜是计算参数个数
马上就要遇见炸弹了但还不知道 phase_3 是干嘛的有点慌
不过往下看 0x00400f28 84: nop0x00400f2c 88: lw gp,24(s8)0x00400f30 92: slti v0,v0,30x00400f34 96: beqz v0,0x400f48 phase_31160x00400f38 100: nop0x00400f3c 104: jal 0x4021f0 explode_bomb0x00400f40 108: nop似乎这个炸弹是根据比较来进行是否爆炸的跟谁比呢92 有语句 slti v0, v0, 3说明是将 v0v0v0 的值与 3 进行了比较那么 v0v0v0 又是什么呢由于前面知道了输入应该是整数 字符 整数因此此时查看 v0v0v0 的值是3 嘶这之前只进行了栈分配输入并没有出现 3 吖栈分配无从下手那就从输入下手
没有输入3却出现了 3而我输入的是三个参数会不会 v0v0v0 里面存的就是参数个数呢为此不妨改变输入试试
我将输入改为只输入 2 个数发现 果然变成了 2不妨输入 1 个参数试试是变成了 1 的。因此此时的比较就是判断我们输入的参数是不是小于 3 个是则爆炸话说回来这样的判断确实有点勉强不过在猜想 试错的策略下确实可以解决一些问题哒此时小于的比较结果为 0即参数个数 3注意 beqz 和 benz区别就跳转即避开了炸弹啦~
做个记录爆炸点1输入参数的个数若小于 3则爆炸
跳转过后从 phase_3116 看起 0x00400f44 112: lw gp,24(s8)0x00400f48 116: lw v0,44(s8)0x00400f4c 120: nop0x00400f50 124: sltiu v1,v0,80x00400f54 128: beqz v1,0x401190 phase_37000x00400f58 132: nopphase_37000x00401190 700: li v0,1200x00401194 704: sb v0,32(s8)0x00401198 708: jal 0x4021f0 explode_bomb0x0040119c 712: nop这段汇编 load 了一些数据gpgpgp 是一个全局指针无从下手v0v0v0 是一个寄存器后面使用了它来进行比较那么我们就先看看 v0v0v0 里面存的是什么 可以看到$s836 ~ $s844 分别存储着我们输入的三个参数 102, ‘t’, 5。其中字符表示为ascii码且数据均为 16 进制
那么phase_3124 的比较语句也就是为了去比较我们输入的第一个参数是否小于 8。若比较结果不小于 8则 $v1 1不发生跳转否则跳转至 phase_3700 的位置炸弹爆炸
做个记录爆炸点2第一个参数若大于 8 则炸弹爆炸
接下来是一个根据第一个参数来进行 switch…case… 的过程大家根据注释食用 0x00400f5c 136: sll v1,v0,0x2 //将第一个输入参数 $v0 变为int型变量的长度0x00400f60 140: lui v0,0x400x00400f64 144: addiu v0,v0,101240x00400f68 148: addu v0,v1,v00x00400f6c 152: lw v0,0(v0) //以上三句是为了找到 switch...case... 语句中我们应该跳转到的子句的地址并保存在 $v0 中//可以使用 x $v0 来进行查看跳转的地址0x00400f70 156: nop0x00400f74 160: jr v0 //跳转到对应的 switch...case... 语句0x00400f78 164: nop我现在输入的第一个参数是 5因此跳转到 phase_3504 的位置。
接下来是相应的一些 case 语句下面就来说说 switch…case… 语句中的相关操作我们就以 case 5 为例从上面可以看到跳转到了 504 的位置哈~ 0x004010cc 504: li v0,116 //$v0 1160x004010d0 508: sb v0,32(s8) //m[$s832]$v01160x004010d4 512: lw v0,-32660(gp)0x004010d8 516: nop首先将立即数 116 存入了内存之中 0x004010dc 520: lw v1,44(v0)0x004010e0 524: lw v0,36(s8)0x004010e4 528: nop0x004010e8 532: mult v1,v00x004010ec 536: mflo v10x004010f0 540: li v0,5130x004010f4 544: beq v1,v0,0x4011e8 phase_37880x004010f8 548: nop0x004010fc 552: jal 0x4021f0 explode_bomb0x00401100 556: nop在这段汇编中我们先查看 v1v1v1 中存的是什么根据前面的经验以及不断试错可以发现将全局指针 $gp-32660 给到 $v0并读取 $v044 的数据时我们读取到的是输入的学号的最后一位假设是 7。
同时前面说过$s836 的位置存储着我们输入的第三个参数我输入的是102现在将它加载到了 $v0 中。
接下来根据 $v1 $v1 * $v0 可知这一句是将学号的最后一位和我们输入的第三个参数相乘这里结果位714。
接着将 513 存入了 $v0 寄存器中然后将我们相乘的结果和 $v0 进行了比较。若相等则发生跳转也就避开炸弹啦否则炸弹爆炸显然 513 ≠ 714炸弹爆炸
做个记录爆炸点3学号的最后一位与输入的第三个数据相乘然后与不同 case 中的数字进行比较若不相等则爆炸
到此已经避开 3 个炸弹啦别急后面还有同时在这里做一个内存记录内存中 $s8 32 的地方存的是 116 哈忘了请看 phase_3504 的位置 0x004011e8 788: nop0x004011ec 792: b 0x4011f8 phase_38040x004011f0 796: nop0x004011f4 800: nop0x004011f8 804: lb v0,40(s8)0x004011fc 808: lb v1,32(s8)0x00401200 812: nop0x00401204 816: beq v1,v0,0x401218 phase_38360x00401208 820: nop0x0040120c 824: jal 0x4021f0 explode_bomb0x00401210 828: nop0x00401214 832: lw gp,24(s8)0x00401218 836: move sp,s80x0040121c 840: lw ra,52(sp)0x00401220 844: lw s8,48(sp)0x00401224 848: addiu sp,sp,560x00401228 852: jr ra0x0040122c 856: nop经过连续跳转我们到了 804 的位置首先 $s840 存着我们输入的第二个字符参数这里我输入的是 ‘t’将它取到 $v0 中
接着根据刚刚的内存分析$s832 存的是 116将它取到 $v1 中
下面就开始比较了由于我们输入的第二个参数是字符因此这是一个字符的比较。走到这里实际上我们之前的 116 不妨去查一查其实是字符 ‘t’因此这里是比较 case 里面设置的字符是不是和我们输入的第二个字符参数相等。若相等跳转到 836 避开炸弹啦否则炸弹爆炸
做个记录爆炸点4每个 case 里面设置了一个字符若我们输入的第二个字符不与这个字符相等则炸弹爆炸
接下来就请看注释了哈~。case 很多所以注释也很多其实看懂了上面的内容所有 case 都是一个套路了
炸弹三总结
爆炸点1 输入参数的个数若小于 3则爆炸 爆炸点2 第一个参数若大于 8 则炸弹爆炸 爆炸点3 学号的最后一位与输入的第三个数据相乘然后与不同 case 中的数字进行比较若不相等则爆炸 爆炸点4 每个 case 里面设置了一个字符若我们输入的第二个字符不与这个字符相等则炸弹爆炸
phase_4
phase_4 是一个递归由 phase_4 之前的 $LC14 发现我们输入的形式是 %d\%d%d 即一个整数。首先我们看调用递归函数之前的部分
先看第一个炸弹怎样才能避开呢 0x004012bc 0: addiu sp,sp,-400x004012c0 4: sw ra,36(sp)0x004012c4 8: sw s8,32(sp)0x004012c8 12: move s8,sp0x004012cc 16: lui gp,0x420x004012d0 20: addiu gp,gp,-200800x004012d4 24: sw gp,16(sp)0x004012d8 28: sw a0,40(s8)0x004012dc 32: lw v1,40(s8)0x004012e0 36: lui v0,0x400x004012e4 40: addiu v0,v0,101560x004012e8 44: move a0,v10x004012ec 48: move a1,v00x004012f0 52: addiu v0,s8,240x004012f4 56: move a2,v0 //前面是为了给函数申请空间0x004012f8 60: lw v0,-32636(gp) //调用输入函数进行输入0x004012fc 64: nop0x00401300 68: move t9,v00x00401304 72: jalr t9 //去判断是否有输入有输入返回1无输入返回00x00401308 76: nop0x0040130c 80: lw gp,16(s8)
--Type RET for more, q to quit, c to continue without paging--c0x00401310 84: move v1,v00x00401314 88: li v0,1 //由于输入一个整数这里就是去判断是否有输入0x00401318 92: bne v1,v0,0x401330 phase_4116 //与 1 进行比较有输入则继续五输入则跳转至 116炸弹爆炸0x0040131c 96: nop在 phase_3 中类似于 jalr t9 这样的指令是去计算参数个数但在此处似乎不太一样。无论我的输入是两个数还是两位数$v0 都返回 1同时我们必须输入一个数。因此在这里我认为是为了判断有无输入了~
做个记录爆炸点1判断有无输入直到有输入
接着往下看 0x00401334 120: nop0x00401338 124: lw gp,16(s8)0x0040133c 128: nop0x00401340 132: lw v0,-32660(gp)0x00401344 136: nop0x00401348 140: lw v0,44(v0)0x0040134c 144: nop首先我们在 phase_3 的分析中说过全局指针 $gp-32660 之后再访问其 44 后的内存存储着我们输入的学号的最后一位所以这一段意为取出输入的学号的最后一位到 $v0 中去。
下面这段汇编进行了一个与运算 0x00401350 148: andi v0,v0,0x10x00401354 152: andi v0,v0,0xff0x00401358 156: beqz v0,0x40139c phase_42240x0040135c 160: nop前两句对学号的最后一位 $v0 进行了两个与运算以判断奇偶举个例子 (5)10(00000101)2(5)_{10}(00000101)_2(5)10(00000101)2将它与 0x1 进行与运算∵0x1(00000001)2∴(00000101)2(00000001)2(00000001)2\because 0x1(00000001)_2 \\ \therefore (00000101)_2\ \ \ \ \ (00000001)_2(00000001)_2∵0x1(00000001)2∴(00000101)2 (00000001)2(00000001)2表示 5 是一个奇数。这里我认为可以仅和 0x1 进行与运算就可以了后面的再与 0xff 进行与运算没搞懂
然后便根据奇偶检验结果进行跳转若为 1表示学号最后一位为奇数继续进行若为 0表示学号最后一位为偶数发生跳转。
假设这里我们输入的学号的最后一位为 7那么继续进行
往下看 0x0040135c 160: nop0x00401360 164: lw v0,24(s8)0x00401364 168: nop0x00401368 172: move a0,v00x0040136c 176: jal 0x401230 func40x00401370 180: nop0x00401374 184: lw gp,16(s8)0x00401378 188: move v1,v00x0040137c 192: li v0,80x00401380 196: beq v1,v0,0x4013d0 phase_42760x00401384 200: nop0x00401388 204: jal 0x4021f0 explode_bomb0x0040138c 208: nop首先load 进了一个数通过在 GDB 中使用 p $v0 可以看到此时 $v0 中的数是我们输入的那个数。 接着我们将 $v0 中的值即56传入了寄存器 $a0 中然后便进入递归函数 func4 了。下面我们就去看看这个递归函数干了什么大家根据注释食用哈~ 0x00401230 0: addiu sp,sp,-400x00401234 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) //m[$s840]$a056这个 $a0 相当于传入的参数在 phase_4 跳转之前已经设置为我们输入的数 56 啦0x00401248 24: lw v0,40(s8) //$v0m[$s840]将我们传入的参数给 $v00x0040124c 28: nop0x00401250 32: slti v0,v0,2 //比较这个参数是否小于 20x00401254 36: bnez v0,0x40129c func4108 //若小于 2$v01跳转到 108 的地方去反之则继续//先到 108 看看发生了什么哈~0x00401258 40: nop0x0040125c 44: lw v0,40(s8) //$v0m[$s840]56将之前存在内存中的参数给到 $v00x00401260 48: nop0x00401264 52: addiu v0,v0,-1 //$v0--相当于 $v0 55了0x00401268 56: move a0,v0 //$a0$v0550x0040126c 60: jal 0x401230 func4 //再次执行函数就是递归调用了//仔细看这里传入的参数是 55联系函数开始时是通过 $a0 传参的这一切就说得通了//这一步相当于是递归调用 f(x-1)0x00401270 64: nop0x00401274 68: move s0,v0 //$v0 是函数的返回值因此这里就是 f(x-1) 函数执行后的返回值//若在这一步在 GDB 中进行调试会有大量continue需要输入因此是个递归函数hhh0x00401278 72: lw v0,40(s8) //$v0m[$s840]56同样将之前存在内存中的参数给到 $v00x0040127c 76: nop0x00401280 80: addiu v0,v0,-2 //$v0 - 2; $v0 自减2
--Type RET for more, q to quit, c to continue without paging--c0x00401284 84: move a0,v0 //又是一个传参0x00401288 88: jal 0x401230 func4 //再次执行函数相当于递归调用 f(x-2) 啦~0x0040128c 92: nop0x00401290 96: addu v0,s0,v0 //此时 $s0 中是 f(x-1) 的返回值 $v0 是 f(x-2) 的返回值二者相加返回给 $v00x00401294 100: b 0x4012a0 func4112 //跳转到 112 归还空间啦~0x00401298 104: nop0x0040129c 108: li v0,1 //$v01下面就是归还栈空间了。这就是递归的终止条件//if($v0 2) return 1;0x004012a0 112: move sp,s80x004012a4 116: lw ra,36(sp)0x004012a8 120: lw s8,32(sp)0x004012ac 124: lw s0,28(sp)0x004012b0 128: addiu sp,sp,400x004012b4 132: jr ra0x004012b8 136: nop因此这个递归函数实质上就是
if (x 2)return 1;
elsereturn f(x-1) f(x-2);这个递归函数就是一个求斐波那契数列的值得到了递归函数的返回值并保存在 $v0 中我们继续看下面的汇编根据注释食用哈 0x00401370 180: nop0x00401374 184: lw gp,16(s8)0x00401378 188: move v1,v0 //将函数调用的返回值给到 $v10x0040137c 192: li v0,8 //$v0 80x00401380 196: beq v1,v0,0x4013d0 phase_4276 //比较 $v1 和 $v0 中的值即函数返回值是否等于 8相等则跳转避开了炸弹否则炸弹爆炸啦0x00401384 200: nop0x00401388 204: jal 0x4021f0 explode_bomb0x0040138c 208: nop0x00401390 212: lw gp,16(s8)0x00401394 216: b 0x4013d0 phase_42760x00401398 220: nop...0x004013d0 276: move sp,s8 //跳转到 276就是简单归还空间了0x004013d4 280: lw ra,36(sp)0x004013d8 284: lw s8,32(sp)0x004013dc 288: addiu sp,sp,400x004013e0 292: jr ra0x004013e4 296: nop以上是奇数的情况偶数的情况相同只是对递归函数返回值的要求不同我们直接看需要比较的值
...0x004013ac 240: nop0x004013b0 244: lw gp,16(s8)0x004013b4 248: move v1,v00x004013b8 252: li v0,130x004013bc 256: beq v1,v0,0x4013d0 phase_42760x004013c0 260: nop
...是 13
因此为了避开这个炸弹就要使我们的斐波那契数列 f(input)f(input)f(input) 的返回值等于相应的值 Inorderavoidthebomb,then{f(input)8,if学号最后一位是奇数f(input)13,if学号最后一位是偶数In\ order\ avoid\ the\ bomb, then\left\{ \begin{aligned} f(input) 8, if \text{学号最后一位是奇数}\\ f(input) 13, if \text{学号最后一位是偶数} \end{aligned} \right.In order avoid the bomb,then{f(input)8,f(input)13,if学号最后一位是奇数if学号最后一位是偶数所以奇数输入 5偶数输入 6成功啦
做个记录爆炸点2输入n斐波那契求值得到 f(n)。根据学号最后一位的奇偶性来判断 f(n) 是否与目标值相等。不相等则爆炸
炸弹4总结
爆炸点1 判断有无输入 爆炸点2 输入n斐波那契求值得到 f(n)。根据学号最后一位的奇偶性来判断 f(n) 是否与目标值相等。不相等则爆炸
phase_5
首先说个题外话在看这道题之前我们看怎样输入时发现 $LC15 那儿有一个 “giants”不是平常的 %d\%d%d 什么的。莫非…就是输入“giants”尝试输入了一下似乎并不是hhh
直接看汇编吧第一个炸弹之前 0x004013e8 0: addiu sp,sp,-720x004013ec 4: sw ra,68(sp)0x004013f0 8: sw s8,64(sp)0x004013f4 12: move s8,sp //申请空间0x004013f8 16: sw a0,72(s8)0x004013fc 20: lw a0,72(s8)0x00401400 24: jal 0x401c78 string_length0x00401404 28: nop0x00401408 32: move v1,v00x0040140c 36: li v0,60x00401410 40: beq v1,v0,0x401420 phase_5560x00401414 44: nop0x00401418 48: jal 0x4021f0 explode_bomb0x0040141c 52: nop
前四句常规申请空间操作。接下来存了又读了个啥我随便输入了“youbom”在 GDB 中看看 可以看到$a0 里面存入的就是我们输入的字符串
...0x004013f8 16: sw a0,72(s8)0x004013fc 20: lw a0,72(s8)
...这两句将它存入了 m[$s872] 的位置。 接下来发生了跳转
...0x00401400 24: jal 0x401c78 string_length
...顾名思义这里跳转到了计算输入字符串的长度并将返回结果存入了专门存储返回值的寄存器 $v0 中。接下来比较了字符串的长度是否为 6否则炸弹爆炸请看下面注释了 0x00401400 24: jal 0x401c78 string_length //字符串长度存储在 $v0 中0x00401404 28: nop0x00401408 32: move v1,v0 //$v1$v0将长度放入 $v0 中0x0040140c 36: li v0,6 //$v060x00401410 40: beq v1,v0,0x401420 phase_556 //比较字符串长度是否为6是则跳转就避开炸弹啦否则炸弹爆炸0x00401414 44: nop0x00401418 48: jal 0x4021f0 explode_bomb0x0040141c 52: nop做个记录爆炸点1输入的字符串长度若不为 6炸弹爆炸
接着往下 0x00401420 56: sw zero,24(s8) //m[$s824]0初始化循环变量 i 00x00401424 60: b 0x4014a8 phase_5192 //跳转至条件判断下面请看 192 了哈0x00401428 64: nop0x0040142c 68: lw v0,24(s8) //$v0m[$s824]拿到之前的循环变量0x00401430 72: lw v1,24(s8) //$v1m[$s824]拿到之前的循环变量0x00401434 76: lw a0,72(s8) //$a0m[$s872]拿到之前的字符串
--Type RET for more, q to quit, c to continue without paging--c0x00401438 80: nop0x0040143c 84: addu v1,a0,v1 //到达字符串中的第 i1 个字符注意 i 是从 0 开始的哈第一次拿到第 1 个字符后面以此类推~0x00401440 88: lb v1,0(v1) //拿到对应的第 i1 个字符0x00401444 92: nop0x00401448 96: andi v1,v1,0xff0x0040144c 100: andi v1,v1,0xf //取得字符所表示的二进制数的后四位//比如第一个字符‘y’其ASCII码的十六进制表示为0x79(01111001)后四位10010x00401450 104: sll v0,v0,0x2 //将 $v0 中的值变为int型变量的长度0x00401454 108: addiu a0,s8,24 //到达之前存储循环变量 i 的地址0x00401458 112: addu v0,a0,v0 //从这个地址向后移 4 位0x0040145c 116: sw v1,12(v0) //将字符的后四位存储在 m[$s8 24 12 i * 4] 的位置
...0x004014a8 192: lw v0,24(s8) //拿到之前存储的循环变量i0x004014ac 196: nop0x004014b0 200: slti v0,v0,6 //循环条件判断i 6 ?0x004014b4 204: bnez v0,0x40142c phase_568 //不为 0 则跳转//若 i 6, $v0 1跳转至循环体部分若 i 6, $v0 0循环结束继续执行。0x004014b8 208: nop以上的汇编目的就是对我们输入的字符串的每一个字符取 ASCII 码的后四位并保存。
紧接着 0x00401460 120: lw a0,24(s8) //取得之前的循环变量 i0x00401464 124: lw v0,24(s8) //取得之前的循环变量 i0x00401468 128: nop0x0040146c 132: sll v0,v0,0x2 //将 i 变为int型长度0x00401470 136: addiu v1,s8,24 //读得 i 的地址0x00401474 140: addu v0,v1,v00x00401478 144: lw v1,12(v0) //回想之前存字符后四位的地方这里就是取得字符的后四位存到 $v1 中0x0040147c 148: lui v0,0x410x00401480 152: addiu v0,v0,125240x00401484 156: addu v0,v1,v00x00401488 160: lb v1,0(v0)0x0040148c 164: addiu v0,s8,240x00401490 168: addu v0,v0,a00x00401494 172: sb v1,4(v0)
看到 148 时有点懵这是干了啥4 句过后又是去访问循环变量的地址。因此我们在这一句设断点看看发生了啥。 这个 9 提示了我们莫非前面还有字符我们使用 x/s $v0-9 发现 这个地址存的就是这样一个字符串。现在我们回到那 4 句汇编 0x00401478 144: lw v1,12(v0)0x0040147c 148: lui v0,0x410x00401480 152: addiu v0,v0,12524 //找到这个内置字符串的开头地址0x00401484 156: addu v0,v1,v0 //从开头地址往后移动 $v0 位//$v0 是什么字符的后四位。我们第一个输入的字符是 ‘y’后四位 1001也就是 9。//我们在第一次使用 x/s $v0 输出的时候输出就是从第 9 位开始的//说明我们字符的后四位就是为了在这个内置字符串中找到相应的字符0x00401488 160: lb v1,0(v0) //为了验证上面的猜想我们在这里输出 $v1 试试发现就是 ‘b’。那么基本思路就有了首先根据我们输入的字符串中的每个字符取 ASCII 码的后四位再根据这后四位再内置字符串中找到对应的字符举个栗子见下面的表格接着…往后看吧
‘y’‘o’‘u’‘b’‘o’‘m’后四位100111110101001011111101十进制915521513“isrveawhobpnutfg”‘b’‘g’‘a’‘r’‘g’‘t’
有了这个字符串接着往下 0x00401488 160: lb v1,0(v0)0x0040148c 164: addiu v0,s8,240x00401490 168: addu v0,v0,a00x00401494 172: sb v1,4(v0) //将它们存入 m[$s8 28 i]的位置Going on 0x004014b8 208: nop0x004014bc 212: sb zero,34(s8) //字符串的尾部置空 \00x004014c0 216: addiu v0,s8,280x004014c4 220: move a0,v0 //这两句到达之前存经过映射后的字符串bgargt的位置0x004014c8 224: lui v0,0x400x004014cc 228: addiu a1,v0,10160 //另一个比较的字符串GDB 查看查看0x004014d0 232: jal 0x401cf8 strings_not_equal0x004014d4 236: nop居然是 “giants”之前差点当作输入破案了就是要通过映射将输入映射为 “giants”
做个记录爆炸点2通过内置字符串映射若结果为“giants”则通过否则炸弹爆炸
下面就看注释了哈 0x004014d8 240: beqz v0,0x4014e8 phase_5256 //比较结果//注意string_NOT_equal若相等返回的是 0而不是 10x004014dc 244: nop0x004014e0 248: jal 0x4021f0 explode_bomb0x004014e4 252: nop0x004014e8 256: move sp,s8 //归还空间拆弹结束0x004014ec 260: lw ra,68(sp)0x004014f0 264: lw s8,64(sp)0x004014f4 268: addiu sp,sp,720x004014f8 272: jr ra0x004014fc 276: nop炸弹五总结
爆炸点1 输入的字符串长度若不为 6炸弹爆炸 爆炸点2 通过内置字符串映射若结果为“giants”则通过否则炸弹爆炸
phase_6 0x00401500 0: addiu sp,sp,-960x00401504 4: sw ra,92(sp)0x00401508 8: sw s8,88(sp)0x0040150c 12: move s8,sp0x00401510 16: lui gp,0x420x00401514 20: addiu gp,gp,-200800x00401518 24: sw gp,16(sp)0x0040151c 28: sw a0,96(s8)0x00401520 32: lui v0,0x410x00401524 36: addiu v0,v0,125920x00401528 40: sw v0,32(s8)0x0040152c 44: addiu v0,s8,360x00401530 48: lw a0,96(s8)0x00401534 52: move a1,v00x00401538 56: jal 0x401ba8 read_six_numbers前面就是申请程序空间然后 read_six_numbers() 读取 6 个数。
接下来就是循环开始了 0x0040153c 60: nop0x00401540 64: lw gp,16(s8)0x00401544 68: sw zero,28(s8) //m[$s828]0设置循环变量 i 00x00401548 72: b 0x40163c phase_6316 //跳转到第一重循环的条件判断看316的位置哈0x0040154c 76: nop
--Type RET for more, q to quit, c to continue without paging--c0x00401550 80: lw v0,28(s8) //取得循环变量 i0x00401554 84: nop0x00401558 88: sll v0,v0,0x2 //将 i 变成int型长度0x0040155c 92: addiu v1,s8,240x00401560 96: addu v0,v1,v00x00401564 100: lw v0,12(v0) //取得输入的第 i 个数可以在 GDB 中 ‘p $v0’ 查看0x00401568 104: nop0x0040156c 108: slti v0,v0,7 //条件判断第 i 个数是否小于 7input[i] 7?0x00401570 112: beqz v0,0x40159c phase_6156 //若大于 7则跳转到156炸弹爆炸否则继续0x00401574 116: nop0x00401578 120: lw v0,28(s8) //取得之前的循环变量 i0x0040157c 124: nop0x00401580 128: sll v0,v0,0x2 //将 i 变成int型长度0x00401584 132: addiu v1,s8,240x00401588 136: addu v0,v1,v00x0040158c 140: lw v0,12(v0) //取得输入的第 i 个数可以在 GDB 中 ‘p $v0’ 查看0x00401590 144: nop0x00401594 148: bgtz v0,0x4015a8 phase_6168 //判断第 i 个数是否大于 0若是则发生跳转避开了炸弹否则炸弹爆炸0x00401598 152: nop0x0040159c 156: jal 0x4021f0 explode_bomb0x004015a0 160: nop0x004015a4 164: lw gp,16(s8)0x004015a8 168: lw v0,28(s8) //取得之前的循环变量 i0x004015ac 172: nop0x004015b0 176: addiu v0,v0,1 //第二重循环变量的设定j i 10x004015b4 180: sw v0,24(s8) //m[$s824]v0将第二重循环的循环变量进行存储0x004015b8 184: b 0x401618 phase_6280 //第二重循环的条件判断下面看280哈0x004015bc 188: nop0x004015c0 192: lw v0,28(s8) //取得第一重循环变量 i0x004015c4 196: nop0x004015c8 200: sll v0,v0,0x2 //将 i 变成int型长度0x004015cc 204: addiu v1,s8,240x004015d0 208: addu v0,v1,v00x004015d4 212: lw v1,12(v0) //取得输入的第 i 个数字0x004015d8 216: lw v0,24(s8) //取得第二重循环变量 j0x004015dc 220: nop0x004015e0 224: sll v0,v0,0x20x004015e4 228: addiu a0,s8,240x004015e8 232: addu v0,a0,v00x004015ec 236: lw v0,12(v0) //取得第 j 个数字0x004015f0 240: nop0x004015f4 244: bne v1,v0,0x401608 phase_6264 //比较 $v0 和 $v1 的值若不相等则跳转避开了炸弹不相等则炸弹爆炸0x004015f8 248: nop0x004015fc 252: jal 0x4021f0 explode_bomb0x00401600 256: nop0x00401604 260: lw gp,16(s8)0x00401608 264: lw v0,24(s8) //取得第二重循环变量 j0x0040160c 268: nop0x00401610 272: addiu v0,v0,1 //j0x00401614 276: sw v0,24(s8)0x00401618 280: lw v0,24(s8) //第二重循环的条件判断0x0040161c 284: nop0x00401620 288: slti v0,v0,6 //条件判断i 6 ?0x00401624 292: bnez v0,0x4015c0 phase_6192 //若 6则 $v0 为1再次执行循环题反之则继续往下执行啦~0x00401628 296: nop0x0040162c 300: lw v0,28(s8)0x00401630 304: nop0x00401634 308: addiu v0,v0,10x00401638 312: sw v0,28(s8)0x0040163c 316: lw v0,28(s8) //取得之前的循环变量0x00401640 320: nop0x00401644 324: slti v0,v0,6 //第一重循环条件判断i 6 ?0x00401648 328: bnez v0,0x401550 phase_6800x0040164c 332: nop做个记录爆炸点1若输入的数 0 或者 7则炸弹爆炸 做个记录爆炸点2若第 i 个数和它后面的某个数相等则炸弹爆炸
在第一个部分进行了一个二重循环。其中第一重循环判断输入的数是不是在 [0,7][0, 7][0,7] 的范围内第二重循环判断第 i 个数是否和它之后的某个数相等
phase_6 中基本都是循环所以接下来第二个循环开始啦 0x0040164c 332: nop0x00401650 336: sw zero,28(s8) //设置第一重循环变量 i 00x00401654 340: b 0x4016f8 phase_6504 //第一重循环条件判断下面看504部分哈0x00401658 344: nop0x0040165c 348: lui v0,0x410x00401660 352: addiu v0,v0,125920x00401664 356: sw v0,32(s8)看到352的时候会有些疑惑究竟存储了个什么。我们在 GDB 中进行调试看看 是一个 node1 标识盲猜是一个节点不妨先放在这儿等会儿回来推一下 0x0040164c 332: nop0x00401650 336: sw zero,28(s8) //设置第一重循环变量 i 00x00401654 340: b 0x4016f8 phase_6504 //第一重循环条件判断下面看504部分哈0x00401658 344: nop0x0040165c 348: lui v0,0x410x00401660 352: addiu v0,v0,125920x00401664 356: sw v0,32(s8)0x00401668 360: li v0,1 //设置第二重循环变量 j 10x0040166c 364: sw v0,24(s8) //m[$s824]$v00x00401670 368: b 0x40169c phase_6412 //第二重循环条件判断下面看 412 吧0x00401674 372: nop0x00401678 376: lw v0,32(s8) //找到一个位置0x0040167c 380: nop0x00401680 384: lw v0,8(v0) //将它向后移0x00401684 388: nop0x00401688 392: sw v0,32(s8) //保存移动之后的位置0x0040168c 396: lw v0,24(s8) //取得第二重循环变量 j0x00401690 400: nop0x00401694 404: addiu v0,v0,1 //j0x00401698 408: sw v0,24(s8)0x0040169c 412: lw v0,28(s8) //获得第一重循环变量 i0x004016a0 416: nop0x004016a4 420: sll v0,v0,0x2 //将 i 变成int型长度0x004016a8 424: addiu v1,s8,240x004016ac 428: addu v0,v1,v00x004016b0 432: lw v1,12(v0) //取得输入的第 i 个数0x004016b4 436: lw v0,24(s8) //取得第二重循环变量 j0x004016b8 440: nop0x004016bc 444: slt v0,v0,v1 //判断 j 是否小于输入的第 i 个数。若是则 1 集训循环若不是则 0结束循环向下执行0x004016c0 448: bnez v0,0x401678 phase_63760x004016c4 452: nop在执行到 388 的位置时我们每次查看 $v0 中的值可以发现 根据这些标识可以确定这是一个链表类型的题目。
那么我们的第二重循环代表什么呢仔细回想一下可以发现就是这段C语言代码
for (int j 1; j a[i]; j)node node-next意思就是根据我们输入的数 s去找到第 s 个节点。
那么找到了我们要干些什么接着往下看 0x004016c4 452: nop0x004016c8 456: lw v0,28(s8) //第一重循环变量0x004016cc 460: nop0x004016d0 464: sll v0,v0,0x20x004016d4 468: addiu v1,s8,240x004016d8 472: addu v0,v1,v00x004016dc 476: lw v1,32(s8) //$v1m[$s832]取了一个数是什么呢可以在 GDB 中看一看下面有截图哈可以看到 $v1 存的就是对应节点的值0x004016e0 480: nop0x004016e4 484: sw v1,36(v0) //m[$v036]$v1将这个值存到内存中。//由于这是一个循环因此存的地址是连续的。因此可以看作将节点的值存到一个数组里面去了假设这个数组就是newList哈~0x004016e8 488: lw v0,28(s8) //取得第一重循环变量 i0x004016ec 492: nop0x004016f0 496: addiu v0,v0,1 //i0x004016f4 500: sw v0,28(s8) //保存循环变量0x004016f8 504: lw v0,28(s8) //取得第一重循环变量0x004016fc 508: nop0x00401700 512: slti v0,v0,6 //第一重循环条件判断i 6 ? 若不小于则继续循环若小于则结束循环往下执行啦0x00401704 516: bnez v0,0x40165c phase_63480x00401708 520: nop在 476 对 $v1 进行查看时发现 node3 中的值是 0x12d由于我第一个输入为 3所以刚好也满足前面的推断。接下来把所有节点的值都列出来啦
节点值node10x0fdnode20x2d5node30x12dnode40x3e5node50x0d4node60x1b0
接下来 0x0040170c 524: lw v0,60(s8)0x00401710 528: nop0x00401714 532: sw v0,32(s8) //到达 newList[0]0x00401718 536: li v0,1 //设置循环变量 i 10x0040171c 540: sw v0,28(s8) //m[$s828]$v0存储循环变量0x00401720 544: b 0x40177c phase_6636 //循环条件判断下面看636哈0x00401724 548: nop0x00401728 552: lw v0,28(s8) //获得循环变量 i0x0040172c 556: nop0x00401730 560: sll v0,v0,0x2 //将 i 变成int型长度0x00401734 564: addiu v1,s8,240x00401738 568: addu v0,v1,v00x0040173c 572: lw v1,36(v0) //得到 newList[i]第一次中这个是第二个节点哦0x00401740 576: lw v0,32(s8) //第一次中这是第一个节点node0x00401744 580: nop0x00401748 584: sw v1,8(v0) //node-nextnewList[i]0x0040174c 588: lw v0,28(s8) //取得循环变量 i0x00401750 592: nop0x00401754 596: sll v0,v0,0x2 //将 i 变成int型长度0x00401758 600: addiu v1,s8,240x0040175c 604: addu v0,v1,v00x00401760 608: lw v0,36(v0) //node node-next0x00401764 612: nop0x00401768 616: sw v0,32(s8) //m[$s832]$v0存储当前节点0x0040176c 620: lw v0,28(s8) //获得循环变量 i0x00401770 624: nop0x00401774 628: addiu v0,v0,1 //i0x00401778 632: sw v0,28(s8) //保存循环变量0x0040177c 636: lw v0,28(s8) //获得循环变量0x00401780 640: nop0x00401784 644: slti v0,v0,6 //条件判断i 6 ?0x00401788 648: bnez v0,0x401728 phase_6552 //若 6则跳转否则继续往下执行0x0040178c 652: nop这一段汇编的意思就是在之前我们已经按照我们输入的顺序将原链表上的节点保存在了数组的对应位置上。此时我们要做的就是将链表按照在数组中的顺序进行一个重新连接。
这两部合二为一即按照我们输入的顺序重构了链表
接着往下看 0x00401790 656: lw v0,32(s8) //经过上面的连接现在node处于最后一个节点0x00401794 660: nop0x00401798 664: sw zero,8(v0) //node-next null0x0040179c 668: lw v0,60(s8)0x004017a0 672: nop0x004017a4 676: sw v0,32(s8) //重置node至链表的firstNode0x004017a8 680: sw zero,28(s8) //设置循环变量 i 00x004017ac 684: b 0x401878 phase_6888 //条件判断0x004017b0 688: nop0x004017b4 692: lw v0,-32660(gp)0x004017b8 696: nop0x004017bc 700: lw v0,44(v0) //获得学号的最后一位0x004017c0 704: nop0x004017c4 708: andi v0,v0,0x10x004017c8 712: andi v0,v0,0xff //老规矩判断奇偶0x004017cc 716: beqz v0,0x401818 phase_6792 //奇数 1不跳转偶数 0跳转0x004017d0 720: nop0x004017d4 724: lw v0,32(s8) //找到前一个节点 node0x004017d8 728: nop0x004017dc 732: lw v1,0(v0) //获得前一个节点的值0x004017e0 736: lw v0,32(s8) //重置node0x004017e4 740: nop0x004017e8 744: lw v0,8(v0) //node node-next0x004017ec 748: nop0x004017f0 752: lw v0,0(v0) //获得后一个节点的值0x004017f4 756: nop0x004017f8 760: slt v0,v1,v0 //比较升序则 $v0 1反之 $v0 00x004017fc 764: beqz v0,0x401854 phase_6852 //升序则不跳转炸弹爆炸反之避开炸弹0x00401800 768: nop0x00401804 772: jal 0x4021f0 explode_bomb0x00401808 776: nop0x0040180c 780: lw gp,16(s8)0x00401810 784: b 0x401854 phase_68520x00401814 788: nop0x00401818 792: lw v0,32(s8)0x0040181c 796: nop0x00401820 800: lw v1,0(v0) //$v1 是前一个节点0x00401824 804: lw v0,32(s8)0x00401828 808: nop0x0040182c 812: lw v0,8(v0)0x00401830 816: nop0x00401834 820: lw v0,0(v0) //$v0 是后一个节点0x00401838 824: nop0x0040183c 828: slt v0,v0,v1 //降序则 1反之则 00x00401840 832: beqz v0,0x401854 phase_6852 //降序则爆炸反之避开炸弹0x00401844 836: nop0x00401848 840: jal 0x4021f0 explode_bomb0x0040184c 844: nop0x00401850 848: lw gp,16(s8)0x00401854 852: lw v0,32(s8) //更新 node0x00401858 856: nop0x0040185c 860: lw v0,8(v0) //node node-next0x00401860 864: nop0x00401864 868: sw v0,32(s8) //保存当前节点地址0x00401868 872: lw v0,28(s8) //获得循环变量 i0x0040186c 876: nop0x00401870 880: addiu v0,v0,1 //i0x00401874 884: sw v0,28(s8) //保存循环变量0x00401878 888: lw v0,28(s8) //获得循环变量0x0040187c 892: nop0x00401880 896: slti v0,v0,5 //条件判断i 5 ?0x00401884 900: bnez v0,0x4017b4 phase_6692 //是则跳转反之结束循环0x00401888 904: nop0x0040188c 908: move sp,s8 //归还空间0x00401890 912: lw ra,92(sp)0x00401894 916: lw s8,88(sp)0x00401898 920: addiu sp,sp,960x0040189c 924: jr ra0x004018a0 928: nop
做个记录爆炸点3学号最后一位若是奇数节点值排序后是升序则爆炸若是偶数节点值排序后是降序则爆炸
炸弹六总结
爆炸点1 若输入的数 0 或者 7则炸弹爆炸 爆炸点2 若第 i 个数和它后面的某个数相等则炸弹爆炸 爆炸点3 学号最后一位若是奇数节点值排序后是升序则爆炸若是偶数节点值排序后是降序则爆炸现在是2021-11-21 1:29
secret_phase
拆完这 6 个炸弹如果能够理解前面的汇编语句算是一种成功。但听说这之中还有一个隐藏炸弹我们现在来看看。 我们首先对phase_defused进行单步调试发现在这里有一个输出为 %d%s\%d\ \%s%d %s 的形式接下来就是 string_not_equal 的比较了那么很显然后面比较的字符串就是通过这一步来进行输入的。
再回想我们整个拆弹过程能按照 %d%s\%d\ \%s%d %s 的格式输入的似乎只有 phase_4虽然那时只是输入了一个整数但是确实是离这个输入最近的。因此我们再 phase_4 的输入时后面再输入一个字符串 “helloworld”接着在 string_not_equal 的地方进行查看
...0x004022e0 124: nop0x004022e4 128: addiu v0,s8,240x004022e8 132: move a0,v00x004022ec 136: lui v0,0x400x004022f0 140: addiu a1,v0,104160x004022f4 144: jal 0x401cf8 strings_not_equal //出现了一个字符串的比较。一个炸弹已经拆解的函数为什么要比较字符串呢我们在 GDB 中进行查看0x004022f8 148: nop0x004022fc 152: lw gp,16(s8)0x00402300 156: bnez v0,0x402354 phase_defused2400x00402304 160: nop0x00402308 164: lui v0,0x400x0040230c 168: addiu a0,v0,104320x00402310 172: lw v0,-32712(gp)0x00402314 176: nop0x00402318 180: move t9,v00x0040231c 184: jalr t90x00402320 188: nop0x00402324 192: lw gp,16(s8)0x00402328 196: lui v0,0x400x0040232c 200: addiu a0,v0,104720x00402330 204: lw v0,-32712(gp)0x00402334 208: nop0x00402338 212: move t9,v00x0040233c 216: jalr t90x00402340 220: nop0x00402344 224: lw gp,16(s8)0x00402348 228: jal 0x401990 secret_phase0x0040234c 232: nop 可以看到$a0 中存储着我们之前输入的字符串而 $a1 中存的是一个“austinpowers”相等返回 1继续向下执行也就进入 secret_phase 啦 接下来大家就根据注释食用吧 0x00401990 0: addiu sp,sp,-400x00401994 4: sw ra,36(sp)0x00401998 8: sw s8,32(sp)0x0040199c 12: move s8,sp0x004019a0 16: lui gp,0x420x004019a4 20: addiu gp,gp,-200800x004019a8 24: sw gp,16(sp)0x004019ac 28: jal 0x401fec read_line0x004019b0 32: nop0x004019b4 36: lw gp,16(s8)0x004019b8 40: sw v0,28(s8) //存储输入的数据这里我输入的是 100x004019bc 44: lw v0,28(s8) //load 保存输入的数据0x004019c0 48: nop0x004019c4 52: move a0,v00x004019c8 56: move a1,zero0x004019cc 60: li a2,100x004019d0 64: lw v0,-32656(gp)0x004019d4 68: nop0x004019d8 72: move t9,v00x004019dc 76: jalr t90x004019e0 80: nop
--Type RET for more, q to quit, c to continue without paging--c0x004019e4 84: lw gp,16(s8)0x004019e8 88: sw v0,24(s8) //保存输入的数据100x004019ec 92: lw v0,24(s8) //load 输入的数据0x004019f0 96: nop0x004019f4 100: addiu v0,v0,-1 //$v0--(9)0x004019f8 104: sltiu v0,v0,1001 //条件判断$v0 1001?0x004019fc 108: bnez v0,0x401a10 secret_phase128 //是则跳转反之炸弹爆炸0x00401a00 112: nop0x00401a04 116: jal 0x4021f0 explode_bomb0x00401a08 120: nop0x00401a0c 124: lw gp,16(s8)0x00401a10 128: lui v0,0x410x00401a14 132: addiu a0,v0,12676 //$a0 0x240x00401a18 136: lw a1,24(s8) //输入的数据100x00401a1c 140: jal 0x4018a4 fun7 //跳转至 fun7 啦做个记录爆炸点1输入数据若 1001则爆炸
这里发生了一个跳转那么接下来我们看看 fun7 干了啥 0x004018a4 0: addiu sp,sp,-320x004018a8 4: sw ra,28(sp)0x004018ac 8: sw s8,24(sp)0x004018b0 12: move s8,sp0x004018b4 16: sw a0,32(s8) //$a00x24第二次$a00x080x004018b8 20: sw a1,36(s8) //$a110第二次$a1100x004018bc 24: lw v0,32(s8) //$v00x24第二次$v00x080x004018c0 28: nop0x004018c4 32: bnez v0,0x4018d8 fun752 //$a00x24不为 0所以跳转至 520x004018c8 36: nop0x004018cc 40: li v0,-10x004018d0 44: b 0x401978 fun72120x004018d4 48: nop0x004018d8 52: lw v0,32(s8) //$v00x24(n1)第二次$v00x080x004018dc 56: nop0x004018e0 60: lw v1,0(v0) //$v136第二次 $v180x004018e4 64: lw v0,36(s8) //$v010第二次 $v0100x004018e8 68: nop0x004018ec 72: slt v0,v0,v1 //$v0 $v1 ? 若是返回 1不跳转反之则跳转0x004018f0 76: beqz v0,0x401924 fun71280x004018f4 80: nop
--Type RET for more, q to quit, c to continue without paging--c0x004018f8 84: lw v0,32(s8) //$v0 0x24(n1)0x004018fc 88: nop0x00401900 92: lw v0,4(v0) //$v0 0x08(n21)0x00401904 96: nop0x00401908 100: move a0,v0 //$a0 0x08(n21)0x0040190c 104: lw a1,36(s8) //$a1 100x00401910 108: jal 0x4018a4 fun70x00401914 112: nop0x00401918 116: sll v0,v0,0x1 //左移 1 位末尾补 10x0040191c 120: b 0x401978 fun72120x00401920 124: nop0x00401924 128: lw v0,32(s8) //第二次$v00x080x00401928 132: nop0x0040192c 136: lw v1,0(v0) //第二次$v180x00401930 140: lw v0,36(s8) //第二次$v0100x00401934 144: nop0x00401938 148: slt v0,v1,v0 //$v1 $v0 ? 若是则不跳转反之跳转0x0040193c 152: beqz v0,0x401974 fun72080x00401940 156: nop0x00401944 160: lw v0,32(s8) //第二次$v00x080x00401948 164: nop0x0040194c 168: lw v0,8(v0) //第二次$v00x160x00401950 172: nop0x00401954 176: move a0,v0 //$a00x160x00401958 180: lw a1,36(s8) //$a1100x0040195c 184: jal 0x4018a4 fun70x00401960 188: nop0x00401964 192: sll v0,v0,0x10x00401968 196: addiu v0,v0,1 //左移 1 位末尾补 10x0040196c 200: b 0x401978 fun72120x00401970 204: nop0x00401974 208: move v0,zero0x00401978 212: move sp,s80x0040197c 216: lw ra,28(sp)0x00401980 220: lw s8,24(sp)0x00401984 224: addiu sp,sp,320x00401988 228: jr ra0x0040198c 232: nop
那么这个函数就相当于是一个二叉搜索树根据 $v0 和 $v1 的大小关系来确定下一节点是左孩子还有右孩子。同时没执行结束一个函数都会将 $v0 向左移动一位末位补 1.
下面来看这个返回值在 secret_phase 中的作用 0x00401a20 144: nop0x00401a24 148: lw gp,16(s8)0x00401a28 152: move v1,v0 //$v1$v0即将返回值给 $v10x00401a2c 156: li v0,7 //$v0 70x00401a30 160: beq v1,v0,0x401a44 secret_phase180 //$v1 7 ? 相等则跳转避开炸弹反之炸弹爆炸0x00401a34 164: nop0x00401a38 168: jal 0x4021f0 explode_bomb0x00401a3c 172: nop0x00401a40 176: lw gp,16(s8)0x00401a44 180: lui v0,0x40按照之前所说每一次函数的执行都会将 $v0 左移 1 位末尾补 1那么 000→001→011→111000 \rightarrow 001 \rightarrow 011 \rightarrow 111000→001→011→111因此我们需要做的就是控制函数的执行次数是 3 次。即二叉搜索树的搜索次数是 3 次。
做个记录爆炸点2二叉搜索数的搜索次数为 3 次否则爆炸
在 GDB 中进行查看很容易得到这棵二叉搜索树我们给出其节点值作图如下
隐藏炸弹总结
做个记录爆炸点1 输入数据若 1001则爆炸 做个记录爆炸点2 二叉搜索数的搜索次数为 3 次否则爆炸
总结
写完文章CSDN 已经开始卡顿了hhh不妨静下心来一个一个看这些炸弹跟着思路走一走。相信真正理解了这些汇编这个实验才算真正达到了目的吧
有什么问题烦请告知哈