C语言缓冲区溢出原理与实践

发布于 / C语言 / Comments Off on C语言缓冲区溢出原理与实践

我们都听说过缓冲区漏洞,那么具体原理是怎样的呢?

首先我们看这个例子:

#include<stdio.h>
int main(){
  int i=0;
  int a[]={1,2,3,4,5,6,7,8,9,10};

  for(i=0;i<=11;i++){
    printf("Hello World!\n", i);
  }
}

我们使用Deepin Linux 15.5 64位操作系统,GCC 6.4.0编译器进行编译,得到了非常正确的结果:

12个"Hello World!",这是我们预料之中的。因为我们让i从0循环到11,i的值也从0,一点点+1,到了11,所以自然就打印出了11个hello world!。

但是,倘若我们在for语句中加一句赋值语句呢?请看下面的代码:

#include<stdio.h>
int main(){
  int i=0;
  int a[]={1,2,3,4,5,6,7,8,9,10};

  for(i=0;i<=11;i++){
    a[i]=0;        //加上了这句赋值语句
    printf("Hello World!\n", i);
  }
}

继续使用我们的GCC编译运行,结果却出乎预料:

输出成了死循环!

这段代码很明显,数组a越界了。可能有的同学在平时的编程中也会遇到过这样的情况,某个数组越界后程序的流程发生了严重的改变,那么是什么原因造成的这种改变呢?我们就上面的例子进行分析。

首先我们查看一下编译器在编译我们的程序时生成的汇编指令:

我们使用 "gcc 文件名.c -S "命令生成汇编指令文件,然后打开"文件名.s":

我们看到了这一段:

  pushq  %rbp
  ......
  subq  $48, %rsp
  movl  $0, -4(%rbp)
  movl  $1, -48(%rbp)
  movl  $2, -44(%rbp)
  movl  $3, -40(%rbp)
  movl  $4, -36(%rbp)
  movl  $5, -32(%rbp)
  movl  $6, -28(%rbp)
  movl  $7, -24(%rbp)
  movl  $8, -20(%rbp)
  movl  $9, -16(%rbp)
  movl  $10, -12(%rbp)
  movl  $0, -4(%rbp)
  jmp  .L2

没有学过汇编看不懂上面的也无所谓,下面这种写法可以直接在运行结果中查看变量存放的位置:

#include<stdio.h>
int main(){
  int i=0;
  int a[]={1,2,3,4,5,6,7,8,9,10};

  for(i=0;i<=11;i++){
    printf("%p->%d\n", &(a[i]), a[i]);
  }
}

运行结果:

看到最后一个数,也就是a[11]的值,等于11,这个值与i的值一模一样,那么会不会是a[11]就等于i呢?我们继续实验:

#include<stdio.h>
int main(){
  int i=100;
  int a[]={1,2,3,4,5,6,7,8,9,10};

  for(i=0;i<=11;i++){
    printf("&i = %p\t&a[11] = %p\n", &a[11], &i);
  }
}

运行结果:

我们发现,a[11]就是i!

其实函数的局部变量在运行的时候是在堆栈中保存的,堆栈的特征是先进后出。分析上面的汇编代码或者那个输出地址的C语言代码我们可以知道,在内存中,变量的存放位置是:

a[0]--a[9],i

至于a[10],可能是GCC编译器在编译过程中特意防止越界而留出的一块地址。而a[11]自然也就是a[10]后面的地址,也就是i。

在循环中,赋值语句a[11]=0等同于i=0,这样就会导致for循环不断进行,也就解释了为什么会死循环。

在一些大型程序的编写时,必须要检查数组边界(包括字符串),否则用户输入的内容一旦超过数组所能容纳的最大容量,后面正好还有这存放其他变量的存储空间,就很容易把其他变量的空间地址覆盖,等同于用户非法的修改了变量。这样就是一个缓冲区溢出漏洞。一旦被有心人精心计算,加以利用,就会造成十分严重的后果。

转载原创文章请注明,转载自: 斐斐のBlog » C语言缓冲区溢出原理与实践
评论已关闭