二進制漏洞挖掘學習——MS09-050
在學習漏洞的時候,按照0Day2書中第24章第1節的內容進行學習的,這章本來是遠程拒絕服務的漏洞(CVE-2009-3103),但是當我在網上搜索這個漏洞的EXP時,意外的發現了Srv2.sys模塊中的另一個漏洞(CVE-2009-2532),而這個漏洞竟然可以實現遠程任意代碼執行,誒,這我就不困了,然后順手兩個漏洞一起分析了,把Srv2.sys模塊對數據包的接收處理過程逆向了一遍,了解了其中的漏洞利用原理。
首先在SRV2.sys模塊中,SrvReceiveHandler(這個函數會處理發送過來的數據包)其主要功能是檢查數據包長度是否正確:

并且分配一個WorkItem項,并將SrvProcessPacket()地址(數據包處理函數)填入到該項對應位置處,并填寫WorkItem項中其他字段內容,用于后續的創建線程函數調用該函數進行數據包處理。


等待系統創建線程后續從隊列中取出該WorkItem項時,系統調用SrvProcessPacket函數處理數據包。


這是該函數中唯一一處修改esi值的地方,此處的操作就是將esi - 16。這里根據微軟官方文檔及其他內核結構體猜測,從WorkItem隊列中取出該WorkItem項時是通過雙向鏈表取出來的,而為了指向WorkItem結構體的起始位置需要-16Byte。
接下來就調用SrvProcessPacket處理數據包了:
在SrvReceiveHandler函數中對WorkItem項的結構體進行賦值時:將SrvProcessPacket()函數的地址寫入 esi+1Ch 中。
在函數SrvProcWorkerThread()函數創建新的線程時調用 esi+1Ch 處的函數,該位置處正好是SrvProcessPacket函數的地址,即為調用SrvProcessPacket函數。

在SrvProcessPacket函數中會調用Smb2ValidateProviderCallback()這個函數。

這部分是通過動態調試得到的結果。具體是怎么因為什么沒有充分的證據鏈,因為太菜了沒有相關的編程經驗,所以沒辦法通過靜態調試弄清楚,不過通過靜態調試也看到了點眉目。
并且會在處理完數據包后,執行SrvProcComleteRequest()函數進行請求處理完成操作,而在這個函數中實現了任意地址寫的功能(通過任意寫先寫出了跳板指令),并且也實現了任意代碼執行的功能,是我們任意代碼執行exp中利用的點。

而在Smb2ValidateProviderCallback()函數中,存在一個漏洞,該漏洞可以造成BSOD,實現遠程拒絕服務。(CVE-2009-3103):
esi 指向接收到的數據包指針(數據包去除掉 NETBIOS Header)


首先esi指向收到的數據包,esi+0Ch字段為PIDHigh字段,正常情況下PIDHigh字段值應該為0,但是這里沒有對eax的值就是PIDHigh字段進行限制,如果我們的PIDHigh字段的值為畸形函數的話,就會造成數組越界,通過查看ValidateRoutines數組值:

(可知如果是正常情況PIDHigh字段為0,那么eax將指向Smb2ValidateNegotiate()函數。)
接下來將進行 call eax操作,就會執行eax指向的函數,如果是正常情況下將執行Smb2ValidateNegotiate()函數,如果eax就會指向一個非法的地址,這就會造成BSOD,進而實現遠程拒絕服務攻擊。(總感覺這里其實也可以利用來進行任意代碼執行)

我們發送的數據包:
NETBIOS Header
buff = b'\x00' #Message Type : Session message
buff += b'\x00' #Flags
buff += b'\x00\x90' #Count of data bytes (netbios header not included)
SMB Base Header
buff += b'\xff\x53\x4d\x42' #Server Component: SMB Protocol[4]
buff += b'\x72' #Command
buff += b'\x00\x00\x00\x00' #Status
buff += b'\x18' #Flags
buff += b'\x53\xc8' #Flags2
buff += b'\x00\x26' #PIDHigh
#buff += b'\xFF\xFF'
buff += b'\x00\x00\x00\x00\x00\x00\x00\x00' #SecurityFeatures
buff += b'\x00\x00' #Reserved
buff += b'\xff\xff' #TID
buff += b'\xff\xfe' #PIDLow
buff += b'\x00\x00' #UID
buff += b'\x00\x00' #MID
buff += b'\x00' #WordCount
buff += b'\x6d\x00' #ByteCount
buff += b'\x02\x50\x43\x20\x4e\x45\x54' #Data
buff += b'\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31'
buff += b'\x2e\x30\x00\x02\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00'
buff += b'\x02\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57'
buff += b'\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61'
buff += b'\x00\x02\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00\x02\x4c'
buff += b'\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00\x02\x4e\x54\x20\x4c'
buff += b'\x4d\x20\x30\x2e\x31\x32\x00\x02\x53\x4d\x42\x20\x32\x2e'
buff += b'\x30\x30\x32\x00'
通過動態調試了解到esi指向的內容為我們發送的數據包:

