解析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》