CSAPP Lab -- Bomb Lab

使用objdump工具对程序进行反汇编。

1
2
3
objdump -s -d bomb > bomb.out  # 可执行指令部分反汇编
objdump -s -j .data bomb > bomb.data # 获取data段
objdump -s -j .rodata bomb > bomb.rodata # 获取rodata段

Phase 1

找到Phase_1部分的汇编代码。

1
2
3
4
5
6
7
8
9
0000000000400ee0 phase_1:
400ee0: 48 83 ec 08 subq $8, %rsp
400ee4: be 00 24 40 00 movl $4203520, %esi
400ee9: e8 4a 04 00 00 callq 1098 <strings_not_equal>
400eee: 85 c0 testl %eax, %eax
400ef0: 74 05 je 5 <phase_1+0x17>
400ef2: e8 43 05 00 00 callq 1347 <explode_bomb>
400ef7: 48 83 c4 08 addq $8, %rsp
400efb: c3 retq

对它以及调用它的上下文简单分析可以将它转为下列C语言代码。(部分强大的反汇编工具允许生成C语言代码,这里建议自己手动尝试)

Test指令用于将两个操作数进行与操作,然后根据结果设置标志寄存器。JE指令表示ZF被置位时跳转。

这里的作用是检测eax是否为0,如果与的结果为0,则ZF置位。如果ZF被置位则跳转到<phase_1+0x17>的位置。

1
2
3
4
5
6
phase_1(char *input) {
int r = strings_not_equal(input, 4203520);
if (r != 0) {
explode_bomb();
}
}

strings_not_equal方法可以猜测是一个是一个字符串比较函数,因此可以猜测4203520(0x402400)是一个地址。

1
2
3
4
5
6
7
8
# objdump -s -j .rodata bomb
402400 426f7264 65722072 656c6174 696f6e73 Border relations
402410 20776974 68204361 6e616461 20686176 with Canada hav
402420 65206e65 76657220 6265656e 20626574 e never been bet
402430 7465722e 00000000 576f7721 20596f75 ter.....Wow! You
402440 27766520 64656675 73656420 74686520 've defused the
402450 73656372 65742073 74616765 2100666c secret stage!.fl
402460 79657273 00000000 00000000 00000000 yers............

从中地址4203520提取出字符串“Border relations with Canada have never been better.”(字符串以0x00结束)。

