计算机系统基础实验2——拆解炸弹

实验二:拆解炸弹实验

要想拆解炸弹我们首先需要知道bomb程序是如何工作的,所以我们先通过 objdump -d bomb >bomb.s 指令来得到程序的汇编代码。

由于这关需要查看大量的汇编代码,同时需要通过查找在很多函数中来回跳转,通过原有的方式登录服务器的方式并不方便,所以通过vscode进行远程连接:

1730367583566

通过vscode连接到远端服务器后,可以方便的查看汇编代码,同时添加注释也更加方便快捷。左侧显示目录下的文件,下方时命令的输入窗口,中间则显示打开的文件内容。

第一关

我们先阅读bomb.c中的内容。找到第一关的函数名:phase_1

1
2
3
4
5
6
7

input = read_line(); /* Get input */

phase_1(input); /* Run the phase */

phase_defused();

在vscode中同时按 ctrl +F 开启查找搜索框

1730367839519

找到第一关的汇编代码。

我们对第一关的汇编代码添加注释,方便我们理解这一关的步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

0000000000400f2d <phase_1>:

400f2d: 48 83 ec 08 sub $0x8,%rsp //%rsp-=8

400f31: be 80 26 40 00 mov $0x402680,%esi //将地址$0x402680赋给%esi

400f36: e8 99 04 00 00 call 4013d4 <strings_not_equal> //调用函数strings_not_equal

400f3b: 85 c0 test %eax,%eax //检测%eax是否为0

400f3d: 74 05 je 400f44 <phase_1+0x17> //如果相等则跳转 400f44

400f3f: e8 64 07 00 00 call 4016a8 <explode_bomb> //引爆炸弹

400f44: 48 83 c4 08 add $0x8,%rsp //%rsp+=8

400f48: c3 ret

这里第一关中,又调用了 strings_not_equal函数,那么我们在查找 strings_not_equal

函数看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

00000000004013d4 <strings_not_equal>:

4013d4: 41 54 push %r12

4013d6: 55 push %rbp

4013d7: 53 push %rbx

4013d8: 48 89 fb mov %rdi,%rbx

4013db: 48 89 f5 mov %rsi,%rbp

4013de: e8 d3 ff ff ff call 4013b6 <string_length>

4013e3: 41 89 c4 mov %eax,%r12d

4013e6: 48 89 ef mov %rbp,%rdi

4013e9: e8 c8 ff ff ff call 4013b6 <string_length>

这里我们先看一下这个函数的前半部分,我们可以发现这个函数有两个参数,第一关参数存放在 %rdi中,第二个参数存放在 %rsi中,结合函数的名字可得,这个函数的功能大概是比较输入字符串和一个固定字符串是否相等。

我们可以在地址 4013d8 上设置一个断点来看一下:

通过 gdb bomb 来调试bomb

1
2
3

break *(0x4013d8)

然后我们运行:

1730369281458

这里我们先随便输入一个字符串,不用在意。

1730380971842

分别打印两个寄存器中内存地址中的内容,可以看到一个字符串是我们自己输入的字符串,另一个字符串就是对比的字符串,也就是这一题的答案。

1730383509616

输入这串字符后,第一关就通关啦。

第二关

我们同样先找到 phase_2 函数的汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

0000000000400f49 <phase_2>:

400f49: 55 push %rbp //将寄存器值入栈

400f4a: 53 push %rbx //将寄存器值入栈

400f4b: 48 83 ec 28 sub $0x28,%rsp //栈指针减去40

400f4f: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax //%fs中的基地址偏移40个字节的位置保存在%rax中

400f56: 00 00

400f58: 48 89 44 24 18 mov %rax,0x18(%rsp) //把%rax中的值保存在栈指针偏移24的位置

400f5d: 31 c0 xor %eax,%eax //%eax = 0

400f5f: 48 89 e6 mov %rsp,%rsi //%rsp的值移到%rsi

400f62: e8 77 07 00 00 call 4016de <read_six_numbers>

这里我们先看调用 read_six_numbers之前的内容,这里在调用函数之前,先给栈开辟了一块40字节的空间,然后将栈顶指针当作函数参数传递给了 %rsi。我们可以知道读取的6个数应该是存在了栈中

