深入Windows NT/2000模組的組織
WebCrazy(http://webcrazy.yeah.net/)
在《小議Windows NT/2000分頁機制 》中我對x86平台Windows NT/2000的非分頁式記憶體內部機制有了較為詳細的說明,從中也可以看出地址空間可以分為進程空間與系統空間,其中每個進程有各自的進程空間,而所有的進程則共用同一個系統空間。所以Windows NT/2000在牽涉到模組管理時也涉及到進程私人的模組管理與系統共用模組管理兩部分,下面我將從這兩方面分別進行介紹。
正因為所有的進程共用同一個系統空間,所以系統模組主要是些作業系統代碼模組或是些裝置驅動程式代碼等(它們一般進程都需要使用到),其位於系統4G記憶體的高端。Windows NT/2000內部由一系統變數PsLoadedModuleList指出,其具體結構為一雙向鏈表。熟悉Windows NT/2000的人,都知道系統在發生藍色當機畫面時,預設情況下都會將系統此時轉儲到MEMORY.DMP檔案中,系統核心調試器i366kd或windbg在調試跟蹤這個轉儲檔案時也會根據這個系統變數重新裝入系統崩潰前的已裝載模組。我下面列出根據這個變數枚舉系統模組的代碼:
//-----------------------------------------------
//
// EnumKernelModules
// Only test on Windows 2000 Server Chinese Edition
// Build 2195(Free)!Programmed By WebCrazy
// (tsu00@263.net ) on 10-27-2000!
//
//-----------------------------------------------
ULONG PsLoadedModuleList=0x8046a4c0; //fetch from symbol file
#define KERNELMOD_IMAGEBASE_OFFSET 0x18
#define KERNELMOD_IMAGENAME_OFFSET 0x24
void EnumKernelModules()
{
PLIST_ENTRY pKernelModuleListHead, pKernelModuleListPtr;
PUNICODE_STRING pImageName;
if(((USHORT)NtBuildNumber)!=2195){
DbgPrint("Only test on Windows 2000 Server Build 2195!/n");
return;
}
DbgPrint("/n Base Addr/tModule Name");
DbgPrint("/n ---------/t-----------/n");
pKernelModuleListHead=pKernelModuleListPtr=(PLIST_ENTRY)(ULONG *)PsLoadedModuleList;
do{
pKernelModuleListPtr=pKernelModuleListPtr->Flink;
DbgPrint(" %08X",
*(ULONG *)((char *) pKernelModuleListPtr+KERNELMOD_IMAGEBASE_OFFSET));
pImageName = (PUNICODE_STRING)(ULONG *)((char *)
pKernelModuleListPtr+KERNELMOD_IMAGENAME_OFFSET);
DbgPrint("/t%S/n",pImageName->Buffer);
}while(pKernelModuleListPtr->Flink!=pKernelModuleListHead);
}
上面PsLoadedModuleList的值我直接從Symbol檔案中獲得,你可根據實際情況予以調整。好,先看看EnumKernelModules輸出結果:
Base Addr Module Name
--------- -----------
80400000 /WINNT/System32/ntoskrnl.exe
80062000 /WINNT/System32/hal.dll
. .
. .
FD0F8000 /SystemRoot/System32/Drivers/Cdfs.SYS
FCDB1000 /SystemRoot/System32/DRIVERS/ipsec.sys
. .
. .
與Softice的mod命令基本相同,但值得注意的是Softice的mod命令不僅輸出進程核心模組,而且也列出特定進程的使用者態模組列表,那麼系統又是如何管理進程特定模組的呢?
由於每個進程都擁有各自的模組,而所有這些模組都要求從使用者態可以訪問到。因此進程模組組織的資料結構應該位於使用者態地址空間中,實際上Windows NT/2000進程模組列表是由PEB(Process Environment Block)結構中的成員指定,Windows NT/2000均將每個擁有使用者態代碼的進程的PEB置於0x7FFDF000處(2G空間以下,使用者態代碼可直接存取)。不過,Windows NT/2000一般通過TEB間接得到PEB的地址,即通過以下代碼取得:
mov eax,fs:[18]
mov eax,[eax+30]
第一條語句獲得當前線程的TEB地址,關於TEB及TEB地址的獲得,請參閱《Windows NT/2000內部資料結構探究》,第二條語句獲得位於TEB位移30h處獲得PEB地址。我想Windows NT/2000使用這種方法可能是考慮相容性吧,我以下提供的代碼直接使用了常量地址。
看看Windbg的分析吧:
> !ntsdexts.version
Version 5.0 (Build 2195) Uniprocessor Free
> !ntsdexts.peb
PEB at 7FFDF000
InheritedAddressSpace: No
ReadImageFileExecOptions: No
BeingDebugged: Yes
ImageBaseAddress: 01000000
Ldr.Initialized: Yes
Ldr.InInitializationOrderModuleList: 71f80 . 72808
Ldr.InLoadOrderModuleList: 71ee0 . 727f8
Ldr.InMemoryOrderModuleList: 71ee8 . 72800
01000000 D:/winnt/system32/calc.exe
77F80000 D:/WINNT/System32/ntdll.dll
77560000 D:/WINNT/system32/SHELL32.dll
77F40000 D:/WINNT/system32/GDI32.DLL
77E60000 D:/WINNT/system32/KERNEL32.DLL
77DF0000 D:/WINNT/system32/USER32.DLL
77D90000 D:/WINNT/system32/ADVAPI32.DLL
77D20000 D:/WINNT/system32/RPCRT4.DLL
77C50000 D:/WINNT/system32/SHLWAPI.DLL
77B30000 D:/WINNT/system32/COMCTL32.DLL
78000000 D:/WINNT/system32/MSVCRT.dll
SubSystemData: 0
ProcessHeap: 70000
ProcessParameters: 20000
WindowTitle: 'D:/winnt/system32/calc.exe'
ImageFile: 'D:/winnt/system32/calc.exe'
. .
. .
. .
Windbg的以上輸出詳細的顯示了PEB各欄位值,對其中資料跟蹤分析後,我寫了以下程式段直接讀取系統結構擷取進程模組列表:
//-----------------------------------------------
//
// EnumUserModules-information from PEB
// Only test on Windows 2000 Server Chinese Edition
// Build 2195(Free)!Programmed By WebCrazy
// (tsu00@263.net ) on 10-27-2000!
//
//-----------------------------------------------
#define PEBADDRESS 0x7ffdf000
#define PEB_LDR_DATA_OFFSET 0x0c
#define LDRDATA_IMAGEBASE_OFFSET 0x10
#define LDRDATA_IMAGENAME_OFFSET 0x1c
#pragma pack(4)
typedef struct _PEB_LDR_DATA
{
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
#pragma pack()
void EnumUserModules(void *kpeb)
{
PLIST_ENTRY pUserModuleListHead, pUserModuleListPtr;
PPEB_LDR_DATA pLdrData;
PUNICODE_STRING pImageName;
if(((USHORT)NtBuildNumber)!=2195){
DbgPrint("Only test on Windows 2000 Server Build 2195!/n");
return;
}
KeAttachProcess(kpeb);
pLdrData=(PPEB_LDR_DATA)(ULONG *)(*(ULONG *)(PEBADDRESS+PEB_LDR_DATA_OFFSET));
if(!pLdrData->Initialized){
DbgPrint("Process:%08X Not Initialized!/n",(ULONG)kpeb);
KeDetachProcess();
return;
}
DbgPrint("/n Base Addr/tModule Name");
DbgPrint("/n ---------/t-----------/n");
pUserModuleListHead=pUserModuleListPtr=
(PLIST_ENTRY)&(pLdrData->InMemoryOrderModuleList);
do{
pUserModuleListPtr=pUserModuleListPtr->Flink;
DbgPrint(" %08X",*(ULONG *)((char *)
pUserModuleListPtr+LDRDATA_IMAGEBASE_OFFSET));
pImageName = (PUNICODE_STRING)(ULONG *)((char *)
pUserModuleListPtr+LDRDATA_IMAGENAME_OFFSET);
DbgPrint("/t%S/n",pImageName->Buffer);
}while(pUserModuleListPtr->Flink!=pUserModuleListHead);
KeDetachProcess();
}
EnumUserModules程式段實現對特定進程(由參數kpeb所指定)模組的枚舉,函數段並未實現對PEB合法性的檢查,如Idle與system進程是純核心態進程,他們並不存在使用者態的PEB,解決辦法可以通過檢查TEB的合法性,這些進程TEB一般情況下都為0。EnumUserModules也沒有對kpeb的合法性檢查,其假設所有kpeb當前在系統中都存在,否則將出現意想不到的結果。雖然只涉及到使用者態資料的讀取,但程式段中使用了KeAttachProcess/KeDetachProcess核心常式,所以程式段只能在驅動代碼中實現。EnumUserModules使用InMemoryOrderModuleList成員枚舉模組列表的(見上Windbg輸出結果,EnumUserModules的輸出結果與其一致),當然你也可以使用InInitializationOrderModuleList或InLoadOrderModuleList成員。
上面已經說明了SoftICE中的mod命令將系統模組與進程模組列出,也就是說其實現了我提供的這兩個程式段(SoftICE還輸出PE模組的PE Header段,可以根據Base Addr按照PE規範取出PE Header的位置,另外我不確信SoftICE是不是使用同樣的方法實現的)。
不論使用者態的可執行Win32模組(.exe)或是核心態的驅動程式(.sys),還是系統動態連結程式庫(.dll)在Windows NT/2000中均是以PE格式存在的,但也未必所有模組均為此格式,實際上所有檔案均可以作為模組出現,如常見的nls檔案等等。關於PE檔案的裝載,Windows NT/2000提供了以ldr開頭的函數族,至於其結構已眾所周知,我將不予介紹。
Windows 2000中實現了枚舉系統模組的PSAPI與ToolHelp API(ToolHelp API在Win9X中早已實現,但Windows NT卻僅使用PSAPI函數),我在跟蹤分析這部分代碼時原以為多少可以參閱一下這些函數的標頭檔中的一些譬如MODULEENTRY32或MODULEINFO等的定義,但系統內部卻使用完全不同的格式.可以這樣說系統內部的結構大而全且僅使用UNICODE格式,而這些API僅呈現API使用的部分定義,向使用者隱藏了好多的內部特徵。不過ToolHelp等使用Section對象(Win32 API稱為FileMapping對象)映射整個模組實現模組的枚舉,不過其最終都使用到EnumUserModules所引用的PEB資料。關於PEB還包含著許多系統資料,如ProcessHeap、ProcessParameters等等,感興趣的話可以使用windbg/Softice好好挖掘挖掘!
參考資料:
1.David Solomom《Inside Windows NT,2nd Edition》