拆除csapp二进制炸弹超详细的解析之phase_2

概述

接着 拆除csapp二进制炸弹超详细的解析之phase_1 继续解析phase_2。

cgdb2截图

phase_2 解析

同样执行si命令:

 96│ bomb.c:
 97│ 81          input = read_line();
 98├──> 0x0000000000400e4e <+174>:   callq  0x40149e <read_line>
 99│
100│ 82          phase_2(input);
101│    0x0000000000400e53 <+179>:   mov    %rax,%rdi
102│    0x0000000000400e56 <+182>:   callq  0x400efc <phase_2>
103│
104│ 83          phase_defused();
105│    0x0000000000400e5b <+187>:   callq  0x4015c4 <phase_defused>



                      Figure 2.1

在 Figure 2.1 中第98行调用 read_line 函数用以读取我们的输入,同样地返回值为%rax,里面存储了我们的输入内容,可以用下面的命令查看:

(gdb) x /s $rax
0x6037d0 <input_strings+80>:    "I'm in phase_2 for test"

可以看到我们输入的内容是 I’m in phase_2 for test

接下来执行si命令,开始执行 Figure 2.1 中的第101行代码,执行结果是把%rax中的值复制到%rdi,%rdi一般作为第1个参数。

然后进入第102行代码里面:

 1│ Dump of assembler code for function phase_2:
 2├──> 0x0000000000400efc <+0>:     push   %rbp
 3│    0x0000000000400efd <+1>:     push   %rbx
 4│    0x0000000000400efe <+2>:     sub    $0x28,%rsp
 5│    0x0000000000400f02 <+6>:     mov    %rsp,%rsi
 6│    0x0000000000400f05 <+9>:     callq  0x40145c <read_six_numbers>
 7│    0x0000000000400f0a <+14>:    cmpl   $0x1,(%rsp)
 8│    0x0000000000400f0e <+18>:    je     0x400f30 <phase_2+52>
 9│    0x0000000000400f10 <+20>:    callq  0x40143a <explode_bomb>
10│    0x0000000000400f15 <+25>:    jmp    0x400f30 <phase_2+52>
11│    0x0000000000400f17 <+27>:    mov    -0x4(%rbx),%eax
12│    0x0000000000400f1a <+30>:    add    %eax,%eax
13│    0x0000000000400f1c <+32>:    cmp    %eax,(%rbx)
14│    0x0000000000400f1e <+34>:    je     0x400f25 <phase_2+41>
15│    0x0000000000400f20 <+36>:    callq  0x40143a <explode_bomb>
16│    0x0000000000400f25 <+41>:    add    $0x4,%rbx
17│    0x0000000000400f29 <+45>:    cmp    %rbp,%rbx
18│    0x0000000000400f2c <+48>:    jne    0x400f17 <phase_2+27>
19│    0x0000000000400f2e <+50>:    jmp    0x400f3c <phase_2+64>
20│    0x0000000000400f30 <+52>:    lea    0x4(%rsp),%rbx
21│    0x0000000000400f35 <+57>:    lea    0x18(%rsp),%rbp
22│    0x0000000000400f3a <+62>:    jmp    0x400f17 <phase_2+27>
23│    0x0000000000400f3c <+64>:    add    $0x28,%rsp
24│    0x0000000000400f40 <+68>:    pop    %rbx
25│    0x0000000000400f41 <+69>:    pop    %rbp
26│    0x0000000000400f42 <+70>:    retq
27│ End of assembler dump.



                     Figure 2.2

在 Figure 2.2 中第2行和第3行的两个的push作用是把%rbp和%rbx两个寄存器中的值入栈,起到了”保护现场”的作用。

第4行的 sub $0x28,%rsp 中的十六进制0x28相对应的十进制值为40,是编译器计算后觉得够用设置的大小。

第5行把栈顶rsp复制到rsi中。

第6行 read_six_numbers 函数读入6个数,当我们执行到第6行 callq 0x40145c 时,再执行si命令,跟进去会显示:

 1│ Dump of assembler code for function read_six_numbers:
 2├──> 0x000000000040145c <+0>:     sub    $0x18,%rsp
 3│    0x0000000000401460 <+4>:     mov    %rsi,%rdx
 4│    0x0000000000401463 <+7>:     lea    0x4(%rsi),%rcx
 5│    0x0000000000401467 <+11>:    lea    0x14(%rsi),%rax
 6│    0x000000000040146b <+15>:    mov    %rax,0x8(%rsp)
 7│    0x0000000000401470 <+20>:    lea    0x10(%rsi),%rax
 8│    0x0000000000401474 <+24>:    mov    %rax,(%rsp)
 9│    0x0000000000401478 <+28>:    lea    0xc(%rsi),%r9
