当前位置:   article > 正文

Windows GDI句柄分析_windbg gdi句柄

windbg gdi句柄

GDI 句柄分析

一直以来对Windows 图形化编程都不太了解,最近遇到了要给显卡驱动相关的项目,发现对于GDI和DIRECT3D都不是非常了解,打算抽空研究一下相关的内容。先从GDI的基本使用开始吧,希望能够借此弄懂相关技术。

和Windows内核句柄不一样,GDI句柄虽然使用非常多,但却不是内核对象,本文来分析一下GDI的技术原理。

1. Win32UserInitialize

我们知道,Windows 的GDI是一个单独的子系统,这个子系统中包含两个重要的东西是CSRSS进程和WIN32K驱动,由这两个模块实现所有的绘图操作,我们先看一下其中的核心之一就是WIN32K,这个模块加载的时候会调用Win32UserInitialize,这个函数是做基础的初始化功能,我们大致看一下这个函数:

NTSTATUS Win32UserInitialize()
{
    //...
    Status = = InitCreateSharedSection();
    //...
    gpsi = SharedAlloc(0x11A8u);
    //...
    gpDispInfo = SharedAlloc(0x78u);
    //...
    gSharedInfo = gpsi;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

我把上面这个很复杂的函数拆解成了三个部分,为什么是这三个部分呢?因为这三个和今天分析的GDI句柄有相关的关系。

Win32UserInitialize这个函数是在会话管理器初始化的时候,加载Win32k驱动的时候调用的,如下:

kd> !thread
THREAD 87a23030  Cid 0160.0164  Teb: 7ffdf000 Win32Thread: 00000000 RUNNING on processor 0
Not impersonating
DeviceMap                 8a2088d8
Owning Process            87b42030       Image:         smss.exe
Attached Process          N/A            Image:         N/A
Wait Start TickCount      1676           Ticks: 0
Context Switch Count      114            IdealProcessor: 0             
UserTime                  00:00:00.000
KernelTime                00:00:00.109
Win32 Start Address smss!NtProcessStartupW (0x4844e960)
Stack Init 8e7d0ed0 Current 8e7d0040 Base 8e7d1000 Limit 8e7ce000 Call 00000000
Priority 8 BasePriority 8 PriorityDecrement 0 IoPriority 2 PagePriority 5
ChildEBP RetAddr      Args to Child              
8e7d07e0 942864ed     00000026 00000008 8e7d0bf8 win32k!Win32UserInitialize (FPO: [Non-Fpo])
8e7d0860 83fc85fe     8e7d0878 00000000 00000008 win32k!DriverEntry+0x2a9 (FPO: [Non-Fpo])
8e7d0920 8406b5fc     94286234 94060000 69c7e89a nt!ExpInitializeSessionDriver+0x3a
8e7d0a5c 83e591ea     00000026 00000000 00000008 nt!NtSetSystemInformation+0x507
8e7d0a5c 83e582bd (T) 00000026 00000000 00000008 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ 8e7d0a70)
8e7d0ae0 8406b56f (T) 00000026 8e7d0bf8 00000008 nt!ZwSetSystemInformation+0x11 (FPO: [3,0,0])
8e7d0c20 83e591ea     00000026 00000000 00000008 nt!NtSetSystemInformation+0x47a
8e7d0c20 779070b4 (T) 00000026 00000000 00000008 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ 8e7d0c34)
0022fda4 77906794 (T) 4844d591 00000026 0022fdd0 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0022fda8 4844d591     00000026 0022fdd0 00000008 ntdll!ZwSetSystemInformation+0xc (FPO: [3,0,0])
0022fdf0 4844dead     00000000 00000000 00000000 smss!SmscpLoadSubSystemsForMuSession+0xe6 (FPO: [Non-Fpo])
0022fe14 4844c0eb     00000003 00351738 00000000 smss!SmscMain+0xd8 (FPO: [Non-Fpo])
0022fec0 4844e946     00000003 00351738 00351748 smss!wmain+0x50 (FPO: [Non-Fpo])
0022ff04 778c5e7a     003517f8 77bbead0 00000000 smss!NtProcessStartupW_AfterSecurityCookieInitialized+0x21f (FPO: [Non-Fpo])
0022ff44 779237c8     4844e960 7ffd4000 00000000 ntdll!__RtlUserThreadStart+0x28 (FPO: [Non-Fpo])
0022ff5c 00000000     4844e960 7ffd4000 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

1.1 InitCreateSharedSection

我们从这个函数可以知道,这个函数应该是创建一个共享内存,这个函数实现如下:

NTSTATUS __stdcall InitCreateSharedSection()
{
  NTSTATUS Status; // eax MAPDST
  int v2; // [esp+4h] [ebp-Ch]
  int v3; // [esp+8h] [ebp-8h]
  UINT_PTR ViewSize; // [esp+Ch] [ebp-4h]

  v2 = 0xC8000;
  v3 = 0;
  Status = MmCreateSection(&ghSectionShared, 0xF001F, 0, &v2, 4, 0x4000000, 0, 0);
  if ( Status < 0 )
    return Status;
  ObDeleteCapturedInsertInfo(ghSectionShared);
  ViewSize = 0;
  gpvSharedBase = 0;
  Status = MmMapViewInSessionSpace(ghSectionShared, &gpvSharedBase, &ViewSize);
  if ( Status < 0 )
  {
    ObfDereferenceObject(ghSectionShared);
OnFailed:
    ghSectionShared = 0;
    return Status;
  }
  gpvSharedAlloc = (PVOID)UserCreateHeap(
                            (int)ghSectionShared,
                            0xC0000,
                            (char *)gpvSharedBase + 0xC0000,
                            0x8000u,
                            (int)UserCommitSharedMemory);
  if ( !gpvSharedAlloc )
  {
    UserSetLastError(8);
    MmUnmapViewInSessionSpace(gpvSharedBase);
    ObfDereferenceObject(ghSectionShared);
    gpvSharedAlloc = 0;
    gpvSharedBase = 0;
    Status = STATUS_NO_MEMORY;
    goto OnFailed;
  }
  Status = 0;
  return Status;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

这个函数是在会话内存中创建一个共享的SECTION,然后在这个共享内存上面创建一个堆,这个共享内存和堆的信息如下:

  1. ghSectionShared : 内存Section。
  2. gpvSharedBase : 会话内存的地址。
  3. gpvSharedAlloc : 创建的堆。

这些共享内存,留给后续的函数使用。

1.2 SharedAlloc

Win32UserInitialize 这个函数中,使用SharedAlloc创建了内存,通过名字我们大致可以看到,这个应该是在一个共享内存中分配具体的内存,如下:

PVOID __stdcall SharedAlloc(SIZE_T Size)
{
  return RtlAllocateHeap(gpvSharedAlloc, 0, Size);
}
  • 1
  • 2
  • 3
  • 4

我们发现,这里创建的共享内存就是我们通过InitCreateSharedSection创建的共享内存。

2. gSharedInfo

这个是一个什么结构呢?在Win7 中这个结构体是符号公开的,我们可以看一下这个结构:

kd> dt win32k!tagSHAREDINFO
   +0x000 psi              : Ptr32 tagSERVERINFO
   +0x004 aheList          : Ptr32 _HANDLEENTRY
   +0x008 HeEntrySize      : Uint4B
   +0x00c pDispInfo        : Ptr32 tagDISPLAYINFO
   +0x010 ulSharedDelta    : Uint4B
   +0x014 awmControl       : [31] _WNDMSG
   +0x10c DefWindowMsgs    : _WNDMSG
   +0x114 DefWindowSpecMsgs : _WNDMSG
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这个结构体中:

  1. psi : 服务端的相关信息,这个名字很奇怪,猜测应该跟历史的CSRSS客户端服务端进程有关吧。
  2. aheList : 句柄表数组,GDI的句柄就是通过这个数组映射的。
  3. HeEntrySize : 句柄表项的大小。
  4. pDispInfo : 显示相关信息。

这个结构中的具体数据如下:

kd> dt win32k!tagSHAREDINFO 8370d1e0  
   +0x000 psi              : 0xff9d0578 tagSERVERINFO
   +0x004 aheList          : 0xff910000 _HANDLEENTRY
   +0x008 HeEntrySize      : 0xc
   +0x00c pDispInfo        : 0xff9d1728 tagDISPLAYINFO
   +0x010 ulSharedDelta    : 0
   +0x014 awmControl       : [31] _WNDMSG
   +0x10c DefWindowMsgs    : _WNDMSG
   +0x114 DefWindowSpecMsgs : _WNDMSG
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

从这个结构中,我们可以得到当前显示相关信息,如下:

kd> dx -r1 ((win32k!tagDISPLAYINFO *)0xff9d1728)
((win32k!tagDISPLAYINFO *)0xff9d1728)                 : 0xff9d1728 [Type: tagDISPLAYINFO *]
    [+0x000] hDev             : 0xffb8a748 [Type: void *]
    [+0x004] pmdev            : 0xffbbd6e0 [Type: void *]
    [+0x008] hDevInfo         : 0x0 [Type: void *]
    [+0x00c] hdcScreen        : 0x3010050 [Type: HDC__ *]
    [+0x010] hdcBits          : 0x1010051 [Type: HDC__ *]
    [+0x014] hdcGray          : 0x10100e7 [Type: HDC__ *]
    [+0x018] hbmGray          : 0x10500e8 [Type: HBITMAP__ *]
    [+0x01c] cxGray           : 320 [Type: int]
    [+0x020] cyGray           : 18 [Type: int]
    [+0x024] pdceFirst        : 0xfd6b9168 [Type: tagDCE *]
    [+0x028] pspbFirst        : 0x0 [Type: tagSPB *]
    [+0x02c] cMonitors        : 0x1 [Type: unsigned long]
    [+0x030] pMonitorPrimary  : 0xff9d1d28 [Type: tagMONITOR *]
    [+0x034] pMonitorFirst    : 0xff9d1d28 [Type: tagMONITOR *]
    [+0x038] rcScreenReal     : {LT(0, 0) RB(1616, 926)  [1616 x 926]} [Type: tagRECT]
    [+0x048] hrgnScreenReal   : 0x0 [Type: HRGN__ *]
    [+0x04c] dmLogPixels      : 0x0 [Type: unsigned short]
    [+0x04e] BitCountMax      : 0x0 [Type: unsigned short]
    [+0x050 ( 0: 0)] fDesktopIsRect   : 0 [Type: int]
    [+0x050 ( 1: 1)] fAnyPalette      : 0 [Type: int]
    [+0x054] DockThresholdMax : 0x39e [Type: unsigned long]
    [+0x058] SpatialListHead  [Type: _KLIST_ENTRY]
    [+0x060] cFullScreen      : 96 [Type: short]
    [+0x062] Spare0           : 32 [Type: short]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

rcScreenReal : {LT(0, 0) RB(1616, 926) [1616 x 926]} [Type: tagRECT]这里我们可以得出当前显示使用的分辨率是1616 * 926(如果是双屏那么就是全部屏幕的大小).

3. GDI 句柄

我们进入今天的主题,上面我们在Win32k!gSharedInfo这个结构中找到的句柄表,那么系统是怎么从这个表中通过句柄找到对象地址的呢?

句柄表的解析我们可以逆向ValidateHwnd这个函数得出,大致的计算公式如下:

HandleEntry = gSharedInfo.aheList + ( (hWnd & 0xffff) * gSharedInfo.HeEntrySize);
  • 1

我们可以通过一个函数来验证,ShowWindow 这个肯定会查找句柄表,过程如下:

kd> kb
 # ChildEBP RetAddr  Args to Child              
00 a5ef7bc8 8359267d fea053c8 00010000 862d0c50 win32k!xxxShowWindow
01 a5ef7be8 882daf55 00020026 00000000 00020026 win32k!NtUserShowWindow+0x8b
  • 1
  • 2
  • 3
  • 4

从这个堆栈我们可以得出两个信息:

  1. GDI句柄的值为00020026
  2. GDI 通过句柄找到的项为fea053c8.

我们来手工查找一下是否正确:

kd> dt win32k!tagSHAREDINFO 8370d1e0  
   +0x000 psi              : 0xff9d0578 tagSERVERINFO
   +0x004 aheList          : 0xff910000 _HANDLEENTRY
   +0x008 HeEntrySize      : 0xc
   +0x00c pDispInfo        : 0xff9d1728 tagDISPLAYINFO
   +0x010 ulSharedDelta    : 0
   +0x014 awmControl       : [31] _WNDMSG
   +0x10c DefWindowMsgs    : _WNDMSG
   +0x114 DefWindowSpecMsgs : _WNDMSG

kd> dt Win32k!_HANDLEENTRY (0xff910000+26*c)
   +0x000 phead            : 0xfea053c8 _HEAD
   +0x004 pOwner           : 0xfe5ddbc0 Void
   +0x008 bType            : 0x1 ''
   +0x009 bFlags           : 0 ''
   +0x00a wUniq            : 2

kd> dx -r1 ((win32k!_HEAD *)0xfea053c8)
((win32k!_HEAD *)0xfea053c8)                 : 0xfea053c8 [Type: _HEAD *]
ReadVirtual: fea053c8 not properly sign extended
    [+0x000] h                : 0x20026 [Type: void *]
    [+0x004] cLockObj         : 0x4 [Type: unsigned long]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

可以看到我们的查找过程是正确的。

4. UserClientDllInitialize

在讨论UserClientDllInitialize这个函数之前,我们看一下现象,在上面分析的时候就知道gSharedInfo是放在共享内存中的,为什么需要这么做呢?

主要有一个好处就是Windows在应用层的时候就可以通过句柄找到GDI对象,而不需要切换到内核中,我们知道切换到内核是需要一定的资源消耗的,因此使用这种方式可以提升一定的速度。

在网上有很多办法就是直接通过应用层的来达到GDI句柄查找对象的,但是没有说明具体原因,主要是因为都是共享内存的,所以在用户层直接查找是行得通的,例如如下:

kd> dd user32!gSharedInfo
75929440  01700578 01640000 0000000c 01701728
75929450  fe2d0000 00000318 01701918 00000000
75929460  00000000 00000318 01701988 00000014
75929470  017019f8 00000000 00000000 00000000
75929480  00000000 00000000 00000000 00000318
75929490  01701a78 00000318 01701b58 00000318
759294a0  01701bc8 00000402 01701888 00000318
759294b0  01701c38 00000318 01701ae8 00000000
kd> dt Win32k!tagSHAREDINFO 75929440  
   +0x000 psi              : 0x01700578 tagSERVERINFO
   +0x004 aheList          : 0x01640000 _HANDLEENTRY
   +0x008 HeEntrySize      : 0xc
   +0x00c pDispInfo        : 0x01701728 tagDISPLAYINFO
   +0x010 ulSharedDelta    : 0xfe2d0000
   +0x014 awmControl       : [31] _WNDMSG
   +0x10c DefWindowMsgs    : _WNDMSG
   +0x114 DefWindowSpecMsgs : _WNDMSG
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

并且我们可以发现,这个内存是只读共享的,所以这个内存给应用层使用也是安全的,如下:

kd> dt Win32k!tagSHAREDINFO 75929440  
   +0x000 psi              : 0x01700578 tagSERVERINFO
   +0x004 aheList          : 0x01640000 _HANDLEENTRY
   +0x008 HeEntrySize      : 0xc
   +0x00c pDispInfo        : 0x01701728 tagDISPLAYINFO
   +0x010 ulSharedDelta    : 0xfe2d0000
   +0x014 awmControl       : [31] _WNDMSG
   +0x10c DefWindowMsgs    : _WNDMSG
   +0x114 DefWindowSpecMsgs : _WNDMSG
kd> !address 0x01700578 


Usage:                  VAD
Base Address:           01640000
End Address:            01708000
Region Size:            000c8000
VA Type:                UserRange
VAD Address:            0xffffffff8729d8a0
Commit Charge:          0x0
Protection:             0x1 [ReadOnly]
Memory Usage:           Section [[Page File]]
No Change:              yes
More info:              !vad 0x1640000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

Win32k 中的gSharedInfo的初始化完成我们已经分析了,那么用户层的是怎么初始化的呢?这个就在UserClientDllInitialize这个函数中,流程如下:

bool __stdcall _UserClientDllInitialize(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
if ( v3 )
  {
    StringCchPrintfW(&pszDest, 0x100u, L"%ws\\%ld%ws", L"\\Sessions", v3, L"\\Windows");
    v11 = CsrClientConnectToServer(&pszDest, 3u, &ConnectionInfo, &ConnectionInfoSize, &gfServerProcess);
  }
  else
  {
    v11 = CsrClientConnectToServer(L"\\Windows", 3u, &ConnectionInfo, &ConnectionInfoSize, &gfServerProcess);
  }
  if ( v11 < 0 )
    return 0;
  if ( !*(_DWORD *)&gfServerProcess )
  {
    v12 = 0;
    if ( !(NtCurrentTeb()->ProcessEnvironmentBlock->BitField & 2) )
    {
      qmemcpy(&gSharedInfo, &v17, 0x11Cu);
      //...
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

大致可以知道应该是通过CsrClientConnectToServer 这个来获取的,这个函数的调用关系如下:

ChildEBP RetAddr  Args to Child              
a5edf9a8 83ebd69d 87a30408 83f6df08 83f6ad20 nt!KiSwapContext+0x26 (FPO: [Uses EBP] [0,0,4])
a5edf9e0 83ebc4f7 87a304c8 87a30408 87a3063c nt!KiSwapThread+0x266
a5edfa08 83eb60cf 87a30408 87a304c8 00000000 nt!KiCommitThreadWait+0x1df
a5edfa84 83eedea3 87a3063c 00000011 8407e801 nt!KeWaitForSingleObject+0x393
a5edfaac 8407c3b2 87a3063c 8407e801 00000000 nt!AlpcpSignalAndWait+0x7b
a5edfad0 8409dad8 8407e801 a5edfb3c 00000000 nt!AlpcpReceiveSynchronousReply+0x27
a5edfb60 8407e864 878aecf0 00020000 0017f234 nt!AlpcpProcessSynchronousRequest+0x276
a5edfbbc 8407e925 878aecf0 0017f234 8407e801 nt!LpcpRequestWaitReplyPort+0x6a
a5edfbe4 882d53e6 00000014 0017f234 0017f234 nt!NtRequestWaitReplyPort+0x4c
WARNING: Frame IP not in any known module. Following frames may be wrong.
a5edfc20 83e7d1ea 00000014 0017f234 0017f234 0x882d53e6
a5edfc20 773570b4 00000014 0017f234 0017f234 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ a5edfc34)
0017f1ec 77356464 7736c7ee 00000014 0017f234 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0017f1f0 7736c7ee 00000014 0017f234 0017f234 ntdll!NtRequestWaitReplyPort+0xc (FPO: [3,0,0])
0017f210 77378c2e 0017f234 00010590 00000000 ntdll!CsrClientCallServer+0xc3 (FPO: [Non-Fpo])
0017f31c 758dd3c7 00000000 00000003 0017f344 ntdll!CsrClientConnectToServer+0x1a4 (FPO: [Non-Fpo])
0017f8c0 773689d8 758c0000 00000001 0017fbac USER32!_UserClientDllInitialize+0x17f (FPO: [Non-Fpo])
0017f8e0 77375c41 758dd711 758c0000 00000001 ntdll!LdrpCallInitRoutine+0x14
0017f9d4 77376175 0017fbac 7ffdf000 7ffde000 ntdll!LdrpRunInitializeRoutines+0x26f (FPO: [Non-Fpo])
0017fb38 77376077 0017fbac 77310000 770911f3 ntdll!LdrpInitializeProcess+0x12da (FPO: [Non-Fpo])
0017fb88 77373663 0017fbac 77310000 00000000 ntdll!_LdrpInitialize+0x78 (FPO: [Non-Fpo])
0017fb98 00000000 0017fbac 77310000 00000000 ntdll!LdrInitializeThunk+0x10 (FPO: [Non-Fpo])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

在这个本地过程调用中响应的函数如下:

kd> kb
 # ChildEBP RetAddr  Args to Child              
00 943f3c24 83e7d1ea 00000838 00408e94 00fcf970 win32k!NtUserProcessConnect
01 943f3c24 773570b4 00000838 00408e94 00fcf970 nt!KiFastCallEntry+0x12a
02 00fcf95c 75481895 7548187b 00000838 00408e94 ntdll!KiFastSystemCallRet
03 00fcf960 7548187b 00000838 00408e94 00fcf990 winsrv!NtUserProcessConnect+0xc
04 00fcf970 754c32c1 00401690 00408e94 00fcf9e0 winsrv!UserClientConnect+0x55
05 00fcf990 754c4d55 00fcf9b0 00fcfaa8 3470f029 CSRSRV!CsrSrvClientConnect+0x60
06 00fcfb04 77315e7a 00000000 77c2c417 00000000 CSRSRV!CsrApiRequestThread+0x3bb
07 00fcfb44 773737c8 754c499a 00000000 00000000 ntdll!__RtlUserThreadStart+0x28
08 00fcfb5c 00000000 754c499a 00000000 00000000 ntdll!_RtlUserThreadStart+0x1b
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

而在NtUserProcessConnect,重要流程如下:

NTSTATUS __stdcall NtUserProcessConnect(HANDLE Handle, PVOID Address)
{
    //...
    KeStackAttachProcess(Object, &v12);
    //...
    UserEnterUserCritSec();
    InitMapSharedSection((int)Object, &Src);
    UserSessionSwitchLeaveCrit();
    //...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

InitMapSharedSection就会在共享内存中映射虚拟内存,完成共享内存的映射,大致方法如下:

Status = MmMapViewOfSection(ghSectionShared,
	Process,
	&pClientBase,
	0,
	0,
	&liOffset,
	&ViewSize,
	ViewUnmap,
	SEC_NO_CHANGE,
	PAGE_EXECUTE_READ)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

5. GDI句柄限制

我们从上面的分析可以发现GDI句柄的计算涉及到 hWnd & 0xffff, 那么似乎GDI的句柄数目只能支持65535 个,其实这个结论是正确的,我们可以看一下句柄表项增长的函数,如下:

signed int __stdcall HMGrowHandleTable()
{
  char *v1; // eax
  tagSERVERINFO *v2; // eax
  int v3; // esi
  signed int v4; // ecx
  _HANDLEENTRY *v5; // eax
  UINT_PTR RegionSize; // [esp+4h] [ebp-4h]

  if ( gpsi->cHandleEntries == 65534 )
    return 0;
  v1 = (char *)gSharedInfo.aheList + gpsi->cbHandleTable;
  if ( v1 >= gpvSharedAlloc )
    return 0;
  RegionSize = 4096;
  if ( CommitReadOnlyMemory((int)ghSectionShared, &RegionSize, v1 - (_BYTE *)gpvSharedBase, 0) < 0 )
    return 0;
  gpsi->cbHandleTable += 4096;
  gpsi->cHandleEntries = gpsi->cbHandleTable / 0xC;
  v2 = gpsi;
  if ( gpsi->cHandleEntries > 65534 )
  {
    gpsi->cHandleEntries = 65534;
    v2 = gpsi;
  }
  v3 = gHandlePages;
  memset(&gSharedInfo.aheList[gHandlePages], 0, 12 * (v2->cHandleEntries - gHandlePages));
  v4 = gpsi->cHandleEntries - 1;
  v5 = &gSharedInfo.aheList[v4];
  while ( v4 >= v3 )
  {
    v5->wUniq = 1;
    if ( v4 & 1 )
    {
      v5->phead = (_HEAD *)dword_BFA1CA18;
      dword_BFA1CA18 = v4;
    }
    else
    {
      v5->phead = (_HEAD *)dword_BFA1CA14;
      dword_BFA1CA14 = v4;
    }
    --v4;
    --v5;
  }
  gHandlePages = gpsi->cHandleEntries;
  return 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

我们通过gpsi->cHandleEntries65534的比较可以得知,整个会话中GDI的句柄数目限定在了65535个。

不仅仅Windows系统限制了GDI句柄的数目 ,单个进程的GDI句柄数目也做了相关的限制,这个限制的值在如下注册表位置:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows
GDIProcessHandleQuota
REG_DWORD
10000
  • 1
  • 2
  • 3
  • 4

6. 关于Win10

自从进入Win10 之后Win32K已经有了很大的变化,更加重要的是上面的这些结构体已经不再公开类型了,那么我们要想查找GDI句柄就更加困难了。但是幸运的是原理还是不变的,只要从上面的分析点出发,Win10环境下面我们还是可以通过GDI句柄找到GDI对象的。

7. 关于不同进程的句柄

我们从上面分析可以发现,GDI句柄和内核对象句柄是不同,内核对象句柄表在单独的进程中,但是GDI对象的句柄表是一个系统全局的对象中,因此可以得出来,每个对象的句柄在不同进程中是相同的,我们可以通过代码验证,代码如下:

void CGDIDlg::OnBnClickedButtonGetWindow()
{
	// TODO: Add your control notification handler code here
	HWND hWnd = ::FindWindowW(L"GdiWindow", L"GDIWINDOW");
	if (hWnd != NULL)
	{
		CString StrMsg;
		StrMsg.Format(L"[GDI] HWND = 0x%08x", hWnd);
		OutputDebugStringW(StrMsg.GetBuffer(0));
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

可以输出相关结果:

[18296] [GDI] HWND = 0x00041a82
[6068] [GDI] HWND = 0x00041a82
[17728] [GDI] HWND = 0x00041a82
[19612] [GDI] HWND = 0x00041a82
  • 1
  • 2
  • 3
  • 4

不同的进程,对应的GDI对象的句柄是一样的,因此GDI对象不同于内核对象,完全可以跨进程使用。

8. 关于句柄类型

找到句柄对应的句柄项之后,我们怎么知道句柄项对应的GDI对象的类型呢?这就需要_HANDLEENTRYbType字段了,具体值信息如下:

kd> dt Win32k!_HANDLEENTRY 0xff910000+3da*c
   +0x000 phead            : 0xfea3a8d0 _HEAD
   +0x004 pOwner           : 0xfd74fdd8 Void
   +0x008 bType            : 0x1 ''
   +0x009 bFlags           : 0 ''
   +0x00a wUniq            : 0xe
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

对于具体的类型定义,有如下,包括了所有的GDI对象类型定义:

typedef enum _HANDLE_TYPE
{
    TYPE_FREE = 0,
    TYPE_WINDOW = 1,
    TYPE_MENU = 2,
    TYPE_CURSOR = 3,
    TYPE_SETWINDOWPOS = 4,
    TYPE_HOOK = 5,
    TYPE_CLIPDATA = 6,
    TYPE_CALLPROC = 7,
    TYPE_ACCELTABLE = 8,
    TYPE_DDEACCESS = 9,
    TYPE_DDECONV = 10,
    TYPE_DDEXACT = 11,
    TYPE_MONITOR = 12,
    TYPE_KBDLAYOUT = 13,
    TYPE_KBDFILE = 14,
    TYPE_WINEVENTHOOK = 15,
    TYPE_TIMER = 16,
    TYPE_INPUTCONTEXT = 17,
    TYPE_HIDDATA = 18,
    TYPE_DEVICEINFO = 19,
    TYPE_TOUCHINPUTINFO = 20,
    TYPE_GESTUREINFOOBJ = 21,
    TYPE_CTYPES,
    TYPE_GENERIC = 255
} HANDLE_TYPE, *PHANDLE_TYPE;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

9. 两个重要结构体

在本部分中使用到最重要的两个结构体就是gSharedInfogpsi,其中:

  1. gSharedInfo是一个结构体的全局变量,类型为win32k!tagSHAREDINFO.
  2. gpsi是一个全局指针,类型为win32k!tagSERVERINFO,并且gpsi指针的值放入到了gSharedInfo.psi中了。

gSharedInfo的结构如下:

2: kd> dd win32k!gSharedInfo L1
9491d1e0  ff9d0578
2: kd> dt win32k!tagSHAREDINFO 9491d1e0  
   +0x000 psi              : 0xff9d0578 tagSERVERINFO
   +0x004 aheList          : 0xff910000 _HANDLEENTRY
   +0x008 HeEntrySize      : 0xc
   +0x00c pDispInfo        : 0xff9d1728 tagDISPLAYINFO
   +0x010 ulSharedDelta    : 0
   +0x014 awmControl       : [31] _WNDMSG
   +0x10c DefWindowMsgs    : _WNDMSG
   +0x114 DefWindowSpecMsgs : _WNDMSG
2: kd> dd win32k!gpsi L1
9491d2fc  ff9d0578
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

从这里我们可以看到gSharedInfo.psi和全局指针win32k!gpsi的值是一样的,gpsi的结构和类容如下:

2: kd> dt win32k!tagSERVERINFO 0xff9d0578
   +0x000 dwSRVIFlags      : 0x516
   +0x004 cHandleEntries   : 0x2aa
   +0x008 mpFnidPfn        : [32] 0x94860fb1     long  win32k!xxxWrapSBWndProc+0
   +0x088 aStoCidPfn       : [7] 0x9486a993     long  win32k!xxxSBWndProc+0
   +0x0a4 mpFnid_serverCBWndProc : [31] 0xf8
   +0x0e4 apfnClientA      : _PFNCLIENT
   +0x140 apfnClientW      : _PFNCLIENT
   +0x19c apfnClientWorker : _PFNCLIENTWORKER
   +0x1c8 cbHandleTable    : 0x2000
   +0x1cc atomSysClass     : [25] 0xc017
   +0x200 dwDefaultHeapBase : 0
   +0x204 dwDefaultHeapSize : 0xc00000
   +0x208 uiShellMsg       : 0xc02b
   +0x20c MBStrings        : [11] tagMBSTRING
   +0x3c4 atomIconSmProp   : 0xc029
   +0x3c6 atomIconProp     : 0xc02a
   +0x3c8 atomContextHelpIdProp : 0xc028
   +0x3ca atomFrostedWindowProp : 0xc02c
   +0x3cc acOemToAnsi      : [256]  ""
   +0x4cc acAnsiToOem      : [256]  ""
   +0x5cc dwInstalledEventHooks : 0x8101
   +0x5d0 aiSysMet         : [97] 0n1920
   +0x754 argbSystemUnmatched : [31] 0x780
   +0x7d0 argbSystem       : [31] 0x15
   +0x84c ahbrSystem       : [31] 0x00000420 HBRUSH__
   +0x8c8 hbrGray          : (null) 
   +0x8cc ptCursor         : tagPOINT
   +0x8d4 ptCursorReal     : tagPOINT
   +0x8dc dwLastRITEventTickCount : 0
   +0x8e0 nEvents          : 0n13743257
   +0x8e4 dtScroll         : 0xdbcdbf
   +0x8e8 dtLBSearch       : 0xf0f0f0
   +0x8ec dtCaretBlink     : 0xffffff
   +0x8f0 ucWheelScrollLines : 0x646464
   +0x8f4 ucWheelScrollChars : 0
   +0x8f8 wMaxLeftOverlapChars : 0n0
   +0x8fc wMaxRightOverlapChars : 0n0
   +0x900 cxSysFontChar    : 0n11842740
   +0x904 cySysFontChar    : 0n16578548
   +0x908 tmSysFont        : tagTEXTMETRICW
   +0x944 dpiSystem        : tagDPISERVERINFO
   +0x95c hIconSmWindows   : 0x00d1b499 HICON__
   +0x960 hIcoWindows      : 0x00dbcdbf HICON__
   +0x964 dwKeyCache       : 0xf0f0f0
   +0x968 dwAsyncKeyCache  : 0xffffff
   +0x96c cCaptures        : 0x646464
   +0x970 oembmi           : [93] tagOEMBITMAPINFO
   +0xf40 rcScreenReal     : tagRECT
   +0xf50 BitCount         : 0x5d
   +0xf52 dmLogPixels      : 0
   +0xf54 Planes           : 0x13 ''
   +0xf55 BitsPixel        : 0 ''
   +0xf58 PUSIFlags        : 0x13
   +0xf5c uCaretWidth      : 0xa1
   +0xf60 UILangID         : 0x5d
   +0xf64 dwLastSystemRITEventTickCountUpdate : 0x13
   +0xf68 adwDBGTAGFlags   : [35] 0x13
   +0xff4 dwTagCount       : 0xd
   +0xff8 dwRIPFlags       : 0xd
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/247385
推荐阅读
相关标签
  

闽ICP备14008679号