click to login
某不知名程序员的网上巢穴。稍安勿躁,正在开发。

NtUserXxx 调用引发 BSOD 的问题分析和扩展

这篇文章通过一次在 Windows XP 和 Windows 7 操作系统内核中分别调用同一个 NtUserXxx 系统调用产生不同现象的问题,对其做了简单分析,并对 Windows XP 中引发异常的相关机制提出扩展探究。

最近在驱动中需要实现在一些 HOOK 处理函数中调用如 NtUserBuildHwndList 这样的 API 对目标样本进程的窗口状态(是否存在窗口等)进行判定。NtUserBuildHwndList 是用来根据线程 ID 生成与线程信息结构体 tagTHREADINFO 关联的 tagDESKTOP 桌面对象中存在的窗口对象句柄列表的 USER 系统调用,其函数声明如下:

NTSTATUS
NtUserBuildHwndList (
    IN  HDESK  hdesk,
    IN  HWND   hwndNext,
    IN  BOOL   fEnumChildren,
    IN  DWORD  idThread,
    IN  UINT   cHwndMax,
    OUT HWND  *phwndFirst,
    OUT PUINT  pcHwndNeeded
    );

实现代码在 Windows 7 下一切正常,但在 Windows XP 中的部分进程上下文中调用时会产生的偶发 BSOD 异常。为了解决该问题,通过内核调试进行分析。

分析

挂上 WinDBG 内核调试模式起动 Windows XP 的虚拟机镜像,加载驱动并执行样本进程。幸运的是很快触发预期的异常。

Access violation - code c0000005 (!!! second chance !!!)
win32k!InternalBuildHwndList+0x1a:
bf835e26 8b402c          mov     eax,dword ptr [eax+2Ch]
kd> dc eax+2Ch l 1
0000002c  ????????                             ????
kd> r eax
eax=00000000
kd> kv
ChildEBP RetAddr  Args to Child
ee609c04 bf835d37 e12dc350 bc6bc8c8 0000000a win32k!InternalBuildHwndList+0x1a (FPO: [Non-Fpo])
ee609c1c bf835fa7 bc6bc8c8 0000000a e2610870 win32k!BuildHwndList+0x4f (FPO: [Non-Fpo])
ee609c60 ede0b2aa 00000000 00000000 00000000 win32k!NtUserBuildHwndList+0xd8 (FPO: [Non-Fpo])
ee609ca8 ede0b3f3 85e45da0 862845a0 c0000001 MyDriver!MyCallOfNtUserBuildHwndList+0x10a (FPO: [Non-Fpo])

根据信息显示,是在 win32k!InternalBuildHwndList 函数中触发了异常。根据栈回溯可知,在我们的驱动模块调用 win32k!NtUserBuildHwndList 例程之后,实际调用 win32k!BuildHwndList 函数,随后进入 win32k!InternalBuildHwndList 例程中。最终在 InternalBuildHwndList 中发生了异常。

win32k!InternalBuildHwndList:
bf835e10 8bff            mov     edi,edi
bf835e12 55              push    ebp
bf835e13 8bec            mov     ebp,esp
bf835e15 56              push    esi
bf835e16 57              push    edi
bf835e17 8b7d0c          mov     edi,dword ptr [ebp+0Ch]
bf835e1a 85ff            test    edi,edi
bf835e1c 74e8            je      win32k!InternalBuildHwndList+0x94 (bf835e06)
bf835e1e 8b7508          mov     esi,dword ptr [ebp+8]
bf835e21 a118ae9abf      mov     eax,dword ptr [win32k!gptiCurrent (bf9aae18)]
bf835e26 8b402c          mov     eax,dword ptr [eax+2Ch]   <- ACCESS VIOLATION, eax=0x00000000
bf835e29 8b8894010000    mov     ecx,dword ptr [eax+194h]
bf835e2f 8b460c          mov     eax,dword ptr [esi+0Ch]

win32k!BuildHwndList:
bf835d08 8bff            mov     edi,edi
bf835d0a 55              push    ebp
bf835d0b 8bec            mov     ebp,esp
bf835d0d a174949abf      mov     eax,dword ptr [win32k!pbwlCache (bf9a9474)]
bf835d12 85c0            test    eax,eax
bf835d14 74cd            je      win32k!BuildHwndList+0x17 (bf835ce3)
bf835d16 832574949abf00  and     dword ptr [win32k!pbwlCache (bf9a9474)],0
bf835d1d 53              push    ebx
bf835d1e 8b5d0c          mov     ebx,dword ptr [ebp+0Ch]
bf835d21 53              push    ebx
bf835d22 ff7508          push    dword ptr [ebp+8]
bf835d25 8d4810          lea     ecx,[eax+10h]
bf835d28 894804          mov     dword ptr [eax+4],ecx
bf835d2b 8b4d10          mov     ecx,dword ptr [ebp+10h]
bf835d2e 50              push    eax
bf835d2f 89480c          mov     dword ptr [eax+0Ch],ecx
bf835d32 e8d9000000      call    win32k!InternalBuildHwndList (bf835e10)   <- CALL InternalBuildHwndList
bf835d37 8b4804          mov     ecx,dword ptr [eax+4]
bf835d3a 3b4808          cmp     ecx,dword ptr [eax+8]

win32k!NtUserBuildHwndList:
bf835f21 6a14            push    14h
bf835f23 68e8d798bf      push    offset win32k!`string'+0x550 (bf98d7e8)
bf835f28 e8dbacfcff      call    win32k!_SEH_prolog (bf800c08)
bf835f2d 6a02            push    2
bf835f2f 5f              pop     edi
bf835f30 e825acfcff      call    win32k!EnterCrit (bf800b5a)
bf835f35 a158aa9abf      mov     eax,dword ptr [win32k!gpsi (bf9aaa58)]
bf835f3a f6400208        test    byte ptr [eax+2],8
bf835f3e 0f8547ffffff    jne     win32k!NtUserBuildHwndList+0x1f (bf835e8b)
bf835f44 8b4d0c          mov     ecx,dword ptr [ebp+0Ch]
bf835f47 33db            xor     ebx,ebx
bf835f49 3bcb            cmp     ecx,ebx
bf835f4b 0f8542ffffff    jne     win32k!NtUserBuildHwndList+0x2b (bf835e93)
bf835f51 33c0            xor     eax,eax
bf835f53 395d14          cmp     dword ptr [ebp+14h],ebx
bf835f56 0f84e0000000    je      win32k!NtUserBuildHwndList+0x69 (bf83603c)
bf835f5c ff7514          push    dword ptr [ebp+14h]
bf835f5f e89439feff      call    win32k!PtiFromThreadId (bf8198f8)
bf835f64 8bf0            mov     esi,eax
bf835f66 3bf3            cmp     esi,ebx
bf835f68 0f8423010000    je      win32k!NtUserBuildHwndList+0x65 (bf836091)
bf835f6e 8b463c          mov     eax,dword ptr [esi+3Ch]
bf835f71 3bc3            cmp     eax,ebx
bf835f73 0f8418010000    je      win32k!NtUserBuildHwndList+0x65 (bf836091)
bf835f79 8b4004          mov     eax,dword ptr [eax+4]
bf835f7c 8b4008          mov     eax,dword ptr [eax+8]
bf835f7f 8b4038          mov     eax,dword ptr [eax+38h]
bf835f82 395d08          cmp     dword ptr [ebp+8],ebx
bf835f85 0f85dd000000    jne     win32k!NtUserBuildHwndList+0x70 (bf836068)
bf835f8b 895de4          mov     dword ptr [ebp-1Ch],ebx
bf835f8e 3bc3            cmp     eax,ebx
bf835f90 0f84ad000000    je      win32k!NtUserBuildHwndList+0xaa (bf836043)
bf835f96 395d10          cmp     dword ptr [ebp+10h],ebx
bf835f99 0f8572ffffff    jne     win32k!NtUserBuildHwndList+0xca (bf835f11)
bf835f9f 56              push    esi
bf835fa0 57              push    edi
bf835fa1 50              push    eax
bf835fa2 e861fdffff      call    win32k!BuildHwndList (bf835d08)   <- CALL BuildHwndList
bf835fa7 8bf0            mov     esi,eax
bf835fa9 8975e0          mov     dword ptr [ebp-20h],esi

发生异常时 eax 寄存器值为零。根据 InternalBuildHwndList 函数的指令序列得知 eax 寄存器存储的是 win32k!gptiCurrent 的值,win32k!gptiCurrent 是一个临界变量。在 NtUserBuildHwndList 函数中通过调用 win32k!EnterCrit 进入临界区,用来确保 USER 相关的各种全局资源能够独占访问。win32k!EnterCrit 通过调用 KeEnterCriticalRegion 进入临界区并通过 ExAcquireResourceExclusiveLite 函数对 gpresUser 资源实施共享锁定之后,调用 PsGetThreadWin32Thread 获取当前线程的线程信息结构体 tagTHREADINFO 指针并赋值给 win32k!gptiCurrent 变量。

kd> u win32k!EnterCrit
win32k!EnterCrit:
bf800b5a ff1524cb98bf    call    dword ptr [win32k!_imp__KeEnterCriticalRegion (bf98cb24)]
bf800b60 6a01            push    1
bf800b62 ff3520ab9abf    push    dword ptr [win32k!gpresUser (bf9aab20)]
bf800b68 ff159ccb98bf    call    dword ptr [win32k!_imp__ExAcquireResourceExclusiveLite (bf98cb9c)]
bf800b6e ff1560cb98bf    call    dword ptr [win32k!_imp__PsGetCurrentThread (bf98cb60)]
bf800b74 50              push    eax
bf800b75 ff15f4d098bf    call    dword ptr [win32k!_imp__PsGetThreadWin32Thread (bf98d0f4)]
bf800b7b a318ae9abf      mov     dword ptr [win32k!gptiCurrent (bf9aae18)],eax
bf800b80 c3              ret

PsGetThreadWin32Thread 函数的指令非常简单:

kd> u PsGetThreadWin32Thread
nt!PsGetThreadWin32Thread:
8052883a 8bff            mov     edi,edi
8052883c 55              push    ebp
8052883d 8bec            mov     ebp,esp
8052883f 8b4508          mov     eax,dword ptr [ebp+8]
80528842 8b8030010000    mov     eax,dword ptr [eax+130h]
80528848 5d              pop     ebp
80528849 c20400          ret     4

获取当前线程 KTHREAD + 0x130 位置的域的值并作为返回值返回。根据 Windows XP 的定义,该偏移位置存储的是 Win32Thread 指针。

kd> dt _KTHREAD
nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListHead   : _LIST_ENTRY
   +0x018 InitialStack     : Ptr32 Void
   +0x01c StackLimit       : Ptr32 Void
   ...
   +0x12c CallbackStack    : Ptr32 Void
   +0x130 Win32Thread      : Ptr32 Void

然而在 InternalBuildWndList 函数中对 win32k!gptiCurrent 指针变量进行操作之前,并未判断该指针是否为空,直接操作则必然引发异常。事实上,在 Windows XP 操作系统中,Win32k 中的很多例程其默认为在调用自己之前,gptiCurrent 已经是一个有效的值,所以并不进行必要的判断。

然而如果当前线程不是 GUI 线程,如控制台应用程序进程的线程,它们的 Win32Thread 域始终是空值,如果不进行判断就直接在内核中调用 NtUserBuildWndList 等函数,就将直接引发前面提到的 BSOD 异常。幸运的是,用户层进程在通过系统服务调用位于 Win32k.sys 中的系统例程时,其通常通过 User32.dll 或 Gdi32.dll 等动态库模块中的函数来进行,此时该线程应已在内核通过 PsConvertToGuiThread 等函数将其转换成 GUI 线程。

在 Windows 中,所有的线程作为非 GUI 线程启动。如果某线程访问任意 USER 或 GDI 系统调用(调用号 >= 0x1000),Windows 将提升该线程为 GUI 线程(nt!PsConvertToGuiThread)并调用进程和线程呼出接口。

这样一来,通过常规方式从用户层到内核层的标准系统调用来调用 User 或 GDI 的系统服务时,操作系统负责处理相关的初始化和转换操作。但像在我们的驱动程序中执行全局的调用时,就需要对调用的环境(进程和线程)进行必要的判断,而不能轻易地擅自直接进行调用。

扩展

至于为什么在 Windows 7 操作系统中没有任何问题的调用,只会在 Windows XP 系统下触发 BSOD,这需要笔者进一步的探索。

Windows XP 和 Windows 7 SP1 x86 系统下关于 win32k!InternalBuildHwndList 函数的实现代码总体相同,唯一不同之处在于 Windows XP 中多了一处针对 AllowAccessUISandbox 函数调用返回值的 if 判断:

struct tagBWL *__stdcall InternalBuildHwndList(struct tagBWL *pbwl, struct tagWND *pwnd, unsigned int flags)
{
  struct tagWND *pwndChild;
  struct tagTHREADINFO *ptiOwner;

  // for ( ; pwnd; pwnd = pwnd->spwndNext )
  for ( ; pwnd; pwnd = (struct tagWND *)*((_DWORD *)pwnd + 0xB) )
  {
    // ptiOwner = pbwl->ptiOwner
    ptiOwner = (struct tagTHREADINFO *)*((_DWORD *)pbwl + 3);
    // if (pbwl->ptiOwner == NULL || pbwl->ptiOwner == GETPTI(pwnd))
    if ( !ptiOwner || ptiOwner == *((struct tagTHREADINFO **)pwnd + 2) )
    {
      if ( AllowAccessUISandbox(*(_DWORD *)(*((_DWORD *)gptiCurrent + 0xB) + 0x194), *((_DWORD *)pwnd + 0x28)) )
      {
        **((_DWORD **)pbwl + 1) = *(_DWORD *)pwnd; // *pbwl->phwndNext = HWq(pwnd);
        *((_DWORD *)pbwl + 1) += 4;                // pbwl->phwndNext++;
        // if (pbwl->phwndNext == pbwl->phwndMax && !ExpandWindowList(&pbwl))
        if ( *((_DWORD *)pbwl + 1) == *((_DWORD *)pbwl + 2) && !ExpandWindowList(&pbwl) )
          break;
      }
    }
    if ( flags & 1 )
    {
      // pwndChild = pwnd->spwndChild
      pwndChild = (struct tagWND *)*((_DWORD *)pwnd + 0xE);
      if ( pwndChild )
      {
        pbwl = InternalBuildHwndList(pbwl, pwndChild, 3u);
        // if (pbwl->phwndNext >= pbwl->phwndMax)
        v4 = *((_DWORD *)pbwl + 1) < *((_DWORD *)pbwl + 2);
        if ( !v4 )
          break;
      }
    }
    if ( !(flags & 2) )
      break;
  }
  return pbwl;
}

在这个判断中,先取 win32k!gptiCurrent 指针变量的值,以其作为基址取偏移为 0x2C 的 DWORD 域值,再将取得的值作为基址取 0x194 偏移位置的 DWORD 域值,将其作为第一个参数;再将 pwnd 参数的 0xA0 偏移位置的 DWORD 域值作为第二个参数,传入 AllowAccessUISandbox 函数调用。

win32k!gptiCurrent 的类型是 win32k!tagTHREADINFO 结构体。根据 WinNT4.0 源码中的定义,tagTHREADINFO + 0x2C 偏移位置的域是 struct tagPROCESSINFO *ppi 指向的是当前线程所属进程的进程信息结构体,存储进程的 GUI 相关的信息。然而由于笔者才疏学浅,根据 WinNT4.0 源码的 tagPROCESSINFO 结构体定义和微软为 Windows 7 SP1 x86 系统提供的调试符号中的 tagPROCESSINFO 结构体定义,笔者仍旧未能找到定义中存在任何与 AllowAccessUISandbox 函数调用有关的域,并且未能完全确定这个函数的实际作用。

该函数的代码十分简单,在 IDA Pseudocode 中显示如下:

signed int __stdcall AllowAccessUISandbox(unsigned int a1, unsigned int a2)
{
  signed int result; // eax@2

  if ( bEnforceUISandbox )
    result = a1 >= a2;
  else
    result = 1;
  return result;
}

函数首先判断 bEnforceUISandbox 全局变量的值,如果为 FALSE 的话函数将直接返回 TRUE;而 bEnforceUISandbox 的值为 TRUE 的话,则根据参数 a1 是否大于等于 a2 选择将 TRUE 或 FALSE 作为函数的返回值。变量 bEnforceUISandbox 是 win32k 中定义的一个全局 BOOL 类型变量,顾名思义应是决定是否启用“强制用户界面沙箱”,这个变量在 win32k!Win32UserInitialize 函数中初始化,值根据注册表中的同名键值决定。该变量在 win32k!IsHandleEntryRestricted 函数中也被作为 if 判断的条件。

那么,AllowAccessUISandbox 函数的作用也应是根据当前进程的 PROCESSINFO 结构体中的某个域和 tagWND 窗口对象结构体中的某个域,将这两个域的值的大小进行判断,以判定当前进程是否具有访问该窗口的权限。

遗憾的是似乎 AllowAccessUISandbox 只在 Windows XP 操作系统中存在,更早和更新系统的 win32k 中都未能找到该函数的任何踪迹。所以转换一下思路,既然根据该函数无法找到线索,那么可以着手查找相关的域在初始赋值时的踪迹。

tagPROCESSINFO 中的域

阅读上一篇文章《通过用户模式回调实施的内核攻击》,得知在某个进程的线程首次转换为 GUI 线程时,通过注册的呼出接口初始化当前线程和所属进程的进线程信息结构体。

当进程的线程首次被转换成 GUI 线程并调用 W32pProcessCallout 时,win32k 将调用 win32k!xxxInitProcessInfo 来初始化预进程 W32PROCESS/PROCESSINFO 结构体。该结构体具体保存针对于每个进程的 GUI 相关的信息,例如相关联的桌面、窗口站,以及 USER 和 GDI 句柄计数。在调用 win32k!xxxUserProcessCallout 初始化 USER 相关的域及随后调用 GdiProcessCallout 初始化 GDI 相关的域之前,该函数通过调用 win32k!xxxAllocateW32Process 分配结构体自身。

在 W32pProcessCallout 函数中,通过调用 xxxAllocateW32Process 分配结构体对象并将其指针写入进程的 EPROCESS 结构体对象中。

NTSTATUS __stdcall AllocateW32Process(int Process)
{
  NTSTATUS Status; // edi@1
  PVOID w32Process; // esi@3

  Status = 0;
  KeEnterCriticalRegion();
  ExAcquireFastMutexUnsafe(gpW32FastMutex);
  if ( !PsGetProcessWin32Process(Process) )
  {
    w32Process = HeavyAllocPool(W32ProcessSize, W32ProcessTag, 1);
    if ( w32Process )
    {
      memset(w32Process, 0, W32ProcessSize);
      *(_DWORD *)w32Process = Process;
      Status = PsSetProcessWin32Process(Process, w32Process, 0);
      if ( Status < 0 )
      {
        *(_DWORD *)(__readfsdword(0x18) + 0x34) = 5; // TEB->LastErrorValue = 5
        HeavyFreePool(w32Process);
      }
      else
      {
        ReferenceW32Process(w32Process);
      }
    }
    else
    {
      *(_DWORD *)(__readfsdword(0x18) + 0x34) = 8; // TEB->LastErrorValue = 8
      Status = 0xC0000017;
    }
  }
  ExReleaseFastMutexUnsafe(gpW32FastMutex);
  KeLeaveCriticalRegion();
  return Status;
}

在上面的代码中可以看到,函数在通过 HeavyAllocPool 分配内存后,将返回的内存指针作为参数传入 PsSetProcessWin32Process 函数,将该指针设置到进程的 EPROCESS 进程体对象中。PsSetProcessWin32Process 是 NT 执行体中的导出函数,其代码中最主要的操作是通过 _InterlockedExchange 将传入的 W32PROCESS 结构体指针原子地写入目标进程体对象中 0x130 偏移位置的域地址。

在 win32k.sys 中全局查找 PsSetProcessWin32Process 函数的引用,发现只在两处调用,一处是这里的 AllocateW32Process 函数中的调用,另一处是在 UserDeleteW32Process 函数中,对进程体对象的 Win32Process 域进行置零。

接下来 W32pProcessCallout 调用 xxxUserProcessCallout 函数,在该函数中调用 xxxInitProcessInfo 函数初始化进程信息结构体。经过反汇编和分析,得到函数的代码和注释:

NTSTATUS __stdcall xxxInitProcessInfo(W32PROCESS *ppi)
{
  ULONG         Flags;
  PEPROCESS     Process;
  PACCESS_TOKEN Token;
  DWORD         cSysExpunge;
  NTSTATUS      status;

  Flags = *(_DWORD *)((BYTE *)ppi + 8);                 // Flags = ppi->W32PF_Flags
  if ( Flags & 0x8000 )                                 // Flags & W32PF_PROCESSCONNECTED
  {
    status = 0x4000001B;                                // STATUS_ALREADY_WIN32
  }
  else
  {
    Process = *(PEPROCESS *)ppi;                        // Process = ppi->Process
    *(_DWORD *)((BYTE *)ppi + 8) = Flags | 0x8000;      // ppi->W32PF_Flags |= W32PF_PROCESSCONNECTED
    Token = (PACCESS_TOKEN)PsReferencePrimaryToken(Process);
    *(_DWORD *)((BYTE *)ppi + 0x194) = SeTokenIsWriteRestricted(Token) != 0 ? 1 : 5;
    PsDereferencePrimaryToken(Token);
    xxxSetProcessInitState(*(PEPROCESS *)ppi, 0x80);    // (ppi.Process, STARTF_FORCEOFFFEEDBACK)
    SetAppStarting(ppi);
    *(_DWORD *)((BYTE *)ppi + 0x94) = gppiList;         // ppi.ppiList = gppiList
    gppiList = ppi;
    if ( BYTE3(gdwPUDFlags) & 8 )
    {
      if ( CheckAllowForeground(*(PEPROCESS *)ppi) )    // CheckAllowForeground(ppi.Process)
        *(_BYTE *)((BYTE *)ppi + 9) |= 1u;
    }
    GetProcessLuid(0, (BYTE *)ppi + 0x160);             // GetProcessLuid(NULL, &ppi->luidSession)
    cSysExpunge = gcSysExpunge;
    *(_DWORD *)((BYTE *)ppi + 0x154) = 0;
    *(_DWORD *)((BYTE *)ppi + 0xA0) = cSysExpunge;      // ppi->cSysExpunge = cSysExpunge
    status = 0;
  }
  return status;
}

留意其中的 SeTokenIsWriteRestricted 函数调用。在调用该函数之前,xxxInitProcessInfo 通过调用 PsReferencePrimaryToken 引用并获取 ppi->Process 进程体中保存的 TOKEN 对象指针,并将该 TOKEN 对象指针作为参数传递给 SeTokenIsWriteRestricted 调用。该函数的代码十分简单:

int __stdcall SeTokenIsWriteRestricted(int a1)
{
  return (*(_DWORD *)(a1 + 0x88) >> 8) & 1;
}

根据相关定义,得知 a1 + 0x88 偏移取的是传入 TOKEN 结构体中的 TokenFlags 域。判断该域是否置位 0x00000100 标志位。该标志位可能的定义如下:

#define TOKEN_IS_WRITE_RESTRICTED 0x00000100

仅通过判断 ((PTOKEN)Token)->TokenFlags 域是否对 TOKEN_IS_WRITE_RESTRICTED 标志位置位,判断给定的 TOKEN 对象是否处于“写受限”状态,并返回判断的结果。

在 xxxInitProcessInfo 函数中调用的 SeTokenIsWriteRestricted 返回时,判断返回值:为 TRUE 时将 (BYTE *)ppi + 0x194 偏移位置的 DWORD 域赋值为 1,否则赋值为 5。

tagWND 中的域

窗口对象在内核中通过 win32k!xxxCreateWindowEx 函数创建。那么可以首先定位到该函数查找线索。遗憾的是该函数反编译代码非常复杂,无法完整贴到该文章中,只能针对部分片段进行分析。该函数的声明如下:

PWND
xxxCreateWindowEx (
    DWORD         dwExStyle,
    PLARGE_STRING ClassVersion,
    PLARGE_STRING ClassName,
    PLARGE_STRING WindowName,
    DWORD         style,
    int           x,
    int           y,
    int           cx,
    int           cy,
    PWND          pwndParent,
    PMENU         pMenu,
    HANDLE        hInstance,
    LPVOID        lpCreateParams,
    DWORD         dwExpWinVerAndFlags,
    LPDWORD       pActivationContextInformation
    );

幸运的是,在函数中明显地找到了关于 tagWND 各个域初始化的语句,源代码过于冗长,在这里列出关键的节选部分:

// pdesk = ptiCurrent->rpdesk
pdesk = (void *)*((BYTE *)gptiCurrent + 0x3C);
v126 = gptiCurrent;
Object = pdesk;
// if (pwndParent != NULL && pwndParent->head.rpdesk != pdesk)
if ( pwndParent && *((void **)pwndParent + 3) != pdesk )
{
  UserSetLastError(87);
  return 0;
}
...
if ( (ULongAdd(0xA4u, *(_DWORD *)(pcls + 0x3C), &ClassName) & 0x80000000) != 0 )
{
  UserSetLastError(87);
  return 0;
}
v39 = HMAllocObject(v126, Object, 1, ClassName); // #define TYPE_WINDOW 1
v40 = v39;
if ( !v39 )
  return 0;
*(_DWORD *)(v39 + 0x20) = a5 & 0xEFFFFFFF;
*(_DWORD *)(v39 + 0x1C) = a1 & 0xFDF7FFFF;
*(_DWORD *)(v39 + 0x64) = v38;
*(_DWORD *)(v39 + 0x8C) = *(_DWORD *)(v38 + 0x3C);
*(_DWORD *)(v39 + 0xA0) = *(_DWORD *)(*(_DWORD *)(v126 + 0x2C) + 0x194);
if ( !ReferenceClass(v38, v39) )
{
  HMFreeObject((PVOID)v40);
  UserSetLastError(0x57F);
  return 0;
}
...

在函数中通过将当前线程的 tagTHREADINFO 结构体指针、当前线程关联的桌面对象 tagDESKTOP 结构体指针、分配对象类型(TYPE_WINDOW)以及分配对象的大小(此时 ClassName 参数被复用为 Size 变量)作为参数传入 HMAllocObject 函数分配内核窗口对象。

分配成功后,根据各个域的偏移对分配的对象结构体的域进行赋值操作。上面的代码中存在对偏移为 0xA0 的域进行赋值的语句:

*(_DWORD *)(v39 + 0xA0) = *(_DWORD *)(*(_DWORD *)(v126 + 0x2C) + 0x194);

其中,v39 变量是当前分配的对象指针,v126 变量以 gptiCurrent 赋值,即当前锁定的线程信息结构体指针。该语句获取 gptiCurrent 偏移 0x2C 位置的 DWORD 域值,参照这篇文章前面部分可知,该偏移位置存储的是相关联的 tagPROCESSINFO 结构体指针。tagPROCESSINFO 结构体的 0x194 偏移存储的就是上一节分析得到的结论。

这就意味着,每一个 tagWND 窗口对象结构体不会对该域进行重新判定和赋值,而是简单地使用所属进程的 tagPROCESSINFO 进程信息结构体中对应的域的值直接赋值。因此,每个窗口对象(甚至其他类似的 USER 对象)中的这个域,均与自身创建时所属的进程对应的域相同。

这两个域存在的作用应该在于针对跨进程的窗口对象操作时的对象隔离,用于阻止低特权级的进程或线程对具有高特权级的进程所拥有的窗口对象进行访问。这是 Windows XP 引入的“UI 沙箱”特性,其中的 AllowAccessUISandbox 函数在 win32k 内部还进行了广泛的调用。然而,在更高版本的操作系统中应该换用了其他的机制实现,在这篇文章中就不进行探究了。

Type Address                              Text
---- -------                              ----
p    ValidateHwnd(x)+77                   call _AllowAccessUISandbox@8; AllowAccessUISandbox(x,x)
p    xxxLoadUserApiHook()-14              call _AllowAccessUISandbox@8; AllowAccessUISandbox(x,x)
p    UserGlobalAtomTableCallout()+34      call _AllowAccessUISandbox@8; AllowAccessUISandbox(x,x)
p    InternalBuildHwndList(x,x,x)+4D      call _AllowAccessUISandbox@8; AllowAccessUISandbox(x,x)
p    xxxCallHook2(x,x,x,x,x)+87           call _AllowAccessUISandbox@8; AllowAccessUISandbox(x,x)
p    xxxInterSendMsgEx(x,x,x,x,x,x,x)+54  call _AllowAccessUISandbox@8; AllowAccessUISandbox(x,x)
p    xxxInterSendMsgEx(x,x,x,x,x,x,x)+6A8 call _AllowAccessUISandbox@8; AllowAccessUISandbox(x,x)
p    NtUserPostThreadMessage(x,x,x,x)+60  call _AllowAccessUISandbox@8; AllowAccessUISandbox(x,x)
p    NtUserSendInput(x,x,x)+3A            call _AllowAccessUISandbox@8; AllowAccessUISandbox(x,x)
p    CheckClipboardAccess()+24            call _AllowAccessUISandbox@8; AllowAccessUISandbox(x,x)
p    zzzAttachThreadInput(x,x,x)+23       call _AllowAccessUISandbox@8; AllowAccessUISandbox(x,x)
p    _BlockInput(x)+3A                    call _AllowAccessUISandbox@8; AllowAccessUISandbox(x,x)
p    HidRequestValidityCheck(x)+20        call _AllowAccessUISandbox@8; AllowAccessUISandbox(x,x)

由于 Windows XP 系统盛行时期与 win32k 相关的公开资料和数据本就不多,更何况时至今日 Windows XP 早已日薄西山,资料更是少之又少了。笔者作为一个初学者,这次对 Windows XP 中的这个机制就探索到这里了。

没有回答

评论:

版权所有 © 2010-2016 小刀志 · 本站基于 WordPress 构建 · 原创内容转载请取得作者同意和授权