系統範圍內擁有相同基址的DLL 以及這樣做的原因
原文連結
http://www.nynaeve.net/?p=198
因為某些原因,系統中的一些DLL需要載入在不同程序的同樣的基地址上(在終端服務會話中,其中一些DLL可能使用輪流基地址) ,儘管微軟沒有明確的文件化, 一些程式的執行依賴於這些“固定基址的DLL”。
這並不意味著這些DLL 的基地址不可改變,但是在系統執行的時候,所有的程序都將會把這些DLL 對映到同樣的基地址上(如果它們確實對映這些DLL 的話)。
當前在系統範圍內需要同樣的基地址的DLL 有 NTDLL, kernel32, and user32,但是它們需要固定基址的原因是不同的。
NTDLL 必須有同樣的基地址,因為它匯出了很多的函式,核心使用這些函式來間接安排使用者模式的呼叫。例如,ntdll!LdrInitializeThunkis 是每一個使用者模式執行緒的真實的啟動地址。如果一個執行緒在進行使用者模式等待且一個使用者模式APC 準備好執行的話,ntdll!KiUserApcDispatcher 被用來執行該APC。在系統初始化的時候,系統解析這些函式(還有其它的)的地址並儲存起來,以在之後需要執行使用者模式程式碼的時候使用。因為系統快取了這些函式地址且每次引用同樣的函式地址,不同程序間NTDLL 的基地址不能是不同的。
另外,有些使用者模式程式也假設NTDLL 中的一部分匯出函式在不同的程序中有同樣的基地址。比如, ntdll!DbgUiRemoteBreakIn 被偵錯程式用來侵入一個程序,偵錯程式假設本地DbgUiRemoteBreakIn 函式的地址和目標程序的遠端DbgUiRemoteBreakIn 地址是一樣的。與ntdll!DbgUiRemoteBreakIn 類似,Kernel32 中的許多函式被假設有同樣的地址,以進行程序間執行緒注入操作。一個例子就是 console control event handler ,在kernel32.dll 初始化的時候, Ctrl-C 事件分發器的函式地址被傳遞給WinSrv.dll (在CSRSS 地址空間)。
WinSrv 本來只是簡單的在第一個程序建立的時候快取分發器指標 (因此需要會中的所有的程序中的kernel32模組的基地址相同). 在現在的系統中, WinSrv 根據每個程序來跟蹤客戶的kernel32 分發器指標,因為32-bit kernel32和64-bit kernel32 的分發器函式的地址不同。諷刺的是,對WinSrv 做出修改的開發者實際上忘了在幾個小地方增加對於當前程序的分發器指標的支援。 (如 kernel32!SetLastConsoleActiveEvent 和 對應的winsrv!SrvConsoleNotifyLastClose 和winsrv!RemoveConsole CSRSS-side 函式). 這些地方,WinSrv仍然錯誤的傳送快取的 (64-bit) Ctrl-C 分發器值給CreateRemoteThread 函式,wow64.dll 有一個特定的hack (wow64!MapContextAddress64TO32),它將在WinSrv 之後清理並修復執行緒起始地址為32-bit kernel32.dll 中的地址。
在WinSrv 和 Ctrl-C 處理做出改變之前,應用程式相容性影響“kernel32 基地址在系統範圍內同樣基地址”的移除。消除這個限制是非常苛刻的(幾乎所有的第三方程式碼注入都非常依賴於這個假設)。因此,由於這個(以及其它的)原因,kernel32 遵守這個限制,其基地址在不同的程序中不可被重定位。
User32被要求在不同的程序間是同樣的基地址,因為win32k.sys使用一個其匯出的函式指標陣列以供內建的視窗類的視窗處理函式使用(以及其它用途)。在會話建立並初始化CSRSS 的時候,WinSrv初始化win32k期間通過NtUserInitializeClientPfnArrays 函式拷貝這個函式指標陣列。Wow64win.dll是NtUser/win32k Wow64 支援模組。提供了32-bit和64-bit 的轉換轉換支援,同時提供了對於這些函式地址的支援,這樣的支援對於Wow64 程序來說是必要的。
user32 和kernel32 的需求可以被放寬以僅僅應用在一個終端會話上,儘管Windows XP 和之後的跨會話除錯支援使得對於kernel32 的支援變得複雜,因為支援偵錯程式侵入除錯的需要,偵錯程式需要在他們的侵入執行緒中使用DebugBreak 。(如果以64-bit kernel32 DebugBreak 出口地址建立了一個32-bit執行緒的話,Wow64 層提供了將DebugBreak 對映到32位地址的轉移輔助)
注意,ASLR 和這個限制不衝突。ASLR 機制在每次引導的時候為一個給定的DLL選擇一個同樣的基地址。因此,在系統啟動的時候,NTDLL 可以擁有一個隨機的起始地址,在重新啟動之前,這個指定的隨機地址被所有的程序所使用。
更新:在Windows Server2003 和之前的作業系統中,kernel32 模組中的一個內部函式被當作CreateRemoteThread 和 CreateProcess 所建立的新執行緒的起始地址。這也是早期32 模組基地址不變的一個特別重要的原因。