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