然后我们看看读取了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

400f67: 83 3c 24 00 cmpl $0x0,(%rsp) //将栈顶元素与0进行比较

400f6b: 79 05 jns 400f72 <phase_2+0x29> //如果(%rsp)-0 大于0,则跳转 400f72

400f6d: e8 36 07 00 00 call 4016a8 <explode_bomb> //若未跳转则爆炸

400f72: 48 89 e5 mov %rsp,%rbp //将栈顶指针赋给%rbp

400f75: bb 01 00 00 00 mov $0x1,%ebx //令%ebx = 1

400f7a: 89 d8 mov %ebx,%eax //令%ebx = %eax

400f7c: 03 45 00 add 0x0(%rbp),%eax //令%eax += (%rbp)

400f7f: 39 45 04 cmp %eax,0x4(%rbp) //令比较%eax 和 (%rbp)的下一个数

400f82: 74 05 je 400f89 <phase_2+0x40> //如果相等就跳转400f89

400f84: e8 1f 07 00 00 call 4016a8 <explode_bomb> //不等就会爆炸

400f89: 83 c3 01 add $0x1,%ebx //%ebx+=1

400f8c: 48 83 c5 04 add $0x4,%rbp //让%rbp指向下一个数字

400f90: 83 fb 06 cmp $0x6,%ebx //比较%ebx和6

400f93: 75 e5 jne 400f7a <phase_2+0x31> //不等就跳转(判断是否进行6次判断)

我们根据这段代码的逻辑可以发现这6个数字有以下要求

  • 第一个数字要大于0
  • a2-a1 = 1,a3-a2 = 2,a4-a3 = 3,a5-a4 = 4,a6-a5 = 5

既然已经知道这6个数字的要求,那么我们接下来看一下 read_six_numbers的内容:

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

00000000004016de <read_six_numbers>:

4016de: 48 83 ec 08 sub $0x8,%rsp // 栈指针减8,分配栈空间

4016e2: 48 89 f2 mov %rsi,%rdx // %rdx = %rsi(指向第一个数的存储位置)

4016e5: 48 8d 4e 04 lea 0x4(%rsi),%rcx // %rcx = %rsi + 4(第二个数)

4016e9: 48 8d 46 14 lea 0x14(%rsi),%rax // %rax = %rsi + 20(第六个数)

4016ed: 50 push %rax // 将第六个数的地址压栈(第8个参数)

4016ee: 48 8d 46 10 lea 0x10(%rsi),%rax // %rax = %rsi + 16(第五个数)

4016f2: 50 push %rax // 将第五个数的地址压栈(第7个参数)

4016f3: 4c 8d 4e 0c lea 0xc(%rsi),%r9 // %r9 = %rsi + 12(第四个数)

4016f7: 4c 8d 46 08 lea 0x8(%rsi),%r8 // %r8 = %rsi + 8(第三个数)

4016fb: be 71 29 40 00 mov $0x402971,%esi // %esi = 格式化字符串地址

401700: b8 00 00 00 00 mov $0x0,%eax // 初始化%eax = 0

401705: e8 36 f5 ff ff call 400c40 <__isoc99_sscanf@plt>

40170a: 48 83 c4 10 add $0x10,%rsp // 恢复栈指针

40170e: 83 f8 05 cmp $0x5,%eax // 判断是否成功读取6个数

401711: 7f 05 jg 401718 <read_six_numbers+0x3a> // 如果大于5则继续

401713: e8 90 ff ff ff call 4016a8 <explode_bomb> // 否则引爆炸弹

401718: 48 83 c4 08 add $0x8,%rsp // 恢复栈指针

40171c: c3 ret // 返回

// 读取的6个数在栈中的顺序:

// 数字1 -> 地址 %rsi

// 数字2 -> 地址 %rsi + 4

// 数字3 -> 地址 %rsi + 8

// 数字4 -> 地址 %rsi + 12

// 数字5 -> 地址 %rsi + 16

// 数字6 -> 地址 %rsi + 20

