solution 1 2 3 4 5 6 7 Houses will begat jobs, jobs will begat houses. 1 2 4 7 11 16 0 -118 6 6 DrEvil 5 115 2 4 1 3 6 5 1001
phase_1 1 2 3 4 5 6 7 8 9 0000000000400e8d <phase_1>: 400e8d: 48 83 ec 08 sub $0x8,%rsp 400e91: be d0 23 40 00 mov $0x4023d0,%esi 400e96: e8 b5 04 00 00 callq 401350 <strings_not_equal> 400e9b: 85 c0 test %eax,%eax 400e9d: 74 05 je 400ea4 <phase_1+0x17> 400e9f: e8 ab 05 00 00 callq 40144f <explode_bomb> 400ea4: 48 83 c4 08 add $0x8,%rsp 400ea8: c3 retq
观察反汇编代码,发现第3行和第4行是实现输入字符串和地址 0x4023d0
处的字符串进行比较,那么就要查看存在这个地址的字符串是什么。
进入gdb调试,b phase_1
在phase_1处加断点,run
,随意输入一串字符
set disassemble-next-line on
显示代码
ni
执行完第3行, p/x $esi
打印 $esi
的位置,得到结果 0x4023d0
,继续 x/100c $esi
打印该地址后100个空间内的内容,得到如下图所示:
找到其中第一个 ‘\0’,前面的内容即是答案:Houses will begat jobs, jobs will begat houses.(顺便看到了后面的内容:Wow! You’ve defused the secret stage! 看样子应该是破解隐藏关卡时打印的内容,剧透了:) )
phase_2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 0000000000400ea9 <phase_2>: 400ea9: 55 push %rbp 400eaa: 53 push %rbx 400eab: 48 83 ec 28 sub $0x28,%rsp 400eaf: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 400eb6: 00 00 400eb8: 48 89 44 24 18 mov %rax,0x18(%rsp) 400ebd: 31 c0 xor %eax,%eax 400ebf: 48 89 e6 mov %rsp,%rsi 400ec2: e8 aa 05 00 00 callq 401471 <read_six_numbers> 400ec7: 83 3c 24 00 cmpl $0x0,(%rsp) 400ecb: 79 05 jns 400ed2 <phase_2+0x29> 400ecd: e8 7d 05 00 00 callq 40144f <explode_bomb> 400ed2: 48 89 e5 mov %rsp,%rbp 400ed5: bb 01 00 00 00 mov $0x1,%ebx 400eda: 89 d8 mov %ebx,%eax 400edc: 03 45 00 add 0x0(%rbp),%eax 400edf: 39 45 04 cmp %eax,0x4(%rbp) 400ee2: 74 05 je 400ee9 <phase_2+0x40> 400ee4: e8 66 05 00 00 callq 40144f <explode_bomb> 400ee9: 83 c3 01 add $0x1,%ebx 400eec: 48 83 c5 04 add $0x4,%rbp 400ef0: 83 fb 06 cmp $0x6,%ebx 400ef3: 75 e5 jne 400eda <phase_2+0x31> 400ef5: 48 8b 44 24 18 mov 0x18(%rsp),%rax 400efa: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 400f01: 00 00 400f03: 74 05 je 400f0a <phase_2+0x61> 400f05: e8 f6 fb ff ff callq 400b00 <__stack_chk_fail@plt> 400f0a: 48 83 c4 28 add $0x28,%rsp 400f0e: 5b pop %rbx 400f0f: 5d pop %rbp 400f10: c3 retq
可以看到第10行调用一个函数叫 read_six_numbers
应该是读取标准输入的六个数。
然后第11、12行可以确定输入的第一个数只要非负即可,所以暂定第一个数为1。
再看一下第18行,可以看到是将 %eax
与 0x4(%rbp)
进行比较,不相等则爆炸。而第23、24行可告诉我们 %ebx
依次递增,不等于6时返回循环。而由第15行知, %ebx
初值为1,也就是五次循环依次将输入的后五个数和 %eax
进行比较。
于是我们需要知道每次循环时 %eax
的值。
再看整个循环体(16-24行),可以发现每次循环除 %ebx
作为循环变量依次递增外,%rbp
的地址也依次向后移动一个 int
大小,而第16、17行告诉我们每次循环时 %eax
的值都是由上一个输入的数再加上循环变量 %rdp
的值得到的,于是得到递推公式:
$$ a_n = a_{n - 1}+(n - 1) $$
其中 $a_1$ 是任意非负整数。
所以答案就是:1 2 4 7 11 16 或 0 1 3 6 10 15 或 2 3 5 8 12 17 或 ……
phase_3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 0000000000400f11 <phase_3>: 400f11: 48 83 ec 18 sub $0x18,%rsp 400f15: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 400f1c: 00 00 400f1e: 48 89 44 24 08 mov %rax,0x8(%rsp) 400f23: 31 c0 xor %eax,%eax 400f25: 48 8d 4c 24 04 lea 0x4(%rsp),%rcx 400f2a: 48 89 e2 mov %rsp,%rdx 400f2d: be cf 25 40 00 mov $0x4025cf,%esi 400f32: e8 79 fc ff ff callq 400bb0 <__isoc99_sscanf@plt> 400f37: 83 f8 01 cmp $0x1,%eax 400f3a: 7f 05 jg 400f41 <phase_3+0x30> 400f3c: e8 0e 05 00 00 callq 40144f <explode_bomb> 400f41: 83 3c 24 07 cmpl $0x7,(%rsp) 400f45: 77 65 ja 400fac <phase_3+0x9b> 400f47: 8b 04 24 mov (%rsp),%eax 400f4a: ff 24 c5 40 24 40 00 jmpq *0x402440(,%rax,8) 400f51: b8 28 01 00 00 mov $0x128,%eax # 0 400f56: eb 05 jmp 400f5d <phase_3+0x4c> 400f58: b8 00 00 00 00 mov $0x0,%eax # 1 400f5d: 2d 3c 02 00 00 sub $0x23c,%eax 400f62: eb 05 jmp 400f69 <phase_3+0x58> 400f64: b8 00 00 00 00 mov $0x0,%eax # 2 400f69: 05 07 03 00 00 add $0x307,%eax 400f6e: eb 05 jmp 400f75 <phase_3+0x64> 400f70: b8 00 00 00 00 mov $0x0,%eax # 3 400f75: 2d 69 02 00 00 sub $0x269,%eax 400f7a: eb 05 jmp 400f81 <phase_3+0x70> 400f7c: b8 00 00 00 00 mov $0x0,%eax # 4 400f81: 05 69 02 00 00 add $0x269,%eax 400f86: eb 05 jmp 400f8d <phase_3+0x7c> 400f88: b8 00 00 00 00 mov $0x0,%eax # 5 400f8d: 2d 69 02 00 00 sub $0x269,%eax 400f92: eb 05 jmp 400f99 <phase_3+0x88> 400f94: b8 00 00 00 00 mov $0x0,%eax 400f99: 05 69 02 00 00 add $0x269,%eax 400f9e: eb 05 jmp 400fa5 <phase_3+0x94> 400fa0: b8 00 00 00 00 mov $0x0,%eax 400fa5: 2d 69 02 00 00 sub $0x269,%eax 400faa: eb 0a jmp 400fb6 <phase_3+0xa5> 400fac: e8 9e 04 00 00 callq 40144f <explode_bomb> 400fb1: b8 00 00 00 00 mov $0x0,%eax 400fb6: 83 3c 24 05 cmpl $0x5,(%rsp) 400fba: 7f 06 jg 400fc2 <phase_3+0xb1> 400fbc: 3b 44 24 04 cmp 0x4(%rsp),%eax 400fc0: 74 05 je 400fc7 <phase_3+0xb6> 400fc2: e8 88 04 00 00 callq 40144f <explode_bomb> 400fc7: 48 8b 44 24 08 mov 0x8(%rsp),%rax 400fcc: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 400fd3: 00 00 400fd5: 74 05 je 400fdc <phase_3+0xcb> 400fd7: e8 24 fb ff ff callq 400b00 <__stack_chk_fail@plt> 400fdc: 48 83 c4 18 add $0x18,%rsp 400fe0: c3 retq
到了第三阶段,显然复杂很多,看上去很多 jmp
命令,应该是分支语句。
从头来看,可以看到第10行调用了 scanf
函数,接着便将 %eax
和1进行比较,不大于就爆炸!
也就是说输入格式不对就爆炸。
不过幸好我们已知输入两个整数。
接下来第14行将第一个数和7进行比较,大于7直接跳到41行,boom!所以第一个数应该小于7.
第17行则是根据输入的第一个数进行跳转。
接下来从后看,由第45、46行可知,第二个数要和最终的 %eax
相等。而由第43行可知第一个数不能大于5.
于是便有六种答案(后一个数是根据代码中的立即数计算得到的),依次是:
0 -118
1 -414
2 158
3 -617
4 0
5 -617
phase_4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 0000000000400fe1 <func4>: # x in %edi, 0 in %esi, 14 in %edx 400fe1: 48 83 ec 08 sub $0x8,%rsp 400fe5: 89 d0 mov %edx,%eax 400fe7: 29 f0 sub %esi,%eax 400fe9: 89 c1 mov %eax,%ecx 400feb: c1 e9 1f shr $0x1f,%ecx 400fee: 01 c8 add %ecx,%eax 400ff0: d1 f8 sar %eax 400ff2: 8d 0c 30 lea (%rax,%rsi,1),%ecx 400ff5: 39 f9 cmp %edi,%ecx 400ff7: 7e 0c jle 401005 <func4+0x24> 400ff9: 8d 51 ff lea -0x1(%rcx),%edx 400ffc: e8 e0 ff ff ff callq 400fe1 <func4> 401001: 01 c0 add %eax,%eax 401003: eb 15 jmp 40101a <func4+0x39> 401005: b8 00 00 00 00 mov $0x0,%eax 40100a: 39 f9 cmp %edi,%ecx 40100c: 7d 0c jge 40101a <func4+0x39> 40100e: 8d 71 01 lea 0x1(%rcx),%esi 401011: e8 cb ff ff ff callq 400fe1 <func4> 401016: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax 40101a: 48 83 c4 08 add $0x8,%rsp 40101e: c3 retq 000000000040101f <phase_4>: 40101f: 48 83 ec 18 sub $0x18,%rsp 401023: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 40102a: 00 00 40102c: 48 89 44 24 08 mov %rax,0x8(%rsp) 401031: 31 c0 xor %eax,%eax 401033: 48 8d 4c 24 04 lea 0x4(%rsp),%rcx 401038: 48 89 e2 mov %rsp,%rdx 40103b: be cf 25 40 00 mov $0x4025cf,%esi 401040: e8 6b fb ff ff callq 400bb0 <__isoc99_sscanf@plt> 401045: 83 f8 02 cmp $0x2,%eax 401048: 75 06 jne 401050 <phase_4+0x31> 40104a: 83 3c 24 0e cmpl $0xe,(%rsp) 40104e: 76 05 jbe 401055 <phase_4+0x36> 401050: e8 fa 03 00 00 callq 40144f <explode_bomb> 401055: ba 0e 00 00 00 mov $0xe,%edx 40105a: be 00 00 00 00 mov $0x0,%esi 40105f: 8b 3c 24 mov (%rsp),%edi 401062: e8 7a ff ff ff callq 400fe1 <func4> 401067: 83 f8 06 cmp $0x6,%eax 40106a: 75 07 jne 401073 <phase_4+0x54> 40106c: 83 7c 24 04 06 cmpl $0x6,0x4(%rsp) 401071: 74 05 je 401078 <phase_4+0x59> 401073: e8 d7 03 00 00 callq 40144f <explode_bomb> 401078: 48 8b 44 24 08 mov 0x8(%rsp),%rax 40107d: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 401084: 00 00 401086: 74 05 je 40108d <phase_4+0x6e> 401088: e8 73 fa ff ff callq 400b00 <__stack_chk_fail@plt> 40108d: 48 83 c4 18 add $0x18,%rsp 401091: c3 retq
先看 phase_4
函数,第34行也调用了 scanf
函数,gdb调试看一下 0x4025cf
处存放的是什么:
可以看到需要输入两个 int
。
继续看第37、38行,是将第一个数和 14 进行比较,大于14就爆炸。
接下来便是将第一个数、0、14作为参数调用 func4
,得到的结果不是6就爆炸。再将第二个数和6比较,也是不相等的话就爆炸!所以要想不爆炸,就要第一个数经过 func4
处理得到6,第二个数就是6.
再看 func4
函数,根据反汇编代码可以得到 func4
函数的定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int func4 (int x, int a, int b) { int c = b - a; c += c >> 31 ; c = (unsigned )c >> 1 ; int d = a + c; if (x < d) { return 2 * func4(x, a, d - 1 ); } else if (x > d) { return 2 * func4(x, d + 1 , b) + 1 ; } else { return 0 ; } }
如何传入 (x, 0, 14) 得到6呢?
可以发现其中的d就是a和b的平均值,也就是x小于平均值是返回一个偶数,大于平均值时返回一个奇数,等于平均值时返回0;那么第一次调用要输入的必然是小于7;
那么接下来就要调用 func4(x, 0, 6)
得到3,x应该大于3;
依次类推 func4(x, 4, 6)
得到1,x应该大于5, func4(x, 6, 6)
得到0,则x应该是6。
所以输入的两个数应该是6 6。
(至于隐藏阶段呢,暂时还没有发现)
phase_5 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 0000000000401092 <phase_5>: 401092: 48 83 ec 18 sub $0x18,%rsp 401096: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 40109d: 00 00 40109f: 48 89 44 24 08 mov %rax,0x8(%rsp) 4010a4: 31 c0 xor %eax,%eax 4010a6: 48 8d 4c 24 04 lea 0x4(%rsp),%rcx 4010ab: 48 89 e2 mov %rsp,%rdx 4010ae: be cf 25 40 00 mov $0x4025cf,%esi 4010b3: e8 f8 fa ff ff callq 400bb0 <__isoc99_sscanf@plt> 4010b8: 83 f8 01 cmp $0x1,%eax 4010bb: 7f 05 jg 4010c2 <phase_5+0x30> 4010bd: e8 8d 03 00 00 callq 40144f <explode_bomb> 4010c2: 8b 04 24 mov (%rsp),%eax 4010c5: 83 e0 0f and $0xf,%eax 4010c8: 89 04 24 mov %eax,(%rsp) 4010cb: 83 f8 0f cmp $0xf,%eax 4010ce: 74 2f je 4010ff <phase_5+0x6d> 4010d0: b9 00 00 00 00 mov $0x0,%ecx 4010d5: ba 00 00 00 00 mov $0x0,%edx 4010da: 83 c2 01 add $0x1,%edx 4010dd: 48 98 cltq 4010df: 8b 04 85 80 24 40 00 mov 0x402480(,%rax,4),%eax 4010e6: 01 c1 add %eax,%ecx 4010e8: 83 f8 0f cmp $0xf,%eax 4010eb: 75 ed jne 4010da <phase_5+0x48> 4010ed: c7 04 24 0f 00 00 00 movl $0xf,(%rsp) 4010f4: 83 fa 0f cmp $0xf,%edx 4010f7: 75 06 jne 4010ff <phase_5+0x6d> 4010f9: 3b 4c 24 04 cmp 0x4(%rsp),%ecx 4010fd: 74 05 je 401104 <phase_5+0x72> 4010ff: e8 4b 03 00 00 callq 40144f <explode_bomb> 401104: 48 8b 44 24 08 mov 0x8(%rsp),%rax 401109: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 401110: 00 00 401112: 74 05 je 401119 <phase_5+0x87> 401114: e8 e7 f9 ff ff callq 400b00 <__stack_chk_fail@plt> 401119: 48 83 c4 18 add $0x18,%rsp 40111d: c3 retq
同样先看一下 scanf
函数要求输入的格式是什么。可以看到依旧是两个 int
整数。
由第14-18行可知,输入的第一个数为15则爆炸。
接下来的21-26行是一个循环,由第28、29行可知,要循环15次才行。而第二个数要和最终的 %ecx
相等, %ecx
则是每次循环 %eax
的和,每次循环时 %eax
不能等于15,只有最后一次循环 %eax
才能等于15。
所以重点就在 mov 0x402480(,%rax,4),%eax
这一句,要经过15次将 %eax
,也就是输入的第一个数,变成15。
而 0x402480(,%rax,4)
则是在 0x402480
偏移 4 * %rax
处的值,应该是一个 int
数组。
打印一下看看:
果然是一个数组,顺藤摸瓜,便可得到输入的第一个数就是5,那么第二个数就是 $\frac{(1+15) * 15}{2} - 5 = 115$ 。
phase_6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 000000000040111e <phase_6>: 40111e: 41 55 push %r13 401120: 41 54 push %r12 401122: 55 push %rbp 401123: 53 push %rbx 401124: 48 83 ec 68 sub $0x68,%rsp 401128: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 40112f: 00 00 401131: 48 89 44 24 58 mov %rax,0x58(%rsp) 401136: 31 c0 xor %eax,%eax 401138: 48 89 e6 mov %rsp,%rsi 40113b: e8 31 03 00 00 callq 401471 <read_six_numbers> 401140: 49 89 e4 mov %rsp,%r12 401143: 41 bd 00 00 00 00 mov $0x0,%r13d 401149: 4c 89 e5 mov %r12,%rbp 40114c: 41 8b 04 24 mov (%r12),%eax 401150: 83 e8 01 sub $0x1,%eax 401153: 83 f8 05 cmp $0x5,%eax 401156: 76 05 jbe 40115d <phase_6+0x3f> 401158: e8 f2 02 00 00 callq 40144f <explode_bomb> 40115d: 41 83 c5 01 add $0x1,%r13d 401161: 41 83 fd 06 cmp $0x6,%r13d 401165: 74 3d je 4011a4 <phase_6+0x86> 401167: 44 89 eb mov %r13d,%ebx 40116a: 48 63 c3 movslq %ebx,%rax 40116d: 8b 04 84 mov (%rsp,%rax,4),%eax 401170: 39 45 00 cmp %eax,0x0(%rbp) 401173: 75 05 jne 40117a <phase_6+0x5c> 401175: e8 d5 02 00 00 callq 40144f <explode_bomb> 40117a: 83 c3 01 add $0x1,%ebx 40117d: 83 fb 05 cmp $0x5,%ebx 401180: 7e e8 jle 40116a <phase_6+0x4c> 401182: 49 83 c4 04 add $0x4,%r12 401186: eb c1 jmp 401149 <phase_6+0x2b> 401188: 48 8b 52 08 mov 0x8(%rdx),%rdx 40118c: 83 c0 01 add $0x1,%eax 40118f: 39 c8 cmp %ecx,%eax 401191: 75 f5 jne 401188 <phase_6+0x6a> 401193: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) 401198: 48 83 c6 04 add $0x4,%rsi 40119c: 48 83 fe 18 cmp $0x18,%rsi 4011a0: 75 07 jne 4011a9 <phase_6+0x8b> 4011a2: eb 19 jmp 4011bd <phase_6+0x9f> 4011a4: be 00 00 00 00 mov $0x0,%esi 4011a9: 8b 0c 34 mov (%rsp,%rsi,1),%ecx 4011ac: b8 01 00 00 00 mov $0x1,%eax 4011b1: ba f0 32 60 00 mov $0x6032f0,%edx 4011b6: 83 f9 01 cmp $0x1,%ecx 4011b9: 7f cd jg 401188 <phase_6+0x6a> 4011bb: eb d6 jmp 401193 <phase_6+0x75> 4011bd: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx 4011c2: 48 8d 44 24 20 lea 0x20(%rsp),%rax 4011c7: 48 8d 74 24 48 lea 0x48(%rsp),%rsi 4011cc: 48 89 d9 mov %rbx,%rcx 4011cf: 48 8b 50 08 mov 0x8(%rax),%rdx 4011d3: 48 89 51 08 mov %rdx,0x8(%rcx) 4011d7: 48 83 c0 08 add $0x8,%rax 4011db: 48 89 d1 mov %rdx,%rcx 4011de: 48 39 f0 cmp %rsi,%rax 4011e1: 75 ec jne 4011cf <phase_6+0xb1> 4011e3: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) 4011ea: 00 4011eb: bd 05 00 00 00 mov $0x5,%ebp 4011f0: 48 8b 43 08 mov 0x8(%rbx),%rax 4011f4: 8b 00 mov (%rax),%eax 4011f6: 39 03 cmp %eax,(%rbx) 4011f8: 7e 05 jle 4011ff <phase_6+0xe1> 4011fa: e8 50 02 00 00 callq 40144f <explode_bomb> 4011ff: 48 8b 5b 08 mov 0x8(%rbx),%rbx 401203: 83 ed 01 sub $0x1,%ebp 401206: 75 e8 jne 4011f0 <phase_6+0xd2> 401208: 48 8b 44 24 58 mov 0x58(%rsp),%rax 40120d: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 401214: 00 00 401216: 74 05 je 40121d <phase_6+0xff> 401218: e8 e3 f8 ff ff callq 400b00 <__stack_chk_fail@plt> 40121d: 48 83 c4 68 add $0x68,%rsp 401221: 5b pop %rbx 401222: 5d pop %rbp 401223: 41 5c pop %r12 401225: 41 5d pop %r13 401227: c3 retq
可以看到又有 read_six_numbers
函数,输入也是6个整数。
接下来15-34行是一个循环判断要求6个数字均不重复且不大于6,那么就是0-6的一个排列。
35-50行又是一个循环,可以看到将内存 0x6032f0
处的内容移到了栈中,看一下这些是什么内容:
发现是一个链表,所以这段就是依次将链表中的 Node* next
按照输入的六个数字的顺序存入栈中。(猜想这个炸弹可能是要将链表的 long data
按顺序排列,第二个数据应该是 int index
,第三个自然就是 next
指针)
51-61行是将节点按照在栈中的顺序重新连接。
然后可以看到第66、67行要求每个节点存储的数值都应该比上一个节点大才不会触发炸弹,所以按照节点的内容,答案应该是:2 4 1 3 6 5。
secret_stage 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 0000000000401228 <fun7>: 401228: 48 83 ec 08 sub $0x8,%rsp 40122c: 48 85 ff test %rdi,%rdi 40122f: 74 2b je 40125c <fun7+0x34> 401231: 8b 17 mov (%rdi),%edx 401233: 39 f2 cmp %esi,%edx 401235: 7e 0d jle 401244 <fun7+0x1c> 401237: 48 8b 7f 08 mov 0x8(%rdi),%rdi 40123b: e8 e8 ff ff ff callq 401228 <fun7> 401240: 01 c0 add %eax,%eax 401242: eb 1d jmp 401261 <fun7+0x39> 401244: b8 00 00 00 00 mov $0x0,%eax 401249: 39 f2 cmp %esi,%edx 40124b: 74 14 je 401261 <fun7+0x39> 40124d: 48 8b 7f 10 mov 0x10(%rdi),%rdi 401251: e8 d2 ff ff ff callq 401228 <fun7> 401256: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax 40125a: eb 05 jmp 401261 <fun7+0x39> 40125c: b8 ff ff ff ff mov $0xffffffff,%eax 401261: 48 83 c4 08 add $0x8,%rsp 401265: c3 retq 0000000000401266 <secret_phase>: 401266: 53 push %rbx 401267: e8 44 02 00 00 callq 4014b0 <read_line> 40126c: ba 0a 00 00 00 mov $0xa,%edx 401271: be 00 00 00 00 mov $0x0,%esi 401276: 48 89 c7 mov %rax,%rdi 401279: e8 12 f9 ff ff callq 400b90 <strtol@plt> 40127e: 48 89 c3 mov %rax,%rbx 401281: 8d 40 ff lea -0x1(%rax),%eax 401284: 3d e8 03 00 00 cmp $0x3e8,%eax 401289: 76 05 jbe 401290 <secret_phase+0x2a> 40128b: e8 bf 01 00 00 callq 40144f <explode_bomb> 401290: 89 de mov %ebx,%esi 401292: bf 10 31 60 00 mov $0x603110,%edi 401297: e8 8c ff ff ff callq 401228 <fun7> 40129c: 83 f8 07 cmp $0x7,%eax 40129f: 74 05 je 4012a6 <secret_phase+0x40> 4012a1: e8 a9 01 00 00 callq 40144f <explode_bomb> 4012a6: bf 00 24 40 00 mov $0x402400,%edi 4012ab: e8 30 f8 ff ff callq 400ae0 <puts@plt> 4012b0: e8 21 03 00 00 callq 4015d6 <phase_defused> 4012b5: 5b pop %rbx 4012b6: c3 retq
接下来就是隐藏阶段了,要想破解隐藏阶段,就要知道在哪里调用了它,在代码中查找 secret_phase
可以发现只在 phase_defused
函数中调用它,那么毫无疑问就是这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 00000000004015d6 <phase_defused>: 4015d6: 48 83 ec 78 sub $0x78,%rsp 4015da: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 4015e1: 00 00 4015e3: 48 89 44 24 68 mov %rax,0x68(%rsp) 4015e8: 31 c0 xor %eax,%eax 4015ea: 83 3d 9b 21 20 00 06 cmpl $0x6,0x20219b(%rip) # 60378c <num_input_strings> 4015f1: 75 5e jne 401651 <phase_defused+0x7b> 4015f3: 4c 8d 44 24 10 lea 0x10(%rsp),%r8 4015f8: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx 4015fd: 48 8d 54 24 08 lea 0x8(%rsp),%rdx 401602: be 19 26 40 00 mov $0x402619,%esi 401607: bf 90 38 60 00 mov $0x603890,%edi 40160c: e8 9f f5 ff ff callq 400bb0 <__isoc99_sscanf@plt> 401611: 83 f8 03 cmp $0x3,%eax 401614: 75 31 jne 401647 <phase_defused+0x71> 401616: be 22 26 40 00 mov $0x402622,%esi 40161b: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi 401620: e8 2b fd ff ff callq 401350 <strings_not_equal> 401625: 85 c0 test %eax,%eax 401627: 75 1e jne 401647 <phase_defused+0x71> 401629: bf f8 24 40 00 mov $0x4024f8,%edi 40162e: e8 ad f4 ff ff callq 400ae0 <puts@plt> 401633: bf 20 25 40 00 mov $0x402520,%edi 401638: e8 a3 f4 ff ff callq 400ae0 <puts@plt> 40163d: b8 00 00 00 00 mov $0x0,%eax 401642: e8 1f fc ff ff callq 401266 <secret_phase> 401647: bf 58 25 40 00 mov $0x402558,%edi 40164c: e8 8f f4 ff ff callq 400ae0 <puts@plt> 401651: 48 8b 44 24 68 mov 0x68(%rsp),%rax 401656: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 40165d: 00 00 40165f: 74 05 je 401666 <phase_defused+0x90> 401661: e8 9a f4 ff ff callq 400b00 <__stack_chk_fail@plt> 401666: 48 83 c4 78 add $0x78,%rsp 40166a: c3 retq
可以发现只在第六个炸弹结束后才会触发调用隐藏阶段的函数。
可以看到第12、13、14行是将第四个炸弹输入的内容重新作为标准输入来用,而第17-21行则是将输入的两个数字之后的字符串和内存中固定的字符串进行比较,查看该字符串:
那么触发隐藏阶段的密码就是 DrEvil
。
接下来看 secret_phase
,看起来不是很复杂。
先调用 read_line
读入内容,接下来是调用 strtol
将 char*
转为 long
和非数字部分,
接下来第32行则是判断输入的数字减 1 后是否大于1000,大于1000则爆炸,即输入的数不大于1001。
之后便是调用 fun7
,参数是存在 0x603110
处的 n1
和输入的数,结果要得到7。
查看n1的内容:
猜测其结构:
1 2 3 4 5 typedef struct { long data; Node* left; Node* right; } Node;
整理得到如下的二叉树(后来发现这一步其实没必要)
graph TD
n1[n1,data=36] --> n21[n21,data=8]
n1 --> n22[n22,data=50]
n21 --> n31[n31,data=6]
n21 --> n32[n32,data=22]
n22 --> n33[n33,data=45]
n22 --> n34[n34,data=107]
n31 --> n41,data=1
n31 --> n42,data=7
n32 --> n43,data=20
n32 --> n44,data=35
n33 --> n45,data=40
n33 --> n46,data=47
n34 --> n47,data=99
n34 --> n48,data=1001
再看 fun7
,根据反汇编代码写出伪代码,看起来是 phase_4 的一个拓展:
1 2 3 4 5 6 7 8 9 10 11 long fun7 (Node* n, long x) { if (n) { return 1 ; } else if (x < n->data) { return 2 * fun7(n->left, x); } else if (x == n->data) { return 0 ; } else { return 2 * fun7(n->right, x) + 1 ; } }
要得到7,就要$x>36$ , fun7(n22, x) == 3
;
依此类推,$x>50$ , fun7(n34, x) == 1
;
这里要得到1有两种方式,一种就是 n34 == NULL
,显然不成立,另一种就是:
$x > 107$ , fun7(n48) == 0
;
所以 x = n48->data
,即1001。
拆弹成功: