堆栈溢出对于我们软件开发人员来说,最严重的后果是破坏了内存中的指针,及其造成的一系列难查的bug。
在当前网络与分布式系统安全中,被广泛利用的50%以上都是缓冲区溢出,其中最著名的例子是1988年利用fingerd漏洞的蠕虫。而缓冲区溢出中,最为危险的是堆栈溢出,因为入侵者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址,带来的危害一种是程序崩溃导致拒绝服务,另外一种就是跳转并且执行一段恶意代码,比如得到shell,然后为所欲为。我在这里演示一下堆栈溢出的原理。
堆栈有关概念首先,介绍一下,与堆栈有关的一些概念:动态内存有两种,堆栈(stack),堆(heap)。堆栈在内存上端,堆在内存下端,当程序执行时,堆栈向下朝堆增长,堆向上朝堆栈增长。通常,局部变量,返回地址,函数的参数,是放在堆栈里面的。
低地址
局部变量
旧的基指针
返回地址
函数的参数(左)
函数的参数(。。。)
函数的参数(右)
高地址
我们可以写一个小程序测试:
Copycode
#include“string.h”
voidtest(char*a);
intmain(intargc,char*argv[])
{
chara[]=“hello”;
test(a);
return0;
}
voidtest(char*a)
{
char*j;
charbuf[5];
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,*(unsignedchar*)j);
}
Main定义一个字符串hello,然后调用test函数,在test函数中,有一个长度为5的局部字符串变量buf,然后把复制参数a复制到buf中,这里因为没有a的长度小于等于buf的长度,所以并没有溢出buf。然后显示各个函数,参数,局部变量的地址,以及局部字符串变量buf和参数a之间的地址,我们看到:
Quote:
&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要覆盖多少数据,数一下就知道了改写如下:
Copycode
#include“string.h”
voidtest(char*a);
voidecho();
intmain(intargc,char*argv[])
{
chara[16];
inTI;
for(i=0;i《16;i++)a[i]=‘x’; //覆盖不重要的部分,为了达到返回地址
a[16]=0xf; //在这里改写了返回地址
a[17]=0x10;
a[18]=0x40;
a[19]=0x00; //一方面高字节正好是00,同时00又是字符串的结尾
test(a);
return0;
}
voidtest(char*a)
{
char*j;
charbuf[5];
strcpy(buf,a); //分配的缓冲区只有5,结果却有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,*(unsignedchar*)j);
}
voidecho()
{
printf(“haha!\n”);
printf(“haha!\n”);
printf(“haha!\n”);
printf(“haha!\n”);
}
结果,我们看到地址显示完以后,出现了echo函数里面的haha!\nhaha!\nhaha!\nhaha!\n,说明溢出跳转成功,但是,在结束的时候出现了程序崩溃,这是因为echo找不到它的返回地址导致的。但是今天我们的目的,利用溢出执行其他代码的任务已经实现了
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)