这里我们根据每个寄存器作为第几个参数以及其中对应的地址,我们可以得到6个数字是按照输入顺序存储的在,所以这里无需额外注意。

这里我们需要查看以下地址 $0x402971上的内容,这个字符串规定了6个数字的输入格式。

我们输入代码 p (char*) 0x402971

1730386841495

所以我们按照规定格式输入满足要求的数字: 1 2 4 7 11 16

1730386937017

这样我们就顺利通过第二关啦。

第三关

1730791784748

我们从而得知这关需要我们输入两个正整数。

然后我们就可以分析一下第三关的汇编代码:

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
98
99
100
101
102
103
104
105
106
107
108
109

0000000000400fb1 <phase_3>:

400fb1: 48 83 ec 18 sub $0x18,%rsp //栈指针减24

400fb5: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax //%fs中的基地址偏移40个字节的位置保存在%rax中

400fbc: 00 00

400fbe: 48 89 44 24 08 mov %rax,0x8(%rsp) //(%rsp+8) = %rax(栈中的第三个值)

400fc3: 31 c0 xor %eax,%eax //%eax^=%eax(%eax = 0)

400fc5: 48 8d 4c 24 04 lea 0x4(%rsp),%rcx //%rcx = %rsp+4 (第4个参数)(第2个读取的数的位置在栈的第2个位置)

400fca: 48 89 e2 mov %rsp,%rdx //%rdx = %rsp (作为第3个传入参数)(第1个读取的数的位置在栈的第1个位)

400fcd: be 7d 29 40 00 mov $0x40297d,%esi //%esi = 0x40297(输入格式字符串的地址)(第2个参数)

400fd2: e8 69 fc ff ff call 400c40 <__isoc99_sscanf@plt>//按照格式进行输入(%d %d)

400fd7: 83 f8 01 cmp $0x1,%eax //比较%eax和1的大小(检测是否成功读取两个数字)

400fda: 7f 05 jg 400fe1 <phase_3+0x30> //如果有符号大于则跳转到400fe1

400fdc: e8 c7 06 00 00 call 4016a8 <explode_bomb> //如果小于则爆照

400fe1: 83 3c 24 07 cmpl $0x7,(%rsp) //比较*(%rsp)的值和7的大小(0<=d1<=7)

400fe5: 77 63 ja 40104a <phase_3+0x99> //无符号大于则跳转40104a(会爆炸)

400fe7: 8b 04 24 mov (%rsp),%eax //%exa = *(%rsp) //%eax = d1

400fea: ff 24 c5 e0 26 40 00 jmp *0x4026e0(,%rax,8) //无条件跳转到*(0x4026e0+%rax*8)的位置(d1 = 0时不跳转)

400ff1: b8 0f 03 00 00 mov $0x30f,%eax //%eax = $0x30f

400ff6: eb 05 jmp 400ffd <phase_3+0x4c>//直接跳转到:400ffd

400ff8: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 1时跳转到这里)

400ffd: 83 e8 77 sub $0x77,%eax //%eax-=119

401000: eb 05 jmp 401007 <phase_3+0x56>//直接跳转到:401007

401002: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 2时跳转到这里)

401007: 05 95 02 00 00 add $0x295,%eax //%eax+=661

40100c: eb 05 jmp 401013 <phase_3+0x62>//直接跳转到:401013

40100e: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 3时跳转到这里)

401013: 2d bc 01 00 00 sub $0x1bc,%eax //%eax-=444

401018: eb 05 jmp 40101f <phase_3+0x6e>//直接跳转到:40101f

40101a: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 4时跳转到这里)

40101f: 05 bc 01 00 00 add $0x1bc,%eax //%eax+=444

401024: eb 05 jmp 40102b <phase_3+0x7a>//直接跳转到:40102b

401026: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 5时跳转到这里)

40102b: 2d bc 01 00 00 sub $0x1bc,%eax //%eax-=444

401030: eb 05 jmp 401037 <phase_3+0x86>//直接跳转到:401037

401032: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 6时跳转到这里)

401037: 05 bc 01 00 00 add $0x1bc,%eax //%eax+=444

