缓冲区溢出
一、实验内容、步骤、结果分析
首先简单说下其原理:
-
缓冲区是内存中存放数据的地方。在程序试图将数据放到机器内存中的某一个位置的时候,因为没有足够的空间就会发生缓冲区溢出。而人为的溢出则是有一定企图的,攻击者写一个超过缓冲区长度的字符串,植入到缓冲区,然后再向一个有限空间的缓冲区中植入超长的字符串,这时可能会出现两个结果:一是过长的字符串覆盖了相邻的存储单元,引起程序运行失败,严重的可导致系统崩溃;另一个结果就是利用这种漏洞可以执行任意指令,甚至可以取得系统root特级权限。
缓冲区是程序运行的时候机器内存中的一个连续块,它保存了给定类型的数据,随着动态分配变量会出现问题。大多时为了不占用太多的内存,一个有动态分配变量的程序在程序运行时才决定给它们分配多少内存。如果程序在动态分配缓冲区放入超长的数据,它就会溢出了。一个缓冲区溢出程序使用这个溢出的数据将汇编语言代码放到机器的内存里,通常是产生root权限的地方。仅仅单个的缓冲区溢出并不是问题的根本所在。但如果溢出送到能够以root权限运行命令的区域,一旦运行这些命令,那可就等于把机器拱手相让了。
通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,进而运行精心准备的指令,以达到攻击的目的。
-
如上图,程序的缓冲区比作一个个格子(内存单元),每个格子中存放不同的东西,有的是命令,有的是数据,当程序需要接收用户数据,程序预先为之分配了4个格子(上图中黄色的0~3号格子)。
按照程序设计,就是要求用户输入的数据不超过4个。
而用户在输入数据时,假设输入了16个数据,而且程序也没有对用户输入数据的多少进行检查(这种情况太常见了,windows系统本身就出过n个缓冲区溢出漏洞),就往预先分配的格子中存放,这样不仅4个分配的格子(内存)被使用了,其后相邻的12个格子中的内容都被新数据覆盖。
一般情况下后面的格子是可以被当成代码执行的,嘿嘿。
缓冲区溢出实例:(使用的软件为virtual c++6.0)
#include<stdio.h>
//20191223zjy
void HelloWord()
{
printf("Hello World");
getchar();
}
void Fun()
{
int arr[5] = {1,2,3,4,5};
arr[6] = (int) HelloWord;
}
int main()
{
Fun();
return 0;
}
运行结果如图所示:
结果分析:
- 我们可以看见Helloworld函数的功能是打印字符“Hello world”,但我们从代码的main函数部分可以看见,我们并没有调用Helloworld函数,那为什么会输出“Hello world”呢?
注意到汇编语言中,一个数组声明时,arr[0]到arr[4]在堆栈中的位置分别是[EBP-0x14]到[EBP-0x4],也就是先声明4*5(字节)的堆栈空间,在从低位向高位存储。
注意下断点那一行代码,等价于C源码中 arr[6] = (int) HelloWord
0041097B MOV DWORD PTR SS:[EBP+0x4],cccchhhh.0040>
将子函数HelloWord的函数地址MOV到[EBP+0x4],而这个地址用来存放上一个函数的返回地址,等执行到下面RETN语句是,本该是pop到eip里的函数返回地址变为了HelloWord的地址,函数继续运行,输出“Hello Word”。
三、预防缓冲区溢出的方法
- 有四种基本的方法保护缓冲区免受缓冲区溢出的攻击和影响。
1、通过操作系统使得缓冲区不可执行,从而阻止攻击者植入攻击代码。
2、强制写正确的代码的方法。
3、利用编译器的边界检查来实现缓冲区的保护。这个方法使得缓冲区溢出不可能出现,从而完全消除了缓冲区溢出的威胁,但是相对而言代价比较大。
4、一种间接的方法,这个方法在程序指针失效前进行完整性检查。虽然这种方法不能使得所有的缓冲区溢出失效,但它能阻止绝大多数的缓冲区溢出攻击。分析这种保护方法的兼容性和性能优势。
四、实验体会
- 缓冲区溢出呢在上学期的信息安全概论课上就有过接触,缓冲区溢出是一种很常见的攻击方式(其实有时候并不算攻击),比如我上小学时打开cf游戏失败,显示错误的地址,就是缓冲区溢出造成的。这次实验呢难度不大,关键在于如何覆盖返回地址,或者说把返回地址和你希望执行指令的地址替换。还有就是现在的win10、Linux系统都有较好的安全性,完成此次实验需要用win2000、xp等系统或virtual C++6.0版本。总的来说这次实验的收获还是挺大的。