此处补充一下strings_not_equal的逻辑,可以根据汇编代码得出下列结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int strings_not_equal(char *input, char *str) { 
if (strlen(input) != strlen(str)) {
return 1;
}

while (*input != 0) {
if (*input == *str) {
input++;
str++;
} else {
return 1;
}
}
return 0;

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
0000000000400efc phase_2:
400efc: 55 pushq %rbp
400efd: 53 pushq %rbx
400efe: 48 83 ec 28 subq $40, %rsp
400f02: 48 89 e6 movq %rsp, %rsi
400f05: e8 52 05 00 00 callq 1362 <read_six_numbers>
400f0a: 83 3c 24 01 cmpl $1, (%rsp)
400f0e: 74 20 je 32 <phase_2+0x34>
400f10: e8 25 05 00 00 callq 1317 <explode_bomb>
400f15: eb 19 jmp 25 <phase_2+0x34>
400f17: 8b 43 fc movl -4(%rbx), %eax
400f1a: 01 c0 addl %eax, %eax
400f1c: 39 03 cmpl %eax, (%rbx)
400f1e: 74 05 je 5 <phase_2+0x29>
400f20: e8 15 05 00 00 callq 1301 <explode_bomb>
400f25: 48 83 c3 04 addq $4, %rbx
400f29: 48 39 eb cmpq %rbp, %rbx
400f2c: 75 e9 jne -23 <phase_2+0x1b>
400f2e: eb 0c jmp 12 <phase_2+0x40>
400f30: 48 8d 5c 24 04 leaq 4(%rsp), %rbx
400f35: 48 8d 6c 24 18 leaq 24(%rsp), %rbp
400f3a: eb db jmp -37 <phase_2+0x1b>
400f3c: 48 83 c4 28 addq $40, %rsp
400f40: 5b popq %rbx
400f41: 5d popq %rbp
400f42: c3 retq

同样对它进行翻译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
phase_2(char *input) {
%rsp -= 40
read_six_number(input, %rsp - 40);
if (*(%rsp) != 1) {
explode_bomb();
}
%rbx = %rsp + 4;
%rbp = %rsp + 24;
loop:
%eax = *(%rbx - 4) * 2;
if (%eax != *(%rbx)) {
explode_bomb();
}
%rbx += 4;
if (%rbp != %rbx) {
goto loop;
}
}

很容易看出来,比较6个数。已经规定了第一个数是1,要求每个后面的数,都是前一个数的两倍。

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
0000000000400f43 phase_3:
400f43: 48 83 ec 18 subq $24, %rsp
400f47: 48 8d 4c 24 0c leaq 12(%rsp), %rcx
400f4c: 48 8d 54 24 08 leaq 8(%rsp), %rdx
400f51: be cf 25 40 00 movl $4203983, %esi
400f56: b8 00 00 00 00 movl $0, %eax
400f5b: e8 90 fc ff ff callq -880 <__isoc99_sscanf@plt>
400f60: 83 f8 01 cmpl $1, %eax
400f63: 7f 05 jg 5 <phase_3+0x27>
400f65: e8 d0 04 00 00 callq 1232 <explode_bomb>
400f6a: 83 7c 24 08 07 cmpl $7, 8(%rsp)
400f6f: 77 3c ja 60 <phase_3+0x6a>
400f71: 8b 44 24 08 movl 8(%rsp), %eax
400f75: ff 24 c5 70 24 40 00 jmpq *4203632(,%rax,8) # switch跳转表
400f7c: b8 cf 00 00 00 movl $207, %eax
400f81: eb 3b jmp 59 <phase_3+0x7b>
400f83: b8 c3 02 00 00 movl $707, %eax
400f88: eb 34 jmp 52 <phase_3+0x7b>
400f8a: b8 00 01 00 00 movl $256, %eax
400f8f: eb 2d jmp 45 <phase_3+0x7b>
400f91: b8 85 01 00 00 movl $389, %eax
400f96: eb 26 jmp 38 <phase_3+0x7b>
400f98: b8 ce 00 00 00 movl $206, %eax
400f9d: eb 1f jmp 31 <phase_3+0x7b>
400f9f: b8 aa 02 00 00 movl $682, %eax
400fa4: eb 18 jmp 24 <phase_3+0x7b>
400fa6: b8 47 01 00 00 movl $327, %eax
400fab: eb 11 jmp 17 <phase_3+0x7b>
400fad: e8 88 04 00 00 callq 1160 <explode_bomb>
400fb2: b8 00 00 00 00 movl $0, %eax
400fb7: eb 05 jmp 5 <phase_3+0x7b>
400fb9: b8 37 01 00 00 movl $311, %eax
400fbe: 3b 44 24 0c cmpl 12(%rsp), %eax
400fc2: 74 05 je 5 <phase_3+0x86>
400fc4: e8 71 04 00 00 callq 1137 <explode_bomb>
400fc9: 48 83 c4 18 addq $24, %rsp
400fcd: c3 retq

同样进行翻译,先说结论,其解释在后面。

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
phase_3 (char *input) {
int r = __isoc99_sscanf(input, 4203983, %rsp + 8, %rsp + 12);
if (r <= 1) {
explode_bomb();
}
if (*(%rsp + 8) > 7) {
goto tag_1;
}
%eax = *(%rsp + 8)
switch(%eax) {
case 0:
%eax = 207;
goto tag_2;
case 1:
%eax = 311;
goto tag_2;
case 2:
%eax = 707;
goto tag_2;
case 3:
%eax = 256;
goto tag_2;
case 4:
%eax = 389;
goto tag_2;
case 5:
%eax = 206;
goto tag_2;
case 6:
%eax = 682;
goto tag_2;
case 7:
%eax = 327;
goto tag_2;
};

tag_1:
explode_bomb();
%eax = 0;
goto tag_2;

tag_2:
if (%eax == *(%rsp + 12)) {
goto ret;
}
explode_bomb();
ret:
return;
}

首先来看下输入给__isoc99_sscanf的字符串,即“%d %d”。即从input中读取两个数,到%esp + 8和%esp + 12的位置。

根据指令jmpq *4203632(,%rax,8),以及下列一系列的操作可以判断是一个switch语句,然后根据这个地址找到switch的跳转表。找到跳转表之后就可以根据%rax的值来还原case的情况。

最后就很简单了,输入两个数,让第一个数对应的case获得的值等于第二个数。

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
000000000040100c phase_4:
40100c: 48 83 ec 18 subq $24, %rsp
401010: 48 8d 4c 24 0c leaq 12(%rsp), %rcx
401015: 48 8d 54 24 08 leaq 8(%rsp), %rdx
40101a: be cf 25 40 00 movl $4203983, %esi
40101f: b8 00 00 00 00 movl $0, %eax
401024: e8 c7 fb ff ff callq -1081 <__isoc99_sscanf@plt>
401029: 83 f8 02 cmpl $2, %eax
40102c: 75 07 jne 7 <phase_4+0x29> # 0x401035
40102e: 83 7c 24 08 0e cmpl $14, 8(%rsp)
401033: 76 05 jbe 5 <phase_4+0x2e> # 0x40103a
401035: e8 00 04 00 00 callq 1024 <explode_bomb>
40103a: ba 0e 00 00 00 movl $14, %edx
40103f: be 00 00 00 00 movl $0, %esi
401044: 8b 7c 24 08 movl 8(%rsp), %edi
401048: e8 81 ff ff ff callq -127 <func4>
40104d: 85 c0 testl %eax, %eax
40104f: 75 07 jne 7 <phase_4+0x4c> # 0x401058
401051: 83 7c 24 0c 00 cmpl $0, 12(%rsp)
401056: 74 05 je 5 <phase_4+0x51> # 0x40105d
401058: e8 dd 03 00 00 callq 989 <explode_bomb>
40105d: 48 83 c4 18 addq $24, %rsp
401061: c3 retq

解释称对应的C语言:

1
2
3
4
5
6
7
8
9
10
11
12
phase_4 (char *input) {
int r = __isoc99_sscanf(input, 4203983, %rsp + 8, %rsp + 12); // 跟phase_3一样读取两个数
if (r != 2 || *(%rsp + 8) > 14) {
explode_bomb();
}

r = func4(*(%rsp + 8), 0, 14);

if (r != 0 || *(%rsp + 12) != 0) {
explode_bomb();
}
}

看来还需要进一步关注func4的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0000000000400fce func4:
400fce: 48 83 ec 08 subq $8, %rsp
400fd2: 89 d0 movl %edx, %eax
400fd4: 29 f0 subl %esi, %eax
400fd6: 89 c1 movl %eax, %ecx
400fd8: c1 e9 1f shrl $31, %ecx
400fdb: 01 c8 addl %ecx, %eax
400fdd: d1 f8 sarl %eax
400fdf: 8d 0c 30 leal (%rax,%rsi), %ecx
400fe2: 39 f9 cmpl %edi, %ecx
400fe4: 7e 0c jle 12 <func4+0x24> # 0x400ff2
400fe6: 8d 51 ff leal -1(%rcx), %edx
400fe9: e8 e0 ff ff ff callq -32 <func4>
400fee: 01 c0 addl %eax, %eax
400ff0: eb 15 jmp 21 <func4+0x39> # 0x401007
400ff2: b8 00 00 00 00 movl $0, %eax
400ff7: 39 f9 cmpl %edi, %ecx
400ff9: 7d 0c jge 12 <func4+0x39> # 0x401007
400ffb: 8d 71 01 leal 1(%rcx), %esi
400ffe: e8 cb ff ff ff callq -53 <func4>
401003: 8d 44 00 01 leal 1(%rax,%rax), %eax
401007: 48 83 c4 08 addq $8, %rsp
40100b: c3 retq

同样对其进行解析,大致可以看出来func4做的是一个二分查找,在y和z中间查找x,并且返回搜索的次数。

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
func4(%edi, %esi, %edx) {
%eax = %edx - %esi;

%ecx = %eax >> 31; // 严格来说不能这么写,shrl代表的是逻辑右移,而不是算数右移
// 在C语言中的>>代表的是算数右移
%eax += %ecx;

%eax >>= 1; // 这里sarl就是算数右移

%ecx = %rax + %rsi; // %ecx这里就代表的是中间数mid = l + (r - l) / 2
if (%ecx <= %edi) { // if mid <= target
%eax = 0;
if (%ecx >= %edi) { // if mid == target
return %eax;
}
%esi = %rcx + 1; // l = mid + 1
%eax = func4(%edi, %esi, %edx);
%eax = %rax * 2 + 1;
} else { // if mid > target
%edx = %rcx - 1; // r = mid - 1
%eax = func4(%edi, %esi, %edx);
%eax *= 2;
}

return %eax;
}

这样就很明显了,要使炸弹不爆炸需要输入两个数,第一个数在(0, 14)之间进行二分查找的搜索次数为0,第二数要求为0。

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
40
0000000000401062 phase_5:
401062: 53 pushq %rbx
401063: 48 83 ec 20 subq $32, %rsp
401067: 48 89 fb movq %rdi, %rbx
40106a: 64 48 8b 04 25 28 00 00 00 movq %fs:40, %rax
401073: 48 89 44 24 18 movq %rax, 24(%rsp)
401078: 31 c0 xorl %eax, %eax
40107a: e8 9c 02 00 00 callq 668 <string_length>
40107f: 83 f8 06 cmpl $6, %eax
401082: 74 4e je 78 <phase_5+0x70> # 0x4010d2
401084: e8 b1 03 00 00 callq 945 <explode_bomb>
401089: eb 47 jmp 71 <phase_5+0x70> # 0x4010d2
40108b: 0f b6 0c 03 movzbl (%rbx,%rax), %ecx
40108f: 88 0c 24 movb %cl, (%rsp)
401092: 48 8b 14 24 movq (%rsp), %rdx
401096: 83 e2 0f andl $15, %edx
401099: 0f b6 92 b0 24 40 00 movzbl 4203696(%rdx), %edx
4010a0: 88 54 04 10 movb %dl, 16(%rsp,%rax)
4010a4: 48 83 c0 01 addq $1, %rax
4010a8: 48 83 f8 06 cmpq $6, %rax
4010ac: 75 dd jne -35 <phase_5+0x29> # 0x40108b
4010ae: c6 44 24 16 00 movb $0, 22(%rsp)
4010b3: be 5e 24 40 00 movl $4203614, %esi
4010b8: 48 8d 7c 24 10 leaq 16(%rsp), %rdi
4010bd: e8 76 02 00 00 callq 630 <strings_not_equal>
4010c2: 85 c0 testl %eax, %eax
4010c4: 74 13 je 19 <phase_5+0x77> # 0x4010d9
4010c6: e8 6f 03 00 00 callq 879 <explode_bomb>
4010cb: 0f 1f 44 00 00 nopl (%rax,%rax)
4010d0: eb 07 jmp 7 <phase_5+0x77> # 0x4010d9
4010d2: b8 00 00 00 00 movl $0, %eax
4010d7: eb b2 jmp -78 <phase_5+0x29> # 0x40108b
4010d9: 48 8b 44 24 18 movq 24(%rsp), %rax
4010de: 64 48 33 04 25 28 00 00 00 xorq %fs:40, %rax
4010e7: 74 05 je 5 <phase_5+0x8c> # 0x4010ee
4010e9: e8 42 fa ff ff callq -1470 <__stack_chk_fail@plt>
4010ee: 48 83 c4 20 addq $32, %rsp
4010f2: 5b popq %rbx
4010f3: c3 retq

还是C语言相较于汇编容易理解一些。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
phase_5(input) {
%rbx = input;

%eax = strlen(input);
if (%eax != 6) {
explode_bomb();
}
%eax = 0;

do {
%ecx = *(%rbx + %rax);
%edx = *((%cl & 0xf) + 4203696);
*(%rsp + %rax + 16) = %dl;
%rax++;
} while (%rax != 6);

*(%rsp + 22) = 0;
%eax = strings_not_equal(*(%rsp + 16), 4203614);

if (%eax != 0) {
explode_bomb();
}
}

这里有一部分看着很奇怪的指令组合。在stack overflow上有类似的讨论what does this instruction do?:- mov %gs:0x14,%eax。忽略它。

1
2
3
4
5
6
7
40106a: 64 48 8b 04 25 28 00 00 00   	movq	%fs:40, %rax
401073: 48 89 44 24 18 movq %rax, 24(%rsp)
401078: 31 c0 xorl %eax, %eax
4010d9: 48 8b 44 24 18 movq 24(%rsp), %rax
4010de: 64 48 33 04 25 28 00 00 00 xorq %fs:40, %rax
4010e7: 74 05 je 5 <phase_5+0x8c> # 0x4010ee
4010e9: e8 42 fa ff ff callq -1470 <__stack_chk_fail@plt>

先看一下地址4203614(0x40245e)的信息,可以得到一个字符串“flyers”。

1
2
402450 73656372 65742073 74616765 2100666c  secret stage!.fl
402460 79657273 00000000 00000000 00000000 yers............

然后看一下地址4203696(0x4024b0)的信息,可以看到一个很长的字符串,但是由于我们仅需要15个字符(偏移量最大为15),因此从中提取6个字符“maduiersnfotvbyl”。

1
2
4024b0 6d616475 69657273 6e666f74 7662796c  maduiersnfotvbyl
4024c0 536f2079 6f752074 68696e6b 20796f75 So you think you

也就是说我们要根据input的信息和字符串“maduiersnfotvbyl”进过函数内的处理得到字符串“flyers”。

首先找到所需字母分别对应的位置。{'f': 0x9, 'l': 0xf, 'y': 0xe, 'e': 0x5, 'r': 0x6, 's': 0x7}。因此我们构造的输入需要低4位分别是上述数字的字符。

用python打印出所有小写字母对应的ascii码值,根据这个结果构造输入“ionefg”。

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
In [18]: for i in range(0, 26):
...: print(f"{chr(i + ord('a'))} -> hex {hex(i + ord('a'))}")
...:
a -> hex 0x61
b -> hex 0x62
c -> hex 0x63
d -> hex 0x64
e -> hex 0x65
f -> hex 0x66
g -> hex 0x67
h -> hex 0x68
i -> hex 0x69
j -> hex 0x6a
k -> hex 0x6b
l -> hex 0x6c
m -> hex 0x6d
n -> hex 0x6e
o -> hex 0x6f
p -> hex 0x70
q -> hex 0x71
r -> hex 0x72
s -> hex 0x73
t -> hex 0x74
u -> hex 0x75
v -> hex 0x76
w -> hex 0x77
x -> hex 0x78
y -> hex 0x79
z -> hex 0x7a

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
83
84
85
86
87
00000000004010f4 phase_6:
4010f4: 41 56 pushq %r14
4010f6: 41 55 pushq %r13
4010f8: 41 54 pushq %r12
4010fa: 55 pushq %rbp
4010fb: 53 pushq %rbx
4010fc: 48 83 ec 50 subq $80, %rsp
401100: 49 89 e5 movq %rsp, %r13
401103: 48 89 e6 movq %rsp, %rsi
401106: e8 51 03 00 00 callq 849 <read_six_numbers>
40110b: 49 89 e6 movq %rsp, %r14
40110e: 41 bc 00 00 00 00 movl $0, %r12d
401114: 4c 89 ed movq %r13, %rbp
401117: 41 8b 45 00 movl (%r13), %eax
40111b: 83 e8 01 subl $1, %eax
40111e: 83 f8 05 cmpl $5, %eax
401121: 76 05 jbe 5 <phase_6+0x34> # 0x401128
401123: e8 12 03 00 00 callq 786 <explode_bomb>
401128: 41 83 c4 01 addl $1, %r12d
40112c: 41 83 fc 06 cmpl $6, %r12d
401130: 74 21 je 33 <phase_6+0x5f> # 0x401153
401132: 44 89 e3 movl %r12d, %ebx
401135: 48 63 c3 movslq %ebx, %rax
401138: 8b 04 84 movl (%rsp,%rax,4), %eax
40113b: 39 45 00 cmpl %eax, (%rbp)
40113e: 75 05 jne 5 <phase_6+0x51> # 0x401145
401140: e8 f5 02 00 00 callq 757 <explode_bomb>
401145: 83 c3 01 addl $1, %ebx
401148: 83 fb 05 cmpl $5, %ebx
40114b: 7e e8 jle -24 <phase_6+0x41> # 0x401135
40114d: 49 83 c5 04 addq $4, %r13
401151: eb c1 jmp -63 <phase_6+0x20> # 0x401114
401153: 48 8d 74 24 18 leaq 24(%rsp), %rsi
401158: 4c 89 f0 movq %r14, %rax
40115b: b9 07 00 00 00 movl $7, %ecx
401160: 89 ca movl %ecx, %edx
401162: 2b 10 subl (%rax), %edx
401164: 89 10 movl %edx, (%rax)
401166: 48 83 c0 04 addq $4, %rax
40116a: 48 39 f0 cmpq %rsi, %rax
40116d: 75 f1 jne -15 <phase_6+0x6c> # 0x401160
40116f: be 00 00 00 00 movl $0, %esi
401174: eb 21 jmp 33 <phase_6+0xa3> # 0x401197
401176: 48 8b 52 08 movq 8(%rdx), %rdx
40117a: 83 c0 01 addl $1, %eax
40117d: 39 c8 cmpl %ecx, %eax
40117f: 75 f5 jne -11 <phase_6+0x82> # 0x401176
401181: eb 05 jmp 5 <phase_6+0x94> # 0x401188
401183: ba d0 32 60 00 movl $6304464, %edx
401188: 48 89 54 74 20 movq %rdx, 32(%rsp,%rsi,2)
40118d: 48 83 c6 04 addq $4, %rsi
401191: 48 83 fe 18 cmpq $24, %rsi
401195: 74 14 je 20 <phase_6+0xb7> # 0x4011ab
401197: 8b 0c 34 movl (%rsp,%rsi), %ecx
40119a: 83 f9 01 cmpl $1, %ecx
40119d: 7e e4 jle -28 <phase_6+0x8f> # 0x401183
40119f: b8 01 00 00 00 movl $1, %eax
4011a4: ba d0 32 60 00 movl $6304464, %edx
4011a9: eb cb jmp -53 <phase_6+0x82> # 0x401176
4011ab: 48 8b 5c 24 20 movq 32(%rsp), %rbx
4011b0: 48 8d 44 24 28 leaq 40(%rsp), %rax
4011b5: 48 8d 74 24 50 leaq 80(%rsp), %rsi
4011ba: 48 89 d9 movq %rbx, %rcx
4011bd: 48 8b 10 movq (%rax), %rdx
4011c0: 48 89 51 08 movq %rdx, 8(%rcx)
4011c4: 48 83 c0 08 addq $8, %rax
4011c8: 48 39 f0 cmpq %rsi, %rax
4011cb: 74 05 je 5 <phase_6+0xde> # 0x4011d2
4011cd: 48 89 d1 movq %rdx, %rcx
4011d0: eb eb jmp -21 <phase_6+0xc9> # 0x4011bd
4011d2: 48 c7 42 08 00 00 00 00 movq $0, 8(%rdx)
4011da: bd 05 00 00 00 movl $5, %ebp
4011df: 48 8b 43 08 movq 8(%rbx), %rax
4011e3: 8b 00 movl (%rax), %eax
4011e5: 39 03 cmpl %eax, (%rbx)
4011e7: 7d 05 jge 5 <phase_6+0xfa> # 0x4011ee
4011e9: e8 4c 02 00 00 callq 588 <explode_bomb>
4011ee: 48 8b 5b 08 movq 8(%rbx), %rbx
4011f2: 83 ed 01 subl $1, %ebp
4011f5: 75 e8 jne -24 <phase_6+0xeb> # 0x4011df
4011f7: 48 83 c4 50 addq $80, %rsp
4011fb: 5b popq %rbx
4011fc: 5d popq %rbp
4011fd: 41 5c popq %r12
4011ff: 41 5d popq %r13
401201: 41 5e popq %r14
401203: c3 retq

手工解析成C语言代码

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
90
91
92
93
94
95
96
97
phase_6(input) {
%r13 = %rsp; // 0x7fffffffea60
read_six_numbers(input, %rsp);
%r14 = %rsp; // 0x7fffffffea60

%r12d = 0;
while (true) {
%rbp = %r13;

%eax = *(%r13); // 第一个数字
%eax--;

if (%eax > 5) {
explode_bomb();
}

%r12d++;

if (%r12d == 6) {
break;
}

%ebx = %r12d;
do {
%rax = %ebx;
%eax = *(%rsp + %rax * 4);
if (*(%rbp) == %eax) {
explode_bomb();
}
%ebx++;
} while (%ebx <= 5);

%r13 += 4;
}
/* 直到此处,可以看出,要求这六个数都小于等于6,并且它们各不相同 */

%rsi = *(%rsp + 24);
%rax = %r14;
%ecx = 7;

do {
%edx = %ecx - *(%rax);
*(%rax) = %edx;
%rax += 4;
} while (%rax != %rsi);
/* 将六个数修改为与7的差值 */

%esi = 0;

do {
%ecx = *(%rsp + %rsi);
if (%ecx <= 1) {
%edx = 6304464; // 这一块的解析在下面
} else {
%eax = 1;
%edx = 6304464;
do {
%rdx = *(%rdx + 8);
%eax++;
} while (%eax != %ecx);
/* 从6304464逐渐往后遍历,p = p->next */
}

*(%rsp + %rsi * 2 + 32) = %rdx; /* 将链表的value存放在 %rsp + 32的位置,每个数占8字节 */
%rsi += 4;
} while (%rsi != 24);
/* 这里就是根据那六个数字来遍历链表,并将链表节点的地址存储在栈中 */

%rbx = *(%rsp + 32); /* 从链表节点数组中取出的六个值的起点值 */
%rax = %rsp + 40; /* %rsp + 4 * 2 + 32 */
%rsi = %rsp + 80;
%rcx = %rbx;
while (true) {
%rdx = *(%rax);
*(%rcx + 8) = %rdx;
%rax += 8;
if (%rax == %rsi) {
break;
}
%rcx = %rdx;
}
/* 此处在把这6个链表节点连接起来 */

*(%rdx + 8) = 0; /* 设置最后一个节点的next为p->next = NULL */
%ebp = 5;
do {
%rax = *(%rbx + 8); /* rbx的初值还是节点数组的第一个值 */
%eax = *(%rax);
if (*(%rbx) < %eax) { /* 如果这个新链表的下一个节点的值比当前大则爆炸 */
explode_bomb(); /* 因为使用指令都是l结尾的,因此把它当作一个双字整数,即long */
}
%rbx = *(%rbx + 8);
%ebp--;
} while (*(%rbx) != %eax);

return;
}

之前的常量都在rodata区,但是6304464这个地址却是data区的,通过gdb将其打印出来。结合%rdx = *(%rdx + 8);可以看出每一个该位置的值,都是下一个位置的地址。大概可以猜测此处是一个类似链表的结构。

1
2
3
4
5
6
7
(gdb) x/24xw 6304464
0x6032d0 <node1>: 0x0000014c 0x00000001 0x006032e0 0x00000000
0x6032e0 <node2>: 0x000000a8 0x00000002 0x006032f0 0x00000000
0x6032f0 <node3>: 0x0000039c 0x00000003 0x00603300 0x00000000
0x603300 <node4>: 0x000002b3 0x00000004 0x00603310 0x00000000
0x603310 <node5>: 0x000001dd 0x00000005 0x00603320 0x00000000
0x603320 <node6>: 0x000001bb 0x00000006 0x00000000 0x00000000

经过分析,其实代码流程已经很清楚了。从input读取六个数,保证它们均小于等于6,并且各不相同;然后将他们的值设为与7的差值;接着根据处理后的这六个数,对链表节点进行重排序,保证新的链表是一个非严格的递减序列。

从上述链表节点的值可以看出,构造的新链表的顺序为3,4,5,6,1,2。因此对应的原六个数分别是4,3,2,1,6,5。

至此拆弹完成,但是真的吗?根据bomb.c最后部分的内容以及汇编代码中看到fun7和secret_phase可以猜测,还有一个隐藏关。

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
0000000000401242 secret_phase:
401242: 53 pushq %rbx
401243: e8 56 02 00 00 callq 598 <read_line>
401248: ba 0a 00 00 00 movl $10, %edx
40124d: be 00 00 00 00 movl $0, %esi
401252: 48 89 c7 movq %rax, %rdi
401255: e8 76 f9 ff ff callq -1674 <strtol@plt>
40125a: 48 89 c3 movq %rax, %rbx
40125d: 8d 40 ff leal -1(%rax), %eax
401260: 3d e8 03 00 00 cmpl $1000, %eax
401265: 76 05 jbe 5 <secret_phase+0x2a> # 0x40126c
401267: e8 ce 01 00 00 callq 462 <explode_bomb>
40126c: 89 de movl %ebx, %esi
40126e: bf f0 30 60 00 movl $6303984, %edi
401273: e8 8c ff ff ff callq -116 <fun7>
401278: 83 f8 02 cmpl $2, %eax
40127b: 74 05 je 5 <secret_phase+0x40> # 0x401282
40127d: e8 b8 01 00 00 callq 440 <explode_bomb>
401282: bf 38 24 40 00 movl $4203576, %edi
401287: e8 84 f8 ff ff callq -1916 <puts@plt>
40128c: e8 33 03 00 00 callq 819 <phase_defused>
401291: 5b popq %rbx
401292: c3 retq

对其进行分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
secret_phase(input) {
%rax = strtol(input, 0, 10); // long int strtol(const char *str, char **endptr, int base)
%rbx = %rax;
%eax = %rax - 1;

if (%rax > 1000) {
explode_bomb();
}

%eax = fun7(6303984, %ebx);
if (%eax != 2) {
explode_bomb();
}

puts(4203576);
phase_defused();
}

可以看得出,从字符串中读取一个数,保证其小于等于1001。然后放入fun7函数处理,要使得fun7的返回值为2。

进一步关注fun7的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0000000000401204 fun7:
401204: 48 83 ec 08 subq $8, %rsp
401208: 48 85 ff testq %rdi, %rdi
40120b: 74 2b je 43 <fun7+0x34> # 0x401238
40120d: 8b 17 movl (%rdi), %edx
40120f: 39 f2 cmpl %esi, %edx
401211: 7e 0d jle 13 <fun7+0x1c> # 0x401220
401213: 48 8b 7f 08 movq 8(%rdi), %rdi
401217: e8 e8 ff ff ff callq -24 <fun7>
40121c: 01 c0 addl %eax, %eax
40121e: eb 1d jmp 29 <fun7+0x39> # 0x40123d
401220: b8 00 00 00 00 movl $0, %eax
401225: 39 f2 cmpl %esi, %edx
401227: 74 14 je 20 <fun7+0x39> # 0x40123d
401229: 48 8b 7f 10 movq 16(%rdi), %rdi
40122d: e8 d2 ff ff ff callq -46 <fun7>
401232: 8d 44 00 01 leal 1(%rax,%rax), %eax
401236: eb 05 jmp 5 <fun7+0x39> # 0x40123d
401238: b8 ff ff ff ff movl $4294967295, %eax
40123d: 48 83 c4 08 addq $8, %rsp
401241: c3 retq

对其进行解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun7(void *p, int x) {
int r;
if (p == NULL) {
return 0xffffffff;
}

if (p->value <= x) {
r = 0;
if (p->value == x) {
return r;
}
p = p->right;
r = fun7(p, x);
r = 2 * r + 1;
} else {
p = p->left;
r = fun7(p, x);
r *= 2;
}

return r;
}

可以看得出fun7的主要功能是在链表中搜索x,并返回搜索的次数。

地址6303984处的部分值如下。从这个值分布可以看出,这应该是一个二叉树的节点,一个节点里面包含一个value,以及两个地址。

1
2
3
4
5
6
7
8
9
10
11
(gdb) x/40xw 0x6030f0
0x6030f0 <n1>: 0x00000024 0x00000000 0x00603110 0x00000000
0x603100 <n1+16>: 0x00603130 0x00000000 0x00000000 0x00000000
0x603110 <n21>: 0x00000008 0x00000000 0x00603190 0x00000000
0x603120 <n21+16>: 0x00603150 0x00000000 0x00000000 0x00000000
0x603130 <n22>: 0x00000032 0x00000000 0x00603170 0x00000000
0x603140 <n22+16>: 0x006031b0 0x00000000 0x00000000 0x00000000
0x603150 <n32>: 0x00000016 0x00000000 0x00603270 0x00000000
0x603160 <n32+16>: 0x00603230 0x00000000 0x00000000 0x00000000
0x603170 <n33>: 0x0000002d 0x00000000 0x006031d0 0x00000000
0x603180 <n33+16>: 0x00603290 0x00000000 0x00000000 0x00000000

需求都已经很明晰了,构造这么一个特殊的输入即可。

我们已经知道了隐藏关的通过值,但是进入隐藏关的方法还需要进一步分析。通过搜索,可以得知,在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
00000000004015c4 phase_defused:
4015c4: 48 83 ec 78 subq $120, %rsp
4015c8: 64 48 8b 04 25 28 00 00 00 movq %fs:40, %rax
4015d1: 48 89 44 24 68 movq %rax, 104(%rsp)
4015d6: 31 c0 xorl %eax, %eax
4015d8: 83 3d 81 21 20 00 06 cmpl $6, 2105729(%rip)
4015df: 75 5e jne 94 <phase_defused+0x7b> # 0x40163f
4015e1: 4c 8d 44 24 10 leaq 16(%rsp), %r8
4015e6: 48 8d 4c 24 0c leaq 12(%rsp), %rcx
4015eb: 48 8d 54 24 08 leaq 8(%rsp), %rdx
4015f0: be 19 26 40 00 movl $4204057, %esi # "%d %d %s"
4015f5: bf 70 38 60 00 movl $6305904, %edi
4015fa: e8 f1 f5 ff ff callq -2575 <__isoc99_sscanf@plt>
4015ff: 83 f8 03 cmpl $3, %eax
401602: 75 31 jne 49 <phase_defused+0x71> # 0x401635
401604: be 22 26 40 00 movl $4204066, %esi # "DrEvil"
401609: 48 8d 7c 24 10 leaq 16(%rsp), %rdi
40160e: e8 25 fd ff ff callq -731 <strings_not_equal>
401613: 85 c0 testl %eax, %eax
401615: 75 1e jne 30 <phase_defused+0x71> # 0x401635
401617: bf f8 24 40 00 movl $4203768, %edi
40161c: e8 ef f4 ff ff callq -2833 <puts@plt>
401621: bf 20 25 40 00 movl $4203808, %edi
401626: e8 e5 f4 ff ff callq -2843 <puts@plt>
40162b: b8 00 00 00 00 movl $0, %eax
401630: e8 0d fc ff ff callq -1011 <secret_phase>
401635: bf 58 25 40 00 movl $4203864, %edi
40163a: e8 d1 f4 ff ff callq -2863 <puts@plt>
40163f: 48 8b 44 24 68 movq 104(%rsp), %rax
401644: 64 48 33 04 25 28 00 00 00 xorq %fs:40, %rax
40164d: 74 05 je 5 <phase_defused+0x90> # 0x401654
40164f: e8 dc f4 ff ff callq -2852 <__stack_chk_fail@plt>
401654: 48 83 c4 78 addq $120, %rsp
401658: c3 retq

这个就不翻了,逻辑挺简单的,从6305904的位置中读取两个数和一个字符串,然后将字符串与4204066地址的串进行比较,如果通过了就进入secret_phase。

因此关键的是6305904的位置是哪里。通过gdb查看该部分的内存,可以猜测,这可能是input的某个部分。

1
2
3
(gdb) x/16xb 6305904
0x603870 <input_strings+240>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x603878 <input_strings+248>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

通过gdb的watch功能,来设置一个内存地址的监测点。通过gdb的输出,可以猜测是phase 4的地方操作了这个地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(gdb) watch *6305904
Hardware watchpoint 2: *6305904
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/bingo/Workdir/bomb/bomb input.file
Welcome to my fiendish little bomb. You have 6 phases with
which to blow yourself up. Have a nice day!
Phase 1 defused. How about the next one?
That's number 2. Keep going!
Halfway there!

Hardware watchpoint 2: *6305904

Old value = 0
New value = 170926135
__memcpy_sse2 () at ../sysdeps/x86_64/multiarch/../memcpy.S:98
98 ../sysdeps/x86_64/multiarch/../memcpy.S: No such file or directory.

进一步进行单步调试,在phase 4的阶段调用read_line函数,操作了地址6305904。

至此,已经可以知道进入隐藏关的方法,以及隐藏关的通关指令。

Tips

GDB工具

如果自己的输入有问题,或者说查看各个函数输入的值,可以试试gdb工具进行反汇编调试。

相关命令参考文档GDB调试命令(二)—反汇编相关

ps:上面的题其实都可以用gdb来“作弊”,因为大多都涉及比较,可以直接通过设置断点来查看寄存器的值。

答案

  1. Border relations with Canada have never been better.
  2. 1 2 4 8 16 32
  3. 0 207
  4. 7 0 DrEvil
  5. ionefg
  6. 4 3 2 1 6 5
  7. 22
Author

Chaos Chen

Posted on

2021-04-12

Updated on

2023-06-30

Licensed under

Commentaires