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行,可以看到是将 %eax0x4(%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) {
// a = 0, b = 14
int c = b - a; // 14 in %eax
c += c >> 31; // 14 in %eax, 0 in %ecx
c = (unsigned)c >> 1; // 7 in %eax
int d = a + c; // 7 in %ecx
// d = a + (b - a) / 2 = (a + b) / 2
if(x < d) { // x < 7
return 2 * func4(x, a, d - 1);
} else if (x > d) { // x > 7
return 2 * func4(x, d + 1, b) + 1;
} else { // x = 7
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 读入内容,接下来是调用 strtolchar* 转为 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。

拆弹成功: