计算机系统实验之buflab

buflab实验是CSAPP课程中bomblab实验之后的实验,相对来说buflab实验难度更大,但是有了bomb实验的基础,想要解决这个实验还是相对比较容易的。另外,要做出实验,需要通读实验文档,里面会涉及到很多重要的提示信息和坑。

实验目的

1.了解关于内存溢出攻击的原理,并通过5个level关卡的实验对堆栈有进一步理解。
2.通过模拟缓冲区溢出攻击,了解如何去进行栈保护。
3.了解程序的运行时操作以及了解这种形式的安全性弱点的性质,以便编写系统代码时可以避免这种情况。

实验环境

个人电脑PC,32位ubuntu系统环境

实验内容及操作步骤

实验内容

根据实验压缩包中提供的可执行文件makecookie和自己设置的userid生成cookie,在理解缓冲区溢出攻击的原理上编写每个level对应的文件,按照buflab实验文档的要求完成5个关卡的任务。

操作步骤

生成用户cookie

(1).首先对实验压缩包进行解压,执行指令tar zxvf buflab-handout.tar.gz。
(2).实验文件中bufbomb文件是一个有缓存区溢出漏洞的程序,hex2raw的作用是让自己编写的漏洞代码转化为一个字符串的格式。makecookie可以根据用户定义的userid生成唯一的cookie,这里定义我的userid为201808010515hmr,执行./makecookie 201808010515hmrs生成cookie。
在这里插入图片描述

进行buf实验

将可执行程序使用objdump指令反汇编生成汇编代码进行查看。
在这里插入图片描述

level0

(1).Level0的任务是让BUFBOMB在test函数中调用getbuf,当getbuf执行return语句时跳转执行smoke的代码,而不是返回test函数。 这里需要注意,漏洞利用字符串可能会破坏与该阶段不直接相关的堆栈部分,但这不会引起问题,因为执行smoke程序后立即退出。
(2).实现:输入超出缓冲区大小的字符串来覆盖getbuf的返回地址,返回地址部分用smoke的入口地址覆盖,这样当程序就不会正常返回而是根据你的返回地址跳转到返回地址对应的内容并执行。关键需要确定栈的大小和需要覆盖的字节数。原理图如下
在这里插入图片描述
(3).通过反汇编,查看getbuf的栈帧的开辟情况,开辟大小为0x38的栈区,接下来的lea语句将buf的首地址放入-0x28(%ebp)处,调用Gets()函数,将输入字符向上存储。故覆盖至返回地址共0x28=40个字节,ebp+4处用smoke的入口地址填充。
在这里插入图片描述
(4).结合反汇编代码,可以知道smoke的入口地址为0x8048e0a,但由于0a表示的ascii是换行符,使用0x8048e0a会导致入口地址不完整。这里是level0关卡的一个坑。因此,使用0x8048e0b。编写的漏洞代码如下,注意编写时用小端法表示。
在这里插入图片描述
(5).最后执行./hew2raw <level0.txt|./bufbomb –u 201808010515hmr,可以看到“Smoke!: You called smoke()”,说明执行getbuf后返回到smoke中执行,实现任务level0。
PS:在linux中“|” 是管道符号,command1 | command2表示将第1个命令的执行结果作为第2个命令的输入传给command2。

level 1

(1). 与level0类似,level1的任务是让BUFBOMB在test函数中调用getbuf,当getbuf执行return语句时跳转执行fizz的代码,而不是返回test函数。不同的是将自己的cookie值作为参数传给fizz函数。Fizz函数如下所示
在这里插入图片描述
(2).实现:与level0相似,输入超出缓冲区大小的字符串来覆盖getbuf的返回地址,返回地址部分用fizz的入口地址覆盖,关键需要确定栈的大小和需要覆盖的字节数。由函数调用过程中栈帧空间的分配可知,fizz的参数在执行fizz之前的栈栈顶之下的位置,即传入参数在当前fizz栈帧中ebp+8处。原理图如下
在这里插入图片描述
这里也可以通过查看fizz函数的汇编代码的方式来确定fizz的参数位置。

 8048db5:	8b 45 08             	mov    0x8(%ebp),%eax
 8048db8:	3b 05 04 d1 04 08    	cmp    0x804d104,%eax

(3).覆盖返回地址部分和level1一致,这里在ebp+4处用fizz的返回地址覆盖,ebp+4-ebp+8处用00覆盖,传参位置ebp+8处用自己的cookie(0x4b87c8ca)覆盖。查看反汇编得到fizz入口地址为0x08048daf。编写的漏洞代码如下
在这里插入图片描述
(4).最后执行./hew2raw <level1.txt|./bufbomb –u 201808010515hmr,可以看到“Fizz!: You called fizz(0x4b87c8ca)”,说明执行getbuf后返回到fizz中,并将cookie作为参数执行,实现任务level1。

level 2

(1).Level2的任务是让BUFBOMB执行bang代码,而不是返回去test。在此之前,必须将全局变量global_value设置为用户ID的cookie,要求利用代码应设置global_value,将bang的地址压入堆栈,然后执行ret导致跳转到bang代码的指令。Bang函数代码如下:
在这里插入图片描述
(2).实现:同样level2最后也不用返回test函数,而是去执行bang函数。在执行函数之前将自己的cookie的值给一个全局变量,并传入bang函数作为参数。不同于level1的是参数是一个全局变量,需要去内存中进行修改。根据实验文档提示,可以自己编写汇编代码去修改全局变量的值,然后利用gcc和objdump生成相应的可执行的机器码,其余部分处理与前面类似。gcc和objdump编译处理如下
在这里插入图片描述
(3).那么如何去找到存储全局变量的地址呢?这里就需要根据汇编代码来进行判断。查看bang的汇编代码,根据cmp指令和bang函数中global_value与cookie的比较可知,在这里插入图片描述下图红框中的0x804d10c就是global_value的存储地址。找到bang函数的入口地址为0x8048d52。
在这里插入图片描述
(4).编写汇编代码如下,将其转化为机器码。

# 汇编代码
movl $0x4b87c8ca, 0x804d10c #将自己的cookie值直接利用mov指令传值给全局变量
push $0x8048d52 #将bang入口地址压栈
ret             #返回bang
# 机器码
level2_code.o:     file format elf32-i386
Disassembly of section .text:
00000000 <.text>:
   0:	c7 05 0c d1 04 08 ca 	movl   $0x4b87c8ca,0x804d10c
   7:	c8 87 4b 
   a:	68 52 8d 04 08       	push   $0x8048d52
   f:	c3                   	ret    

(5).生成的机器码放入buf的首地址,首地址通过gdb调试来查看是0x55683958,这里需要注意查看eax的时机,必须在getbuf函数中将-0x28(%ebp)值传给eax之后。
在这里插入图片描述
在ebp+4处即用0x55683958进行覆盖。编写的漏洞代码如下
在这里插入图片描述
(6).最后执行./hew2raw <level2.txt|./bufbomb –u 201808010515hmr,可以看到“Bang!: You set global_value to 0x4b87c8ca”,说明执行getbuf后返回到bang中,并将cookie的值赋给全局变量global_value作为参数执行,实现任务level2。

level 3

(1).Level3的任务要求修改getbuf的返回值为cookie,而不是值1。漏洞利用代码应将cookie设置为返回值,还原任何损坏的状态,然后按入在堆栈上的正确返回位置,并执行ret指令以真正返回test。做这个实验需要解决两个问题,一是对破坏的栈帧的恢复,二是修改返回值。
(2).实现:前几个level都是跳转到执行其他函数,不返回test,在这个过程中,破坏了原来的返回地址和test函数ebp。Level3需要修改getbuf的返回值后仍然正确返回到test函数。

  • Test函数实际上不管getbuf的执行情况,getbuf执行结束后将返回值放在%eax中,修改返回只需要我们改变%eax的值即可。
  • 接着来考虑栈的恢复,一是eip记录地址,二是恢复pop出test的原ebp。
    使用gdb调试来获取原ebp的值,在getbuf的入口地址出设置断点,可直接查看ebp的值为0x556839b0。
    在这里插入图片描述
    eip存放的是下一条指令的地址,这里是test中在call getbuf之后的地址,根据反汇编代码可以看到地址应为0x8048e50。在这里插入图片描述
    由于找到了ebp原来对值和下一条指令,只需在修改返回值值后恢复原ebp的值即可,并将下一条指令压栈。
    (3).根据eip的值和test原ebp的值,编写如下汇编代码,并生成可执行机器码。
# 汇编代码
movl $0x4b87c8ca, %eax   #设置返回值为cookie
movl $0x556839b0, %ebp   #恢复原ebp的值
push $0x8048e50          #返回后下一条指令的地址
ret             #返回
# 机器码
level3_code.o:     file format elf32-i386
Disassembly of section .text:
00000000 <.text>:
   0:	b8 ca c8 87 4b       	mov    $0x4b87c8ca,%eax
   5:	bd b0 39 68 55       	mov    $0x556839b0,%ebp
   a:	68 50 8e 04 08       	push   $0x8048e50
   f:	c3                   	ret    

(4).生成的机器码放入buf的首地址,首地址和level2一致即0x55683958,在ebp+4处用0x55683958进行覆盖。编写的漏洞代码如下(代码中文字只是为了说明,运行时需删除)在这里插入图片描述
(5).最后执行./hew2raw <level3.txt|./bufbomb –u 201808010515hmr,可以看到“Boom!: getbuf returned 0x4b87c8ca”,说明执行getbuf后返回值变为cookie的值,并成功返回到test函数中,实现任务level3。

level4

(1).Level4的任务是level3的进阶版。getbufn函数将连续运行5次,每次需要我们设置返回值为我们自己的cookie,而不是值1,并正确返回到testn函数中,在testn代码中看到这将导致程序运行“ KABOOM!”。 漏洞利用代码应设置cookie作为返回值,恢复任何损坏的状态,将正确的返回位置压入堆栈,并执行ret指令以真正返回到testn。
(2). 实现:这个level4与level3思路是类似的,而level4调用的函数testn和getbufn需要连续输入5次,这样每次的栈基址会随机化,难点在于确定这些地址。

  • 查看getbuf的汇编代码,可以发现buf的首地址变为-0x208(%ebp),即520个字节的大小。这样每次运行testn的ebp都是不同的,因此每次getbufn保存的test的ebp的值也是随机,不变的是栈顶esp始终固定,由此可以借用esp来表示ebp。
    在这里插入图片描述
  • 在getbufn处设置断点,查看每次保存的ebp的值,根据ebp的值可以得到buf首地址存储的值(ebp-0x208)。接着在testn处设置断点查看test的ebp的值。查看结果如下
ebp                    buf存储位置%ebp-0x208         Test中%ebp值
0x55683980               0x55683778                 0x556839b0
0x556839f0               0x556837e8                 0x55683a20
0x556839c0               0x556837b8                 0x556839f0
0x556839f0               0x556837e8                 0x55683a20
0x556839e0               0x556837d8                 0x55683a10

由上述数据可知getbufn的ebp的值比test的ebp的值小0x30。而getbufn执行ret后esp-0x8的值就是getbufn的ebp。故testn的原ebp的值为esp-0x8-x30=esp+0x28。第1个问题解决,确定testn原ebp的大小。

  • 解决testn的原ebp问题后接下来还需要确定buf首地址。根据实验文档提示,这里使用nop雪橇,nop的作用只是执行eip自增1,而不进行任何操作,当无法猜测buf的首地址,考虑找到buf的最大地址作为跳转地址,即0x556837e8,这样就可以包括5组buf的地址。
    (3).编写汇编代码,并生成的可执行机器码。
# 汇编代码
movl $0x4b87c8ca, %eax   #设置返回值为cookie
leal 0x28(%esp),%ebp   #恢复testn的ebp原栈帧
push $0x8048ce2        #返回后下一条指令的地址
ret  
# 机器码
level4_code.o:     file format elf32-i386
Disassembly of section .text:
00000000 <.text>:
   0:	b8 ca c8 87 4b       	mov    $0x4b87c8ca,%eax
   5:	8d 6c 24 28          	lea    0x28(%esp),%ebp
   9:	68 e2 8c 04 08       	push   $0x8048ce2
   e:	c3                   	ret    

(4).由于缓冲区有520个字节加上返回地址和原ebp,总共528个字节,除去上述机器码15个字节,返回地址4个字节,剩余509个字节用nop填充,对应ascii码为90。编写的漏洞代码如下(篇幅较长,只截部分图)
在这里插入图片描述
(5).执行leve4时,需要加上-n。最后执行./hew2raw <level4.txt -n|./bufbomb –u –n 201808010515hmr,可以看到连续5次输出“getbufn returned 0x4b87c8ca”,说明每次执行getbufn后返回值变为cookie的值,并成功返回到test函数中,实现任务level4。

实验结果及分析

运行实验结果

在这里插入图片描述

实验分析

  • Level0通过覆盖返回地址的方式,在调用getbuf返回时直接跳转到smoke函数中执行,让我们了解缓冲区溢出攻击的基本原理。
  • Level1在Level0的基础上将cookie传参给fizz函数,再进行跳转执行。从Level2开始则需要我们自己去编写执行汇编代码。
  • Level2需要代码去设置全局变量global_value为cookie,再进行传参,跳转执行函数bang。
  • Level3需要设置getbuf的返回值为cookie,并正确返回到test函数,这就要求我们恢复原来被覆盖破坏掉的ebp的值。
  • Level4实现功能仍然同level3一致,但需要多次调用getbufn,使得栈基址随机化。
  • 每次level需要我们编写漏洞机器码,通过hex2raw转化后输入到bufbomb文件中执行,当实现功能后,会输出“NICE JOB!”由上图知,5次Level运行结果均正确,实验完成。

收获与体会

1.5个Level难度依次增加,一步一步引导我们如何利用缓冲区存在的漏洞去执行一些操作,实现我们需要的功能,同时也为我们防止他人缓冲区溢出攻击提供了一种思路。
2.了解关于内存溢出攻击的原理,并通过5个level关卡的实验缓冲区溢出攻击有了深入的理解。
3.实验过程中综合运用gcc,objdump等指令,对其各自的用法更加熟悉,对相关的知识,如函数调用,栈帧的分配有了进一步理解。
4.对于gdb等调试工具更加熟练。
5.实验中每个人的实验的可执行文件都是不同,实验过程中,也帮其他同学看过一些代码,对于不同的反汇编代码更加熟悉,也间接复习了之前计算机系统关于汇编语言的相关知识。
6.对于level3还有另外一种实现方式,不用指定用mov $0x556839b0, %ebp来恢复%ebp,而是在覆盖old %ebp的时候用test的原ebp覆盖即可,也同样能达到效果。
7.运行调试过程中c 表示继续执行被调试程序,直至下一个断点或程序结束
8.这次实验由于每个人生成的cookie的值不同,相对来说还是比较有挑战性的。只能从网上借鉴思路,至于具体的实现还是需要自己一步步去琢磨的。

实验程序包下载

猜你喜欢

转载自blog.csdn.net/weixin_44595362/article/details/107009984