作者:且聽安全
原文鏈接:https://mp.weixin.qq.com/s/hHlscdLIvO0BY173ksq8vA
接上文:
第一部分:樣本分析
CVE-2021-40444-Microsoft MSHTML遠程命令執行漏洞分析(一)
第二部分:漏洞復現
本系列第三篇主要對漏洞成因和原理做個分析。漏洞成因分析可以說即是技術活又是體力活,整個分析過程會比較冗長,這里我們不會像其他分析文章那樣直接給出結論(讓人摸不著頭腦),而是會按照我的分析思路一步一步往前走,那么下面開始。
既然我們基本知道了漏洞樣本的執行流程,那么能想到就是掛上調試器在感興趣的地方下斷點,第一個斷點嘗試下在kernelbase!CreateFileW,找到創建championship.inf文件的地方(由于調用CreateFileW的地方會比較多,這里建議可以設置個條件斷點,比如判斷文件名參數poi(esp+4)+xx地方的值是0x6e0066,即文件后綴為".inf"):

看一下函數調用堆棧,可以看到,大部分處理都是在urlmon這個模塊中:

下面可以通過IDA進行簡單的靜態分析,可以找到在GetSupportedInstallScopesFromFile這個函數中開始對cab包進行處理:

先是通過GetExtnAndBaseFileName判斷傳入文件的后綴名,如果是.cab則返回2(即可以通過上面的判斷,進入下一步處理),其實這里只判斷了3個字節,只要文件名后綴是.cabxx這樣的都能通過驗證,算是一個小bug:

如果臨時目錄創建成功,則通過ExtractInfFile(如下圖)來解壓cab包中的inf類型的文件(注意,這個函數只會解壓后綴是inf的文件),從IDA中可以看到調用GetExtnAndBaseFileName后需要返回值為5(參考前面的圖,當后綴為inf時返回5),因此該函數只會解壓后綴是inf的文件,最終調用ExtractOneFile來實現解壓功能(注意,如果cab中有多個inf文件,則只會解壓第一個遇到的):

從上圖可以看到,該函數先調用了一次Extract,如果成功了才會開始解壓inf文件,我可以簡單跟進去看一下這個Extract(其實后面還會再次調用Extract函數),內部主要的功能是順序調用FDICreate、FDICopy和FDIDestroy,如果任何一個函數失敗或異常則返回錯誤號,否則返回0:

其中FDIDestroy和FDICopy函數則直接調用cabinet中的FDIDestroy和FDICopy,而FDICreate函數會動態加載cabinet.dll,并調用cabinet中的FDICreate函數來創建一個FDI對象:

cabinet.dll我們先不跟進去,還是回到urlmon.dll的ExtractInfFile函數中,程序通過一個循環來遍歷cab包中的文件,如果發現后綴為inf的文件,則會調用ExtractOneFile函數來解壓inf文件,并退出函數:

ExtractOneFile函數其實也很簡單,還是通過Extract函數來實現解壓inf文件的功能,但是跟前面第一次Extract不同的是傳入的參數pfnfdin存在不同。

通過調試可以看到,傳入Extract的參數pfnfdin包含了需要解壓的inf的名稱 ../championship.inf:


現在我們再次進入這個Extract函數,跟進cabinet模塊中的FDICopy看看,下圖是該函數的定義:

FDICopy函數處理過程其實還是會回調urlmon提供的回調函數來實現cab的解壓:openfunc、readfunc等等(這些回調函數是在FDICreate的時候初始化的):


我重點關注的是CreateFileW創建inf文件的地方,cabinet模塊回調了urlmon中的fdiNotifyExtract函數:

現在進入fdiNotifyExtract函數,可以看到前面分別調用了catDirAndFile、AddFile和NeedFile,如果都能成功則最后調用Win32Open來創建inf文件(終于找到關鍵點了):