10│    0x000000000040147c <+32>:    lea    0x8(%rsi),%r8
11│    0x0000000000401480 <+36>:    mov    $0x4025c3,%esi
12│    0x0000000000401485 <+41>:    mov    $0x0,%eax
13│    0x000000000040148a <+46>:    callq  0x400bf0 <__isoc99_sscanf@plt>
14│    0x000000000040148f <+51>:    cmp    $0x5,%eax
15│    0x0000000000401492 <+54>:    jg     0x401499 <read_six_numbers+61>
16│    0x0000000000401494 <+56>:    callq  0x40143a <explode_bomb>
17│    0x0000000000401499 <+61>:    add    $0x18,%rsp
18│    0x000000000040149d <+65>:    retq
19│ End of assembler dump.
 ~│



                      Figure 2.3

可以看到在 Figure 2.3 中第14行 cmp $0x5,%eax ,即如果第13行 callq 0x400bf0 <__isoc99_sscanf@plt> 函数的返回值eax如果
大于5,即第15行 jg 0x401499 <read_six_numbers+61> 会执行,jg表示大于执行,所以会跳转到第17行 add $0x18,%rsp ,
完成栈平衡。

由于我们输入的内容是 I’m in phase_2 for test ,个数刚好为5,所以我们需要修改我们输入的内容,鉴于 Figure 2.2 中第7行
cmpl $0x1,(%rsp),这次我们把我们输入的6个数字都修改为1,再次重新运行我们的程序,当我们运行到 Figure 2.2 中第6行
read_six_numbers函数时,执行si命令,到达第7行,此时我们查看以rsp为起点连续内存中存储的6个数:

(gdb) p $rsp
$1 = (void *) 0x7fffffffdda0
(gdb) x /6wd $rsp
0x7fffffffdda0: 1       1       1       1
0x7fffffffddb0: 1       1

继续执行 Figure 2.2 中的代码,第7行把1和(%rsp)里面保存的值进行比较,第8行如果相等的话程序跳到第20行
执行 lea 0x4(%rsp),%rbx ,第20行lea的作用是取地址而不是取地址里面保存的值,所以第20行的效果是取rsp的地址加4字节的值放入
rbx寄存器中,接下来执行第21行 lea 0x18(%rsp),%rbp ,其中十六进制0x18相对应的十进制值为24, 即24字节,一个整数占4个字节,
所以24 / 4 = 6, 刚好是6个数。

接下来的代码是 crack the code的关键。在 Figure 2.2 中第7行 cmpl $0x1,(%rsp) 比较rsp地址中存储的值是否为1,第8行中的je表示
相等时跳转到第20行执行 lea 0x4(%rsp),%rbx ,rsp的地址加上4字节的大小得到的地址复制i到rbx,第21行 lea 0x18(%rsp),%rbp
表示把最后一个数的地址复制给rbp,第22行jmp表示无条件跳转,现在跳转到第11行,执行 mov -0x4(%rbx),%eax ,相当与把rbx减去
4字节的大小的地址里面的值复制到eax,即把rsp的值复制到eax中,因为在第7行rsp的值为1,然后跳转到第20行,rsp+4后的地址复制给了
rbx,现在rbx又减去4,所以此时即把rsp的值复制到eax中。eax里面的值为1,第12行相当与eax的值乘以2,然后拿eax里面的值和rbx里面
存放的值比较,看看它们是否相等,注意上面我们刚提到rbx的值为rsp加4,即rbx的地址为第2个数的地址。

如果第1个数乘以2等于第2个数,那么执行第14行,然后跳转到第16行执行 add $0x4,%rbx ,即rbx的值更新为原来的rbx加4,也就是
说rbx现在的值为第3个数的地址,执行第17行再次比较 rbp和rbx的值是否相等,如果不相等则再次跳转到第11行,重复执行,直到
rbp和rbx的值不相等,总共循环执行6次,由第1个数乘以2,后面的数等于前面的一个数乘以2,到第6个数为止。

所以我们得到本关卡的通关证为 1 2 4 8 16 32