【施工完成】CSAPP bomb lab
背景
疫情肆虐,在家百无聊赖,于是开始拆炸弹. 炸弹分为6个阶段,每个阶段必须输入一个特定的字符串,否则炸弹就会爆炸. 提供给我们的是一个.c文件和一个linux可执行文件bomb
1
2/***************************************************************************
3 * Dr. Evil's Insidious Bomb, Version 1.1
4 * Copyright 2011, Dr. Evil Incorporated. All rights reserved.
5 *
6 * LICENSE:
7 *
8 * Dr. Evil Incorporated (the PERPETRATOR) hereby grants you (the
9 * VICTIM) explicit permission to use this bomb (the BOMB). This is a
10 * time limited license, which expires on the death of the VICTIM.
11 * The PERPETRATOR takes no responsibility for damage, frustration,
12 * insanity, bug-eyes, carpal-tunnel syndrome, loss of sleep, or other
13 * harm to the VICTIM. Unless the PERPETRATOR wants to take credit,
14 * that is. The VICTIM may not distribute this bomb source code to
15 * any enemies of the PERPETRATOR. No VICTIM may debug,
16 * reverse-engineer, run "strings" on, decompile, decrypt, or use any
17 * other technique to gain knowledge of and defuse the BOMB. BOMB
18 * proof clothing may not be worn when handling this program. The
19 * PERPETRATOR will not apologize for the PERPETRATOR's poor sense of
20 * humor. This license is null and void where the BOMB is prohibited
21 * by law.
22 ***************************************************************************/
23
24#include <stdio.h>
25#include <stdlib.h>
26#include "support.h"
27#include "phases.h"
28
29/*
30 * Note to self: Remember to erase this file so my victims will have no
31 * idea what is going on, and so they will all blow up in a
32 * spectaculary fiendish explosion. -- Dr. Evil
33 */
34
35FILE *infile;
36
37int main(int argc, char *argv[])
38{
39 char *input;
40
41 /* Note to self: remember to port this bomb to Windows and put a
42 * fantastic GUI on it. */
43
44 /* When run with no arguments, the bomb reads its input lines
45 * from standard input. */
46 if (argc == 1) {
47 infile = stdin;
48 }
49
50 /* When run with one argument <file>, the bomb reads from <file>
51 * until EOF, and then switches to standard input. Thus, as you
52 * defuse each phase, you can add its defusing string to <file> and
53 * avoid having to retype it. */
54 else if (argc == 2) {
55 if (!(infile = fopen(argv[1], "r"))) {
56 printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
57 exit(8);
58 }
59 }
60
61 /* You can't call the bomb with more than 1 command line argument. */
62 else {
63 printf("Usage: %s [<input_file>]\n", argv[0]);
64 exit(8);
65 }
66
67 /* Do all sorts of secret stuff that makes the bomb harder to defuse. */
68 initialize_bomb();
69
70 printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
71 printf("which to blow yourself up. Have a nice day!\n");
72
73 /* Hmm... Six phases must be more secure than one phase! */
74 input = read_line(); /* Get input */
75 phase_1(input); /* Run the phase */
76 phase_defused(); /* Drat! They figured it out!
77 * Let me know how they did it. */
78 printf("Phase 1 defused. How about the next one?\n");
79
80 /* The second phase is harder. No one will ever figure out
81 * how to defuse this... */
82 input = read_line();
83 phase_2(input);
84 phase_defused();
85 printf("That's number 2. Keep going!\n");
86
87 /* I guess this is too easy so far. Some more complex code will
88 * confuse people. */
89 input = read_line();
90 phase_3(input);
91 phase_defused();
92 printf("Halfway there!\n");
93
94 /* Oh yeah? Well, how good is your math? Try on this saucy problem! */
95 input = read_line();
96 phase_4(input);
97 phase_defused();
98 printf("So you got that one. Try this one.\n");
99
100 /* Round and 'round in memory we go, where we stop, the bomb blows! */
101 input = read_line();
102 phase_5(input);
103 phase_defused();
104 printf("Good work! On to the next...\n");
105
106 /* This phase will never be used, since no one will get past the
107 * earlier ones. But just in case, make this one extra hard. */
108 input = read_line();
109 phase_6(input);
110 phase_defused();
111
112 /* Wow, they got it! But isn't something... missing? Perhaps
113 * something they overlooked? Mua ha ha ha ha! */
114
115 return 0;
116}
117
好了.开始用gdb拆炸弹.
拆除phase 1
首先看一下phase_1函数的反汇编代码:
1
2(gdb) disas phase_1
3Dump of assembler code for function phase_1:
4 0x0000000000400ee0 <+0>: sub $0x8,%rsp
5 0x0000000000400ee4 <+4>: mov $0x402400,%esi
6 0x0000000000400ee9 <+9>: callq 0x401338 <strings_not_equal>
7 0x0000000000400eee <+14>: test %eax,%eax
8 0x0000000000400ef0 <+16>: je 0x400ef7 <phase_1+23>
9 0x0000000000400ef2 <+18>: callq 0x40143a <explode_bomb>
10 0x0000000000400ef7 <+23>: add $0x8,%rsp
11 0x0000000000400efb <+27>: retq
12End of assembler dump.
13
可以看到phase_1函数通过调用strings_not_equal函数判断两个字符串是否相等,如果不相等就跳到explode_bomb炸掉,否则返回到main函数的执行流.
接下来我们看一下strings_not_equal,暂时省略中间无关的部分.
1
2(gdb) disas strings_not_equal
3Dump of assembler code for function strings_not_equal:
4 0x0000000000401338 <+0>: push %r12
5 0x000000000040133a <+2>: push %rbp
6 0x000000000040133b <+3>: push %rbx
7 0x000000000040133c <+4>: mov %rdi,%rbx
8 0x000000000040133f <+7>: mov %rsi,%rbp
9 0x0000000000401342 <+10>: callq 0x40131b <string_length>
10 0x0000000000401347 <+15>: mov %eax,%r12d
11 0x000000000040134a <+18>: mov %rbp,%rdi
12 0x000000000040134d <+21>: callq 0x40131b <string_length>
13 0x0000000000401352 <+26>: mov $0x1,%edx
14 0x0000000000401357 <+31>: cmp %eax,%r12d
15 0x000000000040135a <+34>: jne 0x40139b <strings_not_equal+99>
16 ...
17 ...
18 ...
19 0x000000000040139b <+99>: mov %edx,%eax
20 0x000000000040139d <+101>: pop %rbx
21 0x000000000040139e <+102>: pop %rbp
22 0x000000000040139f <+103>: pop %r12
23 0x00000000004013a1 <+105>: retq
我们可以看到这里似乎是通过string_length函数求了两个字符串的长度,以此来比较两个函数是否相等. 求字符串长度总需要知道字符串是什么吧? 有戏. 我们再看一下string_length 函数
1
2(gdb) disas string_length
3Dump of assembler code for function string_length:
4 0x000000000040131b <+0>: cmpb $0x0,(%rdi)
5 0x000000000040131e <+3>: je 0x401332 <string_length+23>
6 0x0000000000401320 <+5>: mov %rdi,%rdx
7 0x0000000000401323 <+8>: add $0x1,%rdx
8 0x0000000000401327 <+12>: mov %edx,%eax
9 0x0000000000401329 <+14>: sub %edi,%eax
10 0x000000000040132b <+16>: cmpb $0x0,(%rdx)
11 0x000000000040132e <+19>: jne 0x401323 <string_length+8>
12 0x0000000000401330 <+21>: repz retq
13 0x0000000000401332 <+23>: mov $0x0,%eax
14 0x0000000000401337 <+28>: retq
15End of assembler dump.
16
可以看出,该函数先判断字符串是否为空,然后根据c style 字符串末尾的'\0'作为循环的终止条件得到字符串的长度.
显然,字符串内容存储在 寄存器 rdi的值所在的地址.
我们在string_length 函数开始的位置设置断点,得到两个字符串.第一个为用户输入的字符串,第二个就是phase_1 过关需要的字符串,问题解决!
1
2(gdb) b string_length
3Breakpoint 1 at 0x40131b
4(gdb) c
5The program is not being run.
6(gdb) r
7Starting program: /home/coder/workspace/csapp-lab/bomblab/bomb
8Welcome to my fiendish little bomb. You have 6 phases with
9which to blow yourself up. Have a nice day!
10hello from 111qqz
11
12Breakpoint 1, 0x000000000040131b in string_length ()
13(gdb) print (char *) $rdi
14$1 = 0x603780 <input_strings> "hello from 111qqz "
15(gdb) c
16Continuing.
17
18Breakpoint 1, 0x000000000040131b in string_length ()
19(gdb) print (char *) $rdi
20$2 = 0x402400 "Border relations with Canada have never been better."
21(gdb)
22
拆除phase 2
接下来开始解决第二阶段.
看一下phase_2 的反汇编代码:
1
2disas phase_2
3Dump of assembler code for function phase_2:
4 0x0000000000400efc <+0>: push %rbp
5 0x0000000000400efd <+1>: push %rbx
6 0x0000000000400efe <+2>: sub $0x28,%rsp
7 0x0000000000400f02 <+6>: mov %rsp,%rsi
8 0x0000000000400f05 <+9>: callq 0x40145c <read_six_numbers>
9 0x0000000000400f0a <+14>: cmpl $0x1,(%rsp)
10 0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
11 0x0000000000400f10 <+20>: callq 0x40143a <explode_bomb>
12 0x0000000000400f15 <+25>: jmp 0x400f30 <phase_2+52>
13 0x0000000000400f17 <+27>: mov -0x4(%rbx),%eax
14 0x0000000000400f1a <+30>: add %eax,%eax
15 0x0000000000400f1c <+32>: cmp %eax,(%rbx)
16 0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
17 0x0000000000400f20 <+36>: callq 0x40143a <explode_bomb>
18 0x0000000000400f25 <+41>: add $0x4,%rbx
19=> 0x0000000000400f29 <+45>: cmp %rbp,%rbx
20 0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
21 0x0000000000400f2e <+50>: jmp 0x400f3c <phase_2+64>
22 0x0000000000400f30 <+52>: lea 0x4(%rsp),%rbx
23 0x0000000000400f35 <+57>: lea 0x18(%rsp),%rbp
24 0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27>
25 0x0000000000400f3c <+64>: add $0x28,%rsp
26 0x0000000000400f40 <+68>: pop %rbx
27 0x0000000000400f41 <+69>: pop %rbp
28 0x0000000000400f42 <+70>: retq
29End of assembler dump.
30
接下来看一下read_six_numbers函数
1
2disas read_six_numbers
3Dump of assembler code for function read_six_numbers:
4 0x000000000040145c <+0>: sub $0x18,%rsp
5 0x0000000000401460 <+4>: mov %rsi,%rdx
6 0x0000000000401463 <+7>: lea 0x4(%rsi),%rcx
7 0x0000000000401467 <+11>: lea 0x14(%rsi),%rax
8 0x000000000040146b <+15>: mov %rax,0x8(%rsp)
9 0x0000000000401470 <+20>: lea 0x10(%rsi),%rax
10 0x0000000000401474 <+24>: mov %rax,(%rsp)
11 0x0000000000401478 <+28>: lea 0xc(%rsi),%r9
12 0x000000000040147c <+32>: lea 0x8(%rsi),%r8
13 0x0000000000401480 <+36>: mov $0x4025c3,%esi
14 0x0000000000401485 <+41>: mov $0x0,%eax
15 0x000000000040148a <+46>: callq 0x400bf0 <__isoc99_sscanf@plt>
16 0x000000000040148f <+51>: cmp $0x5,%eax
17 0x0000000000401492 <+54>: jg 0x401499 <read_six_numbers+61>
18 0x0000000000401494 <+56>: callq 0x40143a <explode_bomb>
19 0x0000000000401499 <+61>: add $0x18,%rsp
20 0x000000000040149d <+65>: retq
21End of assembler dump.
22
23
看起来是从stdin读了六个值,如果输入值的个数不足6个,就会炸掉.
从
1
2 0x0000000000401480 <+36>: mov $0x4025c3,%esi
3 (gdb) print (char *) 0x4025c3
4$155 = 0x4025c3 "%d %d %d %d %d %d"
5
可以得出,读入的六个值的类型都为int.
从read_six_numbers的 [+0, +32] 部分的代码可以得出,读取的六个数的存储位置. 依次为:
1
2%rsi %rdx
3%rsi+4 %rcx
4%rsi+8 %r8
5%rsi+12 %r9
6%rsi+16 (%rsp) 或者 %rax
7%rsi+20 (%rsp +8)
8
9
10(gdb) print /x $rdx
11$171 = 0x7fffffffdae0
12(gdb) print /x $rcx
13$172 = 0x7fffffffdae4
14(gdb) print /x $r8
15$173 = 0x7fffffffdae8
16(gdb) print /x $r9
17$174 = 0x7fffffffdaec
18(gdb) print /x $rax
19$175 = 0x7fffffffdaf0
20(gdb) print /x *(int *) ($rsp+8)
21$176 = 0xffffdaf4
22
需要注意的是这些汇编代码中的常数都是十六进制
接下来我们可以从phase_2的 [+14,+20]行中看出,
1
2 0x0000000000400f02 <+6>: mov %rsp,%rsi
3 0x0000000000400f05 <+9>: callq 0x40145c <read_six_numbers>
4 0x0000000000400f0a <+14>: cmpl $0x1,(%rsp)
5 0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
6 0x0000000000400f10 <+20>: callq 0x40143a <explode_bomb>
7
(%rsp)的值必须为1,否则炸弹就炸了. 而(%rsp)的值,实际上就是read_six_numbers中 %rsi的值.
因此 也就是读取的第一个数 %rsi 必须为 1.
第一个数正确后,会跳转到:
1
2
3 0x0000000000400f30 <+52>: lea 0x4(%rsp),%rbx
4 0x0000000000400f35 <+57>: lea 0x18(%rsp),%rbp
5 0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27>
6
可以看到 %rbx的值为读取的第二个数,而%rbp的值为读取的六个数后一个位置,猜想是类似EOF或者EOL之类标示stdin结束的值.
接下来查看 [+27,+48]行代码
1
2 0x0000000000400f17 <+27>: mov -0x4(%rbx),%eax
3 0x0000000000400f1a <+30>: add %eax,%eax
4 0x0000000000400f1c <+32>: cmp %eax,(%rbx)
5 0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
6 0x0000000000400f20 <+36>: callq 0x40143a <explode_bomb>
7 0x0000000000400f25 <+41>: add $0x4,%rbx
8=> 0x0000000000400f29 <+45>: cmp %rbp,%rbx
9 0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
10
11
可以看出这段代码是一个循环,循环终止条件为 %rbx等于 %rbp, 证实了刚刚 %rbp是某种终止符的猜想.
看起来是从第二个数,每个数(%rbx)必须等于前一个数 %eax,也就是 (%rbx - 4)的二倍,否则炸断就会炸.
因此推断,这六个数分别为: 1 2 4 8 16 32
问题解决!
拆除phase 3
接下来开始解决第三阶段.
看一下phase_3 的反汇编代码:
1
2(gdb) disas phase_3
3Dump of assembler code for function phase_3:
4 0x0000000000400f43 <+0>: sub $0x18,%rsp
5 0x0000000000400f47 <+4>: lea 0xc(%rsp),%rcx
6 0x0000000000400f4c <+9>: lea 0x8(%rsp),%rdx
7 0x0000000000400f51 <+14>: mov $0x4025cf,%esi
8 0x0000000000400f56 <+19>: mov $0x0,%eax
9 0x0000000000400f5b <+24>: callq 0x400bf0 <__isoc99_sscanf@plt>
10 0x0000000000400f60 <+29>: cmp $0x1,%eax
11 0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39>
12 0x0000000000400f65 <+34>: callq 0x40143a <explode_bomb>
13 0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp)
14 0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106>
15 0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax
16=> 0x0000000000400f75 <+50>: jmpq *0x402470(,%rax,8)
17 0x0000000000400f7c <+57>: mov $0xcf,%eax
18 0x0000000000400f81 <+62>: jmp 0x400fbe <phase_3+123>
19 0x0000000000400f83 <+64>: mov $0x2c3,%eax
20 0x0000000000400f88 <+69>: jmp 0x400fbe <phase_3+123>
21 0x0000000000400f8a <+71>: mov $0x100,%eax
22 0x0000000000400f8f <+76>: jmp 0x400fbe <phase_3+123>
23 0x0000000000400f91 <+78>: mov $0x185,%eax
24 0x0000000000400f96 <+83>: jmp 0x400fbe <phase_3+123>
25 0x0000000000400f98 <+85>: mov $0xce,%eax
26 0x0000000000400f9d <+90>: jmp 0x400fbe <phase_3+123>
27 0x0000000000400f9f <+92>: mov $0x2aa,%eax
28 0x0000000000400fa4 <+97>: jmp 0x400fbe <phase_3+123>
29 0x0000000000400fa6 <+99>: mov $0x147,%eax
30 0x0000000000400fab <+104>: jmp 0x400fbe <phase_3+123>
31 0x0000000000400fad <+106>: callq 0x40143a <explode_bomb>
32 0x0000000000400fb2 <+111>: mov $0x0,%eax
33 0x0000000000400fb7 <+116>: jmp 0x400fbe <phase_3+123>
34 0x0000000000400fb9 <+118>: mov $0x137,%eax
35 0x0000000000400fbe <+123>: cmp 0xc(%rsp),%eax
36 0x0000000000400fc2 <+127>: je 0x400fc9 <phase_3+134>
37 0x0000000000400fc4 <+129>: callq 0x40143a <explode_bomb>
38 0x0000000000400fc9 <+134>: add $0x18,%rsp
39 0x0000000000400fcd <+138>: retq
40End of assembler dump.
41
42
可以看出这是一段switch 语句 从stdin读了两个值,第一个数为case 的label,然后得到的结果与读入的第二个数比较,不相等就会爆炸.
从
1
2(gdb) print (char *) 0x4025cf
3$18 = 0x4025cf "%d %d"
4
读入的两个值都为int类型.
根据如下代码可以得知,第一个值的范围在[0,7]之间.
1
2 0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp)
3 0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106>
4
[0,7]区间内共有8个整数,于是我们检查从地址 0x402470 开始的8个word,得到:
1
2(gdb) x/16w 0x402470
30x402470: 0x00400f7c 0x00000000 0x00400fb9 0x00000000
40x402480: 0x00400f83 0x00000000 0x00400f8a 0x00000000
50x402490: 0x00400f91 0x00000000 0x00400f98 0x00000000
60x4024a0: 0x00400f9f 0x00000000 0x00400fa6 0x00000000
7
8
再对照 phase_3 的汇编代码,得到如下 case label与返回值的对应关系,其中括号内为十进制表示.
1
20: 0xcf(207)
31: 0x137(311)
42: 0x2c3(707)
53: 0x100(256)
64: 0x185(389)
75: 0xce(206)
86: 0x2aa(682)
97: 0x147(327)
因此phase 3的答案有上述8对,输入任意一对即可.
拆除phase 4
接下来开始解决第四阶段.
汇编代码为:
1
2disas phase_4
3Dump of assembler code for function phase_4:
4 0x000000000040100c <+0>: sub $0x18,%rsp
5 0x0000000000401010 <+4>: lea 0xc(%rsp),%rcx
6 0x0000000000401015 <+9>: lea 0x8(%rsp),%rdx
7 0x000000000040101a <+14>: mov $0x4025cf,%esi
8 0x000000000040101f <+19>: mov $0x0,%eax
9 0x0000000000401024 <+24>: callq 0x400bf0 <__isoc99_sscanf@plt>
10 0x0000000000401029 <+29>: cmp $0x2,%eax
11 0x000000000040102c <+32>: jne 0x401035 <phase_4+41>
12 0x000000000040102e <+34>: cmpl $0xe,0x8(%rsp)
13 0x0000000000401033 <+39>: jbe 0x40103a <phase_4+46>
14 0x0000000000401035 <+41>: callq 0x40143a <explode_bomb>
15 0x000000000040103a <+46>: mov $0xe,%edx
16 0x000000000040103f <+51>: mov $0x0,%esi
17 0x0000000000401044 <+56>: mov 0x8(%rsp),%edi
18 0x0000000000401048 <+60>: callq 0x400fce <func4>
19 0x000000000040104d <+65>: test %eax,%eax
20 0x000000000040104f <+67>: jne 0x401058 <phase_4+76>
21 0x0000000000401051 <+69>: cmpl $0x0,0xc(%rsp)
22 0x0000000000401056 <+74>: je 0x40105d <phase_4+81>
23 0x0000000000401058 <+76>: callq 0x40143a <explode_bomb>
24 0x000000000040105d <+81>: add $0x18,%rsp
25 0x0000000000401061 <+85>: retq
26End of assembler dump.
27
28
根据
1
2(gdb) print (char *) 0x4025cf
3$1 = 0x4025cf "%d %d"
4
可以得知,依然是读取了两个int类型的值. 第一个值存储在 (%rsp+0x8),第二个值存储在(%rsp+0xc)
根据 [+34,+41] 得知, 第一个数的范围是[0,14]
接下来查看func4的汇编代码:
1
2(gdb) disas func4
3Dump of assembler code for function func4:
4 0x0000000000400fce <+0>: sub $0x8,%rsp
5 0x0000000000400fd2 <+4>: mov %edx,%eax
6 0x0000000000400fd4 <+6>: sub %esi,%eax
7 0x0000000000400fd6 <+8>: mov %eax,%ecx
8 0x0000000000400fd8 <+10>: shr $0x1f,%ecx
9 0x0000000000400fdb <+13>: add %ecx,%eax
10 0x0000000000400fdd <+15>: sar %eax
11 0x0000000000400fdf <+17>: lea (%rax,%rsi,1),%ecx
12 0x0000000000400fe2 <+20>: cmp %edi,%ecx
13 0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36>
14 0x0000000000400fe6 <+24>: lea -0x1(%rcx),%edx
15 0x0000000000400fe9 <+27>: callq 0x400fce <func4>
16 0x0000000000400fee <+32>: add %eax,%eax
17 0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
18 0x0000000000400ff2 <+36>: mov $0x0,%eax
19 0x0000000000400ff7 <+41>: cmp %edi,%ecx
20 0x0000000000400ff9 <+43>: jge 0x401007 <func4+57>
21 0x0000000000400ffb <+45>: lea 0x1(%rcx),%esi
22 0x0000000000400ffe <+48>: callq 0x400fce <func4>
23 0x0000000000401003 <+53>: lea 0x1(%rax,%rax,1),%eax
24 0x0000000000401007 <+57>: add $0x8,%rsp
25 0x000000000040100b <+61>: retq
26End of assembler dump.
27
28
这段代码比较复杂,可以使用gdb watchpoints来观察几个寄存器的变化.
调试的时候要注意,需要先disable这些寄存器上的watchpoints,当进入到func4函数中再开启.
最终可以艰难得发现, 读入的第一个值应该为0.
根据phase_4 汇编代码[+69,+76]
1
2 0x0000000000401051 <+69>: cmpl $0x0,0xc(%rsp)
3 0x0000000000401056 <+74>: je 0x40105d <phase_4+81>
4 0x0000000000401058 <+76>: callq 0x40143a <explode_bomb>
5
可以得到读入的第二个值应该为0.
此外,我们注意到第一个值也只有15种可能的取值情况. 第二个值已经确定,因此总的取值情况也只有15种.
不妨暴力枚举得到答案.
综上,输入的两个值为 0 0
拆除phase 5
接下来开始解决第五阶段.
看一下反汇编代码:
1
2(gdb) disas phase_5
3Dump of assembler code for function phase_5:
4 0x0000000000401062 <+0>: push %rbx
5 0x0000000000401063 <+1>: sub $0x20,%rsp
6 0x0000000000401067 <+5>: mov %rdi,%rbx
7 0x000000000040106a <+8>: mov %fs:0x28,%rax
8 0x0000000000401073 <+17>: mov %rax,0x18(%rsp)
9 0x0000000000401078 <+22>: xor %eax,%eax
10 0x000000000040107a <+24>: callq 0x40131b <string_length>
11 0x000000000040107f <+29>: cmp $0x6,%eax
12 0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112>
13 0x0000000000401084 <+34>: callq 0x40143a <explode_bomb>
14 0x0000000000401089 <+39>: jmp 0x4010d2 <phase_5+112>
15 0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx
16 0x000000000040108f <+45>: mov %cl,(%rsp)
17 0x0000000000401092 <+48>: mov (%rsp),%rdx
18 0x0000000000401096 <+52>: and $0xf,%edx
19 0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
20 0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)
21 0x00000000004010a4 <+66>: add $0x1,%rax
22 0x00000000004010a8 <+70>: cmp $0x6,%rax
23 0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>
24 0x00000000004010ae <+76>: movb $0x0,0x16(%rsp)
25 0x00000000004010b3 <+81>: mov $0x40245e,%esi
26 0x00000000004010b8 <+86>: lea 0x10(%rsp),%rdi
27=> 0x00000000004010bd <+91>: callq 0x401338 <strings_not_equal>
28 0x00000000004010c2 <+96>: test %eax,%eax
29 0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119>
30 0x00000000004010c6 <+100>: callq 0x40143a <explode_bomb>
31 0x00000000004010cb <+105>: nopl 0x0(%rax,%rax,1)
32 0x00000000004010d0 <+110>: jmp 0x4010d9 <phase_5+119>
33 0x00000000004010d2 <+112>: mov $0x0,%eax
34 0x00000000004010d7 <+117>: jmp 0x40108b <phase_5+41>
35 0x00000000004010d9 <+119>: mov 0x18(%rsp),%rax
36 0x00000000004010de <+124>: xor %fs:0x28,%rax
37 0x00000000004010e7 <+133>: je 0x4010ee <phase_5+140>
38 0x00000000004010e9 <+135>: callq 0x400b30 <__stack_chk_fail@plt>
39 0x00000000004010ee <+140>: add $0x20,%rsp
40 0x00000000004010f2 <+144>: pop %rbx
41 0x00000000004010f3 <+145>: retq
42End of assembler dump.
43
44
从下面的代码可以得知,读入的字符串的长度为6
1
2 0x0000000000401078 <+22>: xor %eax,%eax
3 0x000000000040107a <+24>: callq 0x40131b <string_length>
4 0x000000000040107f <+29>: cmp $0x6,%eax
5 0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112>
6 0x0000000000401084 <+34>: callq 0x40143a <explode_bomb>
接下来分析这段代码中比较难懂的部分:
1
2 0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx 取读入字符串的第一个字符
3 0x000000000040108f <+45>: mov %cl,(%rsp) %cl与%ecx是相同的,原因是ascii的取值范围可以通过一个字节表示.
4 0x0000000000401092 <+48>: mov (%rsp),%rdx
5 0x0000000000401096 <+52>: and $0xf,%edx 将该字符的ascii值与0xf取and,也就是截取该ascii值的后四个bit,得到的值的范围在0..15,该值存储在%edx中
6 0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx %rdx作为存储在$0x4024b0位置的下标索引
7 0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1) #将得到的新的字符存放在以(%rsp+0x10)开始的位置
8 0x00000000004010a4 <+66>: add $0x1,%rax
9 0x00000000004010a8 <+70>: cmp $0x6,%rax
10 0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>
11
大致可以看出这是一个循环,依次处理每个字符.
在进入这个循环之前, %rbx 存储着 读入字符串的指针,%rax在[+112] 清空了,是0x0
$0x4024b0 位置开始的长度16的候选字符串为 "maduiersnfotvbyl"
因此上面这段循环做的就是从上述候选字符串中选6个字符,依次存储在从(%rsp+0x10)开始的位置
接下来我们看这段代码:
1
2 0x00000000004010b3 <+81>: mov $0x40245e,%esi
3 0x00000000004010b8 <+86>: lea 0x10(%rsp),%rdi
4=> 0x00000000004010bd <+91>: callq 0x401338 <strings_not_equal>
5 0x00000000004010c2 <+96>: test %eax,%eax
6 0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119>
7 0x00000000004010c6 <+100>: callq 0x40143a <explode_bomb>
8
发现是要求经过变换后的字符串与某个给定的字符串相等
给定的字符串为"flyers",找出这6个字符再候选字符串中的位置
分别为 9,15,14,5,6,7 写成十六进制就是 0x9,0xf,0xe,0x5,0x6,0x7
查看ascii表,找到6个最后的十六进制值为上述值的ascii 码,就是答案.
显然,答案有多种. 其中一种答案为:
ionefg
问题解决!
拆除phase 6
一口老血...phase 6真是恶心....花的时间比前面五个加起来还要多
接下来开始解决第六阶段.
看一下反汇编代码:
1
2(gdb) disas phase_6
3Dump of assembler code for function phase_6:
4 0x00000000004010f4 <+0>: push %r14
5 0x00000000004010f6 <+2>: push %r13
6 0x00000000004010f8 <+4>: push %r12
7 0x00000000004010fa <+6>: push %rbp
8 0x00000000004010fb <+7>: push %rbx
9 0x00000000004010fc <+8>: sub $0x50,%rsp
10 0x0000000000401100 <+12>: mov %rsp,%r13
11 0x0000000000401103 <+15>: mov %rsp,%rsi
12 0x0000000000401106 <+18>: callq 0x40145c <read_six_numbers>
13 0x000000000040110b <+23>: mov %rsp,%r14
14 0x000000000040110e <+26>: mov $0x0,%r12d
15 0x0000000000401114 <+32>: mov %r13,%rbp
16 0x0000000000401117 <+35>: mov 0x0(%r13),%eax
17 0x000000000040111b <+39>: sub $0x1,%eax
18 0x000000000040111e <+42>: cmp $0x5,%eax
19 0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52>
20 0x0000000000401123 <+47>: callq 0x40143a <explode_bomb>
21 0x0000000000401128 <+52>: add $0x1,%r12d
22 0x000000000040112c <+56>: cmp $0x6,%r12d
23 0x0000000000401130 <+60>: je 0x401153 <phase_6+95>
24 0x0000000000401132 <+62>: mov %r12d,%ebx
25 0x0000000000401135 <+65>: movslq %ebx,%rax
26 0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax
27 0x000000000040113b <+71>: cmp %eax,0x0(%rbp)
28 0x000000000040113e <+74>: jne 0x401145 <phase_6+81>
29 0x0000000000401140 <+76>: callq 0x40143a <explode_bomb>
30 0x0000000000401145 <+81>: add $0x1,%ebx
31 0x0000000000401148 <+84>: cmp $0x5,%ebx
32 0x000000000040114b <+87>: jle 0x401135 <phase_6+65>
33 0x000000000040114d <+89>: add $0x4,%r13
34 0x0000000000401151 <+93>: jmp 0x401114 <phase_6+32>
35 0x0000000000401153 <+95>: lea 0x18(%rsp),%rsi
36 0x0000000000401158 <+100>: mov %r14,%rax
37 0x000000000040115b <+103>: mov $0x7,%ecx
38 0x0000000000401160 <+108>: mov %ecx,%edx
39 0x0000000000401162 <+110>: sub (%rax),%edx
40 0x0000000000401164 <+112>: mov %edx,(%rax)
41 0x0000000000401166 <+114>: add $0x4,%rax
42 0x000000000040116a <+118>: cmp %rsi,%rax
43 0x000000000040116d <+121>: jne 0x401160 <phase_6+108>
44 0x000000000040116f <+123>: mov $0x0,%esi
45 0x0000000000401174 <+128>: jmp 0x401197 <phase_6+163>
46 0x0000000000401176 <+130>: mov 0x8(%rdx),%rdx
47 0x000000000040117a <+134>: add $0x1,%eax
48 0x000000000040117d <+137>: cmp %ecx,%eax
49 0x000000000040117f <+139>: jne 0x401176 <phase_6+130>
50 0x0000000000401181 <+141>: jmp 0x401188 <phase_6+148>
51 0x0000000000401183 <+143>: mov $0x6032d0,%edx
52 0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2)
53 0x000000000040118d <+153>: add $0x4,%rsi
54 0x0000000000401191 <+157>: cmp $0x18,%rsi
55 0x0000000000401195 <+161>: je 0x4011ab <phase_6+183>
56 0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx
57 0x000000000040119a <+166>: cmp $0x1,%ecx
58 0x000000000040119d <+169>: jle 0x401183 <phase_6+143>
59 0x000000000040119f <+171>: mov $0x1,%eax
60 0x00000000004011a4 <+176>: mov $0x6032d0,%edx
61 0x00000000004011a9 <+181>: jmp 0x401176 <phase_6+130>
62 0x00000000004011ab <+183>: mov 0x20(%rsp),%rbx
63 0x00000000004011b0 <+188>: lea 0x28(%rsp),%rax
64 0x00000000004011b5 <+193>: lea 0x50(%rsp),%rsi
65 0x00000000004011ba <+198>: mov %rbx,%rcx
66 0x00000000004011bd <+201>: mov (%rax),%rdx
67 0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx)
68 0x00000000004011c4 <+208>: add $0x8,%rax
69 0x00000000004011c8 <+212>: cmp %rsi,%rax
70 0x00000000004011cb <+215>: je 0x4011d2 <phase_6+222>
71 0x00000000004011cd <+217>: mov %rdx,%rcx
72 0x00000000004011d0 <+220>: jmp 0x4011bd <phase_6+201>
73 0x00000000004011d2 <+222>: movq $0x0,0x8(%rdx)
74 0x00000000004011da <+230>: mov $0x5,%ebp
75 0x00000000004011df <+235>: mov 0x8(%rbx),%rax
76 0x00000000004011e3 <+239>: mov (%rax),%eax
77=> 0x00000000004011e5 <+241>: cmp %eax,(%rbx)
78 0x00000000004011e7 <+243>: jge 0x4011ee <phase_6+250>
79 0x00000000004011e9 <+245>: callq 0x40143a <explode_bomb>
80 0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx
81 0x00000000004011f2 <+254>: sub $0x1,%ebp
82 0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235>
83 0x00000000004011f7 <+259>: add $0x50,%rsp
84 0x00000000004011fb <+263>: pop %rbx
85 0x00000000004011fc <+264>: pop %rbp
86 0x00000000004011fd <+265>: pop %r12
87 0x00000000004011ff <+267>: pop %r13
88 0x0000000000401201 <+269>: pop %r14
89 0x0000000000401203 <+271>: retq
90End of assembler dump.
91
前半部分代码还算比较好懂:
1
2 0x0000000000401106 <+18>: callq 0x40145c <read_six_numbers>
3 0x000000000040110b <+23>: mov %rsp,%r14
4 0x000000000040110e <+26>: mov $0x0,%r12d
5 0x0000000000401114 <+32>: mov %r13,%rbp
6 0x0000000000401117 <+35>: mov 0x0(%r13),%eax
7 0x000000000040111b <+39>: sub $0x1,%eax
8 0x000000000040111e <+42>: cmp $0x5,%eax
9 0x0000000000401121 <+45>: jbe 0x401128 <phase_6+52>
10 0x0000000000401123 <+47>: callq 0x40143a <explode_bomb>
11 0x0000000000401128 <+52>: add $0x1,%r12d
12 0x000000000040112c <+56>: cmp $0x6,%r12d
13 0x0000000000401130 <+60>: je 0x401153 <phase_6+95>
14 0x0000000000401132 <+62>: mov %r12d,%ebx
15 0x0000000000401135 <+65>: movslq %ebx,%rax
16 0x0000000000401138 <+68>: mov (%rsp,%rax,4),%eax
17 0x000000000040113b <+71>: cmp %eax,0x0(%rbp)
18 0x000000000040113e <+74>: jne 0x401145 <phase_6+81>
19 0x0000000000401140 <+76>: callq 0x40143a <explode_bomb>
20 0x0000000000401145 <+81>: add $0x1,%ebx
21 0x0000000000401148 <+84>: cmp $0x5,%ebx
22 0x000000000040114b <+87>: jle 0x401135 <phase_6+65>
23 0x000000000040114d <+89>: add $0x4,%r13
24 0x0000000000401151 <+93>: jmp 0x401114 <phase_6+32>
25
这段代码是读取6个int 类型的数,然后每个数必须属于[1,6],并且互不相同. 因此输入的6个数为1..6的6个数字的一个排列.
接下来也比较好懂,就是把每个读入的数x变成了7-x.
为了方便说明,接下来省略提及这个变换,将7-x当成读作的数
1
2 0x0000000000401153 <+95>: lea 0x18(%rsp),%rsi
3 0x0000000000401158 <+100>: mov %r14,%rax
4 0x000000000040115b <+103>: mov $0x7,%ecx
5 0x0000000000401160 <+108>: mov %ecx,%edx
6 0x0000000000401162 <+110>: sub (%rax),%edx
7 0x0000000000401164 <+112>: mov %edx,(%rax)
8 0x0000000000401166 <+114>: add $0x4,%rax
9 0x000000000040116a <+118>: cmp %rsi,%rax
10 0x000000000040116d <+121>: jne 0x401160 <phase_6+108>
11
接下来这段简直吐血:
1
2
3 0x000000000040116f <+123>: mov $0x0,%esi
4 0x0000000000401174 <+128>: jmp 0x401197 <phase_6+163>
5 0x0000000000401176 <+130>: mov 0x8(%rdx),%rdx
6 0x000000000040117a <+134>: add $0x1,%eax
7 0x000000000040117d <+137>: cmp %ecx,%eax
8 0x000000000040117f <+139>: jne 0x401176 <phase_6+130>
9 0x0000000000401181 <+141>: jmp 0x401188 <phase_6+148>
10 0x0000000000401183 <+143>: mov $0x6032d0,%edx
11 0x0000000000401188 <+148>: mov %rdx,0x20(%rsp,%rsi,2)
12 0x000000000040118d <+153>: add $0x4,%rsi
13 0x0000000000401191 <+157>: cmp $0x18,%rsi
14 0x0000000000401195 <+161>: je 0x4011ab <phase_6+183>
15 0x0000000000401197 <+163>: mov (%rsp,%rsi,1),%ecx
16 0x000000000040119a <+166>: cmp $0x1,%ecx
17 0x000000000040119d <+169>: jle 0x401183 <phase_6+143>
18 0x000000000040119f <+171>: mov $0x1,%eax
19 0x00000000004011a4 <+176>: mov $0x6032d0,%edx
20 0x00000000004011a9 <+181>: jmp 0x401176 <phase_6+130>
21
在进入上述这段代码之前, %rsp 存储着第一个读入数的内存地址,%ecx是常数7
要注意两个语句,一个是 mov (%rsp,%rsi,1),%ecx 中(%rsp,%rsi,1)表示的是读入的数存放的位置
一个是 mov %rdx,0x20(%rsp,%rsi,2) 中 0x20(%rsp,%rsi,2) 表示的是 某个结果存放的位置.
这段代码先跳到 +163, 拿到读入的数,看是否等于1
- 如果相等: 跳到 +143,把 $0x6032d0 放到 0x20(%rsp,%rsi,2) 的位置
- 如果不相等: 继续执行,eax为1,edx为$0x6032d0,然后跳到 +130,edx变为下一个位置$0x6032e0.这样一直进行读入的数x-1次. (比如读入的数是4,那么就会进行3次edx变为下一个位置的操作,也就是变为$0x603300)
可以看出这是个链表的数据结构.
因此这段代码做的是, 按照读入的数的从小到大的出现的位置,依次把从 $0x6032e0 到$0x603320 放到
栈上(%rsp+0x20+8i)的位置.
举个例子, 假设读入的数为:
15 4 3 2 1 6
那么从 (%rsp+0x20+8i) 开始的数就是:
1$0x603310 $0x603300 $0x6032f0 $0x6032e0 $0x6032d0 $0x603320
2
接下来我们看最后一段代码:
1
2 0x00000000004011ab <+183>: mov 0x20(%rsp),%rbx
3 0x00000000004011b0 <+188>: lea 0x28(%rsp),%rax
4 0x00000000004011b5 <+193>: lea 0x50(%rsp),%rsi
5 0x00000000004011ba <+198>: mov %rbx,%rcx
6 0x00000000004011bd <+201>: mov (%rax),%rdx
7 0x00000000004011c0 <+204>: mov %rdx,0x8(%rcx)
8 0x00000000004011c4 <+208>: add $0x8,%rax
9 0x00000000004011c8 <+212>: cmp %rsi,%rax
10 0x00000000004011cb <+215>: je 0x4011d2 <phase_6+222>
11 0x00000000004011cd <+217>: mov %rdx,%rcx
12 0x00000000004011d0 <+220>: jmp 0x4011bd <phase_6+201>
13 0x00000000004011d2 <+222>: movq $0x0,0x8(%rdx)
14 0x00000000004011da <+230>: mov $0x5,%ebp
15 0x00000000004011df <+235>: mov 0x8(%rbx),%rax
16 0x00000000004011e3 <+239>: mov (%rax),%eax
17 0x00000000004011e5 <+241>: cmp %eax,(%rbx)
18 0x00000000004011e7 <+243>: jge 0x4011ee <phase_6+250>
19 0x00000000004011e9 <+245>: callq 0x40143a <explode_bomb>
20 0x00000000004011ee <+250>: mov 0x8(%rbx),%rbx
21 0x00000000004011f2 <+254>: sub $0x1,%ebp
22 0x00000000004011f5 <+257>: jne 0x4011df <phase_6+235>
23
从 [+235,+245] 可以看出,这里要求从 0x20(%rsp) 开始的栈上存储的地址对应位置的值,必须前一个必须大于后一个.
我们可以查看 从 $0x6032d0位置开始的6个地址中的值:
1(gdb) x/3w 0x6032d0
20x6032d0 <node1>: 0x0000014c 0x00000001 0x00000000
3(gdb) x/3w 0x6032e0
40x6032e0 <node2>: 0x000000a8 0x00000002 0x006032d0
5(gdb) x/3w 0x6032f0
60x6032f0 <node3>: 0x0000039c 0x00000003 0x006032e0
7(gdb) x/3w 0x603300
80x603300 <node4>: 0x000002b3 0x00000004 0x006032f0
9(gdb) x/3w 0x603310
100x603310 <node5>: 0x000001dd 0x00000005 0x00603300
11(gdb) x/3w 0x603320
120x603320 <node6>: 0x000001bb 0x00000006 0x00603310
13
14
按照值的顺序降序node序号,得到 3 4 5 6 1 2
最后不要忘记用7减去每个位置的值,因此最终答案就是: 4 3 2 1 6 5
Posts in this Series
- [施工完成] CSAPP shell lab
- [施工完成] CSAPP Malloc lab
- [施工完成] CSAPP Cachelab
- 【施工完成】CSAPP archlab
- 【施工完成】CSAPP attacklab
- 【施工完成】CSAPP bomb lab
- 【施工完成】CSAPP data lab