動態(tài)鏈接庫的靜態(tài)鏈接導(dǎo)致程序的DLL劫持漏洞 一、 庫 首先明確一下庫的概念,庫里存放的都是二進制編碼??v觀編程技術(shù)的發(fā)展路線,可以看到一條清晰的發(fā)展脈絡(luò):代碼>靜態(tài)庫>動態(tài)庫。 假如我們要編寫一個程序叫做Calc.exe,而現(xiàn)在有現(xiàn)成的庫,庫里面存放的是已經(jīng)編譯好的函數(shù)Add(),Sub()以及其他相關(guān)的符號等等,并且靜態(tài)庫(calcfun.lib)和動態(tài)庫(calcfun.dll)各有一個版本。 那么我們就只需要編碼Calc.exe的主程序,在其中使用庫中的函數(shù)(可以導(dǎo)出函數(shù)類常量等待這些統(tǒng)稱為符號),而不需要在編碼實現(xiàn)這些函數(shù)。 A. 使用靜態(tài)庫 靜態(tài)庫只有一個lib文件calcfun.lib(忽略導(dǎo)出函數(shù)聲明calcfun.h文件),這個lib文件里面包含的就是二進制編碼。在鏈接期間,鏈接器將會抽出庫中我們所引用的符號的二進制編碼,并且把這些二進制編碼合并到生成的Calc.exe中,最終我們得到的程序就只有并且只需要一個Calc.exe就可以運行(這里忽略了操作系統(tǒng)系統(tǒng)所需的庫)。 B. 使用動態(tài)庫 標準的動態(tài)庫一般有兩個文件calcfun.dll,calcfun.lib(忽略導(dǎo)出函數(shù)聲明calcfun.h文件),不要混淆這個lib文件與上述的靜態(tài)庫lib文件,這個lib文件準確的名稱是到導(dǎo)入庫,包含的是dll中導(dǎo)出符號的地址表,只是提供給鏈接器定位dll中符號之用,并不是二進制代碼,真正的二進制代碼存在于calcfun.dll中。為什么會有兩個文件?因為對于動態(tài)鏈接庫的使用方法可以有兩種:靜態(tài)鏈接(隱式鏈接),動態(tài)鏈接(顯示鏈接)。 1) 靜態(tài)鏈接(隱式鏈接) 也只有使用這種方式的時候才會用到動態(tài)庫的lib文件,即導(dǎo)入庫calcfun.lib,這種方式的效果是在生成的calc.exe的PE頭部的導(dǎo)入表中添加了一IMAGE_IMPORT_DESCRIPTOR,并且根據(jù)程序中所引用的符號寫入了相應(yīng)的IMAGE_THUNK_DATA,最終生成的calc.exe若要能正確運行必須依賴于calcfun.dll。這種鏈接方式的實現(xiàn)是設(shè)置鏈接器參數(shù),比如vc的鏈接器可以直接把倒入庫當作參數(shù)傳遞給鏈接器link.exe,也可以使用預(yù)編譯處理:#pragma comment(lib, "calcfun.lib") 2) 動態(tài)鏈接(顯示鏈接) 使用這種鏈接方式只需要一個dll文件,lib文件就是多余的了,因為dll本身也包含自身導(dǎo)出符號的地址表,所以只需要把這個dll載入到我們的程序的地址空間,然后搜索到我們需要使用的函數(shù)或者其他符號的地址就可以了。這種鏈接方式不會添加信息到生成的calc.exe的PE頭部。生成的calc.exe若要能正確云清必須依賴于calcfun.dll。這種鏈接方式的實現(xiàn)是通過LoadLibrary()載入dll模塊,通過GetProcessAddress()來搜索到目標符號的地址,通過FreeLibrary()卸載dll模塊。 3) 載入時機 動態(tài)庫除了上述使用方式不同之外,還有就是真正載入的時機。編譯是我們來做的,而運行時的各種工作都是由操作系統(tǒng)來做的。靜態(tài)鏈接生成的exe中的PE頭部的導(dǎo)入表里包含了所需的dll信息,所以程序Loader會讀取導(dǎo)入表,并加載導(dǎo)入表中的所有的dll(延遲加載除外,本文不討論)。而動態(tài)鏈接庫則是在程序調(diào)用LoadLibrary()函數(shù)時才會載入指定的dll。 靜態(tài)庫沒有動態(tài)庫常見,所以現(xiàn)在普遍存在于windwos操作系統(tǒng)中的都是動態(tài)鏈接庫,從上面的介紹可知道,DLL最終被載入程序的進程空間都是操作系統(tǒng)來完成,那么操作系統(tǒng)如何知道所需的DLL的位置呢?很明顯,操作系統(tǒng)需要一套搜索規(guī)則來加載動態(tài)鏈接庫。 二、 動態(tài)鏈接庫(DLL)的搜索順序 程序可以通過指定全路徑或者使用清單等機制來控制從何處加載DLL,如果沒有使用這些方法,系統(tǒng)將會進行DLL搜索。 A. 影響搜索的要素 1. 如果一個同名的DLL已經(jīng)被加載入內(nèi)存中,系統(tǒng)在解析即將被載入的DLL前只會檢查重定向和清單,無論這個DLL在哪一個目錄,也就是說系統(tǒng)不會去搜索DLL。 2. 如果一個程序所需的DLL在本機的Known DLLs列表中存在,系統(tǒng)將直接使用這個已知的DLL而不會去搜索DLL。當前系統(tǒng)的Know DLLs列表在注冊表中的路徑:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\KnownDLLs 3. 如果一個DLL(例如a.dll)依賴于其他的DLL(例如b.dll,c.dll等等),系統(tǒng)將會只按照模塊名來搜索依賴的DLL(b.dll,c.dll...),即使第一個DLL(a.dll)是通過全路徑加載的,這條規(guī)則也適用。 B. 標準的搜索順序 系統(tǒng)有一套標準的搜索DLL路徑的規(guī)則,這套規(guī)則又分為兩種搜索模式,安全搜索模式,非安全搜索模式。安全搜索模式是默認開啟的,如果要禁用安全搜索模式,可以在注冊表中創(chuàng)建HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\SessionManager\SafeDllSearchMode鍵值,并且設(shè)置值為0。 1. 安全搜索模式順序 >1.exe程序的所在目錄 >2.系統(tǒng)目錄(GetSystemDirectory()獲得) >3.16位系統(tǒng)目錄 >4.Windows目錄 >5.進程當前目錄 >6.系統(tǒng)PATH環(huán)境變量中的目錄 2. 非安全搜索模式 >1.exe程序的所在目錄 >2.進程當前目錄 >3.系統(tǒng)目錄(GetSystemDirectory()獲得) >4.16位系統(tǒng)目錄 >5.Windows目錄. >6.系統(tǒng)PATH環(huán)境變量中的目錄 所有通過靜態(tài)鏈接的動態(tài)鏈接庫都遵循以上A.B搜索順序的規(guī)則。 C. 通過LoadLibrary(LPCTSTR lpFileName)或者LoadLibraryEx(LPCTSTRlpFileName, …)函數(shù)實現(xiàn)的動態(tài)鏈接,則有一下規(guī)則。 1. lpFileName包含文件路徑+文件名 則先搜索指定路徑的文件,文件存在則函數(shù)返回成功,文件不存在函數(shù)返回失敗。 2. lpFileName不包含文件路徑+文件名 函數(shù)將轉(zhuǎn)而使用系統(tǒng)的標準搜索路徑 關(guān)于動態(tài)鏈接庫的搜索順序的更多詳細資料請參閱http://msdn.microsoft.com/en-us/library/ms682586%28VS.85%29.aspx 三、 QQ程序存在的一個DLL劫持利用漏洞 這個漏洞應(yīng)該是QQ2009版本之后到目前為止一直存在的漏洞。漏洞的形成原因是QQ的界面引擎GF.dll引入的。 先來說一下MSIMG32.DLL這個文件,這個文件是windows GDI函數(shù)簇的一個庫其中只有四個函數(shù) 存在于系統(tǒng)目錄C:\windows\system32\msimg32.dll。 我們用Dependency Walker查看一下QQ.exe啟動的時候,存在的DLL依賴關(guān)系: QQ.exe ->GF.dll -> xGraphic32.dll -> Msimg32.dl。 在這里可能有人會說,從那個帶箭頭的圖標來看Msimg32顯示是已經(jīng)被加載過了。有這個疑問是正常的,我們可以看到另一條依賴關(guān)系鏈:QQ.exe -> User32.dll->Msimg32.dl。 乍一看沒有問題,不存在劫持的可能:有這樣一個規(guī)則,Windows在加載時讀取exe的導(dǎo)入表,然后加載依賴模塊,但是無論導(dǎo)入表的順序如何,只要導(dǎo)入表中存在的dll并且在Knows Dll列表中存在,那么系統(tǒng)就會優(yōu)先加載這些模塊,User32.dll是肯定存在與Knows Dll列表中的,那么User32.dll肯定先于GF.dll的加載,這樣一來,系統(tǒng)就會直接加載系統(tǒng)目錄下的User32.dll,User32.dll又會從自己所在目錄加載Msimg32.dll(即系統(tǒng)的原始msimg32.dll),那么后面再加載GF->xGraphic32.dll->Msimg32.dll就會直接返回之前加載過的木塊句柄,這樣看就不存在Msimg32.dll被劫持的可能了。 不過,讀者應(yīng)該看到User32.dll中的msimg32.dll前面的圖標顯示是延遲加載的!所以只要QQ.exe在加載xGraphic32.dll之前不調(diào)用msimg32.dll中導(dǎo)出的任何函數(shù),這個模塊就不會被加載,所以最終第一次加載msimg32.dll應(yīng)該是在xGraphic32.dll中。那么QQ程序中到底是如何呢?我們可以用Process Monitor來驗證一下! 設(shè)置Process Monitor的過濾選項。然后啟動QQ.EXE 在QQ啟動完成顯示出登錄窗口后產(chǎn)看“文件操作”捕獲結(jié)果,搜索msimg32 發(fā)現(xiàn),第一次加載確實實在xGraphic32.dll中! 真相:QQ.EXE中第一次加載msimg32.dll是在一個非系統(tǒng)模塊中,并且msimg32.dll沒有被微軟列為KnownDlls。所以如果第三方軟件開發(fā)者實現(xiàn)了一個模塊,并且在自己的模塊中靜態(tài)鏈接了msimg32.dll的話,那么就留下了一個可以利用系統(tǒng)搜索DLL順序來進行DLL劫持的漏洞。很不幸,GF的依賴模塊xGraphic32.dll就是這樣一個模塊。 結(jié)合上述標準搜索順序,當加載xGraphic32.dll模塊的時候依賴msimg32.dll,然后系統(tǒng)先搜索QQ.exe所在目錄,如果不存在則搜索則繼續(xù)按照規(guī)則搜索,最終能搜索到系統(tǒng)目錄下的原始的msimg32.dll。但是,如果有人通過DLL導(dǎo)出符號轉(zhuǎn)發(fā)器實現(xiàn)了一個偽造的msimg32.dll放置于QQ.exe的目錄中會有什么后果呢?無論有沒有開啟安全搜索,系統(tǒng)總是會第一個搜索QQ.exe所在目錄,從而把偽造的DLL加載到QQ.exe的進程空間,而msimg32.dll的DllMain中可以做任何想做的事,破壞QQ程序已經(jīng)危害不小,如果做成病毒,木馬,后門的話,問題就不是一般的嚴重了。 解決方法: 1. 可以看到xGraphic32.dll中只是用了msimg32.dll導(dǎo)出的一個函數(shù)AlphaBlend,所以可以嘗試將用到這個函數(shù)的地方自己實現(xiàn),不過這個基于效率來說,這個方案不是很可行。 2. 重寫xGraphic32.dll,改用顯式的動態(tài)鏈接,指定全路徑,這個好像是目前來說最好的解決方案了。 3. 讓微軟把Msimg32.dll添加到Known Dlls列表中,這個是最最完美的解決方案,但是需要去和微軟溝通,希望下次看到系統(tǒng)的SP補丁包的時候會實現(xiàn)這個想法。 4 補充一個方法,在QQ.exe中判斷當前目錄是否存在msimg32.dll文件,如果存在就報錯,不存在正常啟動。雖然這個方法比較笨,但是確實很有效的方法,但是如果QQ.exe被逆向的話,找到判斷代碼段并且修改的話,仍然能繼續(xù)劫持,不過這樣做的話就要承擔法律風險責任了。 四、 補充話題:DLL劫持 可以回顧一下曾經(jīng)比較實用的WS2_32.dll一度成為DLL劫持的不二對象,之前微軟沒有把它列為Known Dlls,雖然說這個DLL中函數(shù)比較多,但是如果想實行劫持的話,只需要將目標exe所引入的函數(shù)轉(zhuǎn)發(fā)就可以了。不過并不是所有的程序都需要調(diào)用這個動態(tài)鏈接庫的,因為他是Windows socket函數(shù)簇的模塊。但是msimg就不一樣了,因為User32.DLL也引用了該函數(shù),所以如果沒有把所有函數(shù)都轉(zhuǎn)發(fā)的的話,可能引起錯誤,好在Msimg32.dll只有四個函數(shù),毫不費力就可以完全轉(zhuǎn)發(fā),但是這恰恰是一個嚴重的漏洞產(chǎn)生原因。 |
|