40103c: eb 05 jmp 401043 <phase_3+0x92>//直接跳转到:401043

40103e: b8 00 00 00 00 mov $0x0,%eax //%eax = 0(暂时看来无效)(d1 = 7时跳转到这里)

401043: 2d bc 01 00 00 sub $0x1bc,%eax //%eax-=444

401048: eb 0a jmp 401054 <phase_3+0xa3>//直接跳转到:401054

40104a: e8 59 06 00 00 call 4016a8 <explode_bomb> //

40104f: b8 00 00 00 00 mov $0x0,%eax//%eax = 0(暂时看来无效)

401054: 83 3c 24 05 cmpl $0x5,(%rsp) //比较栈顶的值和5的大小(b1<=5)

401058: 7f 06 jg 401060 <phase_3+0xaf>//有符号数更大则跳转(爆炸)

40105a: 3b 44 24 04 cmp 0x4(%rsp),%eax //比d2和%eax的大小

40105e: 74 05 je 401065 <phase_3+0xb4> //相等则跳转(避免爆炸)

401060: e8 43 06 00 00 call 4016a8 <explode_bomb>

401065: 48 8b 44 24 08 mov 0x8(%rsp),%rax //将栈中第三个值赋给%rax

40106a: 64 48 33 04 25 28 00 xor %fs:0x28,%rax //%rax的值和%fs中的基地址偏移40个字节的位置的值异或

401071: 00 00

401073: 74 05 je 40107a <phase_3+0xc9> //如果相等就跳转

401075: e8 16 fb ff ff call 400b90 <__stack_chk_fail@plt>

40107a: 48 83 c4 18 add $0x18,%rsp //%rsp+=24,恢复栈的大小

40107e: c3 ret

第三关需要注意的是:当d1不同的时候,我们的程序会跳转到不同的地方,就需要不同的d2的值。注意在函数的最后,还限制了第一个数必须小于等于5,所以6和7虽然能通过前面的验证,但是过不了后面的检测。

d1可能的情况一共有5种,对应的正确的d2如下:

  • 0 881
  • 1 98
  • 2 217
  • 3 -444
  • 4 0
  • 5 -444

只要输入其中任一一组值,就可以通过第三关。每一个答案都经过验证(甚至在验证的过程种不小心爆了一次)。

第四关

我们同样首先来看第四关的汇编代码:

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

00000000004010b2 <phase_4>:

4010b2: 48 83 ec 18 sub $0x18,%rsp //%rsp-=24

4010b6: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax

4010bd: 00 00

4010bf: 48 89 44 24 08 mov %rax,0x8(%rsp) //%rax放在(%rsp+8)的地址上

4010c4: 31 c0 xor %eax,%eax //令%eax = 0

4010c6: 48 8d 4c 24 04 lea 0x4(%rsp),%rcx //%rcx = %rsp+4 (第四个参数)

4010cb: 48 89 e2 mov %rsp,%rdx //%rdx = %rsp (第三个参数)

4010ce: be 7d 29 40 00 mov $0x40297d,%esi //%esi = 0x40297d (第二个参数)(%d %d)

4010d3: e8 68 fb ff ff call 400c40 <__isoc99_sscanf@plt> //进行输入

4010d8: 83 f8 02 cmp $0x2,%eax //是否正确读入两个数据

4010db: 75 06 jne 4010e3 <phase_4+0x31> //如果不相等就跳转(爆炸)

4010dd: 83 3c 24 0e cmpl $0xe,(%rsp) //比较d1和和14的大小 (d1 <= 14)

4010e1: 76 05 jbe 4010e8 <phase_4+0x36> //如果无符号小于或等于就跳转(避免爆炸)

4010e3: e8 c0 05 00 00 call 4016a8 <explode_bomb>

4010e8: ba 0e 00 00 00 mov $0xe,%edx //%edx = 14 (第三个参数 = 14)

4010ed: be 00 00 00 00 mov $0x0,%esi //%esi = 0 (第二个参数 = 0)

4010f2: 8b 3c 24 mov (%rsp),%edi //%edi = d1 (第一个参数 = d1)

4010f5: e8 85 ff ff ff call 40107f <func4> //调用函数func4

4010fa: 83 f8 07 cmp $0x7,%eax //比较返回值和7的大小(返回值 == 7)

4010fd: 75 07 jne 401106 <phase_4+0x54> //如果不相等跳转401106,爆炸

4010ff: 83 7c 24 04 07 cmpl $0x7,0x4(%rsp) //比较b2和7的大小(b2 == 7)

401104: 74 05 je 40110b <phase_4+0x59> //相等跳转(避免爆炸)

401106: e8 9d 05 00 00 call 4016a8 <explode_bomb>

40110b: 48 8b 44 24 08 mov 0x8(%rsp),%rax //

401110: 64 48 33 04 25 28 00 xor %fs:0x28,%rax

401117: 00 00

401119: 74 05 je 401120 <phase_4+0x6e>

40111b: e8 70 fa ff ff call 400b90 <__stack_chk_fail@plt>

401120: 48 83 c4 18 add $0x18,%rsp

401124: c3 ret

这里输入的方式和第三关的地址是一样的,所以这一关照样是读取两个整数。

通过阅读汇编代码,我们发现第四关中还调用了一个func4函数,并且这个函数有三个参数,分别为

  • %edi = d1(我们输入的第一个数)
  • %esi = 0
  • %edx = 14

同时,func4还有一个返回值,返回值的结果需要等于7

我们输入的第二个数字b2也需要等于7

就可以通过这一关了。

弄清了这一点,我们再来看func4的汇编代码:

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

000000000040107f <func4>:

40107f: 53 push %rbx

401080: 89 d0 mov %edx,%eax //%eax = %edx = 14

401082: 29 f0 sub %esi,%eax //%eax-= %esi = 14

401084: 89 c3 mov %eax,%ebx //%ebx = %eax = 14

401086: c1 eb 1f shr $0x1f,%ebx //%ebx >> 31(%ebx = 0)

401089: 01 d8 add %ebx,%eax //%eax += %ebx (=14)

40108b: d1 f8 sar $1,%eax //逻辑右移1%eax/=2 = 7

40108d: 8d 1c 30 lea (%rax,%rsi,1),%ebx //%ebx = %rax+ %rsi = 7

401090: 39 fb cmp %edi,%ebx //比较%edi(d1)和%ebx(7)

401092: 7e 0c jle 4010a0 <func4+0x21> //有符号小于等于就跳转4010a0 (d1 <= 14)

401094: 8d 53 ff lea -0x1(%rbx),%edx //%edx = %rbx-1

401097: e8 e3 ff ff ff call 40107f <func4> //再次调用func4

40109c: 01 d8 add %ebx,%eax //%eax+=%ebx

40109e: eb 10 jmp 4010b0 <func4+0x31> //跳转4010b0

4010a0: 89 d8 mov %ebx,%eax //%eax = %ebx(7)

4010a2: 39 fb cmp %edi,%ebx //比较%edi(d1)和%ebx(7)

4010a4: 7d 0a jge 4010b0 <func4+0x31> //有符号大于等于就跳转4010b0

4010a6: 8d 73 01 lea 0x1(%rbx),%esi //%esi = %rbx+1

4010a9: e8 d1 ff ff ff call 40107f <func4> //调用func4

4010ae: 01 d8 add %ebx,%eax //%eax+=%ebx

4010b0: 5b pop %rbx

4010b1: c3

在分析的中途,我发现func4中有两次调用了函数自己,这会让分析变的非常复杂,好在我们发现只要d1满足的一定条件,我们就可以跳过再次调用func4。要满足的条件如下:

  • d1 <= 7
  • d1 >=7

只要满足这两个个条件,我们就可以让两次条件跳转都进行跳转,从而避免再次调用func4

我们可以化简func4代码如下:

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

000000000040107f <func4>:

40107f: 53 push %rbx

401080: 89 d0 mov %edx,%eax //%eax = %edx = 14

401082: 29 f0 sub %esi,%eax //%eax-= %esi = 14

401084: 89 c3 mov %eax,%ebx //%ebx = %eax = 14

401086: c1 eb 1f shr $0x1f,%ebx //%ebx >> 31(%ebx = 0)

401089: 01 d8 add %ebx,%eax //%eax += %ebx (=14)

40108b: d1 f8 sar $1,%eax //逻辑右移1%eax/=2 = 7

40108d: 8d 1c 30 lea (%rax,%rsi,1),%ebx //%ebx = %rax+ %rsi = 7

4010a0: 89 d8 mov %ebx,%eax //%eax = %ebx(7)

4010b0: 5b pop %rbx

4010b1: c3 ret

此时,返回值为%eax = %ebx = 7

满足第四关中对于函数返回值的要求,所以这一关的一个答案就是 7 7

第五关

我们直接看第五关的汇编代码:

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

0000000000401125 <phase_5>:

401125: 53 push %rbx

401126: 48 83 ec 10 sub $0x10,%rsp //栈指针减16

40112a: 48 89 fb mov %rdi,%rbx //%rbx = %rdi(传进来的第一个参数)

40112d: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax

401134: 00 00

401136: 48 89 44 24 08 mov %rax,0x8(%rsp)//(%rsp+8) = %rax

40113b: 31 c0 xor %eax,%eax//%eax = 0

40113d: e8 74 02 00 00 call 4013b6 <string_length>//调用string_length函数

401142: 83 f8 06 cmp $0x6,%eax//比较%eax和6的大小(字符串长度为6

401145: 74 05 je 40114c <phase_5+0x27>//相等跳转,避免爆炸

401147: e8 5c 05 00 00 call 4016a8 <explode_bomb>

40114c: b8 00 00 00 00 mov $0x0,%eax//%eax = 0

401151: 0f b6 14 03 movzbl (%rbx,%rax,1),%edx//%edx = %rbx+%rax(%edx = Si)

401155: 83 e2 0f and $0xf,%edx //%edx &=15(保留前4位)(%edx = Si & 15)

401158: 0f b6 92 20 27 40 00 movzbl 0x402720(%rdx),%edx //%edx = 0x402720+%edx

40115f: 88 14 04 mov %dl,(%rsp,%rax,1) //(%rsp+%rax) = %edx

401162: 48 83 c0 01 add $0x1,%rax //%rax+=1

401166: 48 83 f8 06 cmp $0x6,%rax //比较%rax和6的大小

40116a: 75 e5 jne 401151 <phase_5+0x2c>不等就跳转401151

40116c: c6 44 24 06 00 movb $0x0,0x6(%rsp) //(%rsp+6) = 0

401171: be d6 26 40 00 mov $0x4026d6,%esi //%esi = 0x4026d6

401176: 48 89 e7 mov %rsp,%rdi //%rdi = %rsi

401179: e8 56 02 00 00 call 4013d4 <strings_not_equal>//比较字符串是否相等

40117e: 85 c0 test %eax,%eax //%eax是否等于0

401180: 74 05 je 401187 <phase_5+0x62> //等于则跳转,避免爆炸

401182: e8 21 05 00 00 call 4016a8 <explode_bomb>

401187: 48 8b 44 24 08 mov 0x8(%rsp),%rax

40118c: 64 48 33 04 25 28 00 xor %fs:0x28,%rax

401193: 00 00

401195: 74 05 je 40119c <phase_5+0x77>

401197: e8 f4 f9 ff ff call 400b90 <__stack_chk_fail@plt>

40119c: 48 83 c4 10 add $0x10,%rsp

4011a0: 5b pop %rbx

4011a1: c3 ret

这一关的代码要我们输入长度为6的字符串。其中,这个函数给出了两个固定的地址,我们都先打印出来看看:

  • 0x402720的串为:maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?
  • 0x4026d6的串为:bruin

我们阅读汇编代码可以得到如下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


intphase_5(char str[6])

{

charans[6];

char str2[100] ="maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?";

char str3[10] ="bruins";

for(int i =0;i<6;i++)

{

ans[i] =str2[(int)str[i] &15];

}

if(strings_not_equal(ans,str3))

{

printf("BOOM!!!\n");

return0;

}

return1;

}

所以我们需要从str2中找到前15个字符中的b r u i n s 每个字母的下标,并且给每个下标+=48,再从ascall码表中把每个数字翻译为字符,按顺序输入。

如字符b ,在str2中下标为13,13+48 = 61 为’=’。

按照这样的方法,我们得到字符串 =63487。这就是这一题的其中一个答案。

第六关

第六关涉及多个循环,我们一个一个拆开来看,首先我们来看第一个大循环:

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

00000000004011a2 <phase_6>:

4011a2: 41 55 push %r13

4011a4: 41 54 push %r12

4011a6: 55 push %rbp

4011a7: 53 push %rbx

4011a8: 48 83 ec 68 sub $0x68,%rsp

4011ac: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax

4011b3: 00 00

4011b5: 48 89 44 24 58 mov %rax,0x58(%rsp) //

4011ba: 31 c0 xor %eax,%eax //%eax = 0

4011bc: 48 89 e6 mov %rsp,%rsi //%rsi = %rsp (第二个参数)

4011bf: e8 1a 05 00 00 call 4016de <read_six_numbers> //读取6个整数

4011c4: 49 89 e4 mov %rsp,%r12 //%r12 = %rsp

4011c7: 41 bd 00 00 00 00 mov $0x0,%r13d //%r13d = 0

4011cd: 4c 89 e5 mov %r12,%rbp //%rbp = %r12 (1.rbp = &d1,2.rbp = &d2)

4011d0: 41 8b 04 24 mov (%r12),%eax //%eax = *(%r12)

4011d4: 83 e8 01 sub $0x1,%eax //%eax -= 1

4011d7: 83 f8 05 cmp $0x5,%eax //%eax <= 5?(d1,d2,d3,d4,d5,d6 <=6)

4011da: 76 05 jbe 4011e1 <phase_6+0x3f> //满足则跳转4011e1

4011dc: e8 c7 04 00 00 call 4016a8 <explode_bomb>

4011e1: 41 83 c5 01 add $0x1,%r13d //%r13d +=1

4011e5: 41 83 fd 06 cmp $0x6,%r13d //%r13d == 6?

4011e9: 74 3d je 401228 <phase_6+0x86> //相等就跳转401228

4011eb: 44 89 eb mov %r13d,%ebx //%ebx = %r13d

4011ee: 48 63 c3 movslq %ebx,%rax //%rax = %ebx

4011f1: 8b 04 84 mov (%rsp,%rax,4),%eax //%eax = %rsp+ %rax*4

4011f44011f4: 39 45 00 cmp %eax,0x0(%rbp) //%eax == *(%rbp)?

4011f7: 75 05 jne 4011fe <phase_6+0x5c> //不相等就跳转4011fe

4011f9: e8 aa 04 00 00 call 4016a8 <explode_bomb> //相等就爆炸

4011fe: 83 c3 01 add $0x1,%ebx //%ebx +=1

401201: 83 fb 05 cmp $0x5,%ebx //%ebx <= 5?

401204: 7e e8 jle 4011ee <phase_6+0x4c> //小于等于则跳转(d1,d2,d3,d4,d5,d6 不能有重复数字)

401206: 49 83 c4 04 add $0x4,%r12 //%r12 += 4 (%r12 = &d2)

40120a: eb c1 jmp 4011cd <phase_6+0x2b>//直接跳转4011cd

第一个大循环中还套了一个小循环,大循环确保输入的6个数字范围都在1~6之间,小循环确保输入的6个数字都不重复。那么我们就大概知道了输入的6个数字应该是1到6的排列组合。

后续的要求可能是对于我们输入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

//让%rdx = (dn * 8 + 0x6042f0)

40120c: 48 8b 52 08 mov 0x8(%rdx),%rdx //%rdx = *(%rdx+8)

401210: 83 c0 01 add $0x1,%eax //%eax+=1

401213: 39 c8 cmp %ecx,%eax //%ecx == %eax?

401215: 75 f5 jne 40120c <phase_6+0x6a> //不相等则跳转40120c

//将%rdx存到栈中

401217: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) //*(%rsp+%rsi*2+32) = %rdx

40121c: 48 83 c6 04 add $0x4,%rsi //%rsi+=4

401220: 48 83 fe 18 cmp $0x18,%rsi //%rsi == 24

401224: 75 07 jne 40122d <phase_6+0x8b>//不相等则跳转40122d

401226: eb 19 jmp 401241 <phase_6+0x9f>//相等则跳转401241

//%r13d = 6 %ebx = 6 %rsi = 6

401228: be 00 00 00 00 mov $0x0,%esi //%esi = 0

40122d: 8b 0c 34 mov (%rsp,%rsi,1),%ecx//%ecx = *(%rsp+%rsi)

401230: b8 01 00 00 00 mov $0x1,%eax //%eax = 1

401235: ba f0 42 60 00 mov $0x6042f0,%edx //%edx = 0x6042f0

40123a: 83 f9 01 cmp $0x1,%ecx //%ecx > 1?

40123d: 7f cd jg 40120c <phase_6+0x6a>//dn大于1跳转40120c

40123f: eb d6 jmp 401217 <phase_6+0x75>//否则跳转401217


第二个循环的作用是把地址是 0x6042f0的一个数组中的第 dn 个数放在一个新数组的位置 n 上。

我们找到这个数组的前6个数分别是:

  • 251
  • 652
  • 284
  • 382
  • 235
  • 782

所以这段代码的主要作用是根据我们输入的6个数字对于这6个数进行重新排序,比如我们输入的数为:2 1 4 3 6 5

那么这6个数的排序为:

  • 652
  • 251
  • 382
  • 284
  • 782
  • 235

清楚这段代码的作用后,我们再来看下一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23


401241: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx //%rbx = *(%rsp+32)

401246: 48 8d 44 24 20 lea 0x20(%rsp),%rax //%rax = rsp+32(第1个数地址)

40124b: 48 8d 74 24 48 lea 0x48(%rsp),%rsi //%rsi = %rsp+72(第6个数地址)

401250: 48 89 d9 mov %rbx,%rcx //%rcx = %rbx


401253: 48 8b 50 08 mov 0x8(%rax),%rdx //%rdx = *(%rax+8)

401257: 48 89 51 08 mov %rdx,0x8(%rcx) //*(%rcx+8) = %rdx

40125b: 48 83 c0 08 add $0x8,%rax //%rax += 8

40125f: 48 89 d1 mov %rdx,%rcx //%rcx = %rdx

401262: 48 39 f0 cmp %rsi,%rax //%rax == %rsi

401265: 75 ec jne 401253 <phase_6+0xb1>//不相等则跳转401253

这段代码似乎作用不大,仅仅是将之前我们存在栈中的数据复制到了另一个地方。

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


//确保数组为升序

401267: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) //*(%rdx+8) = 0

40126e: 00

40126f: bd 05 00 00 00 mov $0x5,%ebp //%edp = 5

401274: 48 8b 43 08 mov 0x8(%rbx),%rax //%rax = *(%rbx+8)

401278: 8b 00 mov (%rax),%eax //%eax = *(%rax)

40127a: 39 03 cmp %eax,(%rbx) //*(%rbx)<=%eax

40127c: 7e 05 jle 401283 <phase_6+0xe1>//满足则跳转401283

40127e: e8 25 04 00 00 call 4016a8 <explode_bomb>

401283: 48 8b 5b 08 mov 0x8(%rbx),%rbx //%rbx = *(%rbx+8)

401287: 83 ed 01 sub $0x1,%ebp //%ebp -=1

40128a: 75 e8 jne 401274 <phase_6+0xd2>//%ebp != 0 则跳转

这段代码的作用是确保我们的6个数字必须逐渐增大,结合上面我们的理解,那么我们按照6个数字的大小顺序进行输入:

5 1 3 4 2 6

就可以通关这一题啦。

至此6个炸弹已经全部拆解完毕。

1731399511499


计算机系统基础实验2——拆解炸弹
http://blog.ulna520.com/2024/11/12/experiment2_20241112_193631/
Veröffentlicht am
November 12, 2024
Urheberrechtshinweis