Windows Workstation Service遠程溢出的分析
題註:世界著名安全性群組織eEye Digital Security,於11月11日在http://www.eeye.com 網站首頁公布了他們關於對Windows Workstation服務存在緩衝溢出缺陷的發現。這個缺陷牽涉到的是多數Windows作業系統賴以正常啟動並執行基本服務,可以被遠程利用,相關的TCP連接埠是139和445。這個缺陷的公布,對正處於多事之秋的Windows作業系統安全體系又是一次衝擊。不過,正如CNNS提出的“攻擊是檢驗網路安全的最佳手段”,每一次作業系統的嚴重缺陷和他們的攻擊利用代碼,以及後來出現的蠕蟲,都迫使廠商加速推出安全解決方案,有關這些服務的安全性也將得到一定提升。這種攻與防的迴圈,將伴隨任何一個主流作業系統的生命週期。
本文是CNNS研發部門對此缺陷的攻擊利用技術進行逐步分析的記述,以裨專業人員參考。時間倉促,有任何錯誤與不足之處,歡迎來信交流:snake@cnns.net
///////////////////////////////////////////////////////////////////////////////////////////////////////////
Eeye在11月11日公布了Windows Workstation存在遠程溢出的漏洞,這是微軟的又一個基本服務,存在嚴重的被攻擊缺陷。
本文將就如何利用這個漏洞做逐步分析。
根據eeye的一些公開的資訊來看,漏洞是出在 wkssvc.dll的 vsprintf調用。推斷應該是沒有檢查輸入緩衝的長度。利用函數NetValidateName 可以直接攻擊。
下面的環境是:
用戶端:win2k,和服務端建立 ipc$串連,然後,用 NetValidateName 進行互動,觸發溢出。具體的sample代碼不貼出來了,packetstorm和其他網站已經公布了不少。。。
服務端:被攻擊端(簡體中文win2k + sp3)
開啟windbg,跟蹤相關的函數,開始是 RPCRT4.dll的 NdrServerCall2調用,這個短時間內沒有辦法細看和消化,不理會,繼續跟蹤進去。。。
然後又是一些調用。包括 NdrServerInitializeNew的調用, NdrPointerUnmarshell和 NdrConformantStringUnmarshall的調用,這些也可以不理會,繼續跟蹤,呵呵,機器其實已經重新啟動了N次,沒有關係,虛擬機器。:)
下面是出錯函數的分析,上面的一些初始化動作不理會,總之,錯誤是出在這裡面,利用也是在這個函數返回的時候利用。。
.text:76724CD7 ; int __stdcall sub_76724CD7(HANDLE hFile,int,int)
.text:76724CD7 sub_76724CD7 proc near ; CODE XREF: sub_76724DB5+20p
.text:76724CD7
.text:76724CD7 var_81A = byte ptr -81Ah
.text:76724CD7 var_819 = byte ptr -819h
.text:76724CD7 Buffer = byte ptr -818h
.text:76724CD7 var_817 = byte ptr -817h
.text:76724CD7 NumberOfBytesWritten= dword ptr -14h
.text:76724CD7 SystemTime = _SYSTEMTIME ptr -10h
.text:76724CD7 hFile = dword ptr 8
.text:76724CD7 arg_4 = dword ptr 0Ch
.text:76724CD7 arg_8 = dword ptr 10h
.text:76724CD7
.text:76724CD7 push ebp
.text:76724CD8 mov ebp, esp
.text:76724CDA sub esp, 818h ; //!!這裡只分配了 0x818=2072個位元組的空間給全部變數
.text:76724CE0 cmp [ebp+hFile], 0 ; //判斷是否無效的檔案控制代碼
.text:76724CE4 jz locret_76724DB1 ; //如果是,則返回
.text:76724CEA push edi
.text:76724CEB mov edi, offset unk_76727C60
.text:76724CF0 push esi
.text:76724CF1 push edi ; lpCriticalSection
.text:76724CF2 call ds:EnterCriticalSection ; //進入臨界空間
.text:76724CF8 xor esi, esi
.text:76724CFA cmp dword_76727A3C, esi ; 判斷是否需要列印時間資訊
.text:76724D00 jz short loc_76724D3C
.text:76724D02 lea eax, [ebp+SystemTime] ; 下面進行時間資訊字串的輸出。
.text:76724D05 push eax ; lpSystemTime
.text:76724D06 call ds:GetLocalTime
.text:76724D0C movzx eax, [ebp+SystemTime.wSecond]
.text:76724D10 push eax
.text:76724D11 movzx eax, [ebp+SystemTime.wMinute]
.text:76724D15 push eax
.text:76724D16 movzx eax, [ebp+SystemTime.wHour]
.text:76724D1A push eax
.text:76724D1B movzx eax, [ebp+SystemTime.wDay]
.text:76724D1F push eax
.text:76724D20 movzx eax, [ebp+SystemTime.wMonth]
.text:76724D24 push eax
.text:76724D25 lea eax, [ebp+Buffer]
.text:76724D2B push offset a02u02u02u02u02 ; "%02u/%02u %02u:%02u:%02u "
.text:76724D30 push eax
.text:76724D31 call ds:sprintf ; //at first, format the time string...
.text:76724D37 add esp, 1Ch
.text:76724D3A mov esi, eax
.text:76724D3C
.text:76724D3C loc_76724D3C: ; CODE XREF: sub_76724CD7+29j
.text:76724D3C push [ebp+arg_8]
.text:76724D3F lea eax, [ebp+esi+Buffer] ; 得到輸出緩衝的地址,這裡是 esi-0x818
.text:76724D3F ; 其中,esi是調整的輸出指標。如果列印了時間資訊,
.text:76724D3F ; 則=時間字串的長度。否則,=0。
.text:76724D46 push [ebp+arg_4] ; 這裡的格式是:
.text:76724D46 ; NetpValidateName: checking to see if %ws is valid as type %d name.
.text:76724D46 ;
.text:76724D46 ; *** 注意,是 %ws 和 %d 的參數。
.text:76724D46 ; %ws。。。。比較麻煩的轉換。嘿嘿,還是有辦法的。
.text:76724D46 ;
.text:76724D49 push eax
.text:76724D4A call ds:vsprintf ; 這裡發生了溢出
.text:76724D50 add esp, 0Ch
.text:76724D53 add esi, eax ; 這裡判斷是否 esi+eax = 0。如果沒有輸出,則做個標記=0
.text:76724D55 jz short loc_76724D6D
.text:76724D57 cmp [ebp+esi+var_819], 0Ah ; ...搞不懂為什麼這裡要判斷。如果沒有斷行符號,也做個標記。。:(
.text:76724D57 ;
.text:76724D5F jnz short loc_76724D6D
.text:76724D61 mov dword_76727A3C, 1
.text:76724D6B jmp short loc_76724D78 ; 增加一個斷行符號到輸出緩衝的開頭,很好玩,
.text:76724D6B ;
.text:76724D6D ; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
.text:76724D6D
.text:76724D6D loc_76724D6D: ; CODE XREF: sub_76724CD7+7Ej
.text:76724D6D ; sub_76724CD7+88j
.text:76724D6D xor eax, eax
.text:76724D6F test eax, eax
.text:76724D71 mov dword_76727A3C, eax
.text:76724D76 jz short loc_76724D91
.text:76724D78
.text:76724D78 loc_76724D78: ; CODE XREF: sub_76724CD7+94j
.text:76724D78 mov [ebp+esi+var_819], 0Dh ; 增加一個斷行符號到輸出緩衝的開頭,很好玩,
.text:76724D78 ;
.text:76724D80 mov [ebp+esi+Buffer], 0Ah
.text:76724D88 and [ebp+esi+var_817], 0
.text:76724D90 inc esi
.text:76724D91
.text:76724D91 loc_76724D91: ; CODE XREF: sub_76724CD7+9Fj
.text:76724D91 lea eax, [ebp+NumberOfBytesWritten]
.text:76724D94 push 0 ; lpOverlapped
.text:76724D94 ; 這裡進行寫檔案的動作。
.text:76724D94 ; 注意,WriteFile的第4個參數
.text:76724D94 ; lpNumberOfBytesWritten 是在
.text:76724D94 ; ebp-14的位置,會改寫buffer,所以,
.text:76724D94 ; 如果有shellcode放到那裡,就要小心
.text:76724D94 ; 這個位置的資料了。。
.text:76724D96 push eax ; lpNumberOfBytesWritten
.text:76724D97 lea eax, [ebp+Buffer]
.text:76724D9D push esi ; nNumberOfBytesToWrite
.text:76724D9E push eax ; lpBuffer
.text:76724D9F push [ebp+hFile] ; hFile
.text:76724DA2 call ds:WriteFile
.text:76724DA8 push edi ; lpCriticalSection
.text:76724DA9 call ds:LeaveCriticalSection ; 這裡 LeaveCriticalSection。還好,參數edi沒有被改掉。
.text:76724DA9 ; 否則,進行攻擊的時候又多了很多麻煩了。
.text:76724DAF pop esi
.text:76724DB0 pop edi
.text:76724DB1
.text:76724DB1 locret_76724DB1: ; CODE XREF: sub_76724CD7+Dj
.text:76724DB1 leave
.text:76724DB2 retn 0Ch ; ok,函數返回,嘿嘿,處理好了,就會執行我們的shellcode。
.text:76724DB2 sub_76724CD7 endp
.text:76724DB2
.text:76724DB5
如上分析,程式在vsprintf中,的參數 %ws進行格式化,將NetValidateName的第2個參數作為輸入,格式化後,輸出資料到堆棧中去,當內容太長的時候,就會發生堆疊溢位。
現在分析被攻擊的可能性。
1. 這個函數開始就檢查檔案控制代碼的合法性,如果沒有辦法開啟 %windir%/debug/netsetup.log的話,則這個函數沒有辦法被執行。所以,當觸發該伺服器執行檔案記錄時,串連的帳號如果沒有許可權開啟該檔案,則不能進行以後的攻擊。除非,伺服器使用權限設定不正確,或者是fat32的檔案格式,沒有辦法進行許可權限制。嘿嘿。。。
2. 輸入的長度不長的時候,會發生堆疊溢位,只要在溢出點(大概是 0x818-12)的位置,填入 jmp esp的內容,然後,在下一個地址開始的地方,寫入shellcode,就可以運行代碼了。
3. 輸入的長度很長的時候,會觸發windows的結構化異常保護,如果資料足夠大,把異常結構都覆蓋了,也可以實現跳轉,不過,這個時候就比較危險了,系統再也無法被第2次溢出,因為函數開始的時候,進入了臨界區,而退出的時候,這種情況下,該臨界變數沒有被釋放。
4. NetValidateName第2個參數的緩衝,輸入的時候,是Unicode的,被vsprintf的時候,是 %ws,會被轉換回ANSI字串。這裡調用的vsprintf是msvcrt.dll的同名函數,而非libc的標準庫中的函數。這個vsprintf在轉換的時候,並不是100%都能夠轉換,跟蹤了一下,發現是調用 wctomb的函數來執行。到最後,即使能夠被轉換,最後輸出可能也是0。標準的可見字串是可以被轉換的,但是,多位元組語言的資料就沒有那麼好弄了。也就是說,執行的代碼暫時只能限制在可見字元內,其它的,要看如何小心構造的。更多的細節,需要詳細研究。( 結論就是:多語種通用的攻擊代碼,要實現,還有比較長的一段路要走。。。)
5. 輸入的緩衝中,0x818-12-0x14-strlen(“NetpValidateName: checking to see if ”)的位置的資料,會被 WriteFile的參數改寫,所以,這裡要注意shellcode被破壞。
6. 這個攻擊如果在shellcode執行完成以後,採用exitthread,則應該可以無限次被溢出。。。
總之,上面已經分析了不少東西了,寫出具體的通用的攻擊程式只是時間的問題。裡面好像也沒有更多的技術痛點和技巧,沒有什麼好說了。希望已經實現了的大俠謹慎公布攻擊工具和代碼,本文章只是從技術角度進行分析,這種”垃圾代碼”其實還是比較容易防護的,只要把vsprintf轉換成 vsnprintf就可以避免出現類似的問題!。
Eeye還有一個攻擊方法,是 NetAddAlternateComputerName的攻擊,針對NTFS的格式有效,我沒有看,XP下可以,2K下沒有這個函數,不知道XP能否攻擊2K。。。有空再弄了。
*** 後記 ***
真不知道如何評價這個漏洞,微軟的軟體,這個SERVER和WORKSTATION的服務提供了很強大的服務功能,但是,偏偏存在那麼嚴重的漏洞。。。。恐怖,自己寫程式的時候還是要小心點,尤其是廣為使用的程式。
SNAKE. 2003/11/17 morning.
串連:http://www.cnns.net/news/db/3796.htm