Lab2 缓冲炸弹

一 问题描述

      本题利用程序留出的输入缓冲区,将输入数据转换成字符串存放在栈区中,当输入字符串长度大于预留的缓冲大小时,多出的部分将覆盖栈帧中的其他内容,从而达到改变程序运行顺序,甚至添加其他程序修改数据等目的。

二 解题思路

1、  level 0

实验要求:改变程序的原始执行顺序,使程序在执行完getbuf后返回到smoke函数处运行。

解决本题,重要的是理解栈帧的形成,以及栈区内存的组织结构,增长方式等。

观察汇编代码,可以发现,该字符串的长度为24字节,与栈基地址的偏移为0x18,而%ebp+4位置存放着上一栈帧的基地址,%ebp+8的位置存放着函数的返回地址,知道了函数的返回地址位置就好办了,我们只需要覆盖此处放入目标跳转函数的地址,程序就会自动跳转。

查看汇编代码,找到smoke函数的起始地址为0x8048f95。我们只要将这个地址放在%ebp+8位置处即可,其它地方的数据不影响程序的执行。总共需要输入32字节的数据,考虑到PC都是小段机,后面4个字节数据为95 f8 04 08,前28字节的数据任意即可。由此,得到我们的输入数据:

0011223344556677889900112233445566778899001122334455667795f80408

        使用实验提供的sendstring工具将字节码转换成字符串,作为getbuf的输入数据,通过测试。

The Buffer Bomb

The Buffer Bomb

2、  level 1

实验要求:与实验1大同小异,都是让getbuf()的调用者test()执行一个代码里未调用的函数,实验2中是fizz()函数。并且传入我们的cookie作为参数,让fizz()打印出来。

高地址

本函数参数(cookie)

返回地址

保存的ebp

局部变量(buffer)

调用其他函数参数建立区域

低地址

 

从fizz函数的汇编代码里,我们可以看出:cookie存放的位置应该是(ebp-0x8),以及fizz函数的首地址为:08048f3d;通过栈帧图,可以清楚的看出,我们要做的就是:

①修改参数1的值为我们的cookie;

②把getbuf返回地址修改为fizz函数首地址,达到函数跳转的目的.

那么,同第一题,先对文件写入24字节的数组内容;然后写入4字节ebp(任意,不影响结果);再写入getbuf返回地址,显然返回地址应该写入fizz函数的首地址:3d 8f04 08;然后写入fizz的返回地址(因为只要求我们调用fizz函数即可,所以fizz的返回地址为任意);最后四个字节就是我们的cookie啦.我的cookie应该输入:cb 5e f0 55.

综上,可以得到我的字节码:

The Buffer Bomb

通过测试,结果如下。

The Buffer Bomb

3、  level 2

实验要求:让getbuf()返回到bang()而非test(),并且在执行bang()之前将global_value的值修改为cookie。

The Buffer Bomb

首先我们观察bang的汇编代码,可以推算出cookie的地址为0x804a1e0,global_value的地址为0x804a1d0,我们要做的就是将0x804d1d0内存的值修改为我们的cookie值,显然该地址和栈区相距很远,使用覆盖的方式无法完成任务,但是我们可以通过在栈区放入指令,实现对制定内存地址内容修改的目的。

首先,编写汇编代码。

mov 0x804a1e0, %eax                # 将cookie放入%eax

mov %eax, 0x804a1d0                # 将%eax放入global_value

push $0x8048ee4                    # 将bang的其实地址压栈

ret                                # 返回

通过下列指令编译我们写的汇编代码,再执行反汇编获得机器码。(该过程也可以自行计算,这样操作更简洁)

gcc -m32 -c 2.s              # 2.s是上述机器代码

objdump -d 2.o             # 反汇编获取机器码

这样,我们就可以得到汇编代码对应的机器码如下。

The Buffer Bomb

我们接下来看一下栈帧的情况。

高地址

本函数参数(cookie)

返回地址

