内存溢出原理

来源:互联网 发布:js html radio 编辑:程序博客网 时间:2024/04/29 11:04

转置:http://hi.baidu.com/yjsagacity/item/837cf5c3899ed355bdef69ef

在当前网络与分布式系统安全中,被广泛利用的50%以上都是缓冲区溢出,其中最著名的例子是1988年利用fingerd漏洞的蠕虫。而缓冲区溢出中,最为危险的是堆栈溢出,因为入侵者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址,带来的危害一种是程序崩溃导致拒绝服务,另外一种就是跳转并且执行一段恶意代码,比如得到shell,然后为所欲为。我在这里演示一下堆栈溢出的原理。




首先,介绍一下,与堆栈有关的一些概念:动态内存有两种,堆栈(stack),堆(heap)。堆栈在内存上端,堆在内存下端,当程序执行时,堆栈向下朝堆增长,堆向上朝堆栈增长。通常,局部变量,返回地址,函数的参数,是放在堆栈里面的。

低地址

         局部变量

         旧的基指针

         返回地址

         函数的参数(左)

         函数的参数(。。。)

         函数的参数(右)

高地址



我们可以写一个小程序测试:

#include "string.h"void test(char *a);int main(int argc, char* argv[]){         char a[] = “hello”;         test(a);         return 0;}void test(char *a){         char* j;         char buf[6];         strcpy(buf,a);         printf("&main=%p\n",&main);         printf("&buf=%p\n",&buf);         printf("&a=%p\n",&a);         printf("&test=%p\n",&test);         for ( j=buf-8;j<((char *)&a)+8;j++)                 printf("%p: 0x%x\n",j, *(unsigned char *)j);}



Main定义一个字符串hello,然后调用test函数,在test函数中,有一个长度为6的局部字符串变量buf,然后把复制参数a复制到buf中,这里因为没有a的长度小于等于buf的长度,所以并没有溢出buf。然后显示各个函数,参数,局部变量的地址,以及局部字符串变量buf和参数a之间的地址,我们看到:

&main=0040100A

&buf=0012FF14

&a=0012FF28

&test=00401005

0012FF0C: 0xcc

0012FF0D: 0xcc

0012FF0E: 0xcc

0012FF0F: 0xcc

0012FF10: 0xcc

0012FF11: 0xcc

0012FF12: 0xcc

0012FF13: 0xcc

0012FF14: 0x68                                 h                 这里就是buf了!

0012FF15: 0x65                                 e

0012FF16: 0x6c                                 l

0012FF17: 0x6c                                 l

0012FF18: 0x6f                                 o

0012FF19: 0x0                                 \0

0012FF1A: 0xcc

0012FF1B: 0xcc

0012FF1C: 0x1c                                 这里是

0012FF1D: 0xff                                 两个

0012FF1E: 0x12                                 旧的

0012FF1F: 0x0                                 基指针,不管他

0012FF20: 0x80

0012FF21: 0xff

0012FF22: 0x12

0012FF23: 0x0

0012FF24: 0x34                                 这个就是

0012FF25: 0xb8                                 返回地址了

0012FF26: 0x40                                 和main的地址很

0012FF27: 0x0                                 接近吧!

0012FF28: 0x78                                 这个是

0012FF29: 0xff                                 参数a,即

0012FF2A: 0x12                                 a字符串的

0012FF2B: 0x0                                 地址

0012FF2C: 0xe

0012FF2D: 0x0

0012FF2E: 0x0

0012FF2F: 0x0

由于c编译器不会自己做边界检查的,所以,如果buf中的内容足够长,而不是hello,那么很有可能覆盖掉原来的返回地址,那么程序就会跳转到其他地方,为了试验,我们定义一个简单的函数echo,让test返回的时候跳转到echo。

用printf("&echo=%p\n",&echo); 我已经知道了echo的地址是0x0040100f,从上面的例子已经知道从0012ff14到0012ff27要覆盖多少数据,数一下就知道了&#61514; 改写如下:

#include "string.h"void test(char *a);void echo();int main(int argc, char* argv[]){         char a[16];         int i;         for(i=0;i<16;i++) a='x';//覆盖不重要的部分,为了达到返回地址          a[16]=0xf;                         //在这里改写了返回地址          a[17]=0x10;          a[18]=0x40;          a[19]=0x00;//一方面高字节正好是00,同时00又是字符串的结尾          test(a);                 return 0;}void test(char *a){         char* j;         char buf[6];         strcpy(buf,a);         //分配的缓冲区只有6,结果却有19,溢出了!         printf("&main=%p\n",&main);         printf("&buf=%p\n",&buf);         printf("&a=%p\n",&a);         printf("&echo=%p\n",&echo);         printf("&test=%p\n",&test);         for ( j=buf-8;j<((char *)&a)+8;j++)                 printf("%p: 0x%x\n",j, *(unsigned char *)j);}void echo(){         printf("haha!\n");         printf("haha!\n");         printf("haha!\n");         printf("haha!\n");}


结果,我们看到地址显示完以后,出现了echo函数里面的haha!\nhaha!\nhaha!\nhaha!\n,说明溢出跳转成功,但是,在结束的时候出现了程序崩溃,这是因为echo找不到它的返回地址导致的。但是今天我们的目的,利用溢出执行其他代码的任务已经实现了,我在下一次会告诉大家如何把堆栈溢出和shell code结合起来。
原创粉丝点击