前置知识

常见的汇编指令,特别是各种跳转指令的区别。

http://unixwiz.net/techtips/x86-jumps.html

基础的gdb调试操作,汇编调试(tui)。

phase_1

1
2
3
4
5
6
7
8
9
10
11
(gdb) disassemble phase_1
Dump of assembler code for function phase_1:
0x0000000000400ee0 <+0>: sub $0x8,%rsp
0x0000000000400ee4 <+4>: mov $0x402400,%esi
0x0000000000400ee9 <+9>: call 0x401338 <strings_not_equal>
0x0000000000400eee <+14>: test %eax,%eax
0x0000000000400ef0 <+16>: je 0x400ef7 <phase_1+23>
0x0000000000400ef2 <+18>: call 0x40143a <explode_bomb>
0x0000000000400ef7 <+23>: add $0x8,%rsp
0x0000000000400efb <+27>: ret
End of assembler dump.

<+4>处将0x402400,赋值给%esi,接着调用了一个strings_not_equal函数,该函数显然是用来进行字符串比较的。我们通过查看strings_not_equal的汇编代码,大致可以推测将比较结果放入了%eax中。如果两个字符串相等则跳转到<+23>,如果不相等则会执行explode_bomb函数,炸弹会爆炸。

0x402400很像一个地址,不难推测出这是一个字符串的地址。我们使用调试工具读取其内容。(x命令用来查看内存地址中的值,s是指定使用读取字符串的方式)

1
2
3
4
5
6
7
8
9
10
11
(gdb) x/s 0x402400
0x402400: "Border relations with Canada have never been better."
which to blow yourself up. Have a nice day!
73 input = read_line(); /* Get input */
(gdb) n
Border relations with Canada have never been better.
74 phase_1(input); /* Run the phase */
(gdb) n
75 phase_defused(); /* Drat! They figured it out!
(gdb) n
77 printf("Phase 1 defused. How about the next one?\n");

phase_2

老样子,我们使用gdb的调试命令来查看汇编代码。

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
(gdb) disassemble phase_2
Dump of assembler code for function phase_2:
0x0000000000400efc <+0>: push %rbp
0x0000000000400efd <+1>: push %rbx
0x0000000000400efe <+2>: sub $0x28,%rsp
0x0000000000400f02 <+6>: mov %rsp,%rsi
0x0000000000400f05 <+9>: call 0x40145c <read_six_numbers>
0x0000000000400f0a <+14>: cmpl $0x1,(%rsp)
0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
0x0000000000400f10 <+20>: call 0x40143a <explode_bomb>
0x0000000000400f15 <+25>: jmp 0x400f30 <phase_2+52>
0x0000000000400f17 <+27>: mov -0x4(%rbx),%eax
0x0000000000400f1a <+30>: add %eax,%eax
0x0000000000400f1c <+32>: cmp %eax,(%rbx)
0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
0x0000000000400f20 <+36>: call 0x40143a <explode_bomb>
0x0000000000400f25 <+41>: add $0x4,%rbx
0x0000000000400f29 <+45>: cmp %rbp,%rbx
0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
0x0000000000400f2e <+50>: jmp 0x400f3c <phase_2+64>
0x0000000000400f30 <+52>: lea 0x4(%rsp),%rbx
0x0000000000400f35 <+57>: lea 0x18(%rsp),%rbp
0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27>
0x0000000000400f3c <+64>: add $0x28,%rsp
0x0000000000400f40 <+68>: pop %rbx
0x0000000000400f41 <+69>: pop %rbp
0x0000000000400f42 <+70>: ret
End of assembler dump.

这一次的代码比上一次的更加复杂,而且结构更加复杂,从<+48>可以跳转到<+27>,代表这个结构是非线性的。

首先,我们需要将原始代码分成几个独立的代码块。

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
<+0>:
0x0000000000400efc <+0>: push %rbp
0x0000000000400efd <+1>: push %rbx
0x0000000000400efe <+2>: sub $0x28,%rsp
0x0000000000400f02 <+6>: mov %rsp,%rsi
0x0000000000400f05 <+9>: call 0x40145c <read_six_numbers>
0x0000000000400f0a <+14>: cmpl $0x1,(%rsp)
0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
0x0000000000400f10 <+20>: call 0x40143a <explode_bomb>
<+52>:
0x0000000000400f30 <+52>: lea 0x4(%rsp),%rbx
0x0000000000400f35 <+57>: lea 0x18(%rsp),%rbp
0x0000000000400f3a <+62>: jmp 0x400f17 <phase_2+27>
<+27>:
0x0000000000400f17 <+27>: mov -0x4(%rbx),%eax
0x0000000000400f1a <+30>: add %eax,%eax
0x0000000000400f1c <+32>: cmp %eax,(%rbx)
0x0000000000400f1e <+34>: je 0x400f25 <phase_2+41>
0x0000000000400f10 <+20>: call 0x40143a <explode_bomb>
<+41>:
0x0000000000400f25 <+41>: add $0x4,%rbx
0x0000000000400f29 <+45>: cmp %rbp,%rbx
0x0000000000400f2c <+48>: jne 0x400f17 <phase_2+27>
0x0000000000400f2e <+50>: jmp 0x400f3c <phase_2+64>
<+64>:
0x0000000000400f3c <+64>: add $0x28,%rsp
0x0000000000400f40 <+68>: pop %rbx
0x0000000000400f41 <+69>: pop %rbp
0x0000000000400f42 <+70>: ret

拆解后我们可以观察到,程序开始要求读入六个数字,并且将rsp减去0x28,也就是申请了40个字节的栈空间。不难猜测,读取的六个数字存放在了申请的栈空间中(当然,更为严谨的做法是继续观察read_six_numbers的汇编代码)。接着,将0x1和栈底的元素比较,如果相等则跳转到<+52>块,否则炸弹爆炸。所以第一个输入的值input[0] = 1。

我们继续往下看,<+52>的代码块。它实际上是循环体的初始化,rbp代表第6个int的尾地址,也就是循环的终止点。而rbx是循环的初始值。初始化完成后,跳转到<+27>。

<+27>是循环体。每次取出rbx的前一个值赋值给eax,同时将eax*=2,并且和将eax和rbx作比较。如果不等,则失败。

<+41>是朝下一次循环迭代。最后,遍历完成6个数后,跳转到<+64>。

最后,我们不难写出与这个汇编代码等效的C代码。

1
2
3
4
5
6
7
8
int a[6] = {*, *, *, *, *, *};
if (a[0] != 1) explode_bomb;
for(int i = 1; i < 6; i ++) {
int x = a[i], y = a[i - 1];
y *= 2;
if(x != y) explode_bomb;
}
success;

由于a[0]必须是1,且a[i]必须是a[i-1]的两倍,我们容易得到答案,即输入为1,2,4,8,16,32。

phase_3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
(gdb) disassemble phase_3
Dump of assembler code for function phase_3:
0x0000000000400f43 <+0>: sub $0x18,%rsp
0x0000000000400f47 <+4>: lea 0xc(%rsp),%rcx
0x0000000000400f4c <+9>: lea 0x8(%rsp),%rdx
0x0000000000400f51 <+14>: mov $0x4025cf,%esi
0x0000000000400f56 <+19>: mov $0x0,%eax
0x0000000000400f5b <+24>: call 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000400f60 <+29>: cmp $0x1,%eax
0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39>
0x0000000000400f65 <+34>: call 0x40143a <explode_bomb>
0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp)
0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106>
0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax
0x0000000000400f75 <+50>: jmp *0x402470(,%rax,8)
0x0000000000400f7c <+57>: mov $0xcf,%eax
0x0000000000400f81 <+62>: jmp 0x400fbe <phase_3+123>
0x0000000000400f83 <+64>: mov $0x2c3,%eax
0x0000000000400f88 <+69>: jmp 0x400fbe <phase_3+123>
0x0000000000400f8a <+71>: mov $0x100,%eax
0x0000000000400f8f <+76>: jmp 0x400fbe <phase_3+123>
0x0000000000400f91 <+78>: mov $0x185,%eax
0x0000000000400f96 <+83>: jmp 0x400fbe <phase_3+123>
0x0000000000400f98 <+85>: mov $0xce,%eax
0x0000000000400f9d <+90>: jmp 0x400fbe <phase_3+123>
0x0000000000400f9f <+92>: mov $0x2aa,%eax
0x0000000000400fa4 <+97>: jmp 0x400fbe <phase_3+123>
0x0000000000400fa6 <+99>: mov $0x147,%eax
0x0000000000400fab <+104>: jmp 0x400fbe <phase_3+123>
0x0000000000400fad <+106>: call 0x40143a <explode_bomb>
0x0000000000400fb2 <+111>: mov $0x0,%eax
0x0000000000400fb7 <+116>: jmp 0x400fbe <phase_3+123>
0x0000000000400fb9 <+118>: mov $0x137,%eax
0x0000000000400fbe <+123>: cmp 0xc(%rsp),%eax
0x0000000000400fc2 <+127>: je 0x400fc9 <phase_3+134>
0x0000000000400fc4 <+129>: call 0x40143a <explode_bomb>
0x0000000000400fc9 <+134>: add $0x18,%rsp
0x0000000000400fcd <+138>: ret

有了上面两次的经验,我们将所有看起来像地址的16进制数进行取数/取字符串。

1
2
(gdb) x/s 0x4025cf
0x4025cf: "%d %d"

输入为两个整数,我们输入两个整数后,执行到<+29>处,查看寄存器rex的值为2,推断出里面存储的是输入数值的个数。还有一个地址0x402470,我们一并进行分析。有点不明所以,我们稍后再看。

1
2
(gdb) x/x 0x402470
0x402470: 0x7c

我们对汇编代码进行格式化

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
<+0>:
0x0000000000400f43 <+0>: sub $0x18,%rsp
0x0000000000400f47 <+4>: lea 0xc(%rsp),%rcx
0x0000000000400f4c <+9>: lea 0x8(%rsp),%rdx
0x0000000000400f51 <+14>: mov $0x4025cf,%esi
0x0000000000400f56 <+19>: mov $0x0,%eax
0x0000000000400f5b <+24>: call 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000400f60 <+29>: cmp $0x1,%eax
0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39>
0x0000000000400f65 <+34>: call 0x40143a <explode_bomb>
<+39>:
0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp)
if 7 < 0x8(%rsp)
0x0000000000400fad <+106>: call 0x40143a <explode_bomb> # 将原来的jmp换成了call.
<+46>:
0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax
0x0000000000400f75 <+50>: jmp *0x402470(,%rax,8)
0x0000000000400f7c <+57>: mov $0xcf,%eax
0x0000000000400f81 <+62>: jmp 0x400fbe <phase_3+123>
0x0000000000400f83 <+64>: mov $0x2c3,%eax
0x0000000000400f88 <+69>: jmp 0x400fbe <phase_3+123>
0x0000000000400f8a <+71>: mov $0x100,%eax
0x0000000000400f8f <+76>: jmp 0x400fbe <phase_3+123>
0x0000000000400f91 <+78>: mov $0x185,%eax
0x0000000000400f96 <+83>: jmp 0x400fbe <phase_3+123>
0x0000000000400f98 <+85>: mov $0xce,%eax
0x0000000000400f9d <+90>: jmp 0x400fbe <phase_3+123>
0x0000000000400f9f <+92>: mov $0x2aa,%eax
0x0000000000400fa4 <+97>: jmp 0x400fbe <phase_3+123>
0x0000000000400fa6 <+99>: mov $0x147,%eax
0x0000000000400fab <+104>: jmp 0x400fbe <phase_3+123>
0x0000000000400fad <+106>: call 0x40143a <explode_bomb>
0x0000000000400fb2 <+111>: mov $0x0,%eax
0x0000000000400fb7 <+116>: jmp 0x400fbe <phase_3+123>
0x0000000000400fb9 <+118>: mov $0x137,%eax
<+123>:
0x0000000000400fbe <+123>: cmp 0xc(%rsp),%eax
0x0000000000400fc2 <+127>: je 0x400fc9 <phase_3+134>
0x0000000000400fc4 <+129>: call 0x40143a <explode_bomb>
0x0000000000400fc9 <+134>: add $0x18,%rsp
0x0000000000400fcd <+138>: ret

<+0>在做的事情是读入两个数,如果不是,则爆炸。

<+39>将读入的第一个数和7作比较,如果比7大,则爆炸。

<+46>将第一个数赋值给eax,并且依照其值进行接下来的跳转。如果第一个数是0,则跳转到<+57>;如果是1,则跳转到<+64>,……依此类推。每个分支做的事是给eax赋不同的值,并且最后跳转到<+123>。

<+123>比较第二个数与eax的值,如果不等则爆炸,如果相等则成功。

这段代码事实上是switch语句所表达的逻辑。我们可以轻松的写出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
switch (x)
{
case 0:
x = 0xcf;
break;
case 1:
x = 0x2c3;
break;
case 2:
x = 0x100;
break;
case 3:
x = 0x185;
break;
case 4:
x = 0xce;
break;
case 5:
x = 0x2aa;
break;
case 6:
x = 0x147;
break;
default:
explode_bomb;
}
if(x != y) explode_bomb;
success;

所以,我们输入0~6之间的一个数与其对应的值即可。0 207是一组解。

phase_4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(gdb) disassemble phase_4
Dump of assembler code for function phase_4:
0x000000000040100c <+0>: sub $0x18,%rsp
0x0000000000401010 <+4>: lea 0xc(%rsp),%rcx
0x0000000000401015 <+9>: lea 0x8(%rsp),%rdx
0x000000000040101a <+14>: mov $0x4025cf,%esi
0x000000000040101f <+19>: mov $0x0,%eax
0x0000000000401024 <+24>: call 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000401029 <+29>: cmp $0x2,%eax
0x000000000040102c <+32>: jne 0x401035 <phase_4+41>
0x000000000040102e <+34>: cmpl $0xe,0x8(%rsp)
0x0000000000401033 <+39>: jbe 0x40103a <phase_4+46>
0x0000000000401035 <+41>: call 0x40143a <explode_bomb>
0x000000000040103a <+46>: mov $0xe,%edx
0x000000000040103f <+51>: mov $0x0,%esi
0x0000000000401044 <+56>: mov 0x8(%rsp),%edi
0x0000000000401048 <+60>: call 0x400fce <func4>
0x000000000040104d <+65>: test %eax,%eax
0x000000000040104f <+67>: jne 0x401058 <phase_4+76>
0x0000000000401051 <+69>: cmpl $0x0,0xc(%rsp)
0x0000000000401056 <+74>: je 0x40105d <phase_4+81>
0x0000000000401058 <+76>: call 0x40143a <explode_bomb>
0x000000000040105d <+81>: add $0x18,%rsp
0x0000000000401061 <+85>: ret

令人意外的是,居然是前4个炸弹中代码最短的一个。不用高兴,我们发现<+60>是一个新的函数调用,所以继续查看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
(gdb) disassemble func4
Dump of assembler code for function func4:
0x0000000000400fce <+0>: sub $0x8,%rsp
0x0000000000400fd2 <+4>: mov %edx,%eax # res = x;
0x0000000000400fd4 <+6>: sub %esi,%eax # res -= y;
0x0000000000400fd6 <+8>: mov %eax,%ecx # tmp = res;
0x0000000000400fd8 <+10>: shr $0x1f,%ecx # tmp >>= 31;
0x0000000000400fdb <+13>: add %ecx,%eax # res += tmp;
0x0000000000400fdd <+15>: sar %eax # res >>= 1;
0x0000000000400fdf <+17>: lea (%rax,%rsi,1),%ecx # tmp = res + y * 1;
0x0000000000400fe2 <+20>: cmp %edi,%ecx
0x0000000000400fe4 <+22>: jle 0x400ff2 <func4+36> # if in2 <= tmp;
0x0000000000400fe6 <+24>: lea -0x1(%rcx),%edx # tmp1 = tmp - 1;
0x0000000000400fe9 <+27>: call 0x400fce <func4>
0x0000000000400fee <+32>: add %eax,%eax
0x0000000000400ff0 <+34>: jmp 0x401007 <func4+57>
0x0000000000400ff2 <+36>: mov $0x0,%eax
0x0000000000400ff7 <+41>: cmp %edi,%ecx
0x0000000000400ff9 <+43>: jge 0x401007 <func4+57>
0x0000000000400ffb <+45>: lea 0x1(%rcx),%esi
0x0000000000400ffe <+48>: call 0x400fce <func4>
0x0000000000401003 <+53>: lea 0x1(%rax,%rax,1),%eax
0x0000000000401007 <+57>: add $0x8,%rsp
0x000000000040100b <+61>: ret
End of assembler dump.

首先,我们来观察一下func4,它在其内部调用了本身,也就是说func4是一个递归函数。func4内部没有炸弹,我们关心如何跳出循环即可。所以我们进行倒推,最后一次跳转到<+57>是在<+43>处,此处是一个条件跳转,比较%edi和%ecx,要求%edi>=%ecx即可。

%edi是什么呢?func4中并未出现过%edi,所以我们回到phase_4,这是%edi的最后一次赋值。

1
0x0000000000401044 <+56>:    mov    0x8(%rsp),%edi

%edi的真实面目是我们输入的第一个数!同时我们需要注意,如果从<+43>跳转到<+57>再返回。我们的%rax也就是函数返回值是0,<+36>对其进行了赋值。

继续往上追溯,第一次跳转到<+36>是在<+22>处,注意到从<+0>到<+22>均未出现条件跳转。所以我们直接将他们翻译成c代码(很容易)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int func(int x, int y) {
int res = x;
res -= y;
int tmp = (res >> 31);
res = (res + tmp) >> 1;
tmp = res + y * 1;
if(input_1 <= tmp) {
跳转到<+36>
if(input_1 >= tmp) {
跳转到<+57>
return 0;
}
}
}

这段代码的输入是?从phase_4寻找答案。

1
2
3
4
0x000000000040103a <+46>:    mov    $0xe,%edx
0x000000000040103f <+51>: mov $0x0,%esi
0x0000000000401044 <+56>: mov 0x8(%rsp),%edi
0x0000000000401048 <+60>: call 0x400fce <func4>

x = 14, y = 0。所以tmp = (0+14)>>1 + 0*1 = 7。观察到input_1 <= tmp,input_1 >= tmp,所以input_1 == tmp。因此我们输入的第一个数可以是7。

观察phase_4发现,只有一处提及我们输入的第二个数,也就是<+69>。

1
2
0x0000000000401051 <+69>:    cmpl   $0x0,0xc(%rsp)
0x0000000000401056 <+74>: je 0x40105d <phase_4+81>

如果等于0,则跳转到ret,否则炸弹爆炸。

至此我们成功解决了第四个问题,(7 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
(gdb) disassemble phase_5
Dump of assembler code for function phase_5:
0x0000000000401062 <+0>: push %rbx
0x0000000000401063 <+1>: sub $0x20,%rsp
0x0000000000401067 <+5>: mov %rdi,%rbx
0x000000000040106a <+8>: mov %fs:0x28,%rax
0x0000000000401073 <+17>: mov %rax,0x18(%rsp)
0x0000000000401078 <+22>: xor %eax,%eax
0x000000000040107a <+24>: call 0x40131b <string_length>
0x000000000040107f <+29>: cmp $0x6,%eax
0x0000000000401082 <+32>: je 0x4010d2 <phase_5+112>
0x0000000000401084 <+34>: call 0x40143a <explode_bomb>
0x0000000000401089 <+39>: jmp 0x4010d2 <phase_5+112>
0x000000000040108b <+41>: movzbl (%rbx,%rax,1),%ecx
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx
0x0000000000401096 <+52>: and $0xf,%edx
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)
0x00000000004010a4 <+66>: add $0x1,%rax
0x00000000004010a8 <+70>: cmp $0x6,%rax
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>
0x00000000004010ae <+76>: movb $0x0,0x16(%rsp)
0x00000000004010b3 <+81>: mov $0x40245e,%esi
0x00000000004010b8 <+86>: lea 0x10(%rsp),%rdi
0x00000000004010bd <+91>: call 0x401338 <strings_not_equal>
0x00000000004010c2 <+96>: test %eax,%eax
0x00000000004010c4 <+98>: je 0x4010d9 <phase_5+119>
0x00000000004010c6 <+100>: call 0x40143a <explode_bomb>
0x00000000004010cb <+105>: nopl 0x0(%rax,%rax,1)
0x00000000004010d0 <+110>: jmp 0x4010d9 <phase_5+119>
0x00000000004010d2 <+112>: mov $0x0,%eax
0x00000000004010d7 <+117>: jmp 0x40108b <phase_5+41>
0x00000000004010d9 <+119>: mov 0x18(%rsp),%rax
0x00000000004010de <+124>: xor %fs:0x28,%rax
0x00000000004010e7 <+133>: je 0x4010ee <phase_5+140>
0x00000000004010e9 <+135>: call 0x400b30 <__stack_chk_fail@plt>
0x00000000004010ee <+140>: add $0x20,%rsp
0x00000000004010f2 <+144>: pop %rbx
0x00000000004010f3 <+145>: ret

做到实验五了,对于长的像地址的十六进制数,我们应该能很敏锐地察觉到。<+55>处的0x4024b0和<+81>处的0x40245e显然是地址。我们以字符串方式读取这个两块地址。

1
2
3
4
(gdb) x/s 0x4024b0
0x4024b0 <array.3449>: "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
(gdb) x/s 0x40245e
0x40245e: "flyers"

注意观察<+91>处调用了strings_not_equal函数,该函数看名字就知道,是用来做字符串比较的。通过之前的Machine-Level Programming III: Procedures中的Pass data小节中,我们得知了%rdi和%rsi为函数传参时优先使用的两个寄存器。所以应该是把两个字符串的首地址传入strings_not_equal中,进行字符串比较。一个字符串是”flyers“,另外一个是栈上的字符串。

接着我们来看看,栈上的字符串是如何构建的。

1
2
3
4
5
6
7
8
9
0x000000000040108b <+41>:    movzbl (%rbx,%rax,1),%ecx
0x000000000040108f <+45>: mov %cl,(%rsp)
0x0000000000401092 <+48>: mov (%rsp),%rdx
0x0000000000401096 <+52>: and $0xf,%edx
0x0000000000401099 <+55>: movzbl 0x4024b0(%rdx),%edx
0x00000000004010a0 <+62>: mov %dl,0x10(%rsp,%rax,1)
0x00000000004010a4 <+66>: add $0x1,%rax
0x00000000004010a8 <+70>: cmp $0x6,%rax
0x00000000004010ac <+74>: jne 0x40108b <phase_5+41>

这一段,是我们再熟悉不过的循环。其中%rax是循环变量,<+62>行是给栈上字符数组的第i个元素赋值。赋值成什么呢?注意到0x4024b0(%rdx),也就是一个字符串的第%rdx个字符,而%rdx个字符是我们输入的第%rdx个字符与上0xf后得到。所以我们需要输入6个字符,使其与上0xf后,转换为"maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"这个字符串与之对应下标的字符。转换完成后,要与flyers相同。

flyers->(9,15,14,5,6,7)->)/.%&’

1
2
3
So you got that one.  Try this one.
)/.%&'
Good work! On to the next...

phase_6

Too hard,待完成。