[轉(zhuǎn)]緩沖區(qū)溢出攻擊

原文鏈接:https://www.cnblogs.com/fanzhidongyzby/p/3250405.html

緩沖區(qū)溢出(Buffer Overflow)是計(jì)算機(jī)安全領(lǐng)域內(nèi)既經(jīng)典而又古老的話題。隨著計(jì)算機(jī)系統(tǒng)安全性的加強(qiáng),傳統(tǒng)的緩沖區(qū)溢出攻擊方式可能變得不再奏效,相應(yīng)的介紹緩沖區(qū)溢出原理的資料也變得“大眾化”起來。其中看雪的《0day安全:軟件漏洞分析技術(shù)》一書將緩沖區(qū)溢出攻擊的原理闡述得簡(jiǎn)潔明了。本文參考該書對(duì)緩沖區(qū)溢出原理的講解,并結(jié)合實(shí)際的代碼實(shí)例進(jìn)行驗(yàn)證。不過即便如此,完成一個(gè)簡(jiǎn)單的溢出代碼也需要解決很多書中無法涉及的問題,尤其是面對(duì)較新的具有安全特性的編譯器——比如MS的Visual Studio2010。接下來,我們結(jié)合具體代碼,按照對(duì)緩沖區(qū)溢出原理的循序漸進(jìn)地理解方式去挖掘緩沖區(qū)溢出背后的底層機(jī)制。

# 代碼 <=> 數(shù)據(jù)

顧名思義,緩沖區(qū)溢出的含義是為緩沖區(qū)提供了多于其存儲(chǔ)容量的數(shù)據(jù),就像往杯子里倒入了過量的水一樣。通常情況下,緩沖區(qū)溢出的數(shù)據(jù)只會(huì)破壞程序數(shù)據(jù),造成意外終止。但是如果有人精心構(gòu)造溢出數(shù)據(jù)的內(nèi)容,那么就有可能獲得系統(tǒng)的控制權(quán)!如果說用戶(也可能是黑客)提供了水——緩沖區(qū)溢出攻擊的數(shù)據(jù),那么系統(tǒng)提供了溢出的容器——緩沖區(qū)。

緩沖區(qū)在系統(tǒng)中的表現(xiàn)形式是多樣的,高級(jí)語言定義的變量、數(shù)組、結(jié)構(gòu)體等在運(yùn)行時(shí)可以說都是保存在緩沖區(qū)內(nèi)的,因此所謂緩沖區(qū)可以更抽象地理解為一段可讀寫的內(nèi)存區(qū)域,緩沖區(qū)攻擊的最終目的就是希望系統(tǒng)能執(zhí)行這塊可讀寫內(nèi)存中已經(jīng)被蓄意設(shè)定好的惡意代碼。按照馮·諾依曼存儲(chǔ)程序原理,程序代碼是作為二進(jìn)制數(shù)據(jù)存儲(chǔ)在內(nèi)存的,同樣程序的數(shù)據(jù)也在內(nèi)存中,因此直接從內(nèi)存的二進(jìn)制形式上是無法區(qū)分哪些是數(shù)據(jù)哪些是代碼的,這也為緩沖區(qū)溢出攻擊提供了可能。
圖1 進(jìn)程地址空間分布

圖1是進(jìn)程地址空間分布的簡(jiǎn)單表示。代碼存儲(chǔ)了用戶程序的所有可執(zhí)行代碼,在程序正常執(zhí)行的情況下,程序計(jì)數(shù)器(PC指針)只會(huì)在代碼段和操作系統(tǒng)地址空間(內(nèi)核態(tài))內(nèi)尋址。數(shù)據(jù)段內(nèi)存儲(chǔ)了用戶程序的全局變量,文字池等。??臻g存儲(chǔ)了用戶程序的函數(shù)棧幀(包括參數(shù)、局部數(shù)據(jù)等),實(shí)現(xiàn)函數(shù)調(diào)用機(jī)制,它的數(shù)據(jù)增長方向是低地址方向。堆空間存儲(chǔ)了程序運(yùn)行時(shí)動(dòng)態(tài)申請(qǐng)的內(nèi)存數(shù)據(jù)等,數(shù)據(jù)增長方向是高地址方向。除了代碼段和受操作系統(tǒng)保護(hù)的數(shù)據(jù)區(qū)域,其他的內(nèi)存區(qū)域都可能作為緩沖區(qū),因此緩沖區(qū)溢出的位置可能在數(shù)據(jù)段,也可能在堆、棧段。如果程序的代碼有軟件漏洞,惡意程序會(huì)“教唆”程序計(jì)數(shù)器從上述緩沖區(qū)內(nèi)取指,執(zhí)行惡意程序提供的數(shù)據(jù)代碼!本文分析并實(shí)現(xiàn)棧溢出攻擊方式。

# 函數(shù)棧幀

棧的主要功能是實(shí)現(xiàn)函數(shù)的調(diào)用。因此在介紹棧溢出原理之前,需要弄清函數(shù)調(diào)用時(shí)??臻g發(fā)生了怎樣的變化。每次函數(shù)調(diào)用時(shí),系統(tǒng)會(huì)把函數(shù)的返回地址(函數(shù)調(diào)用指令后緊跟指令的地址),一些關(guān)鍵的寄存器值保存在棧內(nèi),函數(shù)的實(shí)際參數(shù)和局部變量(包括數(shù)據(jù)、結(jié)構(gòu)體、對(duì)象等)也會(huì)保存在棧內(nèi)。這些數(shù)據(jù)統(tǒng)稱為函數(shù)調(diào)用的棧幀,而且是每次函數(shù)調(diào)用都會(huì)有個(gè)獨(dú)立的棧幀,這也為遞歸函數(shù)的實(shí)現(xiàn)提供了可能。

圖2 函數(shù)棧幀

如圖所示,我們定義了一個(gè)簡(jiǎn)單的函數(shù)function,它接受一個(gè)整形參數(shù),做一次乘法操作并返回。當(dāng)調(diào)用function(0)時(shí),arg參數(shù)記錄了值0入棧,并將call function指令下一條指令的地址0x00bd16f0保存到棧內(nèi),然后跳轉(zhuǎn)到function函數(shù)內(nèi)部執(zhí)行。每個(gè)函數(shù)定義都會(huì)有函數(shù)頭和函數(shù)尾代碼,如圖綠框表示。因?yàn)楹瘮?shù)內(nèi)需要用ebp保存函數(shù)棧幀基址,因此先保存ebp原來的值到棧內(nèi),然后將棧指針esp內(nèi)容保存到ebp。函數(shù)返回前需要做相反的操作——將esp指針恢復(fù),并彈出ebp。這樣,函數(shù)內(nèi)正常情況下無論怎樣使用棧,都不會(huì)使棧失去平衡。

sub esp,44h指令為局部變量開辟了棧空間,比如ret變量的位置。理論上,function只需要再開辟4字節(jié)空間保存ret即可,但是編譯器開辟了更多的空間(這個(gè)問題很詭異,你覺得呢?)。函數(shù)調(diào)用結(jié)束返回后,函數(shù)棧幀恢復(fù)到保存參數(shù)0時(shí)的狀態(tài),為了保持棧幀平衡,需要恢復(fù)esp的內(nèi)容,使用add esp,4將壓入的參數(shù)彈出。

之所以會(huì)有緩沖區(qū)溢出的可能,主要是因?yàn)闂?臻g內(nèi)保存了函數(shù)的返回地址。該地址保存了函數(shù)調(diào)用結(jié)束后后續(xù)執(zhí)行的指令的位置,對(duì)于計(jì)算機(jī)安全來說,該信息是很敏感的。如果有人惡意修改了這個(gè)返回地址,并使該返回地址指向了一個(gè)新的代碼位置,程序便能從其它位置繼續(xù)執(zhí)行。

# 三、棧溢出基本原理

上邊給出的代碼是無法進(jìn)行溢出操作的,因?yàn)橛脩魶]有“插足”的機(jī)會(huì)。但是實(shí)際上很多程序都會(huì)接受用戶的外界輸入,尤其是當(dāng)函數(shù)內(nèi)的一個(gè)數(shù)組緩沖區(qū)接受用戶輸入的時(shí)候,一旦程序代碼未對(duì)輸入的長度進(jìn)行合法性檢查的話,緩沖區(qū)溢出便有可能觸發(fā)!比如下邊的一個(gè)簡(jiǎn)單的函數(shù)。

 void fun(unsigned char *data)
{
    unsigned char buffer[BUF_LEN];
    strcpy((char*)buffer,(char*)data);//溢出點(diǎn)  
}

這個(gè)函數(shù)沒有做什么有“意義”的事情(這里主要是為了簡(jiǎn)化問題),但是它是一個(gè)典型的棧溢出代碼。在使用不安全的strcpy庫函數(shù)時(shí),系統(tǒng)會(huì)盲目地將data的全部數(shù)據(jù)拷貝到buffer指向的內(nèi)存區(qū)域。buffer的長度是有限的,一旦data的數(shù)據(jù)長度超過BUF_LEN,便會(huì)產(chǎn)生緩沖區(qū)溢出。

圖3 緩沖區(qū)溢出

由于棧是低地址方向增長的,因此局部數(shù)組buffer的指針在緩沖區(qū)的下方。當(dāng)把data的數(shù)據(jù)拷貝到buffer內(nèi)時(shí),超過緩沖區(qū)區(qū)域的高地址部分?jǐn)?shù)據(jù)會(huì)“淹沒”原本的其他棧幀數(shù)據(jù),根據(jù)淹沒數(shù)據(jù)的內(nèi)容不同,可能會(huì)有產(chǎn)生以下情況:

  1. 淹沒了其他的局部變量。如果被淹沒的局部變量是條件變量,那么可能會(huì)改變函數(shù)原本的執(zhí)行流程。這種方式可以用于破解簡(jiǎn)單的軟件驗(yàn)證。
  2. 淹沒了ebp的值。修改了函數(shù)執(zhí)行結(jié)束后要恢復(fù)的棧指針,將會(huì)導(dǎo)致棧幀失去平衡。
  3. 淹沒了返回地址。這是棧溢出原理的核心所在,通過淹沒的方式修改函數(shù)的返回地址,使程序代碼執(zhí)行“意外”的流程!
  4. 淹沒參數(shù)變量。修改函數(shù)的參數(shù)變量也可能改變當(dāng)前函數(shù)的執(zhí)行結(jié)果和流程。
  5. 淹沒上級(jí)函數(shù)的棧幀,情況與上述4點(diǎn)類似,只不過影響的是上級(jí)函數(shù)的執(zhí)行。當(dāng)然這里的前提是保證函數(shù)能正常返回,即函數(shù)地址不能被隨意修改(這可能很麻煩?。?。

如果在data本身的數(shù)據(jù)內(nèi)就保存了一系列的指令的二進(jìn)制代碼,一旦棧溢出修改了函數(shù)的返回地址,并將該地址指向這段二進(jìn)制代碼的其實(shí)位置,那么就完成了基本的溢出攻擊行為。
圖4 基本棧溢出攻擊

通過計(jì)算返回地址內(nèi)存區(qū)域相對(duì)于buffer的偏移,并在對(duì)應(yīng)位置構(gòu)造新的地址指向buffer內(nèi)部二進(jìn)制代碼的其實(shí)位置,便能執(zhí)行用戶的自定義代碼!這段既是代碼又是數(shù)據(jù)的二進(jìn)制數(shù)據(jù)被稱為shellcode,因?yàn)楣粽呦Mㄟ^這段代碼打開系統(tǒng)的shell,以執(zhí)行任意的操作系統(tǒng)命令——比如下載病毒,安裝木馬,開放端口,格式化磁盤等惡意操作。

# 棧溢出攻擊

上述過程雖然理論上能完成棧溢出攻擊行為,但是實(shí)際上很難實(shí)現(xiàn)。操作系統(tǒng)每次加載可執(zhí)行文件到進(jìn)程空間的位置都是無法預(yù)測(cè)的,因此棧的位置實(shí)際是不固定的,通過硬編碼覆蓋新返回地址的方式并不可靠。為了能準(zhǔn)確定位shellcode的地址,需要借助一些額外的操作,其中最經(jīng)典的是借助跳板的棧溢出方式。

根據(jù)前邊所述,函數(shù)執(zhí)行后,棧指針esp會(huì)恢復(fù)到壓入?yún)?shù)時(shí)的狀態(tài),在圖4中即data參數(shù)的地址。如果我們?cè)诤瘮?shù)的返回地址填入一個(gè)地址,該地址指向的內(nèi)存保存了一條特殊的指令jmp esp——跳板。那么函數(shù)返回后,會(huì)執(zhí)行該指令并跳轉(zhuǎn)到esp所在的位置——即data的位置。我們可以將緩沖區(qū)再多溢出一部分,淹沒data這樣的函數(shù)參數(shù),并在這里放上我們想要執(zhí)行的代碼!這樣,不管程序被加載到哪個(gè)位置,最終都會(huì)回來執(zhí)行棧內(nèi)的代碼。

圖5 借助跳板的棧溢出攻擊

借助于跳板的確可以很好的解決棧幀移位(棧加載地址不固定)的問題,但是跳板指令從哪找呢?“幸運(yùn)”的是,在Windows操作系統(tǒng)加載的大量dll中,包含了許多這樣的指令,比如kernel32.dll,ntdll.dll,這兩個(gè)動(dòng)態(tài)鏈接庫是Windows程序默認(rèn)加載的。如果是圖形化界面的Windows程序還會(huì)加載user32.dll,它也包含了大量的跳板指令!而且更“神奇”的是Windows操作系統(tǒng)加載dll時(shí)候一般都是固定地址,因此這些dll內(nèi)的跳板指令的地址一般都是固定的。我們可以離線搜索出跳板執(zhí)行在dll內(nèi)的偏移,并加上dll的加載地址,便得到一個(gè)適用的跳板指令地址!

//查詢dll內(nèi)第一個(gè)jmp esp指令的位置  int findJmp(char*dll_name)
{
    char* handle=(char*)LoadLibraryA(dll_name);//獲取dll加載地址  
    for(int pos=0;;pos++)//遍歷dll代碼空間      
    {
        if(handle[pos]==(char)0xff&&handle[pos+1]==(char)0xe4)//尋找0xffe4 = jmp  esp          
        {
            return (int)(handle+pos);
        }
    }
}

這里簡(jiǎn)化了搜索算法,輸出第一個(gè)跳板指令的地址,讀者可以選取其他更合適位置。LoadLibraryA庫函數(shù)返回值就是dll的加載地址,然后加上搜索到的跳板指令偏移pos便是最終地址。jmp esp指令的二進(jìn)制表示為0xffe4,因此搜索算法就是搜索dll內(nèi)這樣的字節(jié)數(shù)據(jù)即可。

雖然如此,上述的攻擊方式還不夠好。因?yàn)樵趀sp后繼續(xù)追加shellcode代碼會(huì)將上級(jí)函數(shù)的棧幀淹沒,這樣做并沒有什么好處,甚至可能會(huì)帶來運(yùn)行時(shí)問題。既然被溢出的函數(shù)棧幀內(nèi)提供了緩沖區(qū),我們還是把核心的shellcode放在緩沖區(qū)內(nèi),而在esp之后放上跳轉(zhuǎn)指令轉(zhuǎn)移到原本的緩沖區(qū)位置。由于這樣做使代碼的位置在esp指針之前,如果shellcode中使用了push指令便會(huì)讓esp指令與shellcode代碼越來越近,甚至淹沒自身的代碼。這顯然不是我們想要的結(jié)果,因此我們可以強(qiáng)制抬高esp指針,使它在shellcode之前(低地址位置),這樣就能在shellcode內(nèi)正常使用push指令了。

圖6 調(diào)整shellcode與棧指針

調(diào)整代碼的內(nèi)容很簡(jiǎn)單:

add esp,-X
jmp esp
  • 第一條指令抬高了棧指針到shellcode之前。X代表shellcode起始地址與esp的偏移。如果shellcode從緩沖區(qū)起始位置開始,那么就是buffer的地址偏移。這里不使用sub esp,X指令主要是避免X的高位字節(jié)為0的問題,很多情況下緩沖區(qū)溢出是針對(duì)字符串緩沖區(qū)的,如果出現(xiàn)字節(jié)0會(huì)導(dǎo)致緩沖區(qū)截?cái)?,從而?dǎo)致溢出失敗。
  • 第二條指令就是跳轉(zhuǎn)到shellcode的起始位置繼續(xù)執(zhí)行。(又是jmp esp?。?/li>

通過上述方式便能獲得一個(gè)較為穩(wěn)定的棧溢出攻擊。

# shellcode構(gòu)造

shellcode實(shí)質(zhì)是指溢出后執(zhí)行的能開啟系統(tǒng)shell的代碼。但是在緩沖區(qū)溢出攻擊時(shí),也可以將整個(gè)觸發(fā)緩沖區(qū)溢出攻擊過程的代碼統(tǒng)稱為shellcode,按照這種定義可以把shellcode分為四部分:

  1. 核心shellcode代碼,包含了攻擊者要執(zhí)行的所有代碼。
  2. 溢出地址,是觸發(fā)shellcode的關(guān)鍵所在。
  3. 填充物,填充未使用的緩沖區(qū),用于控制溢出地址的位置,一般使用nop指令填充——0x90表示。
  4. 結(jié)束符號(hào)0,對(duì)于符號(hào)串shellcode需要用0結(jié)尾,避免溢出時(shí)字符串異常。

前邊一直在圍繞溢出地址討論,并解決了shellcode組織的問題,而最核心的代碼如何構(gòu)造并未提及——即攻擊成功后做的事情。其實(shí)一旦緩沖區(qū)溢出攻擊成功后,如果被攻擊的程序有系統(tǒng)的root權(quán)限——比如系統(tǒng)服務(wù)程序,那么攻擊者基本上可以為所欲為了!但是我們需要清楚的是,核心shellcode必須是二進(jìn)制代碼形式。而且shellcode執(zhí)行時(shí)是在遠(yuǎn)程的計(jì)算機(jī)上,因此shellcode是否能通用是一個(gè)很復(fù)雜的問題。我們可以用一段簡(jiǎn)單的代碼實(shí)例來說明這個(gè)問題。

緩沖區(qū)溢出成功后,一般大家都會(huì)希望開啟一個(gè)遠(yuǎn)程的shell控制被攻擊的計(jì)算機(jī)。開啟shell最直接的方式便是調(diào)用C語言的庫函數(shù)system,該函數(shù)可以執(zhí)行操作系統(tǒng)的命令,就像我們?cè)诿钚邢聢?zhí)行命令那樣。假如我們執(zhí)行cmd命令——在遠(yuǎn)程計(jì)算機(jī)上啟動(dòng)一個(gè)命令提示終端(我們可能還不能和它交互,但是可以在這之前建立一個(gè)遠(yuǎn)程管道等),這里僅作為實(shí)例測(cè)試。

為了使system函數(shù)調(diào)用成功,我們需要將“cmd”字符串內(nèi)容壓入??臻g,并將其地址壓入作為system函數(shù)的參數(shù),然后使用call指令調(diào)用system函數(shù)的地址,完成函數(shù)的執(zhí)行。但是這樣做還不夠,如果被溢出的程序沒有加載C語言庫的話,我們還需要調(diào)用Windows的API Loadlibrary加載C語言的庫msvcrt.dll,類似的我們也需要為字符串“msvcrt.dll”開辟??臻g。

xor ebx,ebx ;//ebx=0  
push 0x3f3f6c6c ;//ll??  
push 0x642e7472 ;//rt.d  
push 0x6376736d ;//msvc  
mov [esp+10],ebx ;//'?'->'0'  
mov [esp+11],ebx ;//'?'->'0'  
mov eax,esp ;//"msvcrt.dll"地址  
push eax ;//"msvcrt.dll"  
mov eax,0x77b62864 ;//kernel32.dll:LoadLibraryA  call eax ;//LoadLibraryA("msvcrt.dll")  
add esp,16

push 0x3f646d63 ;//"cmd?"  
mov [esp+3],ebx ;//'?'->'\0'  
mov eax,esp;//"cmd"地址  
push eax ;//"cmd"  
mov eax,0x774ab16f ;//msvcrt.dll:system  
call eax ;//system("cmd")  
add esp,8

上述匯編代碼實(shí)質(zhì)上是如下兩個(gè)函數(shù)調(diào)用語句:

Loadlibrary(“msvcrt.dll”);
system(“cmd”);

不過在構(gòu)造這段匯編代碼時(shí)需要注意不能出現(xiàn)字節(jié)0,為了填充字符串的結(jié)束字符,我們使用已經(jīng)初始化為0的ebx寄存器代替。另外,在對(duì)庫函數(shù)調(diào)用的時(shí)候需要提前計(jì)算出函數(shù)的地址,如Loadlibrary函數(shù)的0x77b62864。計(jì)算方式如下:

int findFunc(char*dll_name,char*func_name)
{
    HINSTANCE handle=LoadLibraryA(dll_name);//獲取dll加載地址  
    return (int)GetProcAddress(handle,func_name);
}

這個(gè)函數(shù)地址是在本地計(jì)算的,如果被攻擊計(jì)算機(jī)的操作系統(tǒng)版本差別較大的話,這個(gè)地址可能是錯(cuò)誤的。不過在《0day安全:軟件漏洞分析技術(shù)》中,作者提供了一個(gè)更好的方式,感興趣的讀者可以參考該書提供的代碼。因此構(gòu)造一個(gè)通用的shellcode并非十分容易,如果想讓攻擊變得有效的話。

# 匯編語言自動(dòng)轉(zhuǎn)換

寫出shellcode后(無論是簡(jiǎn)單的還是通用的),我們還需要將這段匯編代碼轉(zhuǎn)換為機(jī)器代碼。如果讀者對(duì)x86匯編十分熟悉的話,選擇手工敲出二進(jìn)制代碼的話也未嘗不可。不過我們都希望能讓計(jì)算機(jī)幫助做完這些事,既然開發(fā)環(huán)境提供了編譯器,用它們幫忙何樂而不為呢?既不用OllyDbg工具,也不適用其他的第三方工具,我們寫一個(gè)簡(jiǎn)單的函數(shù)來完成這個(gè)工作。

//將內(nèi)嵌匯編的二進(jìn)制指令dump到文件,style指定輸出數(shù)組格式還是二進(jìn)制形式,返回代碼長度  int dumpCode(unsigned char*buffer)
{
    goto END ;//略過匯編代碼  BEGIN:
    __asm
    {
        //在這里定義任意的合法匯編代碼          
    }
END:
    //確定代碼范圍      
    UINT begin,end;
    __asm
    {
        mov eax,BEGIN ;
        mov begin,eax ;
        mov eax,END ;
        mov end,eax ;
    }
    //輸出      
    int len=end-begin;
    memcpy(buffer,(void*)begin,len);
        //四字節(jié)對(duì)齊      
    int fill=(len-len%4)%4;
    while(fill--)buffer[len+fill]=0x90;
    //返回長度      
    return len+fill;
}

因?yàn)镃++是支持嵌入式匯編代碼的,因此在函數(shù)內(nèi)的匯編代碼都會(huì)被整成編譯為二進(jìn)制代碼。實(shí)現(xiàn)二進(jìn)制轉(zhuǎn)換的基本思想是讀取編譯器最終生成的二進(jìn)制代碼段數(shù)據(jù),將數(shù)據(jù)導(dǎo)出到指定的緩沖區(qū)內(nèi)。為了鎖定嵌入式匯編代碼的位置和長度,我們定義了兩個(gè)標(biāo)簽BEGIN和END。這兩個(gè)標(biāo)簽在匯編語言級(jí)別會(huì)被解析為實(shí)際的線性地址,但是在高級(jí)語言級(jí)是無法直接使用這兩個(gè)標(biāo)簽值的,只能使用goto語句跳轉(zhuǎn)使用它們。但是我們可以順?biāo)浦郏褂脙蓚€(gè)局部變量在匯編級(jí)記錄這兩個(gè)標(biāo)簽的值!

//確定代碼范圍  
UINT begin,end;
__asm
{
    mov eax,BEGIN ;
    mov begin,eax ;
    mov eax,END ;
    mov end,eax ;
}

這樣就可以得到嵌入式匯編的代碼范圍了,使用memcpy操作將代碼數(shù)據(jù)拷貝到目標(biāo)緩沖區(qū)即可(后邊還用nop指令將代碼按照四字節(jié)對(duì)齊)。不過我們還需要注意一個(gè)問題,嵌入式匯編在函數(shù)執(zhí)行時(shí)也會(huì)執(zhí)行,這顯然不可以,我們只是把它當(dāng)作數(shù)據(jù)而已(是數(shù)據(jù)?還是代碼?),因此在函數(shù)開始的地方我們使用goto語句直接跳轉(zhuǎn)到嵌入式會(huì)變語句的結(jié)尾——END標(biāo)簽!

# 七、攻擊測(cè)試

按照上述內(nèi)容,相信不難構(gòu)造出一個(gè)簡(jiǎn)單的shellcode并攻擊之前提供的漏洞函數(shù)。但是如果使用VS2010測(cè)試的話可能會(huì)碰到很多問題。經(jīng)過大量的調(diào)試和資料查詢,我們需要設(shè)置三處VS的項(xiàng)目屬性。

  1. 配置->配置屬性->C/C++->基本運(yùn)行時(shí)檢查=默認(rèn)值,避免被檢測(cè)棧幀失衡。
  2. 配置->配置屬性->C/C++->緩沖區(qū)安全檢查=否,避免識(shí)別緩沖區(qū)溢出漏洞。
  3. 配置->配置屬性->鏈接器->高級(jí)->數(shù)據(jù)執(zhí)行保護(hù)(DEP)=否,避免堆棧段不可執(zhí)行。

從這三處設(shè)置看來,目前的編譯器已經(jīng)針對(duì)緩沖區(qū)溢出攻擊做了大量的保護(hù)工作(顯然這會(huì)降低程序的執(zhí)行性能,因此允許用戶配置),使得傳統(tǒng)的緩沖區(qū)溢出攻擊變得沒那么“猖狂”了,但是在計(jì)算機(jī)安全領(lǐng)域,“道高一尺,魔高一丈”,總有人會(huì)找到更隱蔽的攻擊方式讓編譯器開發(fā)者措手不及。本文除了分析緩沖區(qū)溢出攻擊的原理之外,更希望讀者能從中感受到代碼安全的重要性,并結(jié)合編譯器提供的安全功能讓自己的代碼更加安全高效。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容