而在SrvProcCompleteRequest()函數中,存在兩個個漏洞,這兩個漏洞聯合利用可以實現遠程任意代碼執行攻擊。(CVE-2009-2532):
通過精心構造數據包(這部分構造的數據包有的字段并沒有必要,我覺得當時作者將所有處理數據包的函數都逆向后寫的exp,其中有些流程是不會走到的,所以有些字段是多余的)
(u32)(p+0x3C+4) = READ_ADDR;
(u8)(p+0xCC+4) = 0xCC;
(u32)(p+0xAC+4) = TRAMPOLINE_ADDR + off_pass;
(u32)(p+0x0CE+4) = 0x0;
(u32)(p+0x30+4) = READ_ADDR;
(u32)(p+0x78+4) = READ_ADDR;
(u32)(p+0x168+4) = 0x00000000;
(u8)(p+0xE0+4) = 0x90 | 0x04;


這部分剛好調用DPC例程,實際調用的函數為:SrvScavengerThread,不會對我們的攻擊產生影響。
這部分存在一個問題就是為什么SrvScavengerThread函數不會對我們的ShellCode產生影響,沒有具體的依據,不過該函數通過函數名可知是清除線程的函數,不會對我們的ShellCode產生影響,這里由于缺少編程經驗,所以無法判斷該函數的影響。
首先構造的數據包可以通過流程調用SrvProcPartialCompleteCompoundedRequest()函數,利用該函數可以實現任意地址寫的功能。

進入SrvProcPartialCompleteCompoundedRequest()函數中,傳遞過來的是我們發送的數據包。

到達漏洞利用點。

可以看到其中eax可控,為數據包中頭偏移0xAC地址處指向的內容+0xBC(p指向數據包,eax = [p+0xAC] + 0xBC),所以我們可以實現任意地址寫功能,通過多次發送數據包實現寫任意的功能(每發送一次數據包就會將這個地址處的內容++,*(eax)++),exp中利用這個任意寫功能實現向內存中寫入跳板指令,供之后的shellcode執行提供跳板。
這也是這個exp的亮點之一,通過這種方式實現向任意地址寫的攻擊,不是傳統的簡單的任意地址寫的攻擊,是一個比較好的漏洞利用思路。
而從數據包傳送進SrvProcCompleteRequest()函數到執行完SrvProcPartialCompleteCompoundedRequest()函數,其中會對數據包的某些位置進行判斷,所以在數據包的相應字段應該寫入對應的數值:
首先在SrvProcCompleteRequest()函數中要執行到SrvProcPartialCompleteCompoundedRequest()函數中間會有如下判斷:
(u8)(p + 0xCC + 4) = 0xCC;

(u32)(p + 0xAC + 4) = TRAMPOLINE_ADDR + off_pass;

在SrvProcPartialCompleteCompoundedRequest()函數中,要執行完畢中間會有如下判斷:
(u8)(p + 0xE0 + 4) = 0x90 | 0x04;

(u32)(p + 0x78 + 4) = READ_ADDR;

而在通過任意地址寫功能向目標地址寫入跳板指令后,接下來就是通過跳板指令執行真正的攻擊代碼了。

可以看到其中eax可控,為數據包中頭偏移0x168地址處指向的內容(p指向數據包,eax = [p+0x168] ),進而實現了任意代碼執行功能。
這里部分值得注意的點就是esi指向我們發送的數據包,也就是我們輸入的內容,可以通過esi將控制程序流程轉入到我們發送的數據中。
所以exp中之前任意地址寫入的數據就是: 0xC35646(inc esi,push esi,ret)(inc esi是因為0xff會干擾ShellCode執行,所以跳過)
這樣程序就轉入到了我們輸入的數據中,實現了任意代碼執行。
看exp中的ShellCode也挺有意思的,其先發送一個數據包通過之前設置的跳板指令執行在Srv2.sys模塊中實現了一個類似的Hook(在Srv2.sys中開了個后門),之后任何發送過來的數據包都會被執行。
EXP放在了附件(點擊最下方閱讀原文前往下載)中,在Github上搜到的。