保存的ebp

局部变量(buffer)

调用其他函数参数建立区域

低地址

 

此时我们要做的事如下:

①  将上述机器码放到指定位置(我们直接放到buffer首地址)。

②  把getbuf返回地址修改为上述机器码首地址,达到函数跳转的目的;

问题转化为寻找buffer的首地址,使用gdb在0x8048ba6处设置断点,观察%eax的值为0x55587478,该地址即为字符串的起始地址。

The Buffer Bomb

于是可以得到我们的字节码,如下所示。

The Buffer Bomb

运行结果如下,通过测试。

The Buffer Bomb

4、  level 3

实验要求:将getbuf()的返回值修改为我们的cookie,并返回到调用者test()中。

The Buffer Bomb

首先我们需要理清test函数的执行流程,才能搞清楚我们的任务。程序先调用test函数,然后在test函数中调用getbuf后返回test函数(0x8048e92位置),同时修改getbuf返回值为cookie。同时应该考虑到,如果仅仅此刻返回,那么%ebp的值很可能已经被覆盖或者改变,我们必须要确保getbuf在返回之前%ebp的值已经调整为test原来的%ebp,test原来的%ebp使用gdb很容易可以看到为0x555874b0,那么明确我们的任务如下:

The Buffer Bomb

①  将getbuf的返回值修改为cookie;

②  返回test地址为0x8048e92的地方;

③  修改%ebp的返回值为0x555874b0;

④  返回指定的内存区域。

任务已经明确,第一个任务需要修改%eax寄存器的内容,只能通过插入机器码的方式完成;第二个任务执行必须在第一个任务之后,那么插入机器码将是更为行之有效的方法。第三个任务的完成使用插入机器码或者在程序从getbuf跳转到我们的机器码之前均可完成。任务四在上面几道题中已经反复讲述。下面将提供这两种不同的解题。

解题1:任务3在getbuf返回之前完成。

编写机器码如下:

mov 0x804a1e0, %eax                     # 任务1

push $0x8048e92                         # 任务2

ret

通过下列指令编译我们写的汇编代码,再执行反汇编获得机器码。(该过程也可以自行计算,这样操作更简洁)

gcc -m32 -c 3-1.s              # 3-1.s是上述机器代码

objdump –d 3-1.o             # 反汇编获取机器码

这样,我们就可以得到汇编代码对应的机器码如下。

The Buffer Bomb

接下来,就是组织我们的字节码,我们还是先来看一下栈帧的结构。我们的字节码需要从buffer首地址处开始,直到覆盖到返回地址,中间同时覆盖了%ebp,我们只需要将%ebp的位置修改为0x555874b0即可,同样需要注意到小端机的问题。

高地址

本函数参数(cookie)

返回地址

保存的ebp

局部变量(buffer)

调用其他函数参数建立区域

低地址

        字节码如下:

The Buffer Bomb

        注:此处解释一下为什么程序代码的字节码不需要像返回地址和%ebp那样倒着写。I386所有指令的第一个字节隐含着指令的长度,在读取指令的时候也是每次只读取一个字节,然后依次分析该字节的内容,而不是像返回地址一样一次读取4个字节的长度,因此程序代码不需要像返回地址和%ebp那样倒着写。

        按照前面题目的操作将该字节码写入栈区,可以成功完成实验。

解题2:任务3在代码中完成。

编写机器码如下:

mov 0x804a1e0, %eax                       # 任务1

mov $0x555874b0, %ebp                    # 任务3

push $0x8048e92                          # 任务 2

ret

通过下列指令编译我们写的汇编代码,再执行反汇编获得机器码。(该过程也可以自行计算,这样操作更简洁)

gcc -m32 -c3.s              # 3.s是上述机器代码

objdump –d3.o             # 反汇编获取机器码

这样,我们就可以得到汇编代码对应的机器码如下。

The Buffer Bomb

        然后组织我们的字节码如下:

The Buffer Bomb

按照前面题目的操作将该字节码写入栈区,可以成功完成实验。

The Buffer Bomb

5、  level 4

实验要求:用bufbomb的-n参数进入Level 4模式,此时程序不会调用getbuf()而是其升级版getbufn()。getbufn()的调用者会使用alloca库函数随机分配栈空间,然后连续调用getbufn()五次。我们的任务是保证getbufn()每次都返回我们的cookie而不是1。

本题乍一看和level 3极其相似,重点在于该测试函数会连续调用getbufn()五次,而我们的字节码需要连续五次满足题目需求,每次调用getbufn,题目会通过alloc随机分配栈空间,那就意味着每次的buffer起始地址可能是不一样的,从上面的题目我们知道,我们设定的getbuf返回地址都是buffer起始地址,而这个地址此刻又是动态且不固定的,那么我们将难以找到一个固定的静态地址入口。但是我们又只能通过内存覆盖的方式改变函数返回地址,这个地址又必须是一个静态地址。这两者之间看似出现了冲突。

但是,有一个东西是不变的,testn的栈区是不变的,5次调用getbufn的栈空间位置也是不变的,变化的只是buffer的起始地址,而且从下面的汇编代码中可以发现,getbufn的栈空间大小为280个字节,而字符串buffer则需要占用264个字节。

The Buffer Bomb

        现在明确我们的任务如下:

①  将getbufn的返回值修改为cookie;

②  返回testn中getbufn调用后的下一条语句

③  恢复%ebp寄存器的内容,原来的被我们覆盖;

④  返回指定的内存区域。

第一个任务和第二个任务之前已经完成过,此处不再赘述。第三个任务与之前略有不同,之前题目中test的%ebp可以通过覆盖正确的%ebp完成,但是此时我们无法准确推算%ebp的位置,因此只能通过代码的方式完成。

编写汇编代码如下:

mov 0x804a1e0, %eax                  # 将cookie赋给eax作为getbuf返回值

lea 0x18(%esp), %ebp                  # 将ebp寄存器的内容恢复(原来被我们覆盖)

push $0x8048e22                     # testn中getbufn调用后的下一条语句

ret                                 # 返回

通过下列指令编译我们写的汇编代码,再执行反汇编获得机器码。(该过程也可以自行计算,这样操作更简洁)

gcc -m32 -c4.s              # 4.s是上述机器代码

objdump –d4.o             # 反汇编获取机器码

这样,我们就可以得到汇编代码对应的机器码如下。

The Buffer Bomb

解决了操作的机器码,我们还需要解决一个问题. 因为我们要覆盖掉原来的数据,我把这个函数的栈帧情况画一下:

高地址

Getbufn返回地址

存放ebp

栈内容区(280字节)

未知区域(大小不定)

Buffer区(214字节)

调用其他函数参数建立区域(0字节)

低地址

        帧指针申请了264的空间存放buffern,然后是4字节覆盖ebp内容,然后是4位getbufn返回地址(我们应该调整为buffern的首地址呢,这样就可以执行我们上面反汇编出来的机器码了).最后我们要解决的问题就是怎么才能拿到buf的首地址?

        虽然buffern首地址可能会发生改变,但是其变化也是有范围的,即buffern首地址最高为0x55587358(%ebp – 0x108),最低为0x55587348(%ebp –0x118),我们完全可以直接将字节码放在最高开始位置(字符串的存放从低地址开始,向高地址方向增长),可以保证5次返回都不会轮空。因此我们取函数返回地址为0x55587358,这样无论什么情况下,程序都能执行到此处。

这样组织我们的字节码如下:

The Buffer Bomb

将上述字节码转换成字符串,运行程序,可以通过测试。

The Buffer Bomb

事实上,这道题有些虎头蛇尾,解题过程中,只需要把我们的汇编代码放在字符串48字节以后的位置,返回地址直接使用下图中使用gdb观察到的地址都可以完成任务。

The Buffer Bomb


相关文章: