课程实验四 buflab

本文详细介绍了如何通过一步步实验,理解缓冲区溢出原理,利用getbuf漏洞实现smoke、fizz、bang和testn函数的调用,挑战level0至level4的内存溢出攻击技巧。通过GDB调试和汇编代码分析,深入掌握了程序栈结构和函数调用的底层细节。
摘要由CSDN通过智能技术生成

实验题目:内存溢出攻击的实验

 

实验目的:理解缓冲区溢出原理,堆栈的过程,函数调用的实现过程

 

实验环境:个人电脑、32 位ubuntu 18.04环境、GDB调试工具

 

实验内容及操作步骤:

第一步,分析文件

将buflab-handout.tar.gz文件夹进行解压,解压发现里面三个文件都是可执行文件,结合实验指导文档可以知道bufbomb是一个有缓冲区溢出漏洞的程序,makecookie是一个可以根据用户不同的userid(我的即为zjh)生成的唯一的cookie,hex2raw是使编写的缓冲区利用代码转换为一个字符串,只有经过转换以后才可以输入到getbuf中,因为本身test函数就是需要输入字符串格式的,如果直接输入的漏洞利用代码是不可以的。也就是说每次编写的漏洞利用代码都要经过这个文件进行一下转换再输入到bufbomb中。

第二步,实验准备

  1. 先利用mskecookie程序将自己的userid生成的唯一的cookie:

  1. 利用objdump对bufbomb可执行文件进行反汇编,观察我们要做的步骤:

结合实验指导文档以及对反汇编代码观察发现,整个实验是针对getbuf具有的漏洞展开的,getbuf函数类似于gets函数,它是读入一段字符串,字符串以\n(“\n”的对应的ASCII值是0x0a,即输入的字符串不能包含0x0a)或eof表示结束,并将其存储起来,但是getbuf提供的缓冲区只有32个字符大小,getbuf本身又对输入的字符是否超过缓冲区大小进行安全检查,从而带来了缓冲区溢出漏洞。

然后就是bufbomb的不同的参数的含义。主要用到的就是-u,确保不同的userid用不同的cookie,然后就是-n是为了level4,栈基址随机化模式的时候使用的。

本次实验分为level0到level4,与bomb实验类似,依次解决,完成本次实验。

第三步,实验操作

  1. level0: Candle (10 pts)

【任务要求】

在bufbomb中调用以下getbuf函数:

1 void test()

2 {

3      int val;

4   /*在堆栈上放金丝雀来检测可能的损坏*/

5      volatile int local = uniqueval();

6

7      val = getbuf();

8

9   /*检查损坏的堆栈*/

10     if (local != uniqueval()) {

11         printf("Sabotaged!: the stack has been corrupted\n");

12     }

13     else if (val == cookie) {

14         printf("Boom!: getbuf returned 0x%x\n", val);

15         validate(3);

16     } else {

17         printf("Dud: getbuf returned 0x%x\n", val);

18     }

19 }

当getbuf执行返回语句(getbuf的第5行)时,程序通常会继续执行,在函数测试中(在这个函数的第7行)。我们想要改变这种行为。在bufbomb文件中,有一个smoke函数,代码如下:

void smoke()

{

printf("Smoke!: You called smoke()\n");

validate(0);

exit(0);

}

我们的任务是让bufbomb在getbuf执行它的return语句时执行smoke函数,而不是返回测试。要注意输入的字符串也可能损坏部分堆栈,而不是直接与此阶段相关,但这不会造成问题,因为smoke函数导致程序直接退出。

一些建议:

•需要设计输入的字符串这一级别的所有信息可以确定通过level0的 bufbomb的反汇编版本。使用objdump -d来获得这个被拆解的版本。

•注意字节顺序。

•使用gdb来逐步执行getbuf的最后几条指令

•对于getbuf, buf在栈帧中的位置取决于哪个gcc版本,用来编译bufbomb,所以必须读一些汇编来找出它的真正位置。

【解决方法】

首先进入gdb调试,观察到调用路径为:

查看getbuf的反汇编代码:

确定 buffer 的首地址为 -0x28(%ebp),而返回地址存放在 0x4(%ebp),两者间隔 44 个字节,同时返回地址占 4 个字节的空间,所以至少需要 48 个字节长度的字符。

