一个正常的程序在内存中通常分为程序段,数据端和堆栈三部分。程序段里放着程序的机器码和只读数据,这个段通常是只读,对它的写操作是非法的。数据段放的是程序中的静态数据。动态数据则通过堆栈来存放。
void function(int a, int b, int c){
char buffer1[10];
char buffer2[15];
}
void main(){
function(1,2,3);
}
假设我们在Linux下,用gcc对这段源码进行编译,产生汇编代码输出:
$ gcc -S -o example1.s example1.c
看看输出文件中调用函数的那部分:
pushl $3
pushl $2
pushl $1
call function
这就将3个参数推入堆栈里了,并调用function()。指令call会将指令指针IP压入堆栈。在返回时,RET要用到这个保存的IP。在函数中,第一要做的事是进行一些必要的处理。每个函数都必须有这些过程(为了保护呀,不然就找不到了。):
pushl �p
movl %esp,�p
subl $20,%esp
这几条指令将EBP,基址指针放入堆栈。然后将当前SP拷贝到EBP。然后,为本地变量分配空间,并将它们的大小从SP里减掉。由于内存分配是以字为单位的,因此,这里的buffer1用了8字节(2个字,一个字4字节)。Buffer2用了12字节(3个字)。所以这里将ESP减了20。这样,现在,堆栈看起来应该是这样的。
低端内存 高端内存
buffer2 buffer1 sfp ret a b c
< ------ [ ] [ ] [ ] [ ] [ ] [ ] [ ]
栈顶 栈底
那是什么导致了溢出呢?缓冲区溢出就是在一个缓冲区里写入过多的数据。要怎么样利用呢?看下面这个程序:
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 = 'A';
function(large_string);
}
这个程序是一个经典的缓冲区溢出编码错误。函数将一个字符串不经过边界检查,拷贝到另一内存区域。当调用函数function()时,堆栈如下:
低内存端 高内存端
buffer sfp ret *str
< ------ [ ] [ ] [ ] [ ]
栈顶 栈底
很明显,程序执行的结果是"Segmentation fault (core
dumped)"或类似的出错信息。因为从buffer开始的256个字节都将被*str的内容'A'覆盖,包括sfp,
ret,甚至*str。'A'的十六进值为0x41,所以函数的返回地址变成了0x41414141,
这超出了程序的地址空间,所以出现段错误。可见,缓冲区溢出允许我们改变一个函数的返回地址,在这个例子中,我们可以通过修改0x41414141来改变程序返回后的入口地址。同样通过这种方式,就可以改变程序的执行顺序了。
存在象strcpy这样的问题的标准函数还有strcat(),sprintf(),vsprintf(),gets(),scanf(),以及在循环内的getc(),fgetc(),getchar()等。
转自http://www.hackbase.com/tech/2012-08-16/66752.html
/――――――――\ 内存低端
程序段
―――――――――
数据段
―――――――――
堆栈
\―――――――――/内存高端
堆栈是内存中的一个连续的块。一个叫堆栈指针的寄存器(SP)指向堆栈的栈顶。堆栈的底部是一个固定地址。堆栈有一个特点就是,后进先出。也就是说,后放入的数据第一个取出。它支持两个操作,PUSH和POP。PUSH是将数据放到栈的顶端,POP是将栈顶的数据取出。
在高级语言中,程序函数调用和函数中的临时变量都用到堆栈。为什么呢?因为在调用一个函数时,我们需要对当前的操作进行保护,也为了函数执行后,程序可以正确的找到地方继续执行,所以参数的传递和返回值也用到了堆栈。通常对局部变量的引用是通过给出它们对SP的偏移量来实现的。另外还有一个基址指针(FP,在Intel芯片中是BP),许多编译器实际上是用它来引用本地变量和参数的。通常,参数的相对FP的偏移是正的,局部变量是负的。
在C语言中,静态变量是分配在数据段中的,动态变量是分配在堆栈段的。缓冲区溢出是利用堆栈段的溢出的。
当程序中发生函数调用时,计算机做如下操作:首先把参数压入堆栈;然后保存指令寄存器(IP)中的内容,做为返回地址(RET);第三个放入堆栈的是基址寄存器(FP);然后把当前的栈指针(SP)拷贝到FP,做为新的基地址;最后为本地变量留出一定空间,把SP减去适当的数值。
void function(int a, int b, int c){
char buffer1[10];
char buffer2[15];
}
void main(){
function(1,2,3);
}
假设我们在Linux下,用gcc对这段源码进行编译,产生汇编代码输出:
$ gcc -S -o example1.s example1.c
看看输出文件中调用函数的那部分:
pushl $3
pushl $2
pushl $1
call function
这就将3个参数推入堆栈里了,并调用function()。指令call会将指令指针IP压入堆栈。在返回时,RET要用到这个保存的IP。在函数中,第一要做的事是进行一些必要的处理。每个函数都必须有这些过程(为了保护呀,不然就找不到了。):
pushl �p
movl %esp,�p
subl $20,%esp
这几条指令将EBP,基址指针放入堆栈。然后将当前SP拷贝到EBP。然后,为本地变量分配空间,并将它们的大小从SP里减掉。由于内存分配是以字为单位的,因此,这里的buffer1用了8字节(2个字,一个字4字节)。Buffer2用了12字节(3个字)。所以这里将ESP减了20。这样,现在,堆栈看起来应该是这样的。
低端内存
< ------ [ ] [ ] [ ] [ ] [ ] [ ] [ ]
栈顶
那是什么导致了溢出呢?缓冲区溢出就是在一个缓冲区里写入过多的数据。要怎么样利用呢?看下面这个程序:
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
function(large_string);
}
这个程序是一个经典的缓冲区溢出编码错误。函数将一个字符串不经过边界检查,拷贝到另一内存区域。当调用函数function()时,堆栈如下:
低内存端
< ------ [ ] [ ] [ ] [ ]
栈顶
很明显,程序执行的结果是"Segmentation fault (core
dumped)"或类似的出错信息。因为从buffer开始的256个字节都将被*str的内容'A'覆盖,包括sfp,
ret,甚至*str。'A'的十六进值为0x41,所以函数的返回地址变成了0x41414141,
这超出了程序的地址空间,所以出现段错误。可见,缓冲区溢出允许我们改变一个函数的返回地址,在这个例子中,我们可以通过修改0x41414141来改变程序返回后的入口地址。同样通过这种方式,就可以改变程序的执行顺序了。
存在象strcpy这样的问题的标准函数还有strcat(),sprintf(),vsprintf(),gets(),scanf(),以及在循环内的getc(),fgetc(),getchar()等。
转自http://www.hackbase.com/tech/2012-08-16/66752.html