缓存溢出(Buffer overflow),是指在存在缓存溢出安全漏洞的计算机中,攻击者可以用超出常规长度的字符数来填满一个域,通常是内存区地址。在某些情况下,这些过量的字符能够作为“可执行”代码来运行。从而使得攻击者可以不受安全措施的约束来控制被攻击的计算机。
下面让我们了解一下缓存溢出的原理。众说周知,c语言不进行数组的边界检查,在许多运用c语言实现的应用程序中,都假定缓冲区的大小是足够的,其容量肯定大于要拷贝的字符串的长度。然而事实并不总是这样,当程序出错或者恶意的用户故意送入一过长的字符串时,便有许多意想不到的事情发生,超过的那部分字符将会覆盖与数组相邻的其他变量的空间,使变量出现不可预料的值。如果碰巧,数组与子程序的返回地址邻近时,便有可能由于超出的一部分字符串覆盖了子程序的返回地址,而使得子程序执行完毕返回时转向了另一个无法预料的地址,使程序的执行流程发生了错误。甚至,由于应用程序访问了不在进程地址空间范围的地址,而使进程发生违例的故障。这种错误其实是编程中常犯的。
http://hi.baidu.com/caterqiu/item/29598d475bcbf8af61d7b922 参考这哥们汇编调试。
组成部分
|
使用Buffer Overflow 方法来入侵目的主机是黑客们经常采用的一种手段,本文将几篇介绍其机理的文章作了一些加工整理, 对它的机理作出了由浅入深的剖析.
本文分为下面几个部分, 朋友们可以按照自己的兴趣选择不同的章节:
1.关于堆栈的基础知识
2.Buffer Overflow 的原理
3.Shell Code 的编写
4.实际运用中遇到的问题
5.附录 I 若干操作系统/平台上的 Shell Code
6.附录 II 通用 Buffer Overflow 攻击程序
--------------------------------------------------------------------------------
1. 关于堆栈的基础知识
一个应用程序在运行时,它在内存中的映像可以分为三个部分: 代码段, 数据段和堆栈段(参见下图). 代码段对应与运行文件中的 Text Section ,其中包括运行代码和只读数据,这个段在内存中一般被标记为只读,任何企图修改这个段中数据的指令将引发一个 Segmentation Violation 错误.
数据段对应与运行文件中的 Data Section 和 BSS Section ,其中存放的是各种数据(经过初始化的和未经初始化的)和静态变量.
下面我们将详细介绍一下堆栈段. |--------| 虚存低端 | | | 代码段 | | | |--------| | | | 数据段 | | | |--------| | | | 堆栈段 | | | |--------| 虚存高端
堆栈是什么?
如果你学过<<数据结构>>这门课的话, 就会知道堆栈是一种计算机中经常用到的抽象数据类型. 作用于堆栈上的操作主要有两个: Push 和 Pop , 既压入和弹出. 堆栈的特点是LIFO(Last in , First out), 既最后压入堆栈的对象最先被弹出堆栈.
堆栈段的作用是什么?
现在大部分程序员都是在用高级语言进行模块化编程, 在这些应用程序中,不可避免地会出现各种函数调用, 比如调用C 运行库,Win32 API 等等. 这些调用大部分都被编译器编译为Call语句. 当CPU 在执行这条指令时, 除了将IP变为调用函数的入口点以外, 还要将调用后的返回地址放入堆栈. 这些函数调用往往还带有不同数量的入口参数和局部变量, 在这种情况下,编译器往往会生成一些指令将这些数据也存入堆栈(有些也可通过寄存器传递).
我们将一个函数调用在堆栈中存放的这些数据和返回地址称为一个栈帧(Stack Frame).
栈帧的结构:
下面我们通过一个简单的例子来分析一下栈帧的结构. void proc(int i) { int local; local=i; } void main() { proc(1); }
这段代码经过编译器后编译为:(以PC为例)
|
2. Buffer Overflow 的机理
我们先举一个例子说明一下什么是 Buffer Overflow : void function(char *str) { char buffer[16]; strcpy(buffer,str); }
void main() { char large_string[256]; int i;
for( i = 0; i < 255; i++) large_string[i] = 'A';
function(large_string); }
这段程序中就存在 Buffer Overflow 的问题. 我们可以看到, 传递给function的字符串长度要比buffer大很多,而function没有经过任何长度校验直接用strcpy将长字符串拷入buffer. 如果你执行这个程序的话,系统会报告一个 Segmentation Violation 错误.下面我们就来分析一下为什么会这样?
首先我们看一下未执行strcpy时堆栈中的情况: 16 4 4 4 ...[buffer] [ebp] [ret地址] [large_string地址] esp ebp
当执行strcpy时, 程序将256 Bytes拷入buffer中,但是buffer只能容纳16 Bytes,那么这时会发生什么情况呢? 因为C语言并不进行边界检查, 所以结果是buffer后面的250字节的内容也被覆盖掉了,这其中自然也包括ebp, ret地址 ,large_string地址.因为此时ret地址变成了0x41414141h ,所以当过程结束返回时,它将返回到0x41414141h地址处继续执行,但由于这个地址并不在程序实际使用的虚存空间范围内,所以系统会报Segmentation Violation.
从上面的例子中不难看出,我们可以通过Buffer Overflow来改变在堆栈中存放的过程返回地址,从而改变整个程序的流程,使它转向任何我们想要它去的地方.这就为黑客们提供了可乘之机, 最常见的方法是: 在长字符串中嵌入一段代码,并将过程的返回地址覆盖为这段代码的地址, 这样当过程返回时,程序就转而开始执行这段我们自编的代码了. 一般来说,这段代码都是执行个Shell程序(如\bin\sh),因为这样的话,当我们入侵一个带有Buffer Overflow缺陷且具有suid-root属性的程序时,我们会获得一个具有root权限的shell,在这个shell中我们可以干任何事. 因此, 这段代码一般被称为Shell Code.
下面我们就来看一下如何编写Shell Code.(待续)
|
3. Shell Code 的编写 下面是一个创建Shell的C程序shellcode.c: (本文以IntelX86上的Linux为例说明) void main() { char *name[2];
name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); }
我们先将它编译为执行代码,然后再用gdb来分析一下.(注意编译时要用-static选项,否则execve的代码将不会放入执行代码,而是作为动态链接在运行时才链入.) ------------------------------------------------------------------------------ [aleph1]$ gcc -o shellcode -ggdb -static shellcode.c [aleph1]$ gdb shellcode GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions. There is absolutely no warranty for GDB; type "show warranty" for details. GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc... (gdb) disassemble main Dump of assembler code for function main: 0x8000130 <main>: pushl %ebp 0x8000131 <main+1>: movl %esp,%ebp 0x8000133 <main+3>: subl $0x8,%esp 0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp) 0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp) 0x8000144 <main+20>: pushl $0x0 0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax 0x8000149 <main+25>: pushl %eax 0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax 0x800014d <main+29>: pushl %eax
中的某一位置,但没有足够空间时会发生缓冲区溢出。
下面对这种技术做一个详细的介绍。
缓冲区是程序运行时计算机内存中的一个连续的块,它保存了给定类
型的数据。问题随着动态分配变量而出现。为了不用太多的内存,一个有
动态分配变量的程序在程序运行时才决定给他们分配多少内存。
如果程序在动态分配缓冲区放入太多的数据会有什么现象?它溢出了,
漏到了别的地方。一个缓冲区溢出应用程序使用这个溢出的数据将汇编语
言代码放到计算机的内存中,通常是产生root权限的地方。
单单的缓冲区溢出,并不会产生安全问题。只有将溢出送到能够以root
权限运行命令的区域才行。这样,一个缓冲区利用程序将能运行的指令放
在了有root权限的内存中,从而一旦运行这些指令,就是以root权限控制
了计算机。
总结一下上面的描述。缓冲区溢出指的是一种系统攻击的手段,通过
往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序
的堆栈,使程序转而执行其它指令,以达到攻击的目的。据统计,通过缓
冲区溢出进行的攻击占所有系统攻击总数的80%以上。
造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数。例如
下面程序:
example0.c
-----------------------------------------------------------
void function(char *str) {
char buffer[16];
strcpy(buffer,str);
}
-----------------------------------------------------------
上面的strcpy()将直接把str中的内容copy到buffer中。这样只要str
的长度大于16,就会造成buffer的溢出,使程序运行出错。存在象strcpy
这样的问题的标准函数还有strcat(),sprintf(),vsprintf(),gets(),scanf(),
以及在循环内的getc(),fgetc(),getchar()等。
在C语言中,静态变量是分配在数据段中的,动态变量是分配在堆栈
段的。缓冲区溢出是利用堆栈段的溢出的。