catDirAndFile函數:將解壓文件夾路徑和文件名被拼接在一起(注意:該函數并沒有對"../"這樣的字符串進行過濾,導致目錄穿透)。AddFile函數:文件信息加入FDI結構體中(對漏洞觸發沒有什么影響)。NeedFile函數:判斷是否需要該文件(注意:如果驗證不通過將不會調用win32Open)。Win32Open函數:直接調用CreateFileA創建文件。
先來看catDirAndFile函數,主要是會調用PathCchCanonicalizeA對路徑進行一系列的處理(流程比較復雜,看著頭暈,有興趣的可以自己去逆一下,說不定能找到什么別的問題),幸運的是并不會對"../"進行過濾:

而NeedFile功能比較簡單,主要是判斷文件名是否一致,還有一個關鍵點是hfdi+808這個地方必須有數據(在ExtractOneFile函數中,該位置被正常賦值了,因此能夠通過驗證):

最后順利到達Win32Open函數,傳入的路徑就是C:\Users\user\AppData\Local\Temp\Cab82BF\../championship.inf:

到這還沒結束,大家可能還記得,我們在復現漏洞的時候發現釋放的inf文件很快就會被刪除,導致后面無法實現代碼執行,我們繼續往后走來一探究竟。創建完inf文件后,我們回到urlmon中的GetSupportedInstallScopesFromFile函數,它會立即調用DeleteExtractedFiles函數來刪除解壓出來的文件:

但是我們用調試器跟入DeleteExtractedFiles后發現并沒實際調用DeleteFileA,因為file_info[2]中的值非0,所以沒有刪除inf文件。

我們通過內存斷點看一下這個file_info[2]到底是在哪里賦值的,通過斷點我們發現該位置是在urlmon!AddFile函數被賦值為1的,然而后面直到DeleteExtractedFiles函數,該位置始終沒有被清空,因此inf文件得以保留:

到底是怎么回事呢,直接調試分析比較麻煩。根據前面的測試,正常的cab也會將inf文件釋放到Temp目錄,但是會馬上被刪除。因此我們可以用一個正常的cab包再調試一遍看一下,找到該值被清零的位置。很快我們便找到了賦值點:正常流程下fdiNotifyExtract函數會調用MarkExtracted來清空該值。

從fdiNotifyExtract函數中可以看到,當調用參數type為fdintCLOSE_FILE_INFO(值為3)時,才可能調用MarkExtracted:

調用fdiNotifyExtract函數父函數是Cabinet!FDIGetFile,我們仔細看一下FDIGetFile中的處理流程(如下圖),就是循環調用FDIGetDataBlock來讀取inf文件的數據,并調用urlmon!writefunc函數將數據逐塊寫入文件。這里如果文件大小正常,則最后一次循環后將轉跳至LABEL_13調用fdiNotifyExtract,且type就是fdintCLOSE_FILE_INFO,因此文件會被刪除。由于攻擊者偽造了文件大小,導致remain_size超出了文件的正常大小,最終導致FDIGetDataBlock異常并返回0,后面就轉跳至LABEL_20調用urlmon!closefunc并返回了:

現在我們基本理清了inf文件被創建的整個流程,最后我們總結一下:
mshtml.dll在處理<object codebase="http://xx.xx.xx/xxx.cab">對象時,會利用urlmon.dll來實現cab安裝包的下載urlmon.dll模塊會調用urlmon!SetInstallScopeFromFile來設置安裝文件,該文件后綴必須為".cab"- 接著調用
urlmon!ExtractInfFile來釋放cab文件中的一個inf文件,該文件后綴必須為".inf" - 【目錄穿透漏洞】:由于inf文件名以"../"開頭,拼接的文件路徑其實在上一層目錄
- 最后調用
urlmon!Win32Open實現在Temp目錄中創建inf文件 - 【文件駐留漏洞】:文件大小異常使標記位沒有被正常清零,導致inf文件沒有被刪除
至此,漏洞原理分析就告一段落了,整個漏洞分析過程還是很有意思的,下一篇計劃做個補丁分析。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1806/
暫無評論