警告
本文最后更新于 2023-04-22,文中内容可能已过时。
Ubuntu 22.04.1。
将可执行文件bomb
反汇编得到汇编代码,并存放到bomb.s
中,方便后续查看分析。
1
|
objdump -d bomb > bomb.s
|
创建ans.txt
存放输入,后面只需执行./bomb ans.txt
即可方便地运行bomb
。
1
2
3
4
5
6
7
8
9
10
11
|
00000000000015e7 <phase_1>:
15e7: f3 0f 1e fa endbr64
15eb: 48 83 ec 08 sub $0x8,%rsp
15ef: 48 8d 35 56 1b 00 00 lea 0x1b56(%rip),%rsi # 314c <_IO_stdin_used+0x14c>
15f6: e8 26 05 00 00 call 1b21 <strings_not_equal>
15fb: 85 c0 test %eax,%eax
15fd: 75 05 jne 1604 <phase_1+0x1d>
15ff: 48 83 c4 08 add $0x8,%rsp
1603: c3 ret
1604: e8 2c 06 00 00 call 1c35 <explode_bomb>
1609: eb f4 jmp 15ff <phase_1+0x18>
|
此题需要找到目标字符串。
在 0x15ef 处给 %rsi 传值,随后在 0x15f6 处调用strings_not_equal()
函数,猜测 %rsi 中存储的值为目标字符串的地址。用 gdb 调试打印字符串的值即可。
1
2
3
4
5
|
b phase_1
r
<input>
si 3
x/s $rsi #或x/s $rip+0x1b56
|
答案如下:
1
|
Crikey! I have lost my mojo!
|
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
|
000000000000160b <phase_2>:
160b: f3 0f 1e fa endbr64
160f: 55 push %rbp
1610: 53 push %rbx
1611: 48 83 ec 28 sub $0x28,%rsp
1615: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
161c: 00 00
161e: 48 89 44 24 18 mov %rax,0x18(%rsp)
1623: 31 c0 xor %eax,%eax
1625: 48 89 e6 mov %rsp,%rsi
1628: e8 34 06 00 00 call 1c61 <read_six_numbers>
162d: 83 3c 24 00 cmpl $0x0,(%rsp)
1631: 75 07 jne 163a <phase_2+0x2f>
1633: 83 7c 24 04 01 cmpl $0x1,0x4(%rsp)
1638: 74 05 je 163f <phase_2+0x34>
163a: e8 f6 05 00 00 call 1c35 <explode_bomb>
163f: 48 89 e3 mov %rsp,%rbx
1642: 48 8d 6c 24 10 lea 0x10(%rsp),%rbp
1647: eb 09 jmp 1652 <phase_2+0x47>
1649: 48 83 c3 04 add $0x4,%rbx
164d: 48 39 eb cmp %rbp,%rbx
1650: 74 11 je 1663 <phase_2+0x58>
1652: 8b 43 04 mov 0x4(%rbx),%eax
1655: 03 03 add (%rbx),%eax
1657: 39 43 08 cmp %eax,0x8(%rbx)
165a: 74 ed je 1649 <phase_2+0x3e>
165c: e8 d4 05 00 00 call 1c35 <explode_bomb>
1661: eb e6 jmp 1649 <phase_2+0x3e>
1663: 48 8b 44 24 18 mov 0x18(%rsp),%rax
1668: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax
166f: 00 00
1671: 75 07 jne 167a <phase_2+0x6f>
1673: 48 83 c4 28 add $0x28,%rsp
1677: 5b pop %rbx
1678: 5d pop %rbp
1679: c3 ret
167a: e8 d1 fb ff ff call 1250 <__stack_chk_fail@plt>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
0000000000001c61 <read_six_numbers>:
1c61: f3 0f 1e fa endbr64
1c65: 48 83 ec 08 sub $0x8,%rsp
1c69: 48 89 f2 mov %rsi,%rdx
1c6c: 48 8d 4e 04 lea 0x4(%rsi),%rcx
1c70: 48 8d 46 14 lea 0x14(%rsi),%rax
1c74: 50 push %rax
1c75: 48 8d 46 10 lea 0x10(%rsi),%rax
1c79: 50 push %rax
1c7a: 4c 8d 4e 0c lea 0xc(%rsi),%r9
1c7e: 4c 8d 46 08 lea 0x8(%rsi),%r8
1c82: 48 8d 35 82 16 00 00 lea 0x1682(%rip),%rsi # 330b <array.0+0x16b>
1c89: b8 00 00 00 00 mov $0x0,%eax
1c8e: e8 6d f6 ff ff call 1300 <__isoc99_sscanf@plt>
1c93: 48 83 c4 10 add $0x10,%rsp
1c97: 83 f8 05 cmp $0x5,%eax
1c9a: 7e 05 jle 1ca1 <read_six_numbers+0x40>
1c9c: 48 83 c4 08 add $0x8,%rsp
1ca0: c3 ret
1ca1: e8 8f ff ff ff call 1c35 <explode_bomb>
|
在 0x1628 处调用read_six_numbers()
函数,我们可以用 gdb 验证确实是读取 6 个整数。
1
2
3
4
5
|
b *0x555555555c8e
r
<ans1>
<input>
x/s $rsi
|
这里需要注意的是打断点用的是虚拟地址,而非偏移量,bomb.s
中显示偏移量,gdb 调试时显示虚拟地址,对于read_six_numbers()
中的__isoc99_sscanf()
来说,偏移量是 0x1c8e,虚拟地址是 0x555555555c8e。
重新回到phase_2()
的汇编代码,分析可得其核心部分用伪代码表示如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
if(M[rsp]!=0){
explode_bomb();
}else{
if(M[rsp+0x4]==1){
rbx=rsp;
rbp=rsp+0x10;
do{
eax=M[rbx+0x4];
eax=eax+M[rbx];
if(M[rbx+0x8]==eax){
rbx=rbx+0x4;
}else{
explode_bomb();
}
}while(rbx!=rbp);
}else{
explode_bomb();
}
}
|
M[rsp] 代表输入的第一个数,rsp 每加 0x4 代表下一个数。
我们需要构造这样的序列:第一个数为 0,第二个数为 1,此后的数为前面两项之和。
答案如下:
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
|
000000000000167f <phase_3>:
167f: f3 0f 1e fa endbr64
1683: 48 83 ec 18 sub $0x18,%rsp
1687: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
168e: 00 00
1690: 48 89 44 24 08 mov %rax,0x8(%rsp)
1695: 31 c0 xor %eax,%eax
1697: 48 8d 4c 24 04 lea 0x4(%rsp),%rcx
169c: 48 89 e2 mov %rsp,%rdx
169f: 48 8d 35 71 1c 00 00 lea 0x1c71(%rip),%rsi # 3317 <array.0+0x177>
16a6: e8 55 fc ff ff call 1300 <__isoc99_sscanf@plt>
16ab: 83 f8 01 cmp $0x1,%eax
16ae: 7e 1e jle 16ce <phase_3+0x4f>
16b0: 83 3c 24 07 cmpl $0x7,(%rsp)
16b4: 0f 87 9a 00 00 00 ja 1754 <phase_3+0xd5>
16ba: 8b 04 24 mov (%rsp),%eax
16bd: 48 8d 15 bc 1a 00 00 lea 0x1abc(%rip),%rdx # 3180 <_IO_stdin_used+0x180>
16c4: 48 63 04 82 movslq (%rdx,%rax,4),%rax
16c8: 48 01 d0 add %rdx,%rax
16cb: 3e ff e0 notrack jmp *%rax
16ce: e8 62 05 00 00 call 1c35 <explode_bomb>
16d3: eb db jmp 16b0 <phase_3+0x31>
16d5: b8 cf 02 00 00 mov $0x2cf,%eax
16da: 2d 26 01 00 00 sub $0x126,%eax
16df: 05 d8 02 00 00 add $0x2d8,%eax
16e4: 2d 66 01 00 00 sub $0x166,%eax
16e9: 05 66 01 00 00 add $0x166,%eax
16ee: 2d 66 01 00 00 sub $0x166,%eax
16f3: 05 66 01 00 00 add $0x166,%eax
16f8: 2d 66 01 00 00 sub $0x166,%eax
16fd: 83 3c 24 05 cmpl $0x5,(%rsp)
1701: 7f 06 jg 1709 <phase_3+0x8a>
1703: 39 44 24 04 cmp %eax,0x4(%rsp)
1707: 74 05 je 170e <phase_3+0x8f>
1709: e8 27 05 00 00 call 1c35 <explode_bomb>
170e: 48 8b 44 24 08 mov 0x8(%rsp),%rax
1713: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax
171a: 00 00
171c: 75 42 jne 1760 <phase_3+0xe1>
171e: 48 83 c4 18 add $0x18,%rsp
1722: c3 ret
1723: b8 00 00 00 00 mov $0x0,%eax
1728: eb b0 jmp 16da <phase_3+0x5b>
172a: b8 00 00 00 00 mov $0x0,%eax
172f: eb ae jmp 16df <phase_3+0x60>
1731: b8 00 00 00 00 mov $0x0,%eax
1736: eb ac jmp 16e4 <phase_3+0x65>
1738: b8 00 00 00 00 mov $0x0,%eax
173d: eb aa jmp 16e9 <phase_3+0x6a>
173f: b8 00 00 00 00 mov $0x0,%eax
1744: eb a8 jmp 16ee <phase_3+0x6f>
1746: b8 00 00 00 00 mov $0x0,%eax
174b: eb a6 jmp 16f3 <phase_3+0x74>
174d: b8 00 00 00 00 mov $0x0,%eax
1752: eb a4 jmp 16f8 <phase_3+0x79>
1754: e8 dc 04 00 00 call 1c35 <explode_bomb>
1759: b8 00 00 00 00 mov $0x0,%eax
175e: eb 9d jmp 16fd <phase_3+0x7e>
1760: e8 eb fa ff ff call 1250 <__stack_chk_fail@plt>
|
看看要读取什么。
1
2
3
4
5
6
|
b *0x5555555556a6
r
<ans1>
<ans2>
<input>
x/s $rsi
|
需要读取两个整数。对这两个整数有什么要求呢?
由16fd: cmpl $0x5,(%rsp)
可得第一个数不能大于5,由1703: cmp %eax,0x4(%rsp)
可得第二个数必须等于 %eax 的值。假设输入的第一个数是 4。使用 gdb 调试,查看 %eax 的值。
1
2
3
4
5
6
|
b *0x555555555707
r
<ans1>
<ans2>
<input>
p $eax
|
答案如下:
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
|
00000000000017a0 <phase_4>:
17a0: f3 0f 1e fa endbr64
17a4: 48 83 ec 18 sub $0x18,%rsp
17a8: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
17af: 00 00
17b1: 48 89 44 24 08 mov %rax,0x8(%rsp)
17b6: 31 c0 xor %eax,%eax
17b8: 48 89 e1 mov %rsp,%rcx
17bb: 48 8d 54 24 04 lea 0x4(%rsp),%rdx
17c0: 48 8d 35 50 1b 00 00 lea 0x1b50(%rip),%rsi # 3317 <array.0+0x177>
17c7: e8 34 fb ff ff call 1300 <__isoc99_sscanf@plt>
17cc: 83 f8 02 cmp $0x2,%eax
17cf: 75 0b jne 17dc <phase_4+0x3c>
17d1: 8b 04 24 mov (%rsp),%eax
17d4: 83 e8 02 sub $0x2,%eax
17d7: 83 f8 02 cmp $0x2,%eax
17da: 76 05 jbe 17e1 <phase_4+0x41>
17dc: e8 54 04 00 00 call 1c35 <explode_bomb>
17e1: 8b 34 24 mov (%rsp),%esi
17e4: bf 07 00 00 00 mov $0x7,%edi
17e9: e8 77 ff ff ff call 1765 <func4>
17ee: 39 44 24 04 cmp %eax,0x4(%rsp)
17f2: 75 15 jne 1809 <phase_4+0x69>
17f4: 48 8b 44 24 08 mov 0x8(%rsp),%rax
17f9: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax
1800: 00 00
1802: 75 0c jne 1810 <phase_4+0x70>
1804: 48 83 c4 18 add $0x18,%rsp
1808: c3 ret
1809: e8 27 04 00 00 call 1c35 <explode_bomb>
180e: eb e4 jmp 17f4 <phase_4+0x54>
1810: e8 3b fa ff ff call 1250 <__stack_chk_fail@plt>
|
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
|
0000000000001765 <func4>:
1765: f3 0f 1e fa endbr64
1769: b8 00 00 00 00 mov $0x0,%eax
176e: 85 ff test %edi,%edi
1770: 7e 2d jle 179f <func4+0x3a>
1772: 41 54 push %r12
1774: 55 push %rbp
1775: 53 push %rbx
1776: 89 fb mov %edi,%ebx
1778: 89 f5 mov %esi,%ebp
177a: 89 f0 mov %esi,%eax
177c: 83 ff 01 cmp $0x1,%edi
177f: 74 19 je 179a <func4+0x35>
1781: 8d 7f ff lea -0x1(%rdi),%edi
1784: e8 dc ff ff ff call 1765 <func4>
1789: 44 8d 24 28 lea (%rax,%rbp,1),%r12d
178d: 8d 7b fe lea -0x2(%rbx),%edi
1790: 89 ee mov %ebp,%esi
1792: e8 ce ff ff ff call 1765 <func4>
1797: 44 01 e0 add %r12d,%eax
179a: 5b pop %rbx
179b: 5d pop %rbp
179c: 41 5c pop %r12
179e: c3 ret
179f: c3 ret
|
1
2
3
4
5
6
7
|
b *0x5555555557c7
r
<ans1>
<ans2>
<ans3>
<input>
x/s $rsi
|
仍然需要读取两个整数。
由汇编代码得,其中一个无符号整数 v 需满足 v - 2 <= 2。即 v ∈ [2, 4]。
1
2
3
4
5
|
17d1: 8b 04 24 mov (%rsp),%eax
17d4: 83 e8 02 sub $0x2,%eax
17d7: 83 f8 02 cmp $0x2,%eax
17da: 76 05 jbe 17e1 <phase_4+0x41>
17dc: e8 54 04 00 00 call 1c35 <explode_bomb>
|
可以看到,在调用func4()
之前,传了两个参数,其中一个来自我们的输入,一个固定为 7。fun4()
执行完成后,又用另一个输入值和返回值比较。
1
2
3
4
5
|
17e1: 8b 34 24 mov (%rsp),%esi
17e4: bf 07 00 00 00 mov $0x7,%edi
17e9: e8 77 ff ff ff call 1765 <func4>
17ee: 39 44 24 04 cmp %eax,0x4(%rsp)
17f2: 75 15 jne 1809 <phase_4+0x69>
|
随便输不违规的两个数,如3 2
,再用 gdb 调试。
1
2
3
4
5
6
7
8
9
|
b *0x5555555557f2
r
<ans1>
<ans2>
<ans3>
3 2
x $rsp
x $rsp+4
p $eax
|
于是我们可以知道第二个输入值作为func4()
的一个参数,第一个输入值和返回值比较。在这里,顺便得出一个答案66 2
。
用伪代码描述一下func4()
,加深对执行过程的理解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
int func4(int v1,int v2)
{
int res=0;
int v3;
if (v1>0)
{
res=v2;
if (v1!=1)
{
v3=func4(v1-1,v2)+v2;
return v3+func4(v1-2,v2);
}
}
return res;
}
|
答案如下:
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
|
0000000000001815 <phase_5>:
1815: f3 0f 1e fa endbr64
1819: 48 83 ec 18 sub $0x18,%rsp
181d: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
1824: 00 00
1826: 48 89 44 24 08 mov %rax,0x8(%rsp)
182b: 31 c0 xor %eax,%eax
182d: 48 8d 4c 24 04 lea 0x4(%rsp),%rcx
1832: 48 89 e2 mov %rsp,%rdx
1835: 48 8d 35 db 1a 00 00 lea 0x1adb(%rip),%rsi # 3317 <array.0+0x177>
183c: e8 bf fa ff ff call 1300 <__isoc99_sscanf@plt>
1841: 83 f8 01 cmp $0x1,%eax
1844: 7e 5a jle 18a0 <phase_5+0x8b>
1846: 8b 04 24 mov (%rsp),%eax
1849: 83 e0 0f and $0xf,%eax
184c: 89 04 24 mov %eax,(%rsp)
184f: 83 f8 0f cmp $0xf,%eax
1852: 74 32 je 1886 <phase_5+0x71>
1854: b9 00 00 00 00 mov $0x0,%ecx
1859: ba 00 00 00 00 mov $0x0,%edx
185e: 48 8d 35 3b 19 00 00 lea 0x193b(%rip),%rsi # 31a0 <array.0>
1865: 83 c2 01 add $0x1,%edx
1868: 48 98 cltq
186a: 8b 04 86 mov (%rsi,%rax,4),%eax
186d: 01 c1 add %eax,%ecx
186f: 83 f8 0f cmp $0xf,%eax
1872: 75 f1 jne 1865 <phase_5+0x50>
1874: c7 04 24 0f 00 00 00 movl $0xf,(%rsp)
187b: 83 fa 0f cmp $0xf,%edx
187e: 75 06 jne 1886 <phase_5+0x71>
1880: 39 4c 24 04 cmp %ecx,0x4(%rsp)
1884: 74 05 je 188b <phase_5+0x76>
1886: e8 aa 03 00 00 call 1c35 <explode_bomb>
188b: 48 8b 44 24 08 mov 0x8(%rsp),%rax
1890: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax
1897: 00 00
1899: 75 0c jne 18a7 <phase_5+0x92>
189b: 48 83 c4 18 add $0x18,%rsp
189f: c3 ret
18a0: e8 90 03 00 00 call 1c35 <explode_bomb>
18a5: eb 9f jmp 1846 <phase_5+0x31>
18a7: e8 a4 f9 ff ff call 1250 <__stack_chk_fail@plt>
|
1
2
3
4
5
6
7
8
|
b *0x55555555583c
r
<ans1>
<ans2>
<ans3>
<ans4>
<input>
x/s $rsi
|
需要读取两个整数。
再看各个寄存器的作用:
- %eax 用于存储数组元素的值,并作为下一次循环中数组的索引,还作为循环终止条件。
1
2
3
4
|
186a: 8b 04 86 mov (%rsi,%rax,4),%eax
186d: 01 c1 add %eax,%ecx
186f: 83 f8 0f cmp $0xf,%eax
1872: 75 f1 jne 1865 <phase_5+0x50>
|
- %edx 用于存储循环次数。
- %ecx 用于存储累加值。
因此我们得到输入值的所有限制:
- 第一个输入值与 0xF 按位与后不能等于 15,这里我们还可以知道,第一个输入值将作为数组的第一次索引。
1
2
3
4
5
|
1846: 8b 04 24 mov (%rsp),%eax
1849: 83 e0 0f and $0xf,%eax
184c: 89 04 24 mov %eax,(%rsp)
184f: 83 f8 0f cmp $0xf,%eax
1852: 74 32 je 1886 <phase_5+0x71>
|
1
2
|
187b: 83 fa 0f cmp $0xf,%edx
187e: 75 06 jne 1886 <phase_5+0x71>
|
1
2
3
|
1880: 39 4c 24 04 cmp %ecx,0x4(%rsp)
1884: 74 05 je 188b <phase_5+0x76>
1886: e8 aa 03 00 00 call 1c35 <explode_bomb>
|
使用 gdb 打印数组元素的值:
1
2
3
4
5
6
7
8
|
b *0x555555555865
r
<ans1>
<ans2>
<ans3>
<ans4>
<input>
x/16wd $rsi
|
要循环 15 次,且 %eax 在最后一次循环的值为 15,我们可以反推一下得到数组的首个索引:
15 -> 6 ->14 ->2 -> 1 ->10 ->0 -> 8 ->4 -> 9 ->13 ->11 -> 7 -> 3 -> 12 -> 5
因此,数组的首个索引为 5,除去 array[15] = 5 之外的所有值求和即为累加值 115。
答案如下:
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
83
84
85
86
87
88
89
|
00000000000018ac <phase_6>:
18ac: f3 0f 1e fa endbr64
18b0: 41 56 push %r14
18b2: 41 55 push %r13
18b4: 41 54 push %r12
18b6: 55 push %rbp
18b7: 53 push %rbx
18b8: 48 83 ec 60 sub $0x60,%rsp
18bc: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
18c3: 00 00
18c5: 48 89 44 24 58 mov %rax,0x58(%rsp)
18ca: 31 c0 xor %eax,%eax
18cc: 49 89 e5 mov %rsp,%r13
18cf: 4c 89 ee mov %r13,%rsi
18d2: e8 8a 03 00 00 call 1c61 <read_six_numbers>
18d7: 41 be 01 00 00 00 mov $0x1,%r14d
18dd: 49 89 e4 mov %rsp,%r12
18e0: eb 28 jmp 190a <phase_6+0x5e>
18e2: e8 4e 03 00 00 call 1c35 <explode_bomb>
18e7: eb 30 jmp 1919 <phase_6+0x6d>
18e9: 48 83 c3 01 add $0x1,%rbx
18ed: 83 fb 05 cmp $0x5,%ebx
18f0: 7f 10 jg 1902 <phase_6+0x56>
18f2: 41 8b 04 9c mov (%r12,%rbx,4),%eax
18f6: 39 45 00 cmp %eax,0x0(%rbp)
18f9: 75 ee jne 18e9 <phase_6+0x3d>
18fb: e8 35 03 00 00 call 1c35 <explode_bomb>
1900: eb e7 jmp 18e9 <phase_6+0x3d>
1902: 49 83 c6 01 add $0x1,%r14
1906: 49 83 c5 04 add $0x4,%r13
190a: 4c 89 ed mov %r13,%rbp
190d: 41 8b 45 00 mov 0x0(%r13),%eax
1911: 83 e8 01 sub $0x1,%eax
1914: 83 f8 05 cmp $0x5,%eax
1917: 77 c9 ja 18e2 <phase_6+0x36>
1919: 41 83 fe 05 cmp $0x5,%r14d
191d: 7f 05 jg 1924 <phase_6+0x78>
191f: 4c 89 f3 mov %r14,%rbx
1922: eb ce jmp 18f2 <phase_6+0x46>
1924: be 00 00 00 00 mov $0x0,%esi
1929: 8b 0c b4 mov (%rsp,%rsi,4),%ecx
192c: b8 01 00 00 00 mov $0x1,%eax
1931: 48 8d 15 d8 38 00 00 lea 0x38d8(%rip),%rdx # 5210 <node1>
1938: 83 f9 01 cmp $0x1,%ecx
193b: 7e 0b jle 1948 <phase_6+0x9c>
193d: 48 8b 52 08 mov 0x8(%rdx),%rdx
1941: 83 c0 01 add $0x1,%eax
1944: 39 c8 cmp %ecx,%eax
1946: 75 f5 jne 193d <phase_6+0x91>
1948: 48 89 54 f4 20 mov %rdx,0x20(%rsp,%rsi,8)
194d: 48 83 c6 01 add $0x1,%rsi
1951: 48 83 fe 06 cmp $0x6,%rsi
1955: 75 d2 jne 1929 <phase_6+0x7d>
1957: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx
195c: 48 8b 44 24 28 mov 0x28(%rsp),%rax
1961: 48 89 43 08 mov %rax,0x8(%rbx)
1965: 48 8b 54 24 30 mov 0x30(%rsp),%rdx
196a: 48 89 50 08 mov %rdx,0x8(%rax)
196e: 48 8b 44 24 38 mov 0x38(%rsp),%rax
1973: 48 89 42 08 mov %rax,0x8(%rdx)
1977: 48 8b 54 24 40 mov 0x40(%rsp),%rdx
197c: 48 89 50 08 mov %rdx,0x8(%rax)
1980: 48 8b 44 24 48 mov 0x48(%rsp),%rax
1985: 48 89 42 08 mov %rax,0x8(%rdx)
1989: 48 c7 40 08 00 00 00 movq $0x0,0x8(%rax)
1990: 00
1991: bd 05 00 00 00 mov $0x5,%ebp
1996: eb 09 jmp 19a1 <phase_6+0xf5>
1998: 48 8b 5b 08 mov 0x8(%rbx),%rbx
199c: 83 ed 01 sub $0x1,%ebp
199f: 74 11 je 19b2 <phase_6+0x106>
19a1: 48 8b 43 08 mov 0x8(%rbx),%rax
19a5: 8b 00 mov (%rax),%eax
19a7: 39 03 cmp %eax,(%rbx)
19a9: 7d ed jge 1998 <phase_6+0xec>
19ab: e8 85 02 00 00 call 1c35 <explode_bomb>
19b0: eb e6 jmp 1998 <phase_6+0xec>
19b2: 48 8b 44 24 58 mov 0x58(%rsp),%rax
19b7: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax
19be: 00 00
19c0: 75 0d jne 19cf <phase_6+0x123>
19c2: 48 83 c4 60 add $0x60,%rsp
19c6: 5b pop %rbx
19c7: 5d pop %rbp
19c8: 41 5c pop %r12
19ca: 41 5d pop %r13
19cc: 41 5e pop %r14
19ce: c3 ret
19cf: e8 7c f8 ff ff call 1250 <__stack_chk_fail@plt>
|
这道题本质上是输入 6 个 1~6 之间各不相同的数,据此对 node1~node6 排序,需要按节点值降序排序才能拆除炸弹。
使用 gdb 打印 node1~node6 的值:
1
2
3
4
5
6
7
8
9
10
|
b *0x555555555938
r
<ans1>
<ans2>
<ans3>
<ans4>
<ans5>
<input>
x/24wx $rdx
x/wx 0x555555559110
|
按照节点值大小排序,可得答案为:
在bomb.c
末尾看到这句话:
1
2
|
/* Wow, they got it! But isn't something... missing? Perhaps
* something they overlooked? Mua ha ha ha ha! */
|
说明有隐藏炸弹,我们回到bomb.s
看到phase_defused()
藏了一个secret_phase()
。
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
|
0000000000001dde <phase_defused>:
1dde: f3 0f 1e fa endbr64
1de2: 48 83 ec 78 sub $0x78,%rsp
1de6: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
1ded: 00 00
1def: 48 89 44 24 68 mov %rax,0x68(%rsp)
1df4: 31 c0 xor %eax,%eax
1df6: 83 3d f3 38 00 00 06 cmpl $0x6,0x38f3(%rip) # 56f0 <num_input_strings>
1dfd: 74 15 je 1e14 <phase_defused+0x36>
1dff: 48 8b 44 24 68 mov 0x68(%rsp),%rax
1e04: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax
1e0b: 00 00
1e0d: 75 73 jne 1e82 <phase_defused+0xa4>
1e0f: 48 83 c4 78 add $0x78,%rsp
1e13: c3 ret
1e14: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx
1e19: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
1e1e: 4c 8d 44 24 10 lea 0x10(%rsp),%r8
1e23: 48 8d 35 37 15 00 00 lea 0x1537(%rip),%rsi # 3361 <array.0+0x1c1>
1e2a: 48 8d 3d bf 39 00 00 lea 0x39bf(%rip),%rdi # 57f0 <input_strings+0xf0>
1e31: e8 ca f4 ff ff call 1300 <__isoc99_sscanf@plt>
1e36: 83 f8 03 cmp $0x3,%eax
1e39: 74 0e je 1e49 <phase_defused+0x6b>
1e3b: 48 8d 3d 5e 14 00 00 lea 0x145e(%rip),%rdi # 32a0 <array.0+0x100>
1e42: e8 d9 f3 ff ff call 1220 <puts@plt>
1e47: eb b6 jmp 1dff <phase_defused+0x21>
1e49: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
1e4e: 48 8d 35 15 15 00 00 lea 0x1515(%rip),%rsi # 336a <array.0+0x1ca>
1e55: e8 c7 fc ff ff call 1b21 <strings_not_equal>
1e5a: 85 c0 test %eax,%eax
1e5c: 75 dd jne 1e3b <phase_defused+0x5d>
1e5e: 48 8d 3d db 13 00 00 lea 0x13db(%rip),%rdi # 3240 <array.0+0xa0>
1e65: e8 b6 f3 ff ff call 1220 <puts@plt>
1e6a: 48 8d 3d f7 13 00 00 lea 0x13f7(%rip),%rdi # 3268 <array.0+0xc8>
1e71: e8 aa f3 ff ff call 1220 <puts@plt>
1e76: b8 00 00 00 00 mov $0x0,%eax
1e7b: e8 95 fb ff ff call 1a15 <secret_phase>
1e80: eb b9 jmp 1e3b <phase_defused+0x5d>
1e82: e8 c9 f3 ff ff call 1250 <__stack_chk_fail@plt>
|
怎么触发secret_phase()
调用?
先看看在phase_defused()
中__isoc99_sscanf()
读取了什么。
1
2
3
|
1e23: 48 8d 35 37 15 00 00 lea 0x1537(%rip),%rsi # 3361 <array.0+0x1c1>
1e2a: 48 8d 3d bf 39 00 00 lea 0x39bf(%rip),%rdi # 57f0 <input_strings+0xf0>
1e31: e8 ca f4 ff ff call 1300 <__isoc99_sscanf@plt>
|
使用 gdb 调试:
1
2
3
4
5
6
7
8
9
10
|
b *0x555555555e31
r
<ans1>
<ans2>
<ans3>
<ans4>
<ans5>
<ans6>
x/s $rsi
x/s $rdi
|
由下面汇编代码可得只有当__isoc99_sscanf()
的返回值为 3 时才有机会调用secret_phase()
。
1
2
|
1e36: 83 f8 03 cmp $0x3,%eax
1e39: 74 0e je 1e49 <phase_defused+0x6b>
|
继续往下打印 %eax 的值:
%eax 的值为 2,说明__isoc99_sscanf()
读取的是 %rdi 中的存储的地址存储的字符串66 2
,这实际上是我们在 phase_4 的答案。也就是说,我们要进入secret_phase()
,需要在 phase_4 的答案后面追加一个目标字符串。
这里进行了两次地址传送,并调用strings_not_equal()
,可以推断一个是我们追加的字符串的地址,一个是目标字符串的地址。
1
2
3
|
1e49: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi
1e4e: 48 8d 35 15 15 00 00 lea 0x1515(%rip),%rsi # 336a <array.0+0x1ca>
1e55: e8 c7 fc ff ff call 1b21 <strings_not_equal>
|
在 phase_4 的答案后面追加字符串,重新使用 gdb 调试:
1
2
3
4
5
6
7
8
9
10
|
b *0x555555555e55
r
<ans1>
<ans2>
<ans3>
<ans4> <append>
<ans5>
<ans6>
x/s $rsi
x/s $rdi
|
我们得到了目标字符串为DrEvil
。
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
|
0000000000001a15 <secret_phase>:
1a15: f3 0f 1e fa endbr64
1a19: 53 push %rbx
1a1a: e8 87 02 00 00 call 1ca6 <read_line>
1a1f: 48 89 c7 mov %rax,%rdi
1a22: ba 0a 00 00 00 mov $0xa,%edx
1a27: be 00 00 00 00 mov $0x0,%esi
1a2c: e8 af f8 ff ff call 12e0 <strtol@plt>
1a31: 89 c3 mov %eax,%ebx
1a33: 83 e8 01 sub $0x1,%eax
1a36: 3d e8 03 00 00 cmp $0x3e8,%eax
1a3b: 77 26 ja 1a63 <secret_phase+0x4e>
1a3d: 89 de mov %ebx,%esi
1a3f: 48 8d 3d ea 36 00 00 lea 0x36ea(%rip),%rdi # 5130 <n1>
1a46: e8 89 ff ff ff call 19d4 <fun7>
1a4b: 83 f8 03 cmp $0x3,%eax
1a4e: 75 1a jne 1a6a <secret_phase+0x55>
1a50: 48 8d 3d 89 17 00 00 lea 0x1789(%rip),%rdi # 31e0 <array.0+0x40>
1a57: e8 c4 f7 ff ff call 1220 <puts@plt>
1a5c: e8 7d 03 00 00 call 1dde <phase_defused>
1a61: 5b pop %rbx
1a62: c3 ret
1a63: e8 cd 01 00 00 call 1c35 <explode_bomb>
1a68: eb d3 jmp 1a3d <secret_phase+0x28>
1a6a: e8 c6 01 00 00 call 1c35 <explode_bomb>
1a6f: eb df jmp 1a50 <secret_phase+0x3b>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
00000000000019d4 <fun7>:
19d4: f3 0f 1e fa endbr64
19d8: 48 85 ff test %rdi,%rdi
19db: 74 32 je 1a0f <fun7+0x3b>
19dd: 48 83 ec 08 sub $0x8,%rsp
19e1: 8b 17 mov (%rdi),%edx
19e3: 39 f2 cmp %esi,%edx
19e5: 7f 0c jg 19f3 <fun7+0x1f>
19e7: b8 00 00 00 00 mov $0x0,%eax
19ec: 75 12 jne 1a00 <fun7+0x2c>
19ee: 48 83 c4 08 add $0x8,%rsp
19f2: c3 ret
19f3: 48 8b 7f 08 mov 0x8(%rdi),%rdi
19f7: e8 d8 ff ff ff call 19d4 <fun7>
19fc: 01 c0 add %eax,%eax
19fe: eb ee jmp 19ee <fun7+0x1a>
1a00: 48 8b 7f 10 mov 0x10(%rdi),%rdi
1a04: e8 cb ff ff ff call 19d4 <fun7>
1a09: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
1a0d: eb df jmp 19ee <fun7+0x1a>
1a0f: b8 ff ff ff ff mov $0xffffffff,%eax
1a14: c3 ret
|
strtol()
函数用于将一个字符串转换为长整型数值,函数的原型如下:
1
|
long int strtol(const char *str, char **endptr, int base);
|
函数接受三个参数:
str
:要转换的字符串。
endptr
:可选的输出参数,用于返回转换后第一个无效字符的指针。
base
:进制数,可以是 2、8、10 或 16,或者是 0。当base
为 0 时,函数会根据str
的前缀来确定进制数:如果str
以 “0x” 或 “0X” 开头,则按十六进制解释;如果str
以 “0” 开头,则按八进制解释;否则按十进制解释。
函数返回被转换后的长整型数值。如果转换失败,则返回 0。
%rdi,%esi,%edx 分别存储了strtol()
函数将接收的三个参数。
1
2
3
|
1a1f: 48 89 c7 mov %rax,%rdi
1a22: ba 0a 00 00 00 mov $0xa,%edx
1a27: be 00 00 00 00 mov $0x0,%esi
|
由下面汇编代码可得strtol()
的返回值要在 [0x1, 0x3E9] 之间,否则爆炸。
1
2
3
4
|
1a31: 89 c3 mov %eax,%ebx
1a33: 83 e8 01 sub $0x1,%eax
1a36: 3d e8 03 00 00 cmp $0x3e8,%eax
1a3b: 77 26 ja 1a63 <secret_phase+0x4e>
|
我们还需要让fun7()
的返回值为 3。
1
2
3
4
5
|
1a3d: 89 de mov %ebx,%esi
1a3f: 48 8d 3d ea 36 00 00 lea 0x36ea(%rip),%rdi # 5130 <n1>
1a46: e8 89 ff ff ff call 19d4 <fun7>
1a4b: 83 f8 03 cmp $0x3,%eax
1a4e: 75 1a jne 1a6a <secret_phase+0x55>
|
用伪代码描述fun7()
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
int fun7(int rdi,int esi){
if(rdi==0){
eax=0xffffffff;
}else{
edx=M[rdi];
if(edx>esi){
rdi=M[rdi+0x8];
eax=fun7(rdi,esi);
eax=eax+eax;
}else if(edx!=esi){
rdi=M[rdi+0x10];
eax=fun7(rdi,esi);
eax=2*eax+1;
}else{
eax=0;
}
}
return eax;
}
|
根据伪代码edx=M[rdi]
、rdi=M[rdi+0x8]
和rdi=M[rdi+0x10]
我们可以抽象出一个二叉树结构:
1
2
3
4
5
|
struct Node{
int val;
Node* left;
Node* right;
}
|
在调用第一层fun7()
前,二叉树根节点的地址已经先存在了 %rdi 中,据此我们可以打印出所有节点的信息。
1
2
3
4
|
b *0x555555555a46
r
x/3gx $rdi
[...]
|
怎样才能使fun7()
的返回值为 3?
由于 3 是奇数,我们只能希望从 %edx != %esi (在fun7()
里等价于 %edx < %esi )的分支得到返回值。
第一层fun7()
的返回值为 3 -> 第二层fun7()
的返回值为 1 -> 第三层fun7()
的返回值为 0。
使第三层fun7()
的返回值为 0 又有两种情况:
- 走 %edx == %esi 分支。
- 走 %edx > %esi 分支,且第四层
fun7()
的返回值为 0。
注意在调用第一层fun7()
前,strtol()
的返回值先存在了 %esi 中,也就是说,我们输入的字符串转换成的长整型结果将决定我们在调用func7()
时走哪些分支。
现在我们不难得到两种可行的结果,即 n34 的值或 n47 的值:
或
最终,ans.txt
中的内容如下:
1
2
3
4
5
6
7
|
Crikey! I have lost my mojo!
0 1 1 2 3 5
4 0
66 2 DrEvil
5 115
6 5 2 1 4 3
107
|
通关截图: