◆李維峰
(中國飛行試驗(yàn)研究院 陜西 710089)
棧緩存溢出漏洞自1988年出現(xiàn)至今已過去30多年了[1],它以其危害性及廣泛性早已引起了廣大信息安全領(lǐng)域研究人員的重視。多年以來,隨著攻防雙方技術(shù)的交替進(jìn)步,對于此類漏洞的控制已經(jīng)不像最初階段那么無力,但是不得不說,由于棧緩存溢出漏洞的產(chǎn)生根源是程序設(shè)計(jì)不嚴(yán)謹(jǐn)所導(dǎo)致,換個(gè)更直接的說法就是該漏洞的產(chǎn)生可認(rèn)為是人為的編碼疏忽,因此,在未來很長的一段時(shí)間內(nèi)它還將與我們共存。本文將利用Metasploit框架開發(fā)一個(gè)針對軟件bof-server棧緩存溢出漏洞的破解模塊,以此為例,講解此類漏洞的危害及預(yù)防措施。由于Metasploit框架是進(jìn)行滲透測試的優(yōu)秀工具,它提供了大量的漏洞滲透模塊庫,集成了優(yōu)秀的模塊開發(fā)環(huán)境,所以,我們選擇它作為本文的實(shí)驗(yàn)工具。
在微軟的定義里,緩存溢出攻擊是一種攻擊者用自己的代碼覆蓋程序原有代碼的行為,如果被覆蓋的惡意代碼是一段受攻擊者控制的可執(zhí)行代碼,那么攻擊者就可以在目標(biāo)系統(tǒng)中進(jìn)行意料之外的操作。
棧是一種數(shù)據(jù)結(jié)構(gòu),棧中數(shù)據(jù)的寫入和讀取只能從棧頂進(jìn)行操作,它遵循后進(jìn)先出的原則。棧支持兩種操作:push和pop。push是將數(shù)據(jù)添加到棧頂。pop是將數(shù)據(jù)從棧頂彈出。讓我們看一下C程序的內(nèi)存布局、它的內(nèi)容以及它在函數(shù)調(diào)用和返回期間是如何工作的。如圖1所示。
其中,Text:包含要執(zhí)行的程序代碼。Data:包含程序的全局信息。Stack:包含函數(shù)參數(shù),返回地址和函數(shù)的局部變量。它是后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)。隨著新函數(shù)的調(diào)用,它在內(nèi)存中向下增長(從較高的地址空間到較低的地址空間)。Heap:容納所有動(dòng)態(tài)分配的內(nèi)存。每當(dāng)我們使用malloc動(dòng)態(tài)獲取內(nèi)存時(shí),它都是從堆中分配的。隨著需要越來越多的內(nèi)存,堆在內(nèi)存中的增長(從低到高)。
對于基于棧的緩存溢出,我們把注意力集中在寄存器EBP、EⅠP和ESP上。EBP指向堆棧底部的較高內(nèi)存地址,ESP則指向堆棧頂部的較低內(nèi)存位置。EⅠP中存儲(chǔ)的是下一條指令的地址。我們主要關(guān)注EⅠP寄存器,因?yàn)槲覀冃枰俪殖绦虻膱?zhí)行順序。由于EⅠP只是一個(gè)寄存器,所以我們無法為其分配要執(zhí)行指令的內(nèi)存地址。
圖1 C程序中的內(nèi)存結(jié)構(gòu)
圖2 棧的內(nèi)存結(jié)構(gòu)
當(dāng)函數(shù)執(zhí)行時(shí),一個(gè)包含有函數(shù)信息的棧幀(stack frame)會(huì)被壓入棧中。一旦函數(shù)執(zhí)行完畢,棧幀會(huì)被彈出棧,函數(shù)完成執(zhí)行后,將從棧中彈出相關(guān)的棧幀,并在中斷的調(diào)用的函數(shù)中繼續(xù)執(zhí)行。CPU知道必須從何處繼續(xù)執(zhí)行程序,它是從調(diào)用函數(shù)時(shí)壓入棧的返回地址獲得此信息。
為了方便理解,我們舉一個(gè)例子:在主函數(shù)中調(diào)用func()。因此,當(dāng)程序開始時(shí),將調(diào)用main(),并為其分配一個(gè)棧幀并將其壓入棧。接下來main()調(diào)用func(),同樣是分配棧幀,將其壓入棧并將執(zhí)行移交給func(),main()通過將這個(gè)值(返回地址)壓入棧,來指出當(dāng)func()返回(通常是在調(diào)用func()之后的代碼行)時(shí),需要繼續(xù)執(zhí)行的地方。
圖3 棧幀結(jié)構(gòu)
在func()函數(shù)執(zhí)行完后,它的棧幀被彈出,其中存儲(chǔ)的返回地址被加載到EⅠP寄存器中,繼續(xù)執(zhí)行main()。如果我們能夠控制返回地址,我們就能在func()返回時(shí)劫持將要執(zhí)行的指令。
首先,我們下載并運(yùn)行bof-server??梢钥吹竭@個(gè)程序在端口200上提供TCP服務(wù)。如圖4所示:
圖4 程序在端口200上提供TCP服務(wù)
然后,我們向TCP 200端口發(fā)起TELNET連接,建立連接后向其發(fā)送若干隨機(jī)數(shù)據(jù)。如圖5所示:
圖5 建立連接后向其發(fā)送若干隨機(jī)數(shù)據(jù)
我們發(fā)現(xiàn),當(dāng)提供一定數(shù)量的隨機(jī)數(shù)據(jù)之后,連接就斷開了,這是因?yàn)槟繕?biāo)服務(wù)器已經(jīng)崩潰。來看一看目標(biāo)服務(wù)器上的報(bào)錯(cuò)信息,如圖6所示:
圖6 目標(biāo)服務(wù)器上的報(bào)錯(cuò)信息
點(diǎn)擊“click here”,查看詳細(xì)情況,發(fā)現(xiàn)程序是由于無法在地址41414141處找到下一條要執(zhí)行的指令,從而導(dǎo)致了程序的崩潰。因?yàn)槲覀冸S機(jī)輸入的是若干個(gè)字母A,而值41就是字母A的十六進(jìn)制表示,這說明我們輸入的數(shù)據(jù)已經(jīng)超出了緩存的范圍,而且覆蓋了EⅠP寄存器。接下來,程序試圖執(zhí)行41414141這個(gè)地址上指令,顯然這不是一個(gè)有效的地址,因此,程序崩潰了。
由于我們的輸入數(shù)據(jù)超出了程序棧的緩存范圍,引發(fā)了程序的棧緩存溢出漏洞,導(dǎo)致程序崩潰。如果我們控制好輸入的數(shù)據(jù),使得覆蓋EⅠP寄存器的內(nèi)容恰好是我們想要執(zhí)行的代碼地址,那么我們就控制了服務(wù)器,從而完成了漏洞利用。
根據(jù)上一節(jié)的思路,我們將利用Metasploit框架開發(fā)一個(gè)破解模塊,觸發(fā)漏洞并運(yùn)行我們想要執(zhí)行的其他代碼。
模塊開發(fā)的第一個(gè)步驟是找出偏移量,在這個(gè)過程中將用到Metasploit中的兩款工具,分別是pattern_create和pattern_offset。工具pattern_create用來按一定規(guī)律生成字符,例如:#./pattern_create.rb 1000,表示生成1000個(gè)字符。將這些字符發(fā)送給目標(biāo)服務(wù)器后如果程序崩潰,就能得到一個(gè)地址的值,我們得到的地址值是72413372,將該值作為參數(shù),使用工具pattern_offset就能得到具體的偏移量,例如:#./pattern_offset.rb 72413372 1000,表示EⅠP中的地址為72413372,填充1000個(gè)字符。最后,我們得出的偏移量是520,在520個(gè)字節(jié)后面的4個(gè)字節(jié)的數(shù)據(jù)就會(huì)覆蓋EⅠP寄存器。
接下來,我們還將使用Metasploit中的另一個(gè)工具msfpescan來找到程序中JMP ESP指令的地址,在這里我們利用bof-server程序調(diào)用的一個(gè)DLL 文件ws2_32.dll 。命令如下:#./msfpescan -j esp -f/root/Desktop/ws2_32.dll,參數(shù)-j后面的是寄存器名,這里用到的寄存器是ESP,返回結(jié)果為0x71ab9372,這是ws2_32.dll文件中JMP ESP指令的地址。只需要用這個(gè)地址來重寫EⅠP寄存器中的內(nèi)容,就可以執(zhí)行我們的代碼。
到目前為止,我們已經(jīng)掌握了開發(fā)Metasploit程序破解模塊的主要內(nèi)容,讓我們看看代碼是怎樣的。如下所示:
在分析代碼之前,我們先看看模塊中用到的庫,請看表1。
表1 模塊中用到的庫
破解模塊開頭就是包含各種必要的路徑和文件。我們把模塊類型定義為Msf::Exploit::Remote,意味著它是一個(gè)遠(yuǎn)程破解模塊。接下來,我們在initialize方法中定義name,description,author等基本信息。另外,我們還看到大量的其他聲明。請看表2:
表2 破解模塊
避免程序崩潰或payload 不執(zhí)行
讓我們看看上面代碼中用到的一些重要函數(shù),如表3。
表3 重要函數(shù)
在我們之前編寫的模塊中,run方法是輔助模塊的默認(rèn)方法。然而,對于破解模塊而言,默認(rèn)的方法是exploit。
我們使用connect連接目標(biāo)。使用make_nops函數(shù)創(chuàng)造520個(gè)NOP,這個(gè)數(shù)來自initialize函數(shù)中定義的target的Offset。把520個(gè)NOPs存儲(chǔ)到變量buf中。下一條指令,我們通過從target聲明的Ret字段中獲取其值,將JMP ESP地址附加到buf。使用函數(shù)pack(‘V’),我們得到地址的小端格式。在Ret地址之后,我們附加幾個(gè)NOPs作為ShellCode之前的填充。使用Metasploit的優(yōu)點(diǎn)之一是能在運(yùn)行中切換payload。因此,只需要簡單地使用payload.encoded就能把當(dāng)前所選的payload附加在變量buf之后。
圖7 步驟1
接下來,使用函數(shù)sock.put建立與目標(biāo)的連接,參數(shù)是buf。使用handler方法檢查目標(biāo)是否被成功破解,如果成功破解則會(huì)建立連接。最后,用disconnect斷開連接。我們來看看使用的效果:我們設(shè)置必要的參數(shù),payload設(shè)置為Windows/meterpreter/bind_tcp,這意味著到目標(biāo)的直接連接。讓我們看看使用exploit命令后會(huì)發(fā)生什么(圖8)。
圖8 步驟2
顯然,我們編寫的破解模塊成功了,獲得一個(gè)meterpreter會(huì)話,通過該會(huì)話可以在目標(biāo)服務(wù)器上進(jìn)行非授權(quán)操作。
通過上述例子,我們發(fā)現(xiàn)棧緩存溢出漏洞雖然早在二十世紀(jì)八十年代就存在,但至今仍對信息系統(tǒng)安全有著重要的影響。為了規(guī)避該漏洞,目前一般有幾種做法:使內(nèi)存執(zhí)行的堆棧部分為非可執(zhí)行文件;使用更加健壯的C和C++庫;通過編譯器保護(hù)返回地址;使用防火墻[2]等等。最理想的辦法是雇傭最好的程序員謹(jǐn)慎編碼,當(dāng)然這本身就不是一件容易的事。因此,我們需要對程序進(jìn)行嚴(yán)格的模糊測試,降低棧緩存溢出漏洞發(fā)生的概率。
網(wǎng)絡(luò)安全技術(shù)與應(yīng)用2020年7期