悲子吧 关注:3贴子:137
  • 1回复贴,共1

缓冲区溢出原理

只看楼主收藏回复

在当前网络与分布式系统安全中,被广泛利用的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要覆盖多少数据,数一下就知道了 改写如下:
#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结合起来。谢谢大家,by the way,我也是初学者,有理解偏差的地方,希望高手指教,


1楼2006-01-01 15:48回复
    下一篇出来了吗?找不到!!


    IP属地:浙江2楼2008-04-26 13:23
    回复