解析Windows NT/2000視窗對象的組織
WebCrazy(http://webcrazy.yeah.net/)
Microsoft Visual Studio提供的Microsoft Spy對Windows的視窗組織有非常直觀的表現,在Windows視圖下的Window Properties可以顯示視窗的很多內容或屬性。本文將從Windows NT/2000核心出發,說明這些重要的結構在Windows NT/2000中的最底層的組織。
先從視窗組織說起,大家知道枚舉視窗可以通過EnumWindows、EnumDesktopWindows或EnumChildWindows等這些API來實現。我想真正理解Windows NT/2000實現這些API的細節後,應該可以發現很多Windows NT/2000的內部執行情況,於是我認真的研究了這些API,下面我以Windows 2000 Server Build 2195下EnumWindows API的分析步驟敘述如下:
1.user32.dll下的EnumWindows首先調用user32.dll下的InternalEnumWindows,然後InternalEnumWindows又調用BuildHwndList(user32.dll).
2.BuildHwndList調用NtUserBuildHwndList,而這個函數也位於user32.dll模組中。
3.NtUserBuildHwndList只是調用int 2e指令,然後使用ID為0x112e的System Service,即win32k.sys模組中的NtUserBuildHwndList,請參閱《Windows 2000 System Services列表 》。從這開始也就從使用者態進入了核心態。
4.win32k.sys中的NtUserBuildHwndList繼續調用win32k.sys中的BuildHwndList,應該注意的是這兩個常式與上面User32.dll中的相應常式同名,這也是我一直強調模組名的原因。BuildHwndList最後調用InternalBuildHwndList(win32k.sys),這個常式才是我們真正感興趣的地方。InternalBuildHwndList在特定參數下(如EnumDestopWindows API調用,EnumDestopWindows最終也會調用InternalBuildHwndList)是一個遞迴常式以實現視窗的枚舉。
這個分析只是說明了EnumWindows等API的執行流程,真正的細節還是要看一看代碼了。Windows NT/2000下視窗是與特定線程相關聯的,即每個線程都可以擁有視窗,最典型的例子是Windows Explorer(Windows資源管理員),在每開啟一個Explorer視窗,explorer進程都會建立線程與這些視窗關聯。由於這個原因,Microsoft在核心態的ETHREAD(KTEB)中儲存視窗結構(Windows NT早期版本Win32子系統位於使用者態,我這未加以說明),這些結構由ETHREAD中的Win32Thread成員指定。Win32Thread在ETHREAD的位置可由Windbg的以下命令找出:
> !kdex2x86.ethread
Structure ETHREAD (Size:0x240) member offsets:
+0000 Tcb(KTHREAD struct)
+0000 Header(DISPATCHER_HEADER struct)
.
.
.
+0124 Win32Thread
.
.
.
要指出一點的是,Win32Thread成員在SoftICE 4.05 for Windows NT中顯示為Service Table。為便於使用者態代碼更容易獲得Win32Thread的值,Windows NT/2000也線上程的TEB中存取了Win32Thread指標,在Windows 2000 Server Build 2195其位置位於TEB的後0x40處。user32.dll由下列函數獲得Win32Thread的值(i386kd輸出):
kd> x user32!PtiCurrent
77df3686 user32!PtiCurrent
kd> u user32!PtiCurrent
USER32!PtiCurrent:
77df3686 64a118000000 mov eax,fs:[00000018]
77df368c 83784000 cmp dword ptr [eax+0x40],0x0
77df3690 0f8492700200 je USER32!PtiCurrent+0xc (77e1a728)
77df3696 64a118000000 mov eax,fs:[00000018]
77df369c 8b4040 mov eax,[eax+0x40]
77df369f c3 ret
下面是我實現枚舉特定線程擁有的視窗的代碼實現(我從KTEB中獲得Win32Thread):
//------------------------------------------------------------
//
// BuildHwndList--Enum Thread Windows(SoftICE hwnd Command)
// Only test on Windows 2000 Server Build 2195 Chinese Edition
// Build 2195(Free)!Programmed By WebCrazy
// (tsu00@263.net ) on 11-25-2000!
// Welcome to http://webcrazy.yeah.net to get more information
//
//------------------------------------------------------------
#define WIN32THREAD_OFFSET 0x124
#define HWNDLIST_OFFSET 0xb8
#define HWNDHANDLE_OFFSET 0x0
#define HWNDNEXT_OFFSET 0x2c
#define HWNDPARENT_OFFSET 0x30
#define HWNDRECT_OFFSET 0x3c
#define HWNDPROC_OFFSET 0x5c
//RECT:copied from windef.h
typedef struct tagRECT
{
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT, *PRECT;
typedef struct tagHWNDRECT{
RECT WindowRect;
RECT ClientRect;
}HWNDRECT,*PHWNDRECT;
void BuildHwndList(void *kteb)
{
PVOID Win32Thread;
PVOID HwndList;
PHWNDRECT pHwndRect;
if(((USHORT)NtBuildNumber)!=2195){
DbgPrint("Only test on Windows 2000 Server Build 2195!/n");
return;
}
Win32Thread=(PVOID)(*(PULONG)((char *)kteb+WIN32THREAD_OFFSET));
if(!Win32Thread){
DbgPrint("kteb:%08X isn't a win32 thread!/n",kteb);
return;
}
HwndList=(PVOID)(*(PULONG)((char *)Win32Thread+HWNDLIST_OFFSET));
if(!HwndList){
DbgPrint("kteb:%08X isn't a hwnd list!/n",kteb);
return;
}
DbgPrint("@kteb %08X first HwndList at %08X/n",kteb,HwndList);
DbgPrint("HwndList HWND PARENT Window Proc Window(Client) Rect/n");
DbgPrint("-------- -------- -------- ----------- -------------------/n");
do{
pHwndRect=(PHWNDRECT)((char *)HwndList+HWNDRECT_OFFSET);
DbgPrint("%08X %08X %08X %08X %d,%d,%d,%d(%d,%d,%d,%d)/n",
HwndList,
*(PULONG)((char *)HwndList+HWNDHANDLE_OFFSET),
*(PULONG)(*(PULONG)((char *)HwndList+HWNDPARENT_OFFSET)),
*(PULONG)((char *)HwndList+HWNDPROC_OFFSET),
pHwndRect->WindowRect.left,pHwndRect->WindowRect.top,
pHwndRect->WindowRect.right,pHwndRect->WindowRect.bottom,
pHwndRect->ClientRect.left,pHwndRect->ClientRect.top,
pHwndRect->ClientRect.right,pHwndRect->ClientRect.bottom);
HwndList=(PVOID)(*(PULONG)((char *)HwndList+HWNDNEXT_OFFSET));
}while(HwndList);
}
運行一個執行個體,輸出內容大概如下:
@kteb FF7BB020 first HwndList at A0312DA8
HwndList HWND PARENT Window Proc Window(Client) Rect
-------- -------- -------- ----------- -------------------
A0312DA8 0001002A 0001000C 77DFF0DF 0,0,0,0(0,0,0,0)
A0310D50 00010022 0001000C 775331C4 0,0,112,27(4,23,108,23)
A03176B8 0002004A 0001000C 77DFF0DF 0,0,0,0(0,0,0,0)
A031A500 00010082 0001000C 76621AC6 44,44,812,581(48,67,808,577)
A0318FA8 00010062 0001000C 775676F4 0,0,1024,768(0,0,1024,768)
下面我再講講Window Class吧。談到Class,真的可以想到很多東西,如C++的類等等。至於Window Class我覺的還是引用Microsoft文檔的解釋吧:
A window class is a set of attributes that the system uses as a template to create a window. Every
window is a member of a window class. All window classes are process specific.
從上的說明也可以看出Window Class是與特定進程相關聯的。視窗與線程關聯,所以其結構存於ETHREAD(KTEB)中,相應的Windows Class的結構則存於EPROCESS(KPEB)中。視窗結構由Win32Thread指定,Window Class則由Win32Process中指定。Win32Process在EPROCESS中的位置也可由windbg看出:
> !kdextx86.processfields
EPROCESS structure offsets:
.
.
.
Win32Process: 0x214
.
.
.
下面是實現枚舉特定進程的Window Class的代碼:
//----------------------------------------------------------------------
//
// BuildWindowClassList--Enum Process Window Class(SoftICE class Command)
// Only test on Windows 2000 Server Build 2195 Chinese Edition
// Build 2195(Free)!Programmed By WebCrazy(tsu00@263.net ) on 11-25-2000!
// Welcome to http://webcrazy.yeah.net to get more information
//
//----------------------------------------------------------------------
#define WIN32PROCESS_OFFSET 0x214
#define WINPRIVATECLASS_OFFSET 0x38
#define WINGLOBALCLASS_OFFSET 0x3c
#define CLASS_CLASSSTYLE_OFFSET 0x2c
#define CLASS_WINDOWPROC_OFFSET 0x30
#define CLASS_MODULEBASE_OFFSET 0x3c
#define CLASS_CLASSNAME_OFFSET 0x50
void BuildWindowClassList(void *kpeb)
{
PSINGLE_LIST_ENTRY pPrivateClassList,pGlobalClassList;
PVOID Win32Process;
PCHAR pClassName;
Win32Process=(PVOID)(*(PULONG)((char *)kpeb+WIN32PROCESS_OFFSET));
if(!Win32Process){
DbgPrint("kpeb:%08X isn't a win32 process!/n",kpeb);
return;
}
pPrivateClassList=(PSINGLE_LIST_ENTRY)(*(PULONG)((char *)Win32Process+WINPRIVATECLASS_OFFSET));
if(pPrivateClassList){
DbgPrint("Application(kpeb:%08X) Private Class List:/n",kpeb);
DbgPrint("%-35sHandle ModBase WinProc Styles/n","Class Name");
DbgPrint("%-35s-------- -------- -------- --------/n","----------");
do{
pClassName=(PCHAR)(*(PULONG)((char *)pPrivateClassList+CLASS_CLASSNAME_OFFSET));
DbgPrint("%-35s%08X %08X %08X %08X/n",pClassName,pPrivateClassList,
*(PULONG)((char *)pPrivateClassList+CLASS_MODULEBASE_OFFSET),
*(PULONG)((char *)pPrivateClassList+CLASS_WINDOWPROC_OFFSET),
*(PULONG)((char *)pPrivateClassList+CLASS_CLASSSTYLE_OFFSET));
pPrivateClassList=pPrivateClassList->Next;
}while(pPrivateClassList);
}
pGlobalClassList=(PSINGLE_LIST_ENTRY)(*(PULONG)((char *)Win32Process+WINGLOBALCLASS_OFFSET));
if(pGlobalClassList){
DbgPrint("Application(kpeb:%08X) Global Class List:/n",kpeb);
DbgPrint("%-35sHandle ModBase WinProc Styles/n","Class Name");
DbgPrint("%-35s-------- -------- -------- --------/n","----------");
do{
pClassName=(PCHAR)(*(PULONG)((char *)pGlobalClassList+CLASS_CLASSNAME_OFFSET));
DbgPrint("%-35s%08X %08X %08X %08X/n",pClassName,pGlobalClassList,
*(PULONG)((char *)pGlobalClassList+CLASS_MODULEBASE_OFFSET),
*(PULONG)((char *)pGlobalClassList+CLASS_WINDOWPROC_OFFSET),
*(PULONG)((char *)pGlobalClassList+CLASS_CLASSSTYLE_OFFSET));
pGlobalClassList=pGlobalClassList->Next;
}while(pGlobalClassList);
}
}
照例是一個運行執行個體的結果:
Application(kpeb:FF5BA3C0) Private Class List:
Class Name Handle ModBase WinProc Styles
---------- -------- -------- -------- --------
ConsoleIMEClass A031F938 01000000 0100152E 00000000
DDEMLUnicodeServer A031F8A8 77DF0000 77E2E2F9 00000000
DDEMLAnsiServer A031F820 77DF0000 77E2E2F9 00000000
DDEMLUnicodeClient A031F790 77DF0000 77E2E14D 00000000
DDEMLAnsiClient A031F708 77DF0000 77E2E14D 00000000
DDEMLMom A031F688 77DF0000 77DFD316 00000000
Application(kpeb:FF5BA3C0) Global Class List:
Class Name Handle ModBase WinProc Styles
---------- -------- -------- -------- --------
Static A031F610 77DF0000 77E000F9 00004088
IME A031F5A0 77DF0000 77DFF0DF 00004000
.
.
.
上面我給出的兩個程式碼片段,分別實現了SoftICE中的hwnd與class命令的功能。在分析了這兩個命令後,也就可以進一步分析Windows NT/2000內部訊息機制,這是Windows GUI實現的基礎。可以繼續挖掘的東西看來還越來越多了。
參考資料:
1.David Solomom《Inside Windows NT,2nd Edition》