计算机系统基础实验:缓冲区溢出攻击

发布时间 2023-04-21 21:11:52作者: catting123

实验目的

  1. 加深对IA-32函数调用规则和栈帧结构的理解

  2. 了解关于内存溢出攻击的原理,并通过 5 个 level 关卡的实验对堆栈有进一步理解

  3. 通过模拟缓冲区溢出攻击,了解如何去进行栈保护

  4. 了解程序的运行时操作以及了解这种形式的安全性弱点的性质,以便编写系统代码时可以避免这种情况

实验内容

  1. 对目标程序实施缓冲区溢出攻击(buffer overflow attacks)

  2. 通过造成缓冲区溢出来破坏目标程序的栈帧结构

  3. 继而执行一些原来程序中没有的行为

实验环境

  1. 32 位 Linux 操作系统

  2. 下载 buflab-handout 文件夹,并放入虚拟机

实践技能

Linux基本命令、IA32 汇编程序、gdb调试、objdump反汇编、gcc等。

实验要求

  1. Level 0 test 运行完后,不直接返回退出,而是跳到smoke函数处,继续运行,当 smoke 运行完毕后退出

  2. Level 1 在 Level 0 的基础上,使 getbuf 函数的返回指向 fizz 函数,同时将 fizz 函数的参数置为 userid 对应的 cookie 值

  3. Level 2 使 getbuf 调用后不执行 test 函数,而是执行 bang 函数,但是同时要修改global_value 的值为 cookie 值

  4. Level 3 使 getbuf 调用后,返回到 test 当中,但是不能破坏为 test 函数维护的堆栈状态,同时要让 test 函数调用 getbuf 后的返回值为自己的 cookie

  5. Level 4 要求和 Level 3 一致,但需要加上 –n 参数运行 bufbomb,此时会进入 testn 和 getbufn 函数而不是 test 和 getbuf 函数

实验过程

首先进入解压后的文件夹,在命令行输入“bufbomb -u <学号>”,其中“-u <学号>”是需要你提供的命令行参数,目标程序通过getcookie函数将学号转换成一个cookie(和使用makecookie完全一样的cookie),cookie将作为你程序的唯一标识,使你运行程序的栈帧地址与其他同学不一样。以下是我的userid和cookie:

![QQ截图20221125112035](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image002.gif)

由于汇编代码比较多,使用objdump -d命令将其反汇编到bufbomb.asm文件中查看。

![QQ截图20221125113409](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image004.gif)

Level 0

任务是让目标程序调用smoke函数。test函数调用了getbuf函数,getbuf函数的功能是从标准输入(stdin)读入一个字符串。以下是getbuf函数的汇编代码:

![QQ截图20221125114325](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image006.gif)

792行ebp-0x28的地址为Gets函数的参数,Gets函数将以该地址为起点向地址增大的方向保存字符,共0x28(40)个字节,即buf缓冲区为0x28(40)个字节,因此需要将 getbuf 函数的返回地址覆盖为smoke函数第一条语句的地址。以下为smoke函数的汇编代码:

![QQ截图20221125114943](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image008.gif)

361行为smoke函数的首地址0x08048bd2。

攻击字符串用来覆盖数组buf,进而溢出并覆盖ebp和ebp上面的返回地址,构造的攻击字符串的大小应为40+4+4=48个字节。攻击字符串的最后4字节应为smoke函数的首地址0x8048bd2,d2 8b 04 08(小端方式)。

![QQ截图20221202094640](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image010.gif)

为了方便可以把构造的字符串放在一个文本文件中,然后使用hex2raw工具将自己编写的攻击代码转化为字符串格式,最后执行bombbuf攻击目标程序。成功!

![QQ截图20221202093342](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image012.gif)

Level 1

任务是让目标程序使用特定参数调用fizz函数,以下为fizz函数汇编代码:

![QQ截图20221202094815](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image014.gif)