然后查看smoke函数的反汇编代码查看smoke函数的入口地址:

这里看到smoke的入口地址的最后是0x0a,而0x0a是换行\n的ASCII值,所以不可以输入,那么我们就输入0x8048e0b来代替。

【结果检验】

构造如下48 个字节长度的字符串(系统是小端法存储,则多字节数据要倒着写):

尝试运行,成功调用:

  1. level1: Sparkler (10 pts)

【任务要求】

在bufbomb文件中还有一个fizz函数,包含以下C代码:

void fizz(int val)

{

if (val == cookie) {

printf("Fizz!: You called fizz(0x%x)\n", val);

validate(1);

} else

printf("Misfire: You called fizz(0x%x)\n", val);

exit(0);

}

 

与level0类似,我们的任务是让bufbomb执行fizz的代码,而不是返回测试。但是,在这种情况下,必须使它看起来像嘶嘶作响,就好像传递了cookie作为它的参数一样。

一些建议:

•请注意,程序不会真正调用fizz,它只会执行其代码。这对于将cookie放在堆栈的何处具有重要意义。

【解决方法】

观察fizz函数的反汇编代码:

fizz入口函数地址为0x08048daf,发现 fizz 函数传入了一个参数,并且要求与 cookie 相同。且第一个参数的位置为 0x8(%ebp),也就是说需要将ebp+8的位置改为cookie。但由于进入 fizz 是通过 return,ebp 的值会加 4,所以 fizz 的 0x8(%ebp) 对应 getbuf 的0xc(%ebp),需要填充被跳过的 4 个字节

【结果检验】

构造如下字符串(系统是小端法存储,则多字节数据要倒着写):

尝试运行,成功调用:

  1. level2: Firecracker (15 pts)

【任务要求】

一种更复杂的缓冲区攻击形式涉及提供一个字符串来编码实际的机器指令。利用字符串用堆栈上这些指令的起始地址覆盖返回指针。当调用函数(在本例中为getbuf)执行其ret指令时,程序将开始执行堆栈上的指令,而不是返回。通过这种形式的攻击,可以让程序执行几乎任何操作。放在堆栈上的代码称为利用漏洞代码。不过,这种类型的攻击很棘手,因为必须将机器代码放到堆栈上,并将返回指针设置为该代码的开头。

在bufbomb文件中有一个函数bang,具有以下C代码:

int global_value = 0;

void bang(int val)

{

if (global_value == cookie) {

printf("Bang!: You set global_value to 0x%x\n", global_value);

validate(2);

} else

printf("Misfire: global_value = 0x%x\n", global_value);

exit(0);

}

与level0和level1类似,任务是让bufbomb执行bang的代码,而不是返回测试。但是,在此之前,必须将全局变量global\u value设置为用户id的cookie。攻击代码应该设置全局\u值,将bang的地址推送到堆栈上,然后执行ret指令以跳转到bang的代码。

一些建议:

•可以使用gdb获取构建攻击字符串所需的信息。在getbuf中设置断点并运行到此断点。确定全局值的地址和缓冲区的位置等参数。

•手工确定指令序列的字节编码非常繁琐,容易出错。可以通过编写包含要放入堆栈的指令和数据的汇编代码文件,让工具完成所有工作。用gcc -m32 -c汇编这个文件,用objdump -d反汇编它。应该能够得到在提示符处键入的确切字节序列。

•请记住,利用漏洞字符串取决于的计算机、编译器,甚至的用户标识的cookie。在导师指定的一台机器上完成所有工作,并确保在bufbomb的命令行中包含正确的userid。

•编写汇编代码时,注意地址模式的使用。请注意,movl $0x4,%eax将值0x00000004移到寄存器%eax;而movl 0x4,%eax将内存位置0x00000004处的值移动到%eax中。由于该内存位置通常未定义,第二条指令将导致错误!

•不要试图使用jmp或call指令跳转到bang的代码。这些指令使用PC相对寻址,这是非常棘手的设置正确。相反,在堆栈上推送一个地址并使用ret指令。

【解决方法】

  查看bang的反汇编代码, 看一下全局变量存放在哪:

从中可以看到,mov 0x804d10c,%eax指令就是把0x804d10c中的全局变量取出来放入eax寄存器中,然后cmp将全局变量与cookie进行比较。那么首先把我们的cookie写到全局变量的地址中,然后在把bang的入口地址入栈,通过ret指令来执行bang函数,这里的push bang的入口地址在ret,就像仿造一条call指令,首先模仿系统把返回eip地址push,然后ret执行的时候,就会去执行我们这个返回地址所在的这条命令了。根据提示,可以通过编写包含要放入堆栈的指令和数据的汇编代码文件,让工具完成所有工作,则创建一个level2.s文件:

gcc -m32 -c汇编这个文件,再用objdump -d反汇编它,查看得到level2.d文件:

接下来我们就要利用gdb调试找到我们的level2的地址了,用我们的地址来覆盖返回地址,这样就可以执行我们的level2.s代码了:

首先在getbuf函数里面设置断点:可以看到buf的首地址在-0x28(%ebp),我们在0x804926e也就是call Gets之前设置断点来查看eax寄存器中的内容,也就是我们buf的首地址,也就是我们的level2函数的入口地址:

可以看到buf的首地址为0x55683d28

【结果检验】

构造如下字符串(系统是小端法存储,则多字节数据要倒着写):

尝试运行,成功调用:

  1. level3: Dynamite (20 pts)

【任务要求】

我们前面的攻击都导致程序跳转到其他函数的代码,然后导致程序退出。因此,可以使用破坏堆栈的漏洞字符串,覆盖保存的值。

最复杂的缓冲区溢出攻击形式会导致程序执行一些攻击代码,从而更改程序的寄存器/内存状态,但会使程序返回到原始调用函数(在本例中进行测试)。调用函数不受攻击。不过,这种类型的攻击很棘手,因为必须:1)将机器代码放入堆栈,2)将返回指针设置为此代码的开头,3)撤消对堆栈状态的任何损坏。

此级别的工作是提供一个漏洞字符串,该字符串将导致getbuf将cookie返回到test,而不是值1。可以在test的代码中看到,这将导致程序运行“Boom!”攻击代码应该将cookie设置为返回值,恢复任何损坏的状态,在堆栈上推送正确的返回位置,并执行ret指令以真正返回测试。

一些建议:

•可以使用gdb获取构建攻击字符串所需的信息。在getbuf中设置断点并运行到此断点。确定参数,如保存的返回地址。

•手工确定指令序列的字节编码非常繁琐,容易出错。可以通过编写包含要放入堆栈的指令和数据的汇编代码文件,让工具完成所有工作。用gcc组装这个文件,用objdump反汇编它。

•请记住,利用漏洞字符串取决于计算机、编译器,甚至用户标识的cookie。在指定机器上完成所有工作,并确保在bufbomb的命令行中包含正确的userid。

【解决方法】

  

  观察getbuf函数,设置断点,gdb调试得到ebp以及esp的值:

可以看到0x55683d80为原test的ebp,level3函数入口的返回值在level2中已经得到 ,即:0x55683d28 。

eip的地址即返回地址,也就是test中在call getbuf函数的下一条指令的地址。即返回地址为0x8048e50, 则我们要编写的函数的内容主要是,修改eax,恢复原栈状态,写成汇编版本,再用工具完成所有工作(用gcc组装这个文件,用objdump反汇编它),我们只需要在getbuf返回地址时执行我们的level3函数。要执行破坏则一定会先破坏原栈状态的,所以只需要在我们自己的代码中做到恢复就可以。创建一个level3.s文件:

gcc -m32 -c汇编这个文件,再用objdump -d反汇编它,查看得到level3.d文件:

【结果检验】

构造如下字符串(系统是小端法存储,则多字节数据要倒着写):

尝试运行,成功调用:

  1. level4: Nitroglycerin (10 pts)

【任务要求】

请注意:需要使用“-n”命令行标志才能运行此阶段。

从一个运行到另一个运行,特别是由不同的用户运行时,给定过程使用的确切堆栈位置会有所不同。这种变化的一个原因是,当程序开始执行时,所有环境变量的值都放在堆栈的底部附近。环境变量存储为字符串,需要不同的存储量,这取决于它们的值。因此,为给定用户分配的堆栈空间取决于其环境变量的设置。在GDB下运行程序时,堆栈位置也不同,因为GDB将堆栈空间用于自己的一些状态。

在调用getbuf的代码中,我们加入了稳定堆栈的特性,因此getbuf的堆栈帧的位置在运行之间是一致的。这使得可以编写一个知道buf的确切起始地址的攻击字符串。如果试图在普通程序上使用这种漏洞,会发现它有时可以工作,但有时会导致分段错误。因此被称为“炸药”——阿尔弗雷德·诺贝尔研制的一种炸药,含有稳定元素,使其不易发生意外爆炸。

对于这个级别,我们走了相反的方向,使得堆栈位置比通常情况下更不稳定。因此得名“硝化甘油”——一种众所周知不稳定的炸药。

当使用命令行标志“-n”运行bufbomb时,它将以“Nitro”模式运行。程序不调用函数getbuf,而是调用稍有不同的函数getbufn:

此函数类似于getbuf,只是它有512个字符的缓冲区。将需要这个额外的空间来创建一个可靠的漏洞。调用getbufn的代码首先在堆栈上分配一个随机的存储量,这样,如果在getbufn的两个连续执行期间对%ebp的值进行采样,会发现它们之间的差异多达±240

此外,在Nitro模式下运行时,bufbomb要求提供字符串5次,它将执行getbufn 5次,每次都有不同的堆栈偏移量。利用漏洞字符串必须使其每次返回cookie。

同样,此级别的任务是提供一个漏洞字符串,该字符串将导致getbufn将cookie返回到test,而不是值1。可以在test的代码中看到,这将导致程序进入“KABOOM!”的攻击代码应该将cookie设置为返回值,恢复任何损坏的状态,在堆栈上推送正确的返回位置,并执行ret指令以真正返回testn。

一些建议:

•可以使用程序hex2raw发送攻击字符串的多个副本。如果文件exploit.txt中只有一个副本,则可以使用以下命令:

unix> cat exploit.txt | ./hex2raw -n | ./bufbomb -n -u bovik

对于getbufn的所有5次执行,必须使用相同的字符串。

•诀窍是使用nop指令,它用一个字节编码(代码0x90)。

【解决方法】

这一level用的不再是getbuf而是getbufn和testn,都是为了随机化栈基址,则先看一下getbufn函数的汇编代码:

可以看到buf的首地址为-0x208(%ebp)为十进制520个字节大小。在这一level中每次运行testn的ebp都不同,所以每次getbufn里面保存的test的ebp也是随机的,但是栈顶的esp是不变的,我们就要找到每次随机的ebp与esp之间的关系来恢复ebp。我们先通过调试来看一下getbuf里面保存的ebp的值的随机范围为多少。我们在getbufn处设置断点,注意与前面不同的是设置为-n模式,然后每次输入一次string来看一下ebp-0x208的值,然后continue,再次输入,一共输入5次。

则buf的初始地址范围为0x55683b48到0x55683b98,且中间的地址为0x55683ba8

再通过反汇编查看返回函数testn中ebp与esp的关系:

栈的大小为0x4+0x24,则%ebp=0x28(%esp),且call getbufn下一条指令的地址为0x08048ce2

则创建一个level4.s文件:

gcc -m32 -c汇编这个文件,再用objdump -d反汇编它,查看得到level4.d文件:

因为buf的初始地址不确定,在序列中填充的跳转地址只能根据它的大致范围确定,那我们选取buf可能地址中的中间值0x55683ba8,这样当buf移动的时候,该地址始终可以命中nop序列。则总的攻击序列长度= buf长度为520字节+4空格+填充跳转序列4字节 = 528字节。所以攻击序列为509个nop指令+15字节代码序列+4字节修改返回地址代码序列。

【结果检验】

构造如下字符串(系统是小端法存储,则多字节数据要倒着写):

尝试运行,成功调用:

至此,所有level等级的实验全部完成!

 

收获与体会:

本次实验主要是对于程序运行时栈的结构的知识的复习和运用,一开始我对于栈溢出攻击的理解只是覆盖返回地址,但是往等级后做实验就发现了新的方法,并且在实验过程中也对函数的返回方式也有了更深的理解,我觉得在实验过程中对于栈空间的知识进行了更好的复习,这样的话我觉得要想对知识理解更好,还需要结合运用,反复练习,才会对知识认识得更加透彻。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是蒸饺吖~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值