实验概览
BombLab
提供给我们的文件非常简单,只有一个编译不了的C文件bomb.c
,和一个目标代码文件bomb
。当运行bomb
文件时,它会要求输入6个字符串,如果其中的任何一句是错的,炸弹就会“爆炸”。我们必须利用反汇编工具逆向分析这个文件,并找到这6个字符串,从而“拆除”炸弹。
这个实验看起来就非常有趣!
运行一下bomb
文件:
提示我们输入内容,先随便输入试试!
BOOM! ,炸弹果然爆炸了!接下来就要进行紧张刺激的拆弹环节了。
实验过程
bomb.c
代码分析
每一个phase
的结构都是相同的,这里仅以phase_1
为例。
前两行将我们的输入传入phase_1
函数中,如果函数能成功返回,则接下来调用phase_defused
函数,从字面意思理解,此时炸弹就拆除成功了。
那么炸弹什么时候爆炸呢,当然就是函数无法返回的时候了,猜测phase_1
会调用一个中断程序,直接退出了程序。
所以我们的任务就是分析每一个phase_x
函数,使用正确的输入,使得函数能够成功返回。
phase_1
反汇编 phase_1
使用gdb
的disassemble
命令反汇编phase_1
Dump of assembler code for function phase_1:
0x0000000000400ee0 <+0>: sub $0x8,%rsp
0x0000000000400ee4 <+4>: mov $0x402400,%esi
0x0000000000400ee9 <+9>: callq 0x401338 <strings_not_equal>
0x0000000000400eee <+14>: test %eax,%eax
0x0000000000400ef0 <+16>: je 0x400ef7 <phase_1+23>
0x0000000000400ef2 <+18>: callq 0x40143a <explode_bomb>
0x0000000000400ef7 <+23>: add $0x8,%rsp
0x0000000000400efb <+27>: retq
End of assembler dump.
- 第2行,为函数分配栈帧
- 第3行,设置函数
strings_not_equal
传入参数 - 第4行,调用函数
strings_not_equal
,从字面意思理解,猜想如果传入字符串不同,则返回0 - 第5、6行,函数
strings_not_equal
的返回值储存在%eax
中,判断其是否为0,若为0,则跳至第8行,函数返回,炸弹拆除成功;若不为0,则跳至第7行 - 第7行,调用
explode_bomb
函数,从字面意思理解,炸弹爆炸了。
于是,只需利用x/s
指令查看0x402400
位置对应内存存的字符串即可:
这句话就是phase_1
了
key
Border relations with Canada have never been better.
成功!
phase_2
剩下部分的phase
调用与phase_1
都十分类似,直接反汇编即可
反汇编phase_2
全部代码暂不放出,后面分析时再贴上,方便查看
先看前几行
0x0000000000400efc <+0>: push %rbp
0x0000000000400efd <+1>: push %rbx
0x0000000000400efe <+2>: sub $0x28,%rsp
- 第1,2行,将被调用者保存寄存器的值入栈
- 第3行,分配栈帧
0x0000000000400f02 <+6>: mov %rsp,%rsi
0x0000000000400f05 <+9>: callq 0x40145c <read_six_numbers>
- 第5,6行,将栈顶指针
%rsp
传给%rsi
,并作为参数调用函数read_six_numbers
。从字面意思理解,本题是要我们输入6个数字。这里mov %rsp,%rsi
的目的是保存caller
中栈顶的位置,方便在read_six_numbers
中进行改值。我们不妨反汇编read_six_numbers
此时,栈的情况为:
反汇编read_six_numbers
Dump of assembler code for function read_six_numbers:
0x000000000040145c <+0>: sub $0x18,%rsp
0x0000000000401460 <+4>: mov %rsi,%rdx
0x0000000000401463 <+7>: lea 0x4(%rsi),%rcx
0x0000000000401467 <+11>: lea 0x14(%rsi),%rax
0x000000000040146b <+15>: mov %rax,0x8(%rsp)
0x0000000000401470 <+20>: lea 0x10(%rsi),%rax
0x0000000000401474 <+24>: mov %rax,(%rsp)
0x0000000000401478 <+28>: lea 0xc(%rsi),%r9
0x000000000040147c <+32>: lea 0x8(%rsi),%r8
0x0000000000401480 <+36>: mov $0x4025c3,%esi
0x0000000000401485 <+41>: mov $0x0,%eax
0x000000000040148a <+46>: callq 0x400bf0 <__isoc99_sscanf@plt>
0x000000000040148f <+51>: cmp $0x5,%eax
0x0000000000401492 <+54>: jg 0x401499 <read_six_numbers+61>
0x0000000000401494 <+56>: callq 0x40143a <explode_bomb>
0x0000000000401499 <+61>: add $0x18,%rsp
0x000000000040149d <+65>: retq
End of assembler dump.
截至第10行,寄存器及栈存储内容的指向如图所示:
在这个函数中,要做到传6个参数,用来存储6个输入的数字。很明显,这里传入了6个指针,其中4个存在寄存器上,另外2个存在栈上。由于phase_2
函数中的栈指针rsp
与这个函数中的rsi
相等,所以把所有参数存在rsi
之前的位置的目的是在返回phase_2
函数后,能够直接利用phase_2
函数的栈指针来连续地访问这6个数字。
注意到
M[%rsp+0x4]
没有用来传参数,这是为什么呢?因为通过栈传递参数时,所有的数据大小都向8的倍数对齐。
下面的问题就是如何确定这6个数字的先后位置,传递参数的寄存器使用顺序如下:
所以,我们应该输入的6个数字所在的位置就分别是:R[%rsp]
R[%rsp+0x8]
%rsi
%rsi+0x4
%rsi+0x8
%rsi+0xc
返回phase_2
函数后,利用栈顶指针调用就是: %rsp
%rsp+0x4
%rsp+0x8
%rsp+0xc
%rsp+0x10
%rsp+0x14
回到 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>: callq 0x40145c <read_six_numbers>
0x0000000000400f0a <+14>: cmpl $0x1,(%rsp)
0x0000000000400f0e <+18>: je 0x400f30 <phase_2+52>
0x0000000000400f10 <+20>: callq 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>: callq 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>: retq
End of assembler dump.
- 第7,8,9行,比较
(%rsp)
与1是否相等,不相等则引爆。可知第一个数为 1 - 看第20行,第2个数存在
0x(%rsp)
中,设为num_2
,则(%rbx)=num_2
。跳到第11行,这一行将第一个数赋值给%eax
,12,13行是将其翻倍再进行同样的比较。可知第二个数为2 - 跳到第16行,得到了第三个数的地址,第17行把我迷惑了很久,
rbp
和rbx
肯定是不相等的,不知道这样设置的意义。继续跳到第17行,进入循环。 - 这里循环的规律是,每个数都要与上一个数的2倍相等,从而可以得到剩下的4个数分别为:4,8,16,32
key
1 2 4 8 16 32
成功!
phase_3
反汇编 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>: callq 0x400bf0 <__isoc99_sscanf@plt>
0x0000000000400f60 <+29>: cmp $0x1,%eax
0x0000000000400f63 <+32>: jg 0x400f6a <phase_3+39>
0x0000000000400f65 <+34>: callq 0x40143a <explode_bomb>
0x0000000000400f6a <+39>: cmpl $0x7,0x8(%rsp)
0x0000000000400f6f <+44>: ja 0x400fad <phase_3+106>
0x0000000000400f71 <+46>: mov 0x8(%rsp),%eax
0x0000000000400f75 <+50>: jmpq *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>: callq 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>: callq 0x40143a <explode_bomb>
0x0000000000400fc9 <+134>: add $0x18,%rsp
0x0000000000400fcd <+138>: retq
End of assembler dump.
这道题与上道题非常类似。
- 首先看第5行,查看
0x4025cf
位置的内容。可以知道,要求传入2个数字
- 由上道题的启法,第2,3,4行就指明了两个数字的位置分别在栈空间
%rsp+0x8
和%rsp+0xc
。 - 第8,9,10行属于程序健壮性的考虑,判断是否输入了两个数字,否则直接引爆
- 第11,12行,说明了第一个数字应该小于等于7
- 第14行,要求跳到内存
0x402470(,%rax,8)
里面存的位置。rax
指向的内存此时存的就是第1个数,我们只知道这个数时小于等于7的,这里随便假设其为 1 试试!那么接下来就要跳到内存0x402478
中的地址 - 查看内存
0x402478
中的内容:
- 所以,下面就跳到
0x0000000000400fb9
对应代码,也就是第32行,