375行ebp+0x8传入函数的一个参数,376行将这个参数与0x804d140这个内存地址中的值比较,这个内存地址中保存着cookie的值,如果这两个值相等则成功。和第0关类似,首先用fizz函数的首地址覆盖getbuf的返回地址,使目标程序执行fizz函数,然后将fizz函数的返回地址覆盖掉,再用cookie覆盖返回地址上方的参数。这样就可以先跳转到fizz函数,然后在跳转后自己取到cookie作为参数。fizz函数的返回地址可以用任意4个字节的数覆盖,例如可以用00 00 00 00覆盖掉,其作用只是用来占位。

构造攻击字符串为:40(覆盖掉 buf)+4(覆盖掉 ebp 旧值)+4(用 fizz 函数地址0x8048bfa替换,冲掉返回地址)+4(占位置)+4(ebp+8位置为函数第一个参数,把 cookie 值按小端方式放到这里)。

![QQ截图20221202103346](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image016.gif)

我的cookie值为0x5216b8f0,按照上述步骤执行,成功!

![QQ截图20221202103330](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image018.gif)

Level 2

任务是让目标程序调用bang函数,并篡改全局变量。以下为bang函数的汇编代码:

![QQ截图20221202103700](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image020.gif)

因为全局变量存放的位置为.bss节或.data节,并不是存放在栈中的,前面的方法只能修改栈中的内容,无法修改全局变量的内容。所以我们需要换一种思路,先编写一段恶意代码,用其修改全局变量的值。

397行bang函数首地址为0x8048c49。401行将0x804d138内存地址中的内容移动到eax寄存器中,402行将0x804d140内存地址中的内容和eax寄存器中的内容进行比较。使用gdb调试,查看这两个内存地址中的内容:

![QQ截图20221202104606](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image022.gif)

0x804d138内存地址中的内容为全局变量,0x804d140内存地址中的内容为cookie值。

编写恶意代码,mov指令将cookie值赋给全局变量,push指令将bang函数的首地址压栈。

![QQ截图20221202112903](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image024.gif)

将恶意代码保存为.s汇编代码文件,然后用gcc –m32 –c生成.o可重定位目标文件,然后用objdump –d反汇编得到机器码。

![QQ截图20221202112936](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image026.gif)

在getbuf函数中,开辟的占空间为0x34(52)字节,而ebp-0x28只用了0x28(40)字节,因为buf 数组会被加载进入缓冲区,eax寄存器中放的就是buf数组首地址。

![QQ截图20221125114325](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image027.gif)

使用gdb调试,使用b命令在调用Gets函数处设置端点,查看eax寄存器中的内容,得到buf的首地址为0x55683228。

![QQ截图20221202111458](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image029.gif)

构造攻击字符串进行攻击,成功!

![QQ截图20221202113006](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image031.gif)

![QQ截图20221202112835](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image033.gif)

Level 3

任务是无感攻击,并传递有效返回值。前面的攻击都是使目标程序跳转到特定函数,进而利用exit函数结束目标程序运行,在这个过程中我们都把原来的恢复现场需要用的返回地址和原test的ebp给破坏了。boom这一关中,我们将修复这些被我们破坏的栈状态信息,让最后还是回到test中,让被攻击者不容易发现我们动了手脚,另外,构造攻击字符串,使得getbuf都能将正确的cookie值返回给test函数,而不是返回值1。设置返回值也就是更改eax(eax中保存的就是函数的返回值)的值,可以用mov指令设置eax存的为cookie值,更改完要进入test函数继续执行。以下为test函数的汇编代码:

![QQ截图20221202201626](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image035.gif)

需要将call getbuf函数的下一条指令的地址即492行的0x8048d64压入返回地址。编写恶意代码,先将返回值修改为cookie值,然后将call getbuf函数的下一条指令的地址压入返回地址,最后返回。

![QQ截图20221202112903](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image036.gif)

和第2关类似,将这些指令反汇编得到机器码。

![QQ截图20221202202519](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image038.gif)

恶意代码准备好以后,需要将返回地址更改为指向这个代码的位置,把恶意代码放到buf数组缓冲区内,返回地址和第2关一样。接下来要恢复ebp的值,需要得到ebp旧值。使用gdb调试,在getbuf函数第一行push ebp 的地址处设置断点。

![QQ截图20221202202614](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image040.gif)

然后使用x/p命令查看ebp的值,由图可知ebp的值为0x55683270,用原来的值覆盖被修改后的值,即无感攻击。

![QQ截图20221202202735](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image042.gif)

综上,构造攻击字符串进行攻击,成功!

![QQ截图20221202204538](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image044.gif)

![QQ截图20221202203123](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image046.gif)

Level 4

任务是栈帧地址变化时的有效攻击。这一关难度比前面都大,但也是承接上一关的,构造攻击字符串使getbufn函数,返回cookie值至testn函数,而不是返回值1,需要将cookie值设为函数返回值,复原被破坏的栈帧结构,并正确地返回到testn函数。但这一关与之前最大的不同在于地址空间随机化,每次攻击,被攻击函数的栈帧内存地址都不同,也就是函数的栈帧位置每次运行时都不一样,不能准确地跳转到栈空间的某个特定地址。因此,要想办法保证每次都能够正确复原原栈帧被破坏的状态,使程序每次都能够正确返回。

进入这一关需要加入-n选项,调用的函数是testn和getbufn,而不是前面的test和getbuf。这道题有5个test case,需要全部通过才算成功。

以下为testn的汇编代码:

![QQ截图20221202204942](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image048.gif)

虽然ebp是随机的,但是ebp和esp的相对位置是绝对的,根据527行可知ebp = esp + 0x14 + 4 = esp + 0x18。530行调用getbufn函数,返回后要从0x8048dd9这个地址开始执行,需要将这个地址压栈,并设置cookie给eax。

编写恶意代码:

![QQ截图20221202210401](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image050.gif)

将这些指令反汇编成机器码:

![QQ截图20221202210505](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image052.gif)

以下为getbufn函数汇编代码:

![QQ截图20221202205020](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image054.gif)

根据803行可知一共要填入0x208+4+4=0x210(528)字节。因为是随机跳转,所以把恶意代码放在最后,并把前面的全部空间都填上nop(0x90),使用nop进行滑行。nop不会执行任何操作,只有PC加一,机器码为0x90。不管程序跳转到哪个nop,最后都会执行到自己编写的恶意代码处。接下来需要寻找要跳转的地址,因为是随机化,所以buf首地址不确定。

使用用gdb调试,使用b命令在0x8049165地址处设置断点,查看eax的值。每执行一次,都要用c命令继续,然后执行下一次。重复五次,需要注意的是,需要在用r命令时使用-nu加uid。

![QQ截图20221202212706](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image056.gif)

根据调试的结果得到5个buf的起始地址:0x55683048、 0x55682ff8、0x55682fe8、0x55682fe8、0x55683038。取最大的地址0x0x55683048作为返回地址,这样就能滑行到恶意代码处,然后执行恶意代码。

综上编写恶意代码:

![QQ截图20221203173008](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image058.gif)

保存这段代码到.txt文件中,然后执行./hex2raw,注意需要添加-n选项,成功!

![QQ截图20221203172215](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image060.gif)

五个关卡的完整截图如下:

![QQ截图20221203174339](file:///C:/Users/ASUJS/AppData/Local/Temp/msohtmlclip1/01/clip_image062.gif)

完结撒花~~~

实验总结

通过这次实验,学习到了缓冲区溢出的原理和机制,加深了对函数调用规则的理解,也加深了对栈帧结构的理解。课本上大致描述了栈保护机制,通过模拟缓冲区溢出攻击,了解了栈保护机制,也加深了对课本理论知识的理解。把理论付诸实践,提高了自己的动手能力。通过对这些原理的学习,也提醒我以后在自己编写代码的过程中,要避免写出类似的危险代码,避免不必要的损失。同时,我还学习到了程序运行时操作,了解了这种形式的安全性弱点的性质,以后编写代码时也要多加小心。这次实验比上次实验要偏应用一些,第四关很难,所以做了很长时间,但是也学到了很多。