隐藏关卡暂时没找到,前面6个都找到了,前五个不是很难,最后一个比前面复杂很多。
phase_1:
首先反汇编程序
第一句是分配栈空间,第二句是设置函数参数,由此可知(rdi,rsi)一共有两个参数,第一个是输入字符串的地址,第二个是$0x402400,由后面函数名推测,这也是一个地址,函数作用是比较两个字符串是否相等,所以我们要找的字符串应该就在那个地址里,先继续往下看,后面的两句测试eax(返回值)的值如果不等于0,就会调用炸弹爆炸函数,所以必须让前面函数的返回值为0,接着反汇编函数strings_not_equal:
前三句是把被调用者保存的寄存器压入栈中,目的是函数返回时重新赋值,保证调用前调用后寄存器值不变。后面两句是把两个参数保存到被调用者保存的寄存器中,保证值不会发生变化,然后调用函数string_length,推测作用为返回字符串的长度,所以两次调用获得字符串的长度,之后进行比较,如果不相等就返回rdx的值(1)(如果返回1就会爆炸),所以必须让两个长度相等可以可以在测试语句前设置断点,来查看第二个字符串的长度,但是我们先看后面语句(假设长度已经相等),后面一句是把输入字符串的第一个字符取出来放到eax中(前面已经把rdi放到rbx中),接着测试al(rax的低8位,也就是我们输入的第一个字符),如果结果为0(字符串的结束符’\0’的ASCII码就是0),就跳转,跳转后就是设置rax为0并返回,也就是说,这就是我们的目标,由于前面测试了字符串的长度必须与内存中的相等,所以一开始测试结果肯定是false,接着往下看,比较(%rbp)和al的值,(也就是这个函数第一个参数和第二个参数的值的间接引用,也就是两个字符串的第一个字符),相等的话就跳到后面,把两个地址的值加1,到这里我们就已经知道了,这个函数就是比较两个字符串直到末尾,看是否相等,相等就返回0,否则返回1,所以我们接下来用print命令获取字符串的值
由于有52个字符比较长,所以我就只打印了前几个和结尾。
最后答案:
phase_2:
首先把当前栈顶地址设置为第二个参数,调用函数read_six_numbers
反编译read_six_numbers
第二句,把前面一个函数的栈顶地址保存到rdx(第三个参数)中,接着后面几句分别是把栈顶地址+4放到rcx中(第四个参数),把原栈顶地址(rsi)+20放到当前栈顶+8(第8个参数),把栈顶地址(rsi)+16放到当前栈顶(第7个参数),栈顶地址+12放到r9(第6个参数),栈顶地址+8放到r8(第5个参数),把0x4025c3放到esi(第二个参数)
然后调用函数sscanf,该函数的作用是,把一个字符串中的字符,按某种格式写入到参数中(input,格式,参数3,参数4,参数5,参数6,参数7,参数8),查看格式字符串:
函数的返回值是成功读取的数字的个数,后面一句把返回值与5比较,只有大于5炸弹才不会爆炸。
所以该函数(read_six_numbers)作用为从输入的字符串中读取6个数字到栈顶.
接着看后面,把栈顶元素(输入的第一个数字)与1进行比较,不等于就会爆炸,所以第一个数就是1,然后跳转,把栈中存放的第二个数的地址放入bx,然后把第一个数放入ax,ax2与(bx)相比相等就继续循环,到这里就可以知道,结果为6个数,首项为1,倍数为2的等比数列,结果就是1 2 4 8 16 32
phase_3:
前面与phase_2类似,读取两个数到栈中,第一个数和7比较,必须小于等于7(注意是无符号比较),然后根据输入的数跳转到指定位置(该地址存储在内存中),所以先打印出地址,假设输入的数为0
把0xcf放入rax中,跳转之后与输入的第二个数的值(保存在栈中)比较,如果不相等就爆炸,相等就释放内存并返回,也就是说,根据第一个数,第二个数有7种可能的值分别是0 207;1 311;2 707;3 256;4 389;5 206;6 682;7 327;
phase_4:
前面还是一样,读入两个数到栈中,后面判断第一个数的大小,必须小于等于0xe,否则就会爆炸,接下来设置三个参数的值,edi设置为第一个输入的数,esi设置为0,edx设置为0xe,然后调用函数func4,先接着往下看,测试返回值,如果不为0就爆炸,然后判断第二个输入的数,不为0就爆炸,所以第二个数一定是0,然后进入函数func4:
假设三个参数为(x,y,z)
前面几行的作用是把第三个参数减去第二个参数(z-y)的值放到cx和ax中,然后右移31位,加到ax上,也就是把符号位加上减法的结果,然后右移一位,之后再加上第二个参数的值放到cx中,如果结果为正cx=(y + (z - y) / 2),从这里其实可以推测出该函数与二分法有关,然后比较输入的数与计算结果的大小,如果小于等于就把eax清零然后比较计算计算结果与输入的值的关系,如果大于等于,就释放栈空间并返回,到这里其实我们就已经知道一组答案了7 0;因为第一次计算cx的结果是固定的就是7所以只要与它相等就可以返回0,但是还有其它几种情况,首先如果第二次比较不满足大于等于,就把rcx+1放到第二参数中,继续递归调用函数,也就是我们非常熟悉的二分法查找数字,(x,cx+1,z),cx就是中位数,然后再往上看就可以非常轻松的发现如果第一次比较也不满足就把cx-1放到第三个参数中也就是(x,y,cx - 1),最后返回的就是找到了数的时候,然后看返回值,往左边找是2ax,往右边找是2*ax + 1,所以为了得到返回值为0,必须一直往左边找,也就是0 0;1 0;3 0;7 0;这几组值
phase_5:
这个函数的前几行是用于获取金丝雀值放到栈中,用于判断是否局部缓冲区被破坏(详见深入理解计算机系统P199),后面调用函数获取输入字符串长度,长度必须等于6否则就爆炸,接下来的一系列操作是取出第一个字符并且和0xf做与操作,以此为偏移值获取内存中的某个地址(0x4024b0)的字符(也就是映射,一个字母对应变成另一个字母),并且保存到栈中,然后继续取下一个字符,结束循环后,调用判断函数,判断映射获取的字符串是否与内存中的字符串相等,所以我们先获得内存中的字符串
字符串为flyers
a的偏移值为1,所以flyers对应的偏移值分别为9i 15o 14n 5e 6f 7g
所以答案为ionefg
phase_6
前面几行与上面类似,读取6个数到栈中,后面通过循环判断,6个数都在1-6之间并且互不相等,然后跳转到+95,后面这段的意思是把栈中存储的每个数用7减然后重新存进去(对7取补),再后面是取出地址为0x6032d0+(n-1)*8 的地址存入栈中,n为取补后的值,最后一步,根据栈中的值对内存中的原来存的值进行修改,把栈中后一个值(地址)放到(前一个值+8)(取值)的位置,也就是替换掉下面表格里的地址(下面表格是一开始的内存空间),就是把栈中地址对应的值逆序排列,越靠近栈顶所对应的内存中的值越大,然后就是比较判断,只要栈中地址取值按逆序排列,就能拆除炸弹
地址 | 值 |
---|---|
0x6032d0 | 0x14c |
0x6032d8 | 0x6032e0 |
0x6032e0 | 0xa8 |
0x6032e8 | 0x6032f0 |
0x6032f0 | 0x39c |
0x6032f8 | 0x603300 |
0x603300 | 0x2b3 |
0x603308 | 0x603310 |
0x603310 | 0x1dd |
0x603318 | 0x603320 |
0x603320 | 0x1bb |
0x603328 | 0x0 |
所以栈中存储的地址应该为
值(越上面越靠近栈顶) |
---|
0x6032f0 |
0x603300 |
0x603310 |
0x603320 |
0x6032d0 |
0x6032e0 |
所以输入的值应该为4 3 2 1 6 5