您的位置:首页 > 理论基础 > 计算机网络

解析Windows NT/2000窗口对象的组织(http://webcrazy.yeah.net/)

2005-01-31 17:46 465 查看
解析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》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息