Windows在实现上几乎将所有的事物都抽象为对象来进行管理,常见的比如Process,Thread,Event,Window,Menu,Font,Bitmap等。同时将所有的对象分为三类:KernelObject,GDIObject,和 UserObject。具体参加下表:

Object Instance
Kernel Object Access token, Change notification, Communications device, Console input, Console screen buffer, Desktop, Event, Event log, File, File mapping, Find file, Heap, I/O completion port, Job, Mailslot, Memory resource notification, Module, Mutex, Pipe, Process, Semaphore, Socket, Thread, Timer, Update resource, Window station
User Object Acceleratortable, Caret, Cursor, DDEconversation, Hook, Icon, Menu, Window, Windowposition
GDI Object Bitmap, Brush, DC, Enhanced metafile, Enhanced-metafile DC, Font, Memory DC, Metafile, Metafile DC, Palette, Pen and extended pen, Region

User Object和GDI Object的管理是由图像化组件win32k来进行管理的,在理解User Object的句柄管理之前,需要先了解下与其相关的初始化动作:

初始化

为了方便理解,下文相关的代码内容被精简,删除了大部分不想关的逻辑。

Win32UserInitialize

Win32UserInitialize中包含句柄管理的初始化动作,其由Win32k入口例程DriverEntry调用;调用流程:DriverEntry -> Win32UserInitialize

  1. NTSTATUS Win32UserInitialize(VOID)
  2. {
  3. NTSTATUS Status;
  4. ...
  5. Status = InitCreateSharedSection();
  6. ...
  7. gpsi = (PSERVERINFO)SharedAlloc(sizeof(SERVERINFO));
  8. ...
  9. // Initialize the handle manager.
  10. HMInitHandleTable(gpvSharedBase);
  11. // Setup shared info block.
  12. gSharedInfo.psi = gpsi;
  13. gSharedInfo.pDispInfo = gpDispInfo;
  14. ...
  15. }

InitCreateSharedSection

该函数负责创建共享的Section结构,同时映射到Session地址空间,最终在此Section的地址范围中,将其一部分地址用来创建Heap;涉及到的全局变量有ghSectionShared,gpvSharedBase和gpvSharedAlloc 。

由此可以得到一个概念:句柄表和Heap(gpvSharedAlloc)在地址上是连续的。

调用流程:Win32UserInitialize -> InitCreateSharedSection


NTSTATUS InitCreateSharedSection(VOID)
{
    ULONG             ulHeapSize;
    ULONG             ulHandleTableSize;
    NTSTATUS          Status;
    LARGE_INTEGER     SectionSize;
    SIZE_T            ViewSize;
    PVOID             pHeapBase;
    ulHeapSize        = ROUND_UP_TO_PAGES(USRINIT_SHAREDSECT_SIZE * 1024);
    ulHandleTableSize = ROUND_UP_TO_PAGES(0x10000 * sizeof(HANDLEENTRY));
    SectionSize.LowPart  = ulHeapSize + ulHandleTableSize;
    SectionSize.HighPart = 0;
    Status = Win32CreateSection(&ghSectionShared,
                                SECTION_ALL_ACCESS,
                                (POBJECT_ATTRIBUTES)NULL,
                                &SectionSize,
                                PAGE_EXECUTE_READWRITE,
                                SEC_RESERVE,
                                (HANDLE)NULL,
                                NULL,
                                TAG_SECTION_SHARED);
    ViewSize = 0;
    gpvSharedBase = NULL;
    Status = Win32MapViewInSessionSpace(ghSectionShared, &gpvSharedBase, &ViewSize);
    pHeapBase = ((PBYTE)gpvSharedBase + ulHandleTableSize);
    // Create shared heap.
    gpvSharedAlloc = UserCreateHeap(
            ghSectionShared,
            ulHandleTableSize,
            pHeapBase,
            ulHeapSize,
            UserCommitSharedMemory);
    UserAssert(Win32HeapGetHandle(gpvSharedAlloc) == pHeapBase);
    return STATUS_SUCCESS;
}

SharedAlloc

从Heap(gpvSharedAlloc)中申请空间,如:gpsi。

__inline PVOID
SharedAlloc(ULONG cb)
{
    return Win32HeapAlloc(gpvSharedAlloc, cb, 0, 0);
}

重点全局变量介绍

gpsi

描述全局的server info,就User Object对象管理,涉及如下两个成员:

typedef struct tagSERVERINFO {
    ...
    +0x8 cHandleEntries : QWORD           // Handle Entry的个数
    ...
    +0x360 cbHandleEntries:DWORD         // 所有Handle Entry总共字节大小
    ...
} SERVERINFO;
PSERVERINFO gpsi = NULL;

cHandleEntries 保存当前所有的Handle Entry个数;cbHandleEntries表示所有的Handle Entry的内存字节数。

gahti

全局对象类型信息数组,用来描述所有User Object的相关信息,比如对象大小,类型index等,结构及部分注释如下:

struct tagHANDLETYPEINFO
{
  PVOID fnDestroy;
  ULONG types;
  ULONG dwCreateFlag;
  ULONG dwAllocSize;
  ULONG dwAllocTag;
};

gSharedInfo

tagSharedInfo变量,非指针,其保存gpsi指针和句柄表指针

struct tagSHAREDINFO
{
  PVOID psi;              // 指向ServerInfo的指针 == gpsi
  PVOID aheList;          // 句柄表指针
  ULONG aheSize;          // 句柄表Entry大小 
  ULONG unknown0;
  PVOID pDispInfo;
};
SHAREDINFO gSharedInfo;

句柄表指针指向的是一个Handle Entry数组,每个Entry大小为0x20,结构如下:

struct __declspec(align(8)) _HANDLEENTRY
{
  PVOID phead;
  ULONG_PTR nOwnerID;
  PVOID pDesktop;
  BYTE bType;
  BYTE bFlags;
  WORD wUniq;
};

gpKernelHandleTable

内核独享的句柄表的指针,保存实际对象的地址,结构如下:

struct _KERNEL_HANDLENTRY
{
  PVOID pObject;           // User Object对象地址,初始化时其值代表下一个空闲的index值;
  PVOID pOwner; 
  PVOID pUnknown;
};

gHandlePages

HANDLEPAGE全局变量,用以描述句柄表所在页面是否有空闲index,从Pool中申请

struct _HANDLEPAGE
{
  ULONG_PTR iheLimit;
  ULONG_PTR iheFreeEven;
  ULONG_PTR iheFreeOdd;
};
HANDLEPAGE gHandlePages;

其中iheLimit用来描述当前HandlePage所描述的所有HandleEntries的个数,iheFreeEven描述空闲Handle的偶数Index,iheFreeOdd用来描述空闲Handle奇数index。

User Object的句柄管理

在理解了上述全局变量后,再来看句柄管理就容易理解很多了,先看初始化:

句柄表初始化:HMInitHandleTable()

__int64 HMInitHandleTable()
{
 PHANDLEENTRYpSharedBase; // rdi
 unsigned intstatus; // ebx
 PKERNEL_HANDLENTRYpKernelHandleTable; // rsi
 char v4; // [rsp+20h] [rbp-18h]
 pSharedBase=(PHANDLEENTRY)gpvSharedBase;
 CLockDomainSharedAllowAllRecursion<DLT_HANDLEMANAGER>::CLockDomainSharedAllowAllRecursion<DLT_HANDLEMANAGER>(&v4);
 status= 0;
 pKernelHandleTable= (PKERNEL_HANDLENTRY)gpKernelHandleTable;
 gHandlePages.iheLimit = 0i64;
 gHandlePages.iheFreeOdd = 0i64;
 gHandlePages.iheFreeEven = 0i64;
 gSharedInfo.aheList =pSharedBase;
 gSharedInfo.aheSize =0x20;
 *((_QWORD *)gpsi + 1) = 0i64;                 // cHandleEntries
 *((_DWORD *)gpsi + 0xD8) = 0;               // cbHandleEntrie
  if ( (unsigned int)HMGrowHandleTable() )
 {
 pKernelHandleTable->pObject= 0i64;
 pSharedBase->bType= 0;
 status= 1;
pSharedBase->wUniq= 1;
 gHandlePages.iheFreeEven = 2i64;
 }
 else
 {
 gSharedInfo.aheList = 0i64;
 }
 returnstatus;
}

从上面代码初始化代码可以看到,gSharedInfo.aheList就是gpvSharedBase,与InitCreateSharedSection描述的一致。其次由于当前还没有一个有效的句柄表页面,因此初始化gHandlePages和gpsi描述的成员为0,同时调用HMGrowHandleTable()开始申请第一个页面。

句柄表页面管理:HMGrowHandleTable()

__int64 HMGrowHandleTable(void)
{
  char *pCurTableEnd; // rax
  signed __int64 iheLimit; // rbx
  signed __int64 iheLimit_New; // rdx
  ULONG_PTR *v3; // r8
  _WORD *v4; // rcx
  __int64 result; // rax
  __int64 nGrowSize; // [rsp+30h] [rbp+8h]
  if ( *((_QWORD *)gpsi + 1) == 0xFFFEi64 )     // gpis + 1是cHandleEntries
    return 0i64;
  pCurTableEnd = (char *)gSharedInfo.aheList + *((unsigned int *)gpsi + 0xD8);
  if ( (unsigned __int64)pCurTableEnd >= gpvSharedAlloc )// can't above heap base
    return 0i64;
  nGrowSize = 0x1000i64;
  if ( (signed int)CommitReadOnlyMemory(
                     ghSectionShared,
                     &nGrowSize,
                     (unsigned int)((_DWORD)pCurTableEnd - gpvSharedBase),// offset
                     0i64) < 0
    || (signed int)MmCommitSessionMappedView(0x18i64 * *((_QWORD *)gpsi + 1) + gpKernelHandleTable, nGrowSize) < 0 )
  {
    return 0i64;
  }
  *((_DWORD *)gpsi + 0xD8) += 0x1000;           // 0xD8*4 偏移是cbHandleEntires
  *((_QWORD *)gpsi + 1) = (unsigned __int64)*((unsigned int *)gpsi + 0xD8) >> 5;// Handle Entry大小是0x20, Kernel Handle Entry是0x18
  if ( *((_QWORD *)gpsi + 1) > 0xFFFEui64 )
    *((_QWORD *)gpsi + 1) = 0xFFFEi64;
  iheLimit = gHandlePages.iheLimit;
  memset(
    (char *)gSharedInfo.aheList + 0x20 * gHandlePages.iheLimit,
    0,
    0x20 * (*((_QWORD *)gpsi + 1) - gHandlePages.iheLimit));// 初始化新增加的句柄Entries
  memset((void *)(gpKernelHandleTable + 24 * iheLimit), 0, 24 * (*((_QWORD *)gpsi + 1) - iheLimit));// 初始化新增加的内核句柄Entries
  iheLimit_New = *((_QWORD *)gpsi + 1) - 1i64;
  v3 = (ULONG_PTR *)(gpKernelHandleTable + 24 * iheLimit_New);
  if ( iheLimit_New >= iheLimit )
  {
    v4 = (char *)gSharedInfo.aheList + 0x20 * (*((_QWORD *)gpsi + 1) - 1i64) + 26;
    do
    {
      *v4 = 1;                                  // uniq 固定为1
      if ( iheLimit_New & 1 )
      {
        *v3 = gHandlePages.iheFreeOdd;
        gHandlePages.iheFreeOdd = iheLimit_New;
      }
      else
      {
        *v3 = gHandlePages.iheFreeEven;

之前已经知道句柄表使用的地址范围为ghSectionShared代表的空间,因此该函数内部通过提交内存的方式动态增长handle entry和kernel handle entry页面,同时负责初始化entry中的值。需要注意的是handle entry中每一个新增的uniq固定为1,kernel handle entry中的pObject默认保存的是下一个空闲的index值。

句柄表的申请:HMAllocObject(…)

PVOID HMAllocObject(
    PTHREADINFO ptiOwner,
    PDESKTOP pdeskSrc,
    BYTE bType,
    DWORD size)

在上面逻辑很清楚后,HMAllocObject所做动作的理解就比较简单了。
首先通过gHandlePages来获取空闲句柄的index值,若空间不够,则调用HMGrowHandleTable动态增长句柄表页面:

  while ( 1 )
  {
    if ( (_BYTE)nObjectType != 1 && gHandlePages.iheFreeOdd )
    {
      pHandleIndex = &gHandlePages.iheFreeOdd;
      goto LABEL_6;
    }
    if ( gHandlePages.iheFreeEven )
      break;
    if ( !(unsigned int)HMGrowHandleTable() )
      goto LABEL_74;
  }
  pHandleIndex = &gHandlePages.iheFreeEven;

其次,利用参数bType从全局变量gahi获取指定对象的信息,包括CreateFlag,AllocSize,AllocTag等;其中CreateFlag用以标记该对象从何种内存区域分配空间,常见有如下三种类型:

#define OCF_DESKTOPHEAP         0x10         Desktop Heap区域
#define OCF_USEPOOLIFNODESKTOP  0x20         直接从pool中分配,若标记了0x200,则使用type isolation              
#define OCF_SHAREDHEAP          0x40         gpvSharedAlloc区域

根据不同的类型在不同的区域申请对象,同时填充一些必要的信息;
之后,利用获取到的HandleIndex值,定位到对应的Handle Entry和Kernel Handle Entry,并初始化其中成员值:

  nHandleIndex = *pHandleIndex;
  pHandleEntry = (PHANDLEENTRY)((char *)gSharedInfo.aheList + 0x20 * *pHandleIndex);// gSharedInfo+0x8
  v16 = (unsigned int)nHandleIndex < giheLast;
  v17 = (_DWORD)nHandleIndex == giheLast;
  pKernelHandleEntry = (PKERNEL_HANDLENTRY)(gpKernelHandleTable + 24 * nHandleIndex);
  *piheFree2 = (ULONG_PTR)pKernelHandleEntry->pObject;

而最终句柄值的计算公式:

 nHandleValue= (signed int)nHandleIndex | (unsigned __int64)(*(unsigned __int16 *)((char *)gSharedInfo.aheList + nHandleIndex * gSharedInfo.aheSize + 0x1A) << 16);
 *pObjectHead = nHandleValue;

句柄值最终保存在开辟的对象的前8个字节中。

总结

  1. User Object所涉及到的多个全局变量的解惑。
  2. User Object的句柄的初始化及申请过程。
  3. User Object的申请位置,及共享区域保存的数据值信息。

待进一步思考

由于DesktopAlloc和gpvSharedAlloc都是用了内核下的HEAP,而且用户态通过HMValidateHandle返回的对象地址(用户态地址)所使用的的物理页面和HEAP是一致的,因此可以从用户态读取对象信息,从而获得了句柄值和偏移值。

因此可以衍生如下问题:

  1. win32k中是否有大量的共享页面,这些共享页面是否有内核地址信息泄露?
  2. 内核下的Heap和用户态下的Heap有什么区别?它的实现机制是如何;
  3. NtUserCall 使用了函数表win32kfull!apfnSimpleCall,因此可以Fuzz下函数。

HMAllocObject逆向后的完整伪代码

非必须,作为在上文理解过程中辅助。

PVOID __fastcall HMAllocObject(_QWORD *ThreadOwner, _QWORD *DeskSrc, unsigned __int8 ObjectType, unsigned int ObjectSize)
{
  __int64 v4; // rsi
  size_t nObjectSize; // rdi
  _QWORD *v6; // rbp
  __int64 nObjectType; // r14
  _QWORD *pDeskSrc2; // r12
  __int16 nCreateFlag; // bx
  ULONG_PTR *pHandleIndex; // r15
  size_t v11; // rbp
  ULONG v12; // edi
  _QWORD *pObjectHead; // rdi
  ULONG_PTR nHandleIndex; // r12
  PHANDLEENTRY pHandleEntry; // r15
  bool v16; // cf
  bool v17; // zf
  PKERNEL_HANDLENTRY pKernelHandleEntry; // r14
  unsigned __int8 v19; // r8
  __int64 v20; // rax
  unsigned __int64 nHandleValue; // rdx
  unsigned int v22; // eax
  PVOID result; // rax
  __int64 v24; // rax
  unsigned __int64 *v25; // rcx
  __int64 (__fastcall *pObjectBody)(_QWORD, _QWORD, _QWORD); // rax
  void *v27; // r14
  __int64 v28; // rcx
  unsigned int v29; // eax
  __int64 v30; // rdx
  __int64 pObjectBody2; // rax
  __int64 v32; // rax
  signed __int64 v33; // rcx
  _QWORD *v34; // rax
  char v35; // al
  char v36; // cl
  int v37; // er8
  BOOL fUsePoolIfNoDesktop; // [rsp+44h] [rbp-54h]
  __int64 nTypeOff; // [rsp+48h] [rbp-50h]
  ULONG_PTR *piheFree2; // [rsp+50h] [rbp-48h]
  _QWORD *ptiOwner; // [rsp+A0h] [rbp+8h]
  _QWORD *pDeskSrc; // [rsp+A8h] [rbp+10h]
  unsigned __int8 bType; // [rsp+B0h] [rbp+18h]
  bType = ObjectType;
  pDeskSrc = DeskSrc;
  ptiOwner = ThreadOwner;
  v4 = 0i64;
  nObjectSize = ObjectSize;
  v6 = ThreadOwner;
  nObjectType = ObjectType;
  pDeskSrc2 = DeskSrc;
  fUsePoolIfNoDesktop = 0;
  GetDomainLockRef(14i64);
  nTypeOff = nObjectType;
  nCreateFlag = gahti[nObjectType].dwCreateFlag;// 注意这是word* ++,因此每个Entry大小是24字节,偏移为12
  if ( nCreateFlag & 3 )                        // bCreateFlags & (OCF_PROCESSOWNED | OCF_THREADOWNED)
  {
    v4 = v6[0x34];
    if ( *(_DWORD *)(v4 + 0x44) >= gUserProcessHandleQuota )
    {
LABEL_74:
      v33 = 0x486i64;
LABEL_77:
      UserSetLastError(v33);
      return 0i64;
    }
  }
  while ( 1 )
  {
    if ( (_BYTE)nObjectType != 1 && gHandlePages.iheFreeOdd )
    {
      pHandleIndex = &gHandlePages.iheFreeOdd;
      goto LABEL_6;
    }
    if ( gHandlePages.iheFreeEven )
      break;
    if ( !(unsigned int)HMGrowHandleTable() )
      goto LABEL_74;
  }
  pHandleIndex = &gHandlePages.iheFreeEven;
LABEL_6:
  piheFree2 = pHandleIndex;
  if ( nCreateFlag & 0x10 && pDeskSrc2 )        // OCF_DESKTOPHEAP
  {
    if ( !qword_1C025D6A8 )
      goto LABEL_76;
    if ( (signed int)qword_1C025D6A8() < 0 )
      goto LABEL_76;
    pObjectHead = HMAllocateUserOrIsolatedType(nObjectSize, nCreateFlag, nObjectType);
    if ( !pObjectHead )
      goto LABEL_76;
    pObjectBody = DesktopAlloc;
    if ( DesktopAlloc )
      pObjectBody = (__int64 (__fastcall *)(_QWORD, _QWORD, _QWORD))DesktopAlloc(
                                                                      pDeskSrc2,
                                                                      gahti[nObjectType].dwAllocSize,
                                                                      ((_DWORD)nObjectType << 16) | 5u);
    pObjectHead[5] = pObjectBody;
    if ( !pObjectBody )
    {
      HMFreeUserOrIsolatedType(nCreateFlag, nObjectType, pObjectHead);
      goto LABEL_76;
    }
    v27 = (void *)pObjectHead[3];
    ObfReferenceObject(pDeskSrc2);
    pObjectHead[3] = pDeskSrc2;
    if ( v27 )
      ObfDereferenceObject(v27);                // may be bug
    v28 = pObjectHead[5];
    pObjectHead[4] = pObjectHead;
    pObjectHead[6] = v28 - pDeskSrc2[0x10];
  }
  else if ( nCreateFlag & 0x40 )                // OCF_SHAREDHEAP
  {
    if ( gahti[nTypeOff].dwAllocSize )
    {
      v30 = gahti[nTypeOff].types;
      pObjectHead = Win32AllocPoolZInit(nObjectSize);
      if ( !pObjectHead )
        goto LABEL_76;
      pObjectBody2 = RtlAllocateHeap(gpvSharedAlloc, 0i64, gahti[nTypeOff].dwAllocSize);
      pObjectHead[5] = pObjectBody2;
      if ( !pObjectBody2 )
      {
        Win32FreePool(pObjectHead);
        goto LABEL_76;
      }
      pObjectHead[3] = 0i64;
      pObjectHead[4] = 0i64;
      pObjectHead[6] = pObjectBody2 - gpvSharedAlloc;
    }
    else
    {
      v34 = (_QWORD *)RtlAllocateHeap(gpvSharedAlloc, 0i64, nObjectSize);
      pObjectHead = v34;
      if ( !v34 )
        goto LABEL_76;
      v34[3] = 0i64;
      v34[4] = 0i64;
      v34[6] = (char *)v34 - gpvSharedAlloc;
      v34[5] = 0i64;
    }
  }
  else
  {
    fUsePoolIfNoDesktop = !pDeskSrc2 && nCreateFlag & 0x20;
    v11 = nObjectSize;
    if ( nCreateFlag & 0x200 )
    {
      pObjectHead = HMAllocateIsolatedType();
    }
    else
    {
      v12 = gahti[nTypeOff].types;
      if ( qword_1C025DBD0 && (signed int)qword_1C025DBD0() >= 0 )
      {
        if ( Win32AllocPoolImpl )
          pObjectHead = (_QWORD *)Win32AllocPoolImpl(33i64, v11, v12);
        else
          pObjectHead = 0i64;
        if ( !pObjectHead )
          goto LABEL_76;
        memset(pObjectHead, 0, v11);
      }
      else
      {
        pObjectHead = 0i64;
      }
    }
    if ( !pObjectHead )
    {
LABEL_76:
      v33 = 8i64;
      goto LABEL_77;
    }
    if ( (_BYTE)nObjectType == 1 )              // TYPE_WINDOW
    {
      v32 = Win32AllocPoolWithQuotaZInit(0x140ui64);
      pObjectHead[5] = v32;
      if ( !v32 )
      {
        HMFreeUserOrIsolatedType(nCreateFlag, 1u, pObjectHead);
        pObjectHead = 0i64;
      }
    }
    if ( nCreateFlag & 0x100 )
    {
      LockObjectAssignment(pObjectHead + 3, pDeskSrc2);
      pObjectHead[4] = pObjectHead;
    }
    v6 = ptiOwner;
  }
  if ( !pObjectHead )
    goto LABEL_76;
  nHandleIndex = *pHandleIndex;
  pHandleEntry = (PHANDLEENTRY)((char *)gSharedInfo.aheList + 0x20 * *pHandleIndex);// gSharedInfo+0x8
  v16 = (unsigned int)nHandleIndex < giheLast;
  v17 = (_DWORD)nHandleIndex == giheLast;
  pKernelHandleEntry = (PKERNEL_HANDLENTRY)(gpKernelHandleTable + 24 * nHandleIndex);
  *piheFree2 = (ULONG_PTR)pKernelHandleEntry->pObject;
  if ( !v16 && !v17 )
    giheLast = nHandleIndex;
  v19 = bType;
  pHandleEntry->bType = bType;
  pKernelHandleEntry->pObject = pObjectHead;
  if ( nCreateFlag & 0x40 )
  {
    pHandleEntry->phead = (PVOID)pObjectHead[6];
  }
  else if ( nCreateFlag & 0x10 && pDeskSrc )
  {
    pHandleEntry->phead = (PVOID)pObjectHead[6];
    pHandleEntry->pDesktop = **(PVOID **)pDeskSrc[1];
  }
  else
  {
    pHandleEntry->phead = 0i64;
  }
  if ( fUsePoolIfNoDesktop )
    pHandleEntry->bFlags |= 0x40u;
  if ( nCreateFlag & 2 )
  {
    *((_DWORD *)pObjectHead + 4) = 0;
    pKernelHandleEntry->pOwner = (PVOID)v6[0x34];
    v20 = PsGetProcessId(*(_QWORD *)v6[0x34]);
    v19 = bType;
    pHandleEntry->nOwnerID = v20;
    if ( nCreateFlag & 4 )
      pObjectHead[3] = v6[0x34];
  }
  else if ( nCreateFlag & 1 )
  {
    pKernelHandleEntry->pOwner = v6;
    v24 = PsGetThreadId(*v6, 0i64);
    v19 = bType;
    pHandleEntry->nOwnerID = v24;
    pObjectHead[2] = pKernelHandleEntry->pOwner;
  }
  nHandleValue = (signed int)nHandleIndex | (unsigned __int64)(*(unsigned __int16 *)((char *)gSharedInfo.aheList
                                                                                   + nHandleIndex * gSharedInfo.aheSize
                                                                                   + 0x1A) << 16);
  *pObjectHead = nHandleValue;
  if ( gahti[nTypeOff].dwAllocSize )
  {
    v25 = (unsigned __int64 *)pObjectHead[5];
    *v25 = nHandleValue;
    v25[1] = pObjectHead[6];
  }
  if ( v4 )
  {
    v22 = ++*(_DWORD *)(v4 + 0x44);
    if ( v22 > *(_DWORD *)(v4 + 0x48) )
      *(_DWORD *)(v4 + 0x48) = v22;
  }
  if ( ++giheCount > (unsigned int)giheCountPeak )
    giheCountPeak = giheCount;
  if ( nCreateFlag & 3 )
  {
    PsGetProcessId(*(_QWORD *)v6[52]);
    if ( Microsoft_Windows_Win32kEnableBits & 0x20000000000i64 )
    {
      v35 = GetEtwUserHandleType(bType);
      McTemplateK0pqqq_EtwWriteTransfer(v36, (unsigned __int64)&UserCreateHandle, v37, *pObjectHead, v35);
    }
  }
  else
  {
    v29 = GetEtwUserHandleType(v19);
    EtwTraceUserCreateHandle(*pObjectHead, v29, 0i64);
  }
  result = pKernelHandleEntry->pObject;
  pKernelHandleEntry->pUnknown = 0i64;
  return result;
}