1. 缓冲区溢出
缓冲区溢出漏洞是程序由于缺乏对缓冲区边界条件检查而引起的一种异常行为,通常是程序向缓冲区中写数据,但内容超过了程序员设定的缓冲区边界,从而覆盖了相邻的内存区域,造成覆盖程序中的其他变量甚至影响控制流的敏感数据,造成程序的非预期行为。
eg:
如图所示,在内存中保存了相邻的两个变量,A是char[]字符串类型,作为缓冲区用于存储外部输入的字符串,长度为8Byte,而B是短整数型。在程序执行时,某指令向A中写入了长度大于8的字符串,超过了A的边界覆盖了B的内容,造成B的值被改变。A中写入的字符串是"abcdefghi",长度为9,加上结束符'\0'之后修改B的值,从原先的0xffff变成0x0069。
根据缓冲区溢出的内存位置不同,将缓冲区溢出又分为栈溢出和堆溢出。
2. 栈溢出
程序执行过程的栈,是由OS创建与维护的,同时也支持了程序内的函数调用功能。在进行函数调用时,程序会将返回地址压入栈中,而执行完被调用函数代码之后,则会通过ret指令从栈中弹出返回地址,装载到EIP指令寄存器,从而继续程序的运行。
栈溢出发生在程序向位于栈中的内存地址写数据时,当写入的数据长度超过栈分配给缓冲区的空间时,就会造成栈溢出。攻击者可以有几种方式来利用这种漏洞:
(1)覆盖缓冲区附近的程序变量,改变程序的执行流程和结束,从而达到攻击者的目的。
(2)覆盖栈中保存的函数返回地址,修改为攻击者指定的地址,当程序返回时,程序流程将跳转到攻击者指定地址,理想情况下可以执行任意代码。
(3)覆盖某个函数指针或程序异常处理结构,只要溢出之后目标函数或异常处理程序被执行,同样可以让程序跳转到任意地址。
3. 堆溢出
堆是程序运行时动态分配的内存,用户一般通过malloc、new等函数申请内存,通过返回的起始地址指针对分配的内存进行操作,使用完之后要通过free、delete等函数释放这部分内存,否则会造成内存泄露。堆的操作分为分配、释放、合并三种,因为堆在内存中的位置不固定,大小比较自由,多次申请、释放后可能会更加凌乱,系统从性能、空间利用率还有越来越受重视的安全角度出发来管理堆,具体实现比较复杂。这里介绍最常见的空闲堆块操作引起的堆缓冲区溢出。
同一个堆中的堆块在内存中通常是连续的,由此很可能发生的状况是:在向一个已分配堆块中写入数据时,由于数据长度超出了该堆块的大小,导致数据溢出覆盖堆块后方的相邻空闲堆块。
我们不能保证每次安全漏洞的利用都是成功的,即使程序中真实存在着安全漏洞。这是因为OS的内存管理机制非常复杂,且不受攻击者的控制和了解,从而使得内存、寄存器的内容、参与利用的代码块地址经常动态变化且出乎攻击者意料,导致溢出攻击成功具有一定概率。