栈是从高地址向低地址方向增涨,堆的方向相反。
在一次函数调用中,栈中将被依次压入:参数,返回地址,EBP。如果函数有局部变量,接下来,就在栈中开辟相应的空间以构造变量。
在C语言程序中,参数的压栈顺序是反向的。比如func(a,b,c)。在参数入栈的时候,是:先压c,再压b,最后a。在取参数的时候,由于栈的先入后 出,先取栈顶的a,再取b,最后取c。
C语言是不作栈溢出检查,如下代码可以正常编译运行。
#include<stdio.h> main(){ char buf[2]; printf("enter a string shorter than 2.\n"); scanf("%s",buf); printf("buf=%s\n",buf); }
如果函数局部变量发生栈溢出,就会依次覆盖重写EBP(4个字节)、返回地址(4个字节)、函数参数。函数的“返回地址”被重写是非常危险的,因为“返回地址”可能指向了一段恶意代码而我们却毫无察觉。
下面的代码中funcA的局部变量发生栈溢出,使得funcA的返回地址成为funcB的入口地址。不过幸好在运行的时候发现了这种行为,报告了“segmentation fault”。
#include <stdio.h> #include <string.h> #define BUFLENGTH 2 void funcA(char* str) { char buf[BUFLENGTH]; strcpy(buf,str); //危险,可能造成栈溢出 printf("strlen(buf)=%d\tbuf=%s\n",strlen(buf),buf); printf("不安全的代码被调用\n"); } //下面的函数是恶意代码 void funcB() { printf("恶意代码被调用\n"); } void main() { //以不安全的方式调用函数funcA char bufNasty[BUFLENGTH+8]; memset(bufNasty,'A',sizeof(bufNasty)); int *ptr=(int*)&bufNasty[BUFLENGTH+4]; *ptr=0x65850408; funcA(bufNasty); }
当然如何知道funcB的地址是0x65850408呢?可以使用反汇编工具查看:
objdump -x attack
也可以在使用gdb时通过在funcB处设置断点看到funcB的地址。
gdb>b funcB