4、函數(shù)轉(zhuǎn)發(fā)器 函數(shù)轉(zhuǎn)發(fā)器(function forwarder)是DLL輸出段中的一個條目,用來將一個函數(shù)調(diào)用轉(zhuǎn)發(fā)到另一個DLL中的另一個函數(shù)。例如:如果用Visual C++的Dumpbin工具來查看Kernel32.dll那么我們會看到類似下面的輸出。 c:/Windows/System32>dumpbin -exports Kernel32.dll
710 2C5 RtlFillMemory (forwarded to NTDLL.RtlFillMemory) 711 2C6 RtlMoveMemory (forwarded to NTDLL.RtlMoveMemory) 712 2C7 RtlUnwind (forwarded to NTDLL.RtlUnwind) 713 2C8 RtlZeroMemory (forwarded to NTDLL.RtlZeroMemory) 這個輸出顯示了4個被轉(zhuǎn)發(fā)的函數(shù),如果應(yīng)用程序調(diào)用了RtlFillMemory,那么我們的可執(zhí)行文件會被動的鏈接到Kernel32.dll。當(dāng)可執(zhí)行文件運(yùn)行的時(shí)候,加載程序會載入Kernel32.dll并發(fā)現(xiàn)被轉(zhuǎn)發(fā)的函數(shù)實(shí)際上是在NTDLL.dll中,然后他會將NTDLL.dll模塊也一并載入。當(dāng)可執(zhí)行文件調(diào)用RtlFillMemory的時(shí)候,它實(shí)際上調(diào)用的是NTDLL.dll中的RtlFillMemory函數(shù)。 如果我們調(diào)用RtlFillMemory ,那么GerProcAddress會先在Kernel32.dll的導(dǎo)出段中查找,并發(fā)現(xiàn)RtlFillMemory是一個轉(zhuǎn)發(fā)器函數(shù),于是它會遞歸調(diào)用GetProcAddress,在NTDLL的導(dǎo)出段中查找RtlFillMemory 函數(shù)。 我們也可以在自己的DLL模塊中使用函數(shù)轉(zhuǎn)發(fā)器,最簡單的方法是使用pragma指示符,如下所示: #pragma comment(linker,"/export:someFunc=DllWork.someOtherFunc") 這個pragma告訴鏈接器,正在編譯的DLL應(yīng)該導(dǎo)出一個名為someFunc的函數(shù),但實(shí)際上實(shí)現(xiàn)someFunc的是另一個名為someOtherFunc的函數(shù),該函數(shù)被包含在另一個DllWork.dll的模塊中。我們必須為每個想要轉(zhuǎn)發(fā)的函數(shù)單獨(dú)創(chuàng)建一行pragma。 5、已知的DLL 系統(tǒng)對操作系統(tǒng)提供的某些DLL進(jìn)行了特殊處理,這些DLL被稱為已知的DLL(known DLL)。除了操作系統(tǒng)在載入它們的時(shí)候總是在同一個目錄中查找之外,它們與其它的DLL并沒有什么不同。在注冊表中有這么一個注冊表項(xiàng): HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Session Manager/KnownDLLs 我們可以看到,這個注冊表項(xiàng)包含了一組值名,這些值名是一些DLL的名稱。每個值名的數(shù)據(jù)正好等于值名加上.dll擴(kuò)展名。(但這并不是必須的,稍候就會看到)。當(dāng)LoadLibrary或LoadLibraryEx被調(diào)用的時(shí)候,函數(shù)首先會檢查我們傳入的DLL的名字是否包含了.dll擴(kuò)展名。如果沒有包含,那么函數(shù)會用正常的搜索順序來搜索這個DLL。 如果我們指定了.dll擴(kuò)展名,那么這兩個函數(shù)會先將擴(kuò)展名去掉,然后再在KnownDLLs注冊表中搜索,看其中是否有與之相符的值名。如果沒有值名與之相符,那么函數(shù)會使用正常的搜索規(guī)則。但是,如果找到了與之相符的值名,那么系統(tǒng)會查看與值名相對于的數(shù)據(jù),并試圖用該數(shù)據(jù)來載入DLL。系統(tǒng)還會從注冊表項(xiàng)的DllDirectory值所表示的目錄中開始搜索DLL。在Windows XP中DllDirectory的默認(rèn)值為%SystemRoot%/system32 為了舉例說明這一過程,假設(shè)我們在KnownDLLs注冊表項(xiàng)中添加了下列值: value name:SomeLib value data:SomeOtherLib.dll 調(diào)用LoadLibrary("SomeLib");的時(shí)候,系統(tǒng)會用正常的搜索規(guī)則來對這個DLL進(jìn)行定位. 但是,如果調(diào)用LoadLibrary("SomeLib.dll");的時(shí)候,系統(tǒng)會在knownDllsz注冊表中發(fā)現(xiàn)有一個與之相符的名稱。因此系統(tǒng)試圖載入的DLL是SomeOtherLib.dll而不是SomeLib.dll。它首先會在%SystemRoot%/system32目錄中查找SomeOtherLib.dll。如果在這個目錄中找到了該文件,那么系統(tǒng)就會將它載入,如果系統(tǒng)未能在這個目錄中找到該文件,那么LoadLibrary會失敗并返回NULL,這時(shí)調(diào)用GetLastError將返回ERROR_FILE_NOT_FOUND。 6、DLL重定向 最初開發(fā)Windows的時(shí)候,內(nèi)存和磁盤空間都非常寶貴,因此為了節(jié)約這些寶貴的資源,Windows的設(shè)計(jì)目標(biāo)是盡可能的共享資源。出于這樣的考慮,Microsoft建議將多個應(yīng)用程序所共享的模塊放在Windows的系統(tǒng)目錄中,這使得系統(tǒng)能方便的定位和共享文件。C/C++運(yùn)行庫以及MFC就是很好的例子。 隨著時(shí)間的推移,這成為了一個嚴(yán)重的問題,這是因?yàn)榘惭b程序可能會用老版本的文件覆蓋這個目錄中的文件,或用不完全兼容的新版本的文件覆蓋這個目錄中的文件,從而妨礙用戶的其它應(yīng)用程序的正常運(yùn)行。今天,硬盤不僅容量大而且價(jià)格便宜,內(nèi)存也夠用而且相對來說價(jià)格比以前要便宜很多。因此,Microsoft現(xiàn)在強(qiáng)烈建議開發(fā)人員將應(yīng)用程序的文件放到自己的目錄中,并且絕對不要碰Windows系統(tǒng)目錄中的任何東西。這樣既可以防止我們的應(yīng)用程序妨礙其它應(yīng)用程序,也可以避免其它應(yīng)用程序妨礙我們的應(yīng)用程序。 為了幫助開發(fā)人員,Microsoft從Windows 2000開始新增了一項(xiàng)DLL重定向特性。這項(xiàng)特性強(qiáng)制操作系統(tǒng)的加載程序 首先從應(yīng)用程序的目錄中載入模塊。只有加載程序無法找到要找的文件時(shí),才會在其它目錄中搜索。 為了強(qiáng)制加載程序總是先檢查應(yīng)用程序的目錄,我們所要做的就是將一個文件放到應(yīng)用程序的目錄中。這個文件的內(nèi)容無關(guān)緊要,但它的文件名必須是AppName.local。舉個例子,如果我們有一個名為SuperApp.exe的可執(zhí)行文件,那么重定向文件的名稱必須是SuperApp.exe.local。 LoadLibrary(Ex)在內(nèi)部做了修改,來檢查這個文件存在與否。如果應(yīng)用程序的目錄中存在這個文件,那么系統(tǒng)會載入這個目錄中的模塊。如果應(yīng)用程序的目錄中不存在這個文件,那么LoadLibrary的工作方式與以往相同。注意:除了創(chuàng)建一個.local文件,我們還可以創(chuàng)建一個名為.locald的文件夾。在這種情況下,我們可以將自己的DLL保存在這個文件夾中,讓W(xué)indows能夠輕易找到他們。 注意:為了安全性的緣故,Windows vista中這項(xiàng)特性在默認(rèn)情況下是關(guān)閉的----因?yàn)樗赡軙瓜到y(tǒng)從應(yīng)用程序的文件夾中載入偽造的系統(tǒng)DLL,而不是從Windows的系統(tǒng)文件夾中載入真正的系統(tǒng)DLL。為了打開這項(xiàng)特性,我們必須在HKLM/SoftWare/Microsoft/WindowsNT/CurrentVersion/Image File Execution Options注冊表項(xiàng)中增加一個條目DWORD DevOverrideEnable,并將它的值設(shè)為1。 |
|