第二个炸弹

2025-05-18
1826字

接下来把断点打在第82行,开始拆除第二个炸弹。

run answer.txt之后,还是mov %rax,%rdi,然后调用phase_2

我们可以直接查看bomb.s中的汇编代码,必要时再在gdb中操作。

后续列出的汇编代码进行了格式化,指令地址放在了;开头的注释中。

phase_2 上

phase_2汇编代码如下:

 1push %rbp                      ;400efc
 2push %rbx                      ;400efd
 3sub  $0x28,%rsp                ;400efe
 4mov  %rsp,%rsi                 ;400f02
 5call 40145c <read_six_numbers> ;400f05
 6cmpl $0x1,(%rsp)               ;400f0a
 7je   400f30 <phase_2+0x34>     ;400f0e
 8call 40143a <explode_bomb>     ;400f10
 9jmp  400f30 <phase_2+0x34>     ;400f15
10mov  -0x4(%rbx),%eax           ;400f17
11add  %eax,%eax                 ;400f1a
12cmp  %eax,(%rbx)               ;400f1c
13je   400f25 <phase_2+0x29>     ;400f1e
14call 40143a <explode_bomb>     ;400f20
15add  $0x4,%rbx                 ;400f25
16cmp  %rbp,%rbx                 ;400f29
17jne  400f17 <phase_2+0x1b>     ;400f2c
18jmp  400f3c <phase_2+0x40>     ;400f2e
19lea  0x4(%rsp),%rbx            ;400f30
20lea  0x18(%rsp),%rbp           ;400f35
21jmp  400f17 <phase_2+0x1b>     ;400f3a
22add  $0x28,%rsp                ;400f3c
23pop  %rbx                      ;400f40
24pop  %rbp                      ;400f41
25ret                            ;400f42

前3行和后3行在Introduction中已经介绍过了,第3行开辟了40字节的临时空间。

第4行的mov %rsp,%rsi%rsp作为函数的第2个参数,而第5行的函数名是read_six_numbers,似乎意味着在read_six_numbers内,直接读取了6个数字放在了phase_2的栈帧空间内。

这样有些武断,还是看下read_six_numbers的汇编代码。

read_six_numbers

 1sub  $0x18,%rsp                     ;40145c
 2mov  %rsi,%rdx                      ;401460
 3lea  0x4(%rsi),%rcx                 ;401463
 4lea  0x14(%rsi),%rax                ;401467
 5mov  %rax,0x8(%rsp)                 ;40146b
 6lea  0x10(%rsi),%rax                ;401470
 7mov  %rax,(%rsp)                    ;401474
 8lea  0xc(%rsi),%r9                  ;401478
 9lea  0x8(%rsi),%r8                  ;40147c
10mov  $0x4025c3,%esi                 ;401480
11mov  $0x0,%eax                      ;401485
12call 400bf0 <__isoc99_sscanf@plt>   ;40148a
13cmp  $0x5,%eax                      ;40148f
14jg   401499 <read_six_numbers+0x3d> ;401492
15call 40143a <explode_bomb>          ;401494
16add  $0x18,%rsp                     ;401499
17ret                                 ;40149d

前2条指令都比较好理解(注意此时%rsi指向phase_2的栈顶)。

lea 0x4(%rsi),%rcx这条指令的意思是,将%rsi的值加上0x4,结果保存到%rcx中。

mov %rax,0x8(%rsp)这条指令的意思是,将%rax的内容放到首地址为(%rsp) + 0x8的内存块中,其中(%rsp)表示对%rsp取内容。

所以在执行mov $0x4025c3,%esi之前,相关寄存器和栈应该长这样:

图中紫色的是phase_2的栈帧(注意里面的数字是十进制的),箭头表示这些寄存器和两个橙色的内存块(每个8 Bytes)是作为指针使用的,分别指向这些地方。所以我们之前判断的没错,phase_2栈帧最后的24个字节,是作为6个参数(第3参数到第8参数)传入到sscanf函数(<__isoc99_sscanf@plt>)中保存读取到的6个数字的。

接下来的两条指令mov $0x4025c3,%esi0x4025c3这个地址作为sscanf的第2个参数,$0x0,%eax预先将返回值设置为0。

sscanf的第一个参数呢?%rdi的值没有改动过,所以它还是read_line函数的返回值,也就是answer.txt的第二行。

sscanf的函数签名是int sscanf(const char *str, const char *format, ...);,所以第二个参数0x4025c3指向的就是格式化字符串,用x /s 0x4025c3查看它的值:

%d %d %d %d %d %d

所以第二个炸弹的密码应该是6个用空格分隔的数字。

执行完sscanf之后,还进行了参数检查:

  • cmp $0x5,%eax:等价于执行(%eax) - 0x5,但是不保存结果,而是用结果来更新标志寄存器。
  • jg 401499:Jump if Greater。

也就是如果sscanf的返回值大于5就会跳过炸弹引爆函数。

phase_2 下

通过前面的分析我们已经知道,read_six_numbers读取了6个数字保存在phase_2的栈帧中。

接下来的几条指令:

6cmpl $0x1,(%rsp)               ;400f0a
7je   400f30 <phase_2+0x34>     ;400f0e
8call 40143a <explode_bomb>     ;400f10
9jmp  400f30 <phase_2+0x34>     ;400f15

判断第一个数字是不是1,如果不是1直接引爆炸弹,如果不是跳转到400f30这条指令。

10mov  -0x4(%rbx),%eax       ;400f17
11add  %eax,%eax             ;400f1a
12cmp  %eax,(%rbx)           ;400f1c
13je   400f25 <phase_2+0x29> ;400f1e
14call 40143a <explode_bomb> ;400f20
15add  $0x4,%rbx             ;400f25
16cmp  %rbp,%rbx             ;400f29
17jne  400f17 <phase_2+0x1b> ;400f2c
18jmp  400f3c <phase_2+0x40> ;400f2e
19lea  0x4(%rsp),%rbx        ;400f30
20lea  0x18(%rsp),%rbp       ;400f35
21jmp  400f17 <phase_2+0x1b> ;400f3a

400f30开始的这3条指令,计算了两个地址,然后跳转到了上方400f17,如果一切顺利,指令从400f17执行到了400f2c,又会跳转回400f17

所以这是一个典型的循环结构,400f30开始的这三条指令定义了循环的初始条件:%rbx指向第2个数字,%rbp指向第6个数字之上的位置。

然后看一下这个循环做了什么:

  • 第10行:把前一个数字放到%eax
  • 第11行:等价于%eax = (%eax) + (%eax)%eax内的值变为原来的两倍。
  • 第12-14行:如果%rbx(当前数字)和%eax(前一个数字的两倍)相等,就跳过炸弹引爆函数。
  • 第15-18行:将%rbx原地加4(移到下一个数字),并判断是否和%rbp相等,相等说明6个数字已经循环完毕,需要跳出循环。

综上所述,这个炸弹的密码是第1个数字是1、公比为2、长度为6的等比数列,也就是1 2 4 8 16 32,写入answer.txt并运行:

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!

BOOM!!!
The bomb has blown up.

没有问题。

总结

这个炸弹主要让你了解在汇编层面循环是如何实现的,解密过程也更复杂一些。