堆棧溢出對于我們軟件開發(fā)人員來說,最嚴(yán)重的后果是破壞了內(nèi)存中的指針,及其造成的一系列難查的bug。
在當(dāng)前網(wǎng)絡(luò)與分布式系統(tǒng)安全中,被廣泛利用的50%以上都是緩沖區(qū)溢出,其中最著名的例子是1988年利用fingerd漏洞的蠕蟲。而緩沖區(qū)溢出中,最為危險(xiǎn)的是堆棧溢出,因?yàn)槿肭终呖梢岳枚褩R绯觯诤瘮?shù)返回時(shí)改變返回程序的地址,讓其跳轉(zhuǎn)到任意地址,帶來的危害一種是程序崩潰導(dǎo)致拒絕服務(wù),另外一種就是跳轉(zhuǎn)并且執(zhí)行一段惡意代碼,比如得到shell,然后為所欲為。我在這里演示一下堆棧溢出的原理。
堆棧有關(guān)概念
首先,介紹一下,與堆棧有關(guān)的一些概念:動態(tài)內(nèi)存有兩種,堆棧(stack),堆(heap)。堆棧在內(nèi)存上端,堆在內(nèi)存下端,當(dāng)程序執(zhí)行時(shí),堆棧向下朝堆增長,堆向上朝堆棧增長。通常,局部變量,返回地址,函數(shù)的參數(shù),是放在堆棧里面的。
低地址
局部變量
舊的基指針
返回地址
函數(shù)的參數(shù)(左)
函數(shù)的參數(shù)(。。。)
函數(shù)的參數(shù)(右)
高地址
我們可以寫一個(gè)小程序測試:
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定義一個(gè)字符串hello,然后調(diào)用test函數(shù),在test函數(shù)中,有一個(gè)長度為5的局部字符串變量buf,然后把復(fù)制參數(shù)a復(fù)制到buf中,這里因?yàn)闆]有a的長度小于等于buf的長度,所以并沒有溢出buf。然后顯示各個(gè)函數(shù),參數(shù),局部變量的地址,以及局部字符串變量buf和參數(shù)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 兩個(gè)
0012FF1E:0x12 舊的
0012FF1F:0x0 基指針,不管他
0012FF20:0x80
0012FF21:0xff
0012FF22:0x12
0012FF23:0x0
0012FF24:0x34 這個(gè)就是
0012FF25:0xb8 返回地址了
0012FF26:0x40 和main的地址很
0012FF27:0x0 接近吧!
0012FF28:0x78 這個(gè)是
0012FF29:0xff 參數(shù)a,即
0012FF2A:0x12
a字符串的
0012FF2B:0x0 地址
0012FF2C:0xe
0012FF2D:0x0
0012FF2E:0x0
0012FF2F:0x0
由于c編譯器不會自己做邊界檢查的,所以,如果buf中的內(nèi)容足夠長,而不是hello,那么很有可能覆蓋掉原來的返回地址,那么程序就會跳轉(zhuǎn)到其他地方,為了試驗(yàn),我們定義一個(gè)簡單的函數(shù)echo,讓test返回的時(shí)候跳轉(zhuǎn)到echo。
用printf(“&echo=%p\n”,&echo);我已經(jīng)知道了echo的地址是0x0040100f,從上面的例子已經(jīng)知道從0012ff14到0012ff27要覆蓋多少數(shù)據(jù),數(shù)一下就知道了?改寫如下:
Copycode
#include“string.h”
voidtest(char*a);
voidecho();
intmain(intargc,char*argv[])
{
chara[16];
inti;
for(i=0;i《16;i++)a[i]=‘x’; //覆蓋不重要的部分,為了達(dá)到返回地址
a[16]=0xf; //在這里改寫了返回地址
a[17]=0x10;
a[18]=0x40;
a[19]=0x00; //一方面高字節(jié)正好是00,同時(shí)00又是字符串的結(jié)尾
test(a);
return0;
}
voidtest(char*a)
{
char*j;
charbuf[5];
strcpy(buf,a); //分配的緩沖區(qū)只有5,結(jié)果卻有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”);
}
結(jié)果,我們看到地址顯示完以后,出現(xiàn)了echo函數(shù)里面的haha!\nhaha!\nhaha!\nhaha!\n,說明溢出跳轉(zhuǎn)成功,但是,在結(jié)束的時(shí)候出現(xiàn)了程序崩潰,這是因?yàn)閑cho找不到它的返回地址導(dǎo)致的。但是今天我們的目的,利用溢出執(zhí)行其他代碼的任務(wù)已經(jīng)實(shí)現(xiàn)了
評論
查看更多