句柄表篇——总结与提升

写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。

  看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。


🔒 华丽的分割线 🔒


小节

  本篇的知识挺简单的,就介绍了两个表:进程句柄表和全局句柄表。进程句柄表归属进程私有,每一个进程一个句柄表。上一篇的课后思考题答案先不急的给你,下面我们来扩展一下句柄表的相关知识,你在来看看你写的代码。

句柄表结构体扩展

  如下是WRK里面的句柄表结构,重点是看注释,介绍扩展一下我讲解基础没讲到知识点:

typedef struct _HANDLE_TABLE {

    //
    //  A pointer to the top level handle table tree node.
    //

    ULONG_PTR TableCode;

    //
    //  The process who is being charged quota for this handle table and a
    //  unique process id to use in our callbacks
    //

    struct _EPROCESS *QuotaProcess;
    HANDLE UniqueProcessId;


    //
    // These locks are used for table expansion and preventing the A-B-A problem
    // on handle allocate.
    //

#define HANDLE_TABLE_LOCKS 4

    EX_PUSH_LOCK HandleTableLock[HANDLE_TABLE_LOCKS];

    //
    //  The list of global handle tables.  This field is protected by a global
    //  lock.
    //

    LIST_ENTRY HandleTableList;

    //
    // Define a field to block on if a handle is found locked.
    //
    EX_PUSH_LOCK HandleContentionEvent;

    //
    // Debug info. Only allocated if we are debugging handles
    //
    PHANDLE_TRACE_DEBUG_INFO DebugInfo;

    //
    //  The number of pages for additional info.
    //  This counter is used to improve the performance
    //  in ExGetHandleInfo
    //
    LONG ExtraInfoPages;

    //
    //  This is a singly linked list of free table entries.  We don't actually
    //  use pointers, but have each store the index of the next free entry
    //  in the list.  The list is managed as a lifo list.  We also keep track
    //  of the next index that we have to allocate pool to hold.
    //

    ULONG FirstFree;

    //
    // We free handles to this list when handle debugging is on or if we see
    // that a thread has this handles bucket lock held. The allows us to delay reuse
    // of handles to get a better chance of catching offenders
    //

    ULONG LastFree;

    //
    // This is the next handle index needing a pool allocation. Its also used as a bound
    // for good handles.
    //

    ULONG NextHandleNeedingPool;

    //
    //  The number of handle table entries in use.
    //

    LONG HandleCount;

    //
    // Define a flags field
    //
    union {
        ULONG Flags;

        //
        // For optimization we reuse handle values quickly. This can be a problem for
        // some usages of handles and makes debugging a little harder. If this
        // bit is set then we always use FIFO handle allocation.
        //
        BOOLEAN StrictFIFO : 1;
    };

} HANDLE_TABLE, *PHANDLE_TABLE;

  然后我再把WinDbg获取的结构体拿出来:

kd> dt _HANDLE_TABLE
ntdll!_HANDLE_TABLE
   +0x000 TableCode        : Uint4B
   +0x004 QuotaProcess     : Ptr32 _EPROCESS
   +0x008 UniqueProcessId  : Ptr32 Void
   +0x00c HandleTableLock  : [4] _EX_PUSH_LOCK
   +0x01c HandleTableList  : _LIST_ENTRY
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK
   +0x028 DebugInfo        : Ptr32 _HANDLE_TRACE_DEBUG_INFO
   +0x02c ExtraInfoPages   : Int4B
   +0x030 FirstFree        : Uint4B
   +0x034 LastFree         : Uint4B
   +0x038 NextHandleNeedingPool : Uint4B
   +0x03c HandleCount      : Int4B
   +0x040 Flags            : Uint4B
   +0x040 StrictFIFO       : Pos 0, 1 Bit

  可以说是一模一样的,编制操作系统时为了保持兼容性,结构体的每一个成员的含义是一样的。
  有没有注意到HandleCount这个成员,这个是我们做思考题的一个坑,就是我知道遍历有效句柄的次数,就得需要这个值,其实也可以不用,因为句柄是按物理页进行的。当然,如何获取一个进程的句柄数可以通过ObGetProcessHandleCount这个函数。这个函数是未导出的,如下是我整理好的伪代码:

int __stdcall ObGetProcessHandleCount(PEPROCESS Process)
{
  _HANDLE_TABLE *objtable; // eax
  int handleCount; // esi

  objtable = ObReferenceProcessHandleTable(Process);
  if ( !objtable )
    return 0;
  handleCount = objtable->HandleCount;
  ObDereferenceProcessHandleTable(Process);
  return handleCount;
}

_HANDLE_TABLE *__stdcall ObReferenceProcessHandleTable(_EPROCESS *Process)
{
  _HANDLE_TABLE *objtable; // edi

  objtable = 0;
  if ( ExAcquireRundownProtection(&Process->RundownProtect) )
  {
    objtable = Process->ObjectTable;
    if ( !objtable )
      ExReleaseRundownProtection(&Process->RundownProtect);
  }
  return objtable;
}

void __stdcall ObDereferenceProcessHandleTable(_EPROCESS *eprocess)
{
  ExReleaseRundownProtection(&eprocess->RundownProtect);
}

ObReferenceObjectByHandle 浅析

  我们来看看操作系统是如何使用句柄的,定位到我们熟悉的函数ObReferenceObjectByHandle

NTSTATUS __stdcall ObReferenceObjectByHandle(HANDLE Handle, ACCESS_MASK DesiredAccess, POBJECT_TYPE ObjectType, KPROCESSOR_MODE AccessMode, PVOID *Object, POBJECT_HANDLE_INFORMATION HandleInformation)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  currentThread = KeGetCurrentThread();
  attachedProcess = currentThread;
  *Object = 0;
  if ( Handle >= 0 )
  {
    objHandleTable = currentThread->Tcb.ApcState.Process->ObjectTable;
  }
  else
  {
    if ( Handle == -1 )
    {
      if ( ObjectType == PsProcessType || !ObjectType )
      {
        attachedProcess = currentThread->Tcb.ApcState.Process;
        if ( (~attachedProcess->GrantedAccess & DesiredAccess) == 0 || !AccessMode )
        {
          if ( HandleInformation )
          {
            HandleInformation->GrantedAccess = attachedProcess->GrantedAccess;
            HandleInformation->HandleAttributes = 0;
          }
          if ( ObpTraceEnabled )
            ObpPushStackInfo(&attachedProcess[-1u].584, 1);
          ++attachedProcess[-1u].Flags;
LABEL_12:
          *Object = attachedProcess;
          return 0;
        }
        return STATUS_ACCESS_DENIED;
      }
      return STATUS_OBJECT_TYPE_MISMATCH;
    }
    if ( Handle == -2 )
    {
      if ( ObjectType == PsThreadType || !ObjectType )
      {
        if ( (~currentThread->GrantedAccess & DesiredAccess) == 0 || !AccessMode )
        {
          if ( HandleInformation )
          {
            HandleInformation->GrantedAccess = currentThread->GrantedAccess;
            HandleInformation->HandleAttributes = 0;
          }
          if ( ObpTraceEnabled )
            ObpPushStackInfo(&currentThread[-1].ReadClusterSize, 1);
          ++attachedProcess[-1].Flags;
          goto LABEL_12;
        }
        return STATUS_ACCESS_DENIED;
      }
      return STATUS_OBJECT_TYPE_MISMATCH;
    }
    if ( AccessMode )
      return STATUS_INVALID_HANDLE;
    Handle = (Handle ^ 0x80000000);
    objHandleTable = ::ObpKernelHandleTable;
  }
  --attachedProcess->WorkingSetLock.Contention;
  ObpKernelHandleTable = objHandleTable;
  handleEntry = ExMapHandleToPointerEx(objHandleTable, Handle, AccessMode);
  if ( handleEntry )
  {
    v12 = (handleEntry->InfoTable & 0xFFFFFFF8);
    BackTraceHash = v12;
    if ( v12[1].Object == ObjectType || !ObjectType )
    {
      if ( (NtGlobalFlag & 0x2000) != 0 )
      {
        if ( AccessMode || HandleInformation )
        {
          v14 = ObpTranslateGrantedAccessIndex(handleEntry->GrantedAccessIndex);
          v12 = BackTraceHash;
          v15 = v14;
        }
        else
        {
          v15 = Handle;
        }
      }
      else
      {
        v15 = ~ObpAccessProtectCloseBit & handleEntry->GrantedAccess;
      }
      if ( (~v15 & DesiredAccess) == 0 || !AccessMode )
      {
        if ( *(ObpKernelHandleTable + 44) )
        {
          v16 = ExpGetHandleInfo(ObpKernelHandleTable, Handle, 1);
          v12 = BackTraceHash;
          ObjectTypea = v16;
        }
        else
        {
          ObjectTypea = 0;
        }
        if ( HandleInformation )
        {
          HandleInformation->GrantedAccess = v15;
          if ( (ObpAccessProtectCloseBit & handleEntry->GrantedAccess) != 0 )
            v17 = handleEntry->ObAttributes & 6 | 1;
          else
            v17 = handleEntry->ObAttributes & 6;
          HandleInformation->HandleAttributes = v17;
        }
        if ( (handleEntry->Object & 4) != 0
          && ObjectTypea
          && ObjectTypea->Mutex.SystemResourcesList.Flink
          && DesiredAccess
          && AccessMode )
        {
          ObpAuditObjectAccess(Handle, ObjectTypea, v12[1].ObAttributes + 64, DesiredAccess);
          v12 = BackTraceHash;
        }
        if ( ObpTraceEnabled )
          ObpPushStackInfo(v12, 1);
        ++BackTraceHash->ObAttributes;
        ExUnlockHandleTableEntry(ObpKernelHandleTable, handleEntry);
        v19 = attachedProcess->WorkingSetLock.Contention++ == 4294967295;
        if ( v19 && attachedProcess->Pcb.ActiveProcessors != &attachedProcess->Pcb.ActiveProcessors )
        {
          LOBYTE(v18) = 1;
          BYTE1(attachedProcess->Pcb.SwapListEntry.Next) = 1;
          HalRequestSoftwareInterrupt(v18);
        }
        *Object = &BackTraceHash[3];
        return 0;
      }
      v13 = STATUS_ACCESS_DENIED;
    }
    else
    {
      v13 = STATUS_OBJECT_TYPE_MISMATCH;
    }
    ExUnlockHandleTableEntry(ObpKernelHandleTable, handleEntry);
  }
  else
  {
    v13 = STATUS_INVALID_HANDLE;
  }
  v19 = attachedProcess->WorkingSetLock.Contention++ == 4294967295;
  if ( v19 && attachedProcess->Pcb.ActiveProcessors != &attachedProcess->Pcb.ActiveProcessors )
  {
    LOBYTE(v10) = 1;
    BYTE1(attachedProcess->Pcb.SwapListEntry.Next) = 1;
    HalRequestSoftwareInterrupt(v10);
  }
  return v13;
}

  当然上面的翻译的伪代码也是不准确的,有些函数我不熟悉,逆向不太明白,如下是我逆向的结果,由于汇编太长了,请点击折叠观看:

🔒 点击查看代码 🔒
; NTSTATUS __stdcall ObReferenceObjectByHandle(HANDLE Handle, ACCESS_MASK DesiredAccess, POBJECT_TYPE ObjectType, KPROCESSOR_MODE AccessMode, PVOID *Object, POBJECT_HANDLE_INFORMATION HandleInformation)
                public _ObReferenceObjectByHandle@24
_ObReferenceObjectByHandle@24 proc near ; CODE XREF: IopUnloadDriver(x,x)+1B8↑p
                                        ; MmCreateSection(x,x,x,x,x,x,x,x)+221↑p ...

ObpKernelHandleTable= dword ptr -8
OBJHeader       = dword ptr -4
Handle          = dword ptr  8
DesiredAccess   = dword ptr  0Ch
ObjectType      = dword ptr  10h
AccessMode      = byte ptr  14h
Object          = dword ptr  18h
HandleInformation= dword ptr  1Ch

                mov     edi, edi
                push    ebp
                mov     ebp, esp
                push    ecx
                push    ecx
                push    ebx
                push    esi
                push    edi
                mov     eax, large fs:_KPCR.PrcbData.CurrentThread
                mov     edi, [ebp+Object]
                xor     ebx, ebx        ; EBX = 0
                cmp     [ebp+Handle], ebx ; CMP Handle , 0
                mov     esi, eax        ; ESI = CurrentThread
                mov     [edi], ebx
                jge     NotFakeHandle   ; >=0 JMP
                cmp     [ebp+Handle], -1
                jnz     short CheckIsThreadFake ; != -1 JMP
                mov     eax, [ebp+ObjectType]
                cmp     eax, _PsProcessType ; 判断是否为 Process 类型
                jz      short CurrentProcessHandle ; 是的话跳走
                cmp     eax, ebx
                jnz     short loc_4D9B4F

CurrentProcessHandle:                   ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+31↑j
                mov     esi, [esi+_KTHREAD.ApcState.Process]
                mov     ecx, [esi+_EPROCESS.GrantedAccess]
                mov     eax, ecx        ; 至此 esi = AttachedProcess , eax = ecx = GrantedAccess
                not     eax             ; 按位取反 Access
                test    [ebp+DesiredAccess], eax ; 如果是0的话,说明一致
                jz      short DesiredIsGranted ; 一致的话跳走
                cmp     [ebp+AccessMode], KernelMode
                jnz     short IsUserMode

DesiredIsGranted:                       ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+47↑j
                mov     eax, [ebp+HandleInformation]
                cmp     eax, ebx        ; CMP HandleInformation , 0
                lea     edx, [esi-18h]  ; _OBJECT_HEADER
                mov     [ebp+OBJHeader], edx
                jz      short HandleInformationIsNULL
                mov     [eax+_OBJECT_HANDLE_INFORMATION.GrantedAccess], ecx
                mov     [eax+_OBJECT_HANDLE_INFORMATION.HandleAttributes], ebx

HandleInformationIsNULL:                ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+5A↑j
                cmp     _ObpTraceEnabled, 0
                jz      short ObpTraceEnabledIsFalse
                push    1               ; char
                push    edx             ; BackTraceHash
                call    _ObpPushStackInfo@8 ; ObpPushStackInfo(x,x)

ObpTraceEnabledIsFalse:                 ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+68↑j
                mov     eax, 1
                mov     ecx, [ebp+OBJHeader]
                xadd    [ecx+_OBJECT_HEADER.PointerCount], eax ; 引用计数加1

loc_4D9B33:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+F0↓j
                mov     [edi], esi
                jmp     loc_4D9D25
; ---------------------------------------------------------------------------

CheckIsThreadFake:                      ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+26↑j
                cmp     [ebp+Handle], -2
                jnz     short NotCurrentThread ; Handle != -2 跳走
                mov     eax, [ebp+ObjectType]
                cmp     eax, _PsThreadType
                jz      short IsThreadType
                cmp     eax, ebx        ; cmp eax , 0
                jz      short IsThreadType

loc_4D9B4F:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+35↑j
                mov     eax, STATUS_OBJECT_TYPE_MISMATCH
                jmp     ProcEnding
; ---------------------------------------------------------------------------

IsThreadType:                           ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+93↑j
                                        ; ObReferenceObjectByHandle(x,x,x,x,x,x)+97↑j
                mov     ecx, [esi+_ETHREAD.GrantedAccess]
                mov     eax, ecx
                not     eax
                test    [ebp+DesiredAccess], eax
                jz      short DesiredIsGranted_0
                cmp     [ebp+AccessMode], KernelMode
                jz      short DesiredIsGranted_0

IsUserMode:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+4D↑j
                mov     eax, STATUS_ACCESS_DENIED
                jmp     ProcEnding
; ---------------------------------------------------------------------------

DesiredIsGranted_0:                     ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+B0↑j
                                        ; ObReferenceObjectByHandle(x,x,x,x,x,x)+B6↑j
                mov     eax, [ebp+HandleInformation]
                cmp     eax, ebx        ; CMP HandleInformation , 0
                lea     edx, [esi-18h]  ; _OBJECT_HEADER
                mov     [ebp+OBJHeader], edx
                jz      short loc_4D9B8A
                mov     [eax+_OBJECT_HANDLE_INFORMATION.GrantedAccess], ecx
                mov     [eax+_OBJECT_HANDLE_INFORMATION.HandleAttributes], ebx

loc_4D9B8A:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+CD↑j
                cmp     _ObpTraceEnabled, 0
                jz      short ObpTraceEnabledIsFalse_0
                push    1               ; char
                push    edx             ; BackTraceHash
                call    _ObpPushStackInfo@8 ; ObpPushStackInfo(x,x)

ObpTraceEnabledIsFalse_0:               ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+DB↑j
                mov     eax, 1
                mov     ecx, [ebp+OBJHeader]
                xadd    [ecx+_OBJECT_HEADER.PointerCount], eax ; 引用计数加1
                jmp     short loc_4D9B33
; ---------------------------------------------------------------------------

NotCurrentThread:                       ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+88↑j
                cmp     [ebp+AccessMode], KernelMode
                jnz     short IsUserMode_1
                xor     [ebp+Handle], 80000000h
                mov     eax, _ObpKernelHandleTable
                jmp     short loc_4D9BCF
; ---------------------------------------------------------------------------

IsUserMode_1:                           ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+F6↑j
                mov     eax, STATUS_INVALID_HANDLE
                jmp     ProcEnding
; ---------------------------------------------------------------------------

NotFakeHandle:                          ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1C↑j
                mov     eax, [esi+_KTHREAD.ApcState.Process]
                mov     eax, [eax+_EPROCESS.ObjectTable]

loc_4D9BCF:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+104↑j
                push    dword ptr [ebp+AccessMode] ; char
                dec     [esi+_ETHREAD.Tcb.KernelApcDisable]
                push    [ebp+Handle]    ; BugCheckParameter1
                mov     [ebp+ObpKernelHandleTable], eax
                push    eax             ; BugCheckParameter2
                call    _ExMapHandleToPointerEx@12 ; ExMapHandleToPointerEx(x,x,x)
                mov     edi, eax
                cmp     edi, ebx
                jz      InvalidHandleProc
                mov     edx, [edi+_HANDLE_TABLE_ENTRY.___u0.InfoTable]
                mov     eax, [ebp+ObjectType]
                and     edx, 0FFFFFFF8h
                cmp     [edx+8], eax
                mov     [ebp+OBJHeader], edx
                jz      short loc_4D9C09
                cmp     eax, ebx
                jz      short loc_4D9C09
                mov     ebx, STATUS_OBJECT_TYPE_MISMATCH
                jmp     short ErrorProc
; ---------------------------------------------------------------------------

loc_4D9C09:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+146↑j
                                        ; ObReferenceObjectByHandle(x,x,x,x,x,x)+14A↑j
                test    byte ptr _NtGlobalFlag+1, 20h
                jz      short loc_4D9C30
                cmp     [ebp+AccessMode], KernelMode
                jnz     short IsUserMode_0
                cmp     [ebp+HandleInformation], ebx
                jz      short loc_4D9C3E

IsUserMode_0:                           ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+160↑j
                xor     eax, eax
                mov     ax, [edi+4]
                push    eax
                call    _ObpTranslateGrantedAccessIndex@4 ; ObpTranslateGrantedAccessIndex(x)
                mov     edx, [ebp+OBJHeader]
                mov     ebx, eax
                jmp     short loc_4D9C41
; ---------------------------------------------------------------------------

loc_4D9C30:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+15A↑j
                mov     eax, _ObpAccessProtectCloseBit
                mov     ebx, [edi+4]
                not     eax
                and     ebx, eax
                jmp     short loc_4D9C41
; ---------------------------------------------------------------------------

loc_4D9C3E:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+165↑j
                mov     ebx, [ebp+Handle]

loc_4D9C41:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+178↑j
                                        ; ObReferenceObjectByHandle(x,x,x,x,x,x)+186↑j
                mov     eax, ebx
                not     eax
                test    [ebp+DesiredAccess], eax
                jz      short loc_4D9C63
                cmp     [ebp+AccessMode], 0
                jz      short loc_4D9C63
                mov     ebx, STATUS_ACCESS_DENIED

ErrorProc:                              ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+151↑j
                push    edi             ; BugCheckParameter2
                push    [ebp+ObpKernelHandleTable] ; int
                call    _ExUnlockHandleTableEntry@8 ; ExUnlockHandleTableEntry(x,x)
                jmp     loc_4D9D2E
; ---------------------------------------------------------------------------

loc_4D9C63:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+192↑j
                                        ; ObReferenceObjectByHandle(x,x,x,x,x,x)+198↑j
                mov     eax, [ebp+ObpKernelHandleTable]
                cmp     dword ptr [eax+2Ch], 0
                jz      short loc_4D9C7F
                push    1
                push    [ebp+Handle]
                push    eax
                call    _ExpGetHandleInfo@12 ; ExpGetHandleInfo(x,x,x)
                mov     edx, [ebp+OBJHeader]
                mov     [ebp+ObjectType], eax
                jmp     short loc_4D9C83
; ---------------------------------------------------------------------------

loc_4D9C7F:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1B4↑j
                and     [ebp+ObjectType], 0

loc_4D9C83:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1C7↑j
                mov     eax, [ebp+HandleInformation]
                test    eax, eax
                jz      short loc_4D9CA7
                mov     [eax+4], ebx
                mov     ecx, _ObpAccessProtectCloseBit
                test    [edi+4], ecx
                mov     ecx, [edi]
                jz      short loc_4D9CA2
                and     ecx, 6
                or      ecx, 1
                jmp     short loc_4D9CA5
; ---------------------------------------------------------------------------

loc_4D9CA2:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1E2↑j
                and     ecx, 6

loc_4D9CA5:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1EA↑j
                mov     [eax], ecx

loc_4D9CA7:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1D2↑j
                test    byte ptr [edi], 4
                jz      short loc_4D9CDA
                mov     eax, [ebp+ObjectType]
                test    eax, eax
                jz      short loc_4D9CDA
                cmp     dword ptr [eax], 0
                jz      short loc_4D9CDA
                cmp     [ebp+DesiredAccess], 0
                jz      short loc_4D9CDA
                cmp     [ebp+AccessMode], 0
                jz      short loc_4D9CDA
                push    [ebp+DesiredAccess]
                mov     ecx, [edx+8]
                add     ecx, 40h ; '@'
                push    ecx
                push    eax
                push    [ebp+Handle]
                call    _ObpAuditObjectAccess@16 ; ObpAuditObjectAccess(x,x,x,x)
                mov     edx, [ebp+OBJHeader]

loc_4D9CDA:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1F4↑j
                                        ; ObReferenceObjectByHandle(x,x,x,x,x,x)+1FB↑j ...
                cmp     _ObpTraceEnabled, 0
                jz      short loc_4D9CEB
                push    1               ; char
                push    edx             ; BackTraceHash
                call    _ObpPushStackInfo@8 ; ObpPushStackInfo(x,x)

loc_4D9CEB:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+22B↑j
                mov     eax, 1
                mov     ecx, [ebp+OBJHeader]
                xadd    [ecx+_OBJECT_HANDLE_INFORMATION.HandleAttributes], eax
                push    edi             ; BugCheckParameter2
                push    [ebp+ObpKernelHandleTable] ; int
                call    _ExUnlockHandleTableEntry@8 ; ExUnlockHandleTableEntry(x,x)
                inc     [esi+_ETHREAD.Tcb.KernelApcDisable]
                jnz     short MovObject
                lea     eax, [esi+_ETHREAD.Tcb.ApcState]
                cmp     [eax+_KAPC_STATE.ApcListHead.Flink], eax
                jz      short MovObject
                mov     cl, 1
                mov     byte ptr [esi+49h], 1
                call    ds:__imp_@HalRequestSoftwareInterrupt@4 ; HalRequestSoftwareInterrupt(x)

MovObject:                              ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+24F↑j
                                        ; ObReferenceObjectByHandle(x,x,x,x,x,x)+256↑j
                mov     eax, [ebp+OBJHeader]
                mov     ecx, [ebp+Object]
                add     eax, 18h
                mov     [ecx], eax

loc_4D9D25:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+7F↑j
                xor     eax, eax
                jmp     short ProcEnding
; ---------------------------------------------------------------------------

InvalidHandleProc:                      ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+132↑j
                mov     ebx, STATUS_INVALID_HANDLE

loc_4D9D2E:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+1A8↑j
                inc     [esi+_ETHREAD.Tcb.KernelApcDisable]
                jnz     short loc_4D9D49
                lea     eax, [esi+_ETHREAD.Tcb.ApcState]
                cmp     [eax+_KAPC_STATE.ApcListHead.Flink], eax
                jz      short loc_4D9D49
                mov     cl, 1
                mov     [esi+_ETHREAD.Tcb.ApcState.KernelApcPending], 1
                call    ds:__imp_@HalRequestSoftwareInterrupt@4 ; HalRequestSoftwareInterrupt(x)

loc_4D9D49:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+27E↑j
                                        ; ObReferenceObjectByHandle(x,x,x,x,x,x)+285↑j
                mov     eax, ebx

ProcEnding:                             ; CODE XREF: ObReferenceObjectByHandle(x,x,x,x,x,x)+9E↑j
                                        ; ObReferenceObjectByHandle(x,x,x,x,x,x)+BD↑j ...
                pop     edi
                pop     esi
                pop     ebx
                leave
                retn    18h
_ObReferenceObjectByHandle@24 endp

  为了方便讲解,我就用伪代码,具体细节请参考我的汇编注释。我们把注意力放到如下代码上:

  if ( Handle == -1 )
    {
      if ( ObjectType == PsProcessType || !ObjectType )
      {
        attachedProcess = currentThread->Tcb.ApcState.Process;
        if ( (~attachedProcess->GrantedAccess & DesiredAccess) == 0 || !AccessMode )
        {
          if ( HandleInformation )
          {
            HandleInformation->GrantedAccess = attachedProcess->GrantedAccess;
            HandleInformation->HandleAttributes = 0;
          }
          if ( ObpTraceEnabled )
            ObpPushStackInfo(&attachedProcess[-1u].584, 1);
          ++attachedProcess[-1u].Flags;
LABEL_12:
          *Object = attachedProcess;
          return 0;
        }
        return STATUS_ACCESS_DENIED;
      }
      return STATUS_OBJECT_TYPE_MISMATCH;
    }
    if ( Handle == -2 )
    {
      if ( ObjectType == PsThreadType || !ObjectType )
      {
        if ( (~currentThread->GrantedAccess & DesiredAccess) == 0 || !AccessMode )
        {
          if ( HandleInformation )
          {
            HandleInformation->GrantedAccess = currentThread->GrantedAccess;
            HandleInformation->HandleAttributes = 0;
          }
          if ( ObpTraceEnabled )
            ObpPushStackInfo(&currentThread[-1].ReadClusterSize, 1);
          ++attachedProcess[-1].Flags;
          goto LABEL_12;
        }
        return STATUS_ACCESS_DENIED;
      }
      return STATUS_OBJECT_TYPE_MISMATCH;
    }

  看到函数会提前判断句柄的值是否为负数,再判断是否是-1还是-2。通过伪代码可知,如果是-1就是获取了当前进程的结构体,如果是-2就会找当前线程的结构体,这个值被称之为伪句柄。
  我敢说只要你在三环与进程线程相关打交道的时候,肯定用到过伪句柄。用没用过GetCurrentProcessGetCurrentThread这俩函数?

; HANDLE __stdcall GetCurrentProcess()
                public _GetCurrentProcess@0
_GetCurrentProcess@0 proc near          ; CODE XREF: UnhandledExceptionFilter(x)+81↓p
                                        ; UnhandledExceptionFilter(x)+69E↓p ...
                or      eax, 0FFFFFFFFh
                retn
_GetCurrentProcess@0 endp

; HANDLE __stdcall GetCurrentThread()
                public _GetCurrentThread@0
_GetCurrentThread@0 proc near           ; DATA XREF: .text:off_7C802654↑o
                push    0FFFFFFFEh
                pop     eax
                retn
_GetCurrentThread@0 endp

  上面的函数是不是挺弱智的,为啥我必须调用这样的函数获取伪句柄呢,直接一个宏定义不就行了?为什么不能用CloseHandle关闭伪句柄是不是明白了?

  好,下面我继续接着扩展。当我们的句柄为负值时,操作系统就会使用ObpKernelHandleTable这个表,这个是通过逆向得到的结果。我们使用WinDbg看看里面有什么东西:

kd> dd ObpKernelHandleTable
8055a7d8  e1001cc8 00000000 00000000 00000000
8055a7e8  00000000 00000000 00000000 00000000
8055a7f8  00000000 00000000 00000000 00000000
8055a808  00000000 00000000 00000000 00000000
8055a818  00000000 00000000 00000000 00000000
8055a828  00000000 00000000 00000000 00000000
8055a838  00000000 00000000 00000000 00000000
8055a848  00000000 00000000 00000000 00000000
kd> dt _HANDLE_TABLE e1001cc8
ntdll!_HANDLE_TABLE
   +0x000 TableCode        : 0xe1002000
   +0x004 QuotaProcess     : (null) 
   +0x008 UniqueProcessId  : 0x00000004 Void
   +0x00c HandleTableLock  : [4] _EX_PUSH_LOCK
   +0x01c HandleTableList  : _LIST_ENTRY [ 0xe100f384 - 0x8055c448 ]
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK
   +0x028 DebugInfo        : (null) 
   +0x02c ExtraInfoPages   : 0n0
   +0x030 FirstFree        : 0x64c
   +0x034 LastFree         : 0
   +0x038 NextHandleNeedingPool : 0x800
   +0x03c HandleCount      : 0n221
   +0x040 Flags            : 0
   +0x040 StrictFIFO       : 0y0
kd> dq 0xe1002000
ReadVirtual: e1002000 not properly sign extended
e1002000  fffffffe`00000000 001f0fff`89fb09e9
e1002010  00000000`89fb0329 000f003f`e13c9119
e1002020  00000000`e1011449 00020019`e13d6731
e1002030  00020019`e13d2791 00020019`e101f421
e1002040  0002001f`e13de479 00020019`e13c3079
e1002050  00020019`e13d1419 0002001f`e13bee51
e1002060  00020019`e13d84d1 001f0003`89fa7a11
e1002070  00000040`00000000 000000a8`00000000

  关于该函数的扩展,就这些。

句柄表扩展

  为了方便大家进行学习句柄表,我们给出一个小结论,我在WinDbg找到了一个进程的句柄表,如下所示:

kd> dq 0xe10ec000
e10ec000  fffffffe`00000000 00000000`00000000
e10ec010  00000004`00000000 00000008`00000000
e10ec020  0000000c`00000000 00000010`00000000
e10ec030  00000014`00000000 00000018`00000000
e10ec040  0000001c`00000000 00000020`00000000
e10ec050  00000024`00000000 00000028`00000000
e10ec060  0000002c`00000000 00000030`00000000
e10ec070  00000034`00000000 00000038`00000000

  每一个QWORD,发现了什么规律了吗?每一个项目展示的前半部分是索引,后面就是我们对应的结构体地址,每一个索引都是4的倍数。
  其实每一个句柄都是一个结构体,名为_HANDLE_TABLE_ENTRY,我们可以看一下它的结构:

kd> dt _HANDLE_TABLE_ENTRY 
nt!_HANDLE_TABLE_ENTRY
   +0x000 Object           : Ptr32 Void
   +0x000 ObAttributes     : Uint4B
   +0x000 InfoTable        : Ptr32 _HANDLE_TABLE_ENTRY_INFO
   +0x000 Value            : Uint4B
   +0x004 GrantedAccess    : Uint4B
   +0x004 GrantedAccessIndex : Uint2B
   +0x006 CreatorBackTraceIndex : Uint2B
   +0x004 NextFreeTableEntry : Int4B

  可以看出,这是个用共用体复杂嵌套出的结构体,我们可以看看WRK的定义:

typedef struct _HANDLE_TABLE_ENTRY {

    //
    //  The pointer to the object overloaded with three ob attributes bits in
    //  the lower order and the high bit to denote locked or unlocked entries
    //

    union {

        PVOID Object;

        ULONG ObAttributes;

        PHANDLE_TABLE_ENTRY_INFO InfoTable;

        ULONG_PTR Value;
    };

    //
    //  This field either contains the granted access mask for the handle or an
    //  ob variation that also stores the same information.  Or in the case of
    //  a free entry the field stores the index for the next free entry in the
    //  free list.  This is like a FAT chain, and is used instead of pointers
    //  to make table duplication easier, because the entries can just be
    //  copied without needing to modify pointers.
    //

    union {

        union {

            ACCESS_MASK GrantedAccess;

            struct {

                USHORT GrantedAccessIndex;
                USHORT CreatorBackTraceIndex;
            };
        };

        LONG NextFreeTableEntry;
    };

} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;

  目前掌握的细节已经在进程句柄表里解释了,也就扩展这些。如想更好的掌握这些细节,推荐潘爱民的《Windows内核原理与实现》,注意这本书基于WRK,仅供参考。

PsLookupProcessByProcessId 浅析

  对于操作系统,它是如何实现通过句柄查找对应的结构体地址呢?我们可以通过以逆向PsLookupProcessByProcessId为例子进行:

NTSTATUS __stdcall PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process)
{
  _KTHREAD *v2; // esi
  struct _EPROCESS **v3; // eax
  int v4; // ecx
  ULONG_PTR v5; // ebx
  struct _EPROCESS *v6; // edi
  bool v7; // zf
  HANDLE ProcessIda; // [esp+10h] [ebp+8h]

  v2 = KeGetCurrentThread();
  --v2->KernelApcDisable;
  v3 = ExMapHandleToPointer(PspCidTable, ProcessId);
  v5 = v3;
  ProcessIda = STATUS_INVALID_PARAMETER;
  if ( v3 )
  {
    v6 = *v3;
    if ( (*v3)->Pcb.Header.Type == 3 && v6->GrantedAccess && ObReferenceObjectSafe(*v3) )
    {
      ProcessIda = 0;
      *Process = &v6->Pcb;
    }
    ExUnlockHandleTableEntry(PspCidTable, v5);
  }
  v7 = v2->KernelApcDisable++ == -1;
  if ( v7 && v2->ApcState.ApcListHead[0].Flink != &v2->ApcState )
  {
    LOBYTE(v4) = 1;
    v2->ApcState.KernelApcPending = 1;
    HalRequestSoftwareInterrupt(v4);
  }
  return ProcessIda;
}

  我们可以看出这个函数功能主要是由ExMapHandleToPointer实现的,点击去看看:

_EPROCESS *__stdcall ExMapHandleToPointer(int pspcidtable, int processid)
{
  _EPROCESS *eprocess; // eax
  _EPROCESS *eprocess_1; // esi
  int v5; // ebx
  _EPROCESS *v8; // eax
  int *v9; // esi
  ULONG v10; // eax
  int v11; // [esp+4h] [ebp-8h]
  _EPROCESS *v12; // [esp+8h] [ebp-4h]
  _DWORD *pspcidtablea; // [esp+14h] [ebp+8h]

  if ( (processid & 0x7FC) == 0 )
    return 0;
  eprocess = ExpLookupHandleTableEntry(pspcidtable, processid);
  eprocess_1 = eprocess;
  if ( eprocess )
  {
    v12 = eprocess;
    while ( 1 )
    {
      v5 = *&eprocess_1->Pcb.Header.Type;
      v11 = *&eprocess_1->Pcb.Header.Type;
      if ( (*&eprocess_1->Pcb.Header.Type & 1) != 0 )
      {
        _ECX = v12;
        _EDX = v5 - 1;
        __asm { cmpxchg [ecx], edx }
        if ( v11 == v5 )
          return eprocess_1;
      }
      else if ( !v5 )
      {
        break;
      }
      ExpBlockOnLockedHandleEntry(pspcidtable, eprocess_1);
    }
  }
  pspcidtablea = *(pspcidtable + 40);
  if ( pspcidtablea )
  {
    v8 = (*pspcidtablea)++;
    v9 = &pspcidtablea[20 * ((v8 + 1) & 0xFFF) + 1];
    *v9 = *&KeGetCurrentThread()[1].DebugActive;
    v9[2] = processid;
    v9[3] = 3;
    v10 = RtlWalkFrameChain(&pspcidtablea[20 * ((v8 + 1) & 0xFFF) + 5], 0x10u, 0);
    RtlWalkFrameChain(&v9[v10 + 4], 16 - v10, 1u);
  }
  return 0;
}

  这个函数是由ExpLookupHandleTableEntry实现的,继续:

unsigned int __stdcall ExpLookupHandleTableEntry(_HANDLE_TABLE *pspcidtable, int processid)
{
  unsigned int index; // eax
  int pageCount; // ecx
  _HANDLE_TABLE *tablebase; // esi
  int base; // ecx
  unsigned int processindex; // [esp+Ch] [ebp+Ch]

  processindex = processid & 0xFFFFFFFC;
  index = processindex >> 2;
  if ( processindex >= pspcidtable->NextHandleNeedingPool )
    return 0;
  pageCount = pspcidtable->TableCode & 3;
  tablebase = (pspcidtable->TableCode & 0xFFFFFFFC);
  if ( !pageCount )
    return tablebase + 8 * index;
  if ( pageCount == 1 )
  {
    base = *(&tablebase->TableCode + (processindex >> 11));
  }
  else
  {
    index = (processindex >> 2) - (processindex >> 21 << 19);
    base = *(*(&tablebase->TableCode + (processindex >> 21)) + 4 * (index >> 9));
  }
  return base + 8 * (index & 0x1FF);
}

  这个就是操作系统实现的伪C代码了,上面的pageCount就是句柄表的级数,我们重点分析一下它是怎样查找的:

pageCount 值为 0

  tablebase就是我们的真正的句柄表项目的地址,这个很简单,正如其实现:tablebase + 8 * index
  你可能会有疑问,为什么有这条代码:processindex = processid & 0xFFFFFFFC;这个代码就是去掉余数,保证这个就是4的倍数。processindex >> 2就是我们之前所谓的CID / 4。剩下的应该没有理解上的难度了。

pageCount 值为 1

  我们之前介绍过句柄表是按物理页的,如果是一级,那么第一层一个项目可以存储4 * 1024 / 8个句柄,也就是 29,由于还要除以4,所以就是 211,我们的Base算出来了,那么真正的index还没有计算,但是操作系统是这样算的:base + 8 * (index & 0x1FF)
  为什么index & 0x1FF就是我们的真正的索引呢?0x200十进制为512,也就是级数为1的一个项目存储的句柄数,我只需取出余数即可,由于这个是二进制,除数又是2,我们就可以用与运算进行替代。

pageCount 值为 2

  这个更复杂一些,但思想是统一的。如果值为2,第一层一个项目存储的项目为4096 / 4 * 4096 / 8,也就是 219,第二层一个项目就是29,第一层的base就是*(&tablebase->TableCode + (processindex >> 21),只需要再找第二层就是真正的Base了,也就是所谓的4 * (index >> 9)偏移。
  剩下就是Index了,是不是对于这个代码非常懵:(processindex >> 2) - (processindex >> 21 << 19)
  上面的就是为了重用上一种情况的公式,把它转换到第二层,作用就是看看第一层(级数为2的层数)剥削后剩下的句柄个数。如果读懂这一块,就没问题了,如果实在看不懂,我换一种写法:index - index >> 19 << 19,这样是不是明白了?

遍历全局句柄表练习参考

  有了上面一大波铺垫,那么我们就开始了写上一篇的思考题参考了:

🔒 点击查看代码 🔒
#include <ntddk.h>  

typedef struct _HANDLE_TABLE {
    ULONG_PTR TableCode;
    struct _EPROCESS* QuotaProcess;
    HANDLE UniqueProcessId;

#define HANDLE_TABLE_LOCKS 4

    EX_PUSH_LOCK HandleTableLock[HANDLE_TABLE_LOCKS];
    LIST_ENTRY HandleTableList;
    EX_PUSH_LOCK HandleContentionEvent;
    int* DebugInfo;
    LONG ExtraInfoPages;
    ULONG FirstFree;
    ULONG LastFree;
    ULONG NextHandleNeedingPool;
    LONG HandleCount;
    union {
        ULONG Flags;
        BOOLEAN StrictFIFO : 1;
    };

} HANDLE_TABLE, * PHANDLE_TABLE;

unsigned int* __stdcall ExpLookupHandleTableEntry(struct _HANDLE_TABLE* pspcidtable, int cid)
{
    unsigned int index;
    int pageCount; 
    int tablebase; 
    int base; 
    unsigned int processindex; 

    processindex = cid & 0xFFFFFFFC;
    index = processindex >> 2;
    if (processindex >= pspcidtable->NextHandleNeedingPool)
        return 0;
    pageCount = pspcidtable->TableCode & 3;
    tablebase = (pspcidtable->TableCode & 0xFFFFFFFC);
    if (!pageCount)
        return *(int*)(tablebase + 8 * index);
    if (pageCount == 1)
    {
        base = *(int*)(tablebase + (processindex >> 11));
    }
    else
    {
        index = (processindex >> 2) - (processindex >> 21 << 19);
        base = *((char*)*(int*)(tablebase + (processindex >> 21)) + 4 * (index >> 9));
    }
    return *(int*)(base + 8 * (index & 0x1FF));
}

VOID UnloadDriver(PDRIVER_OBJECT DriverObject)
{
    DbgPrint("Unloaded Successfully!");
}

UNICODE_STRING process, thread;

void ShowInfo(int* TableCode, int cid)
{
    int* addr = ((int)ExpLookupHandleTableEntry(TableCode, cid) & ~3);
    if (addr && MmIsAddressValid(addr))
    {
        int* type = addr[-4];

        if (!type)
        {
            return;
        }

        PUNICODE_STRING str = &type[16];

        if (!RtlCompareUnicodeString(str, &process, FALSE))
        {
            UCHAR* imgname = &addr[93];
            DbgPrint("PID : %d , Type : %wZ , Name : %s\n", cid, str, imgname);
            return;
        }

        if (!RtlCompareUnicodeString(str, &thread, FALSE))
        {
            DbgPrint("TID : %d , Type : %wZ\n", cid, str);
            return;
        }
    }
}


NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    DriverObject->DriverUnload = UnloadDriver;
    DbgPrint("Loaded Successfully!");

    RtlInitUnicodeString(&process, L"Process");
    RtlInitUnicodeString(&thread, L"Thread");

    UNICODE_STRING PsLookupProcessThreadByCid;
    RtlInitUnicodeString(&PsLookupProcessThreadByCid, L"PsLookupProcessThreadByCid");
    UCHAR* f = MmGetSystemRoutineAddress(&PsLookupProcessThreadByCid);

    DWORD32* PspCidTable = *(DWORD32*)(f + 32);

    UINT32 TableCode = *PspCidTable;

    switch (TableCode & 3)
    {
        case 0:
            for (int i = 0; i < 512; i++)
            {
                ShowInfo(TableCode, 4 * i);
            }
            break;

        case 1:
            for (int i = 0; i < 512 * 1024; i++)
            {
                ShowInfo(TableCode, 4 * i);
            }
            break;
        case 2:
            for (int i = 0; i < 512 * 1024 * 1024; i++)
            {
                ShowInfo(TableCode, 4 * i);
            }
            break;
    }

    return STATUS_SUCCESS;
}

  效果如下图所示:

  到最后,我们看看WRK是如何写的ExpLookupHandleTableEntry代码:

🔒 点击查看代码 🔒
PHANDLE_TABLE_ENTRY
ExpLookupHandleTableEntry (
    IN PHANDLE_TABLE HandleTable,
    IN EXHANDLE tHandle
    )

/*++

Routine Description:

    This routine looks up and returns the table entry for the
    specified handle value.

Arguments:

    HandleTable - Supplies the handle table being queried

    tHandle - Supplies the handle value being queried

Return Value:

    Returns a pointer to the corresponding table entry for the input
        handle.  Or NULL if the handle value is invalid (i.e., too large
        for the tables current allocation.

--*/

{
    ULONG_PTR i,j,k;
    ULONG_PTR CapturedTable;
    ULONG TableLevel;
    PHANDLE_TABLE_ENTRY Entry = NULL;
    EXHANDLE Handle;

    PUCHAR TableLevel1;
    PUCHAR TableLevel2;
    PUCHAR TableLevel3;

    ULONG_PTR MaxHandle;

    PAGED_CODE();


    //
    // Extract the handle index
    //
    Handle = tHandle;

    Handle.TagBits = 0;

    MaxHandle = *(volatile ULONG *) &HandleTable->NextHandleNeedingPool;

    //
    // See if this can be a valid handle given the table levels.
    //
    if (Handle.Value >= MaxHandle) {
        return NULL;        
    }

    //
    // Now fetch the table address and level bits. We must preserve the
    // ordering here.
    //
    CapturedTable = *(volatile ULONG_PTR *) &HandleTable->TableCode;

    //
    //  we need to capture the current table. This routine is lock free
    //  so another thread may change the table at HandleTable->TableCode
    //

    TableLevel = (ULONG)(CapturedTable & LEVEL_CODE_MASK);
    CapturedTable = CapturedTable - TableLevel;

    //
    //  The lookup code depends on number of levels we have
    //

    switch (TableLevel) {
        
        case 0:
            
            //
            //  We have a simple index into the array, for a single level
            //  handle table
            //


            TableLevel1 = (PUCHAR) CapturedTable;

            //
            // The index for this level is already scaled by a factor of 4. Take advantage of this
            //

            Entry = (PHANDLE_TABLE_ENTRY) &TableLevel1[Handle.Value *
                                                       (sizeof (HANDLE_TABLE_ENTRY) / HANDLE_VALUE_INC)];

            break;
        
        case 1:
            
            //
            //  we have a 2 level handle table. We need to get the upper index
            //  and lower index into the array
            //


            TableLevel2 = (PUCHAR) CapturedTable;

            i = Handle.Value % (LOWLEVEL_COUNT * HANDLE_VALUE_INC);

            Handle.Value -= i;
            j = Handle.Value / ((LOWLEVEL_COUNT * HANDLE_VALUE_INC) / sizeof (PHANDLE_TABLE_ENTRY));

            TableLevel1 =  (PUCHAR) *(PHANDLE_TABLE_ENTRY *) &TableLevel2[j];
            Entry = (PHANDLE_TABLE_ENTRY) &TableLevel1[i * (sizeof (HANDLE_TABLE_ENTRY) / HANDLE_VALUE_INC)];

            break;
        
        case 2:
            
            //
            //  We have here a three level handle table.
            //


            TableLevel3 = (PUCHAR) CapturedTable;

            i = Handle.Value  % (LOWLEVEL_COUNT * HANDLE_VALUE_INC);

            Handle.Value -= i;

            k = Handle.Value / ((LOWLEVEL_COUNT * HANDLE_VALUE_INC) / sizeof (PHANDLE_TABLE_ENTRY));

            j = k % (MIDLEVEL_COUNT * sizeof (PHANDLE_TABLE_ENTRY));

            k -= j;

            k /= MIDLEVEL_COUNT;


            TableLevel2 = (PUCHAR) *(PHANDLE_TABLE_ENTRY *) &TableLevel3[k];
            TableLevel1 = (PUCHAR) *(PHANDLE_TABLE_ENTRY *) &TableLevel2[j];
            Entry = (PHANDLE_TABLE_ENTRY) &TableLevel1[i * (sizeof (HANDLE_TABLE_ENTRY) / HANDLE_VALUE_INC)];

            break;

        default :
            _assume (0);
    }

    return Entry;
}

OBJECT_HEADER 扩展

  这里我们来介绍一些该结构体的扩展知识,你可以通过这些内容运用于对抗技术。先看看其结构体:

kd> dt _OBJECT_HEADER
nt!_OBJECT_HEADER
   +0x000 PointerCount     : Int4B
   +0x004 HandleCount      : Int4B
   +0x004 NextToFree       : Ptr32 Void
   +0x008 Type             : Ptr32 _OBJECT_TYPE
   +0x00c NameInfoOffset   : UChar
   +0x00d HandleInfoOffset : UChar
   +0x00e QuotaInfoOffset  : UChar
   +0x00f Flags            : UChar
   +0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : Ptr32 Void
   +0x014 SecurityDescriptor : Ptr32 Void
   +0x018 Body             : _QUAD

  我们把关注点放到Type中,之前我们也涉及过,用来判断该对象体的类型,再看一看其结构:

kd> dt _OBJECT_TYPE
ntdll!_OBJECT_TYPE
   +0x000 Mutex            : _ERESOURCE
   +0x038 TypeList         : _LIST_ENTRY
   +0x040 Name             : _UNICODE_STRING
   +0x048 DefaultObject    : Ptr32 Void
   +0x04c Index            : Uint4B
   +0x050 TotalNumberOfObjects : Uint4B
   +0x054 TotalNumberOfHandles : Uint4B
   +0x058 HighWaterNumberOfObjects : Uint4B
   +0x05c HighWaterNumberOfHandles : Uint4B
   +0x060 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0ac Key              : Uint4B
   +0x0b0 ObjectLocks      : [4] _ERESOURCE

  然后看到TypeInfo这个成员,这个是一个结构体,dt一下:

kd> dt _OBJECT_TYPE_INITIALIZER
ntdll!_OBJECT_TYPE_INITIALIZER
   +0x000 Length           : Uint2B
   +0x002 UseDefaultObject : UChar
   +0x003 CaseInsensitive  : UChar
   +0x004 InvalidAttributes : Uint4B
   +0x008 GenericMapping   : _GENERIC_MAPPING
   +0x018 ValidAccessMask  : Uint4B
   +0x01c SecurityRequired : UChar
   +0x01d MaintainHandleCount : UChar
   +0x01e MaintainTypeList : UChar
   +0x020 PoolType         : _POOL_TYPE
   +0x024 DefaultPagedPoolCharge : Uint4B
   +0x028 DefaultNonPagedPoolCharge : Uint4B
   +0x02c DumpProcedure    : Ptr32     void 
   +0x030 OpenProcedure    : Ptr32     long 
   +0x034 CloseProcedure   : Ptr32     void 
   +0x038 DeleteProcedure  : Ptr32     void 
   +0x03c ParseProcedure   : Ptr32     long 
   +0x040 SecurityProcedure : Ptr32     long 
   +0x044 QueryNameProcedure : Ptr32     long 
   +0x048 OkayToCloseProcedure : Ptr32     unsigned char 

  我们把注意力放到后面几个成员,也就是用来回调函数的地方:

   +0x02c DumpProcedure    : Ptr32     void 
   +0x030 OpenProcedure    : Ptr32     long 
   +0x034 CloseProcedure   : Ptr32     void 
   +0x038 DeleteProcedure  : Ptr32     void 
   +0x03c ParseProcedure   : Ptr32     long 
   +0x040 SecurityProcedure : Ptr32     long 
   +0x044 QueryNameProcedure : Ptr32     long 
   +0x048 OkayToCloseProcedure : Ptr32     unsigned char 

  如果我们把函数地址挂到上面,当调用相应的内核函数的时候,就就会回调该函数,用WRK代码片段来进行示例:

VOID
ObpDecrementHandleCount (
    PEPROCESS Process,
    POBJECT_HEADER ObjectHeader,
    POBJECT_TYPE ObjectType,
    ACCESS_MASK GrantedAccess
    )
{
    POBJECT_HEADER_HANDLE_INFO HandleInfo;
    POBJECT_HANDLE_COUNT_DATABASE HandleCountDataBase;
    POBJECT_HANDLE_COUNT_ENTRY HandleCountEntry;
    PVOID Object;
    ULONG CountEntries;
    ULONG ProcessHandleCount;
    ULONG_PTR SystemHandleCount;
    LOGICAL HandleCountIsZero;

    PAGED_CODE();


    Object = (PVOID)&ObjectHeader->Body;

    ProcessHandleCount = 0;

    ObpLockObject( ObjectHeader );

    SystemHandleCount = ObjectHeader->HandleCount;

    HandleCountIsZero = ObpDecrHandleCount( ObjectHeader );

    if ( HandleCountIsZero &&
        (ObjectHeader->Flags & OB_FLAG_EXCLUSIVE_OBJECT)) {

        OBJECT_HEADER_TO_QUOTA_INFO_EXISTS( ObjectHeader )->ExclusiveProcess = NULL;
    }

    if (ObjectType->TypeInfo.MaintainHandleCount) {

        HandleInfo = OBJECT_HEADER_TO_HANDLE_INFO_EXISTS( ObjectHeader );

        if (ObjectHeader->Flags & OB_FLAG_SINGLE_HANDLE_ENTRY) {

            ASSERT(HandleInfo->SingleEntry.Process == Process);
            ASSERT(HandleInfo->SingleEntry.HandleCount > 0);

            ProcessHandleCount = HandleInfo->SingleEntry.HandleCount--;
            HandleCountEntry = &HandleInfo->SingleEntry;

        } else {

            HandleCountDataBase = HandleInfo->HandleCountDataBase;

            if (HandleCountDataBase != NULL) {

                CountEntries = HandleCountDataBase->CountEntries;
                HandleCountEntry = &HandleCountDataBase->HandleCountEntries[ 0 ];

                while (CountEntries) {

                    if ((HandleCountEntry->HandleCount != 0) &&
                        (HandleCountEntry->Process == Process)) {

                        ProcessHandleCount = HandleCountEntry->HandleCount--;

                        break;
                    }

                    HandleCountEntry++;
                    CountEntries--;
                }
            }
            else {
                HandleCountEntry = NULL;
            }
        }

        if (ProcessHandleCount == 1) {

            HandleCountEntry->Process = NULL;
            HandleCountEntry->HandleCount = 0;
        }
    }

    ObpUnlockObject( ObjectHeader );

    if (ObjectType->TypeInfo.CloseProcedure) {

#if DBG
        KIRQL SaveIrql;
#endif

        ObpBeginTypeSpecificCallOut( SaveIrql );

        (*ObjectType->TypeInfo.CloseProcedure)( Process,
                                                Object,
                                                GrantedAccess,
                                                ProcessHandleCount,
                                                SystemHandleCount );

        ObpEndTypeSpecificCallOut( SaveIrql, "Close", ObjectType, Object );

    }

    ObpDeleteNameCheck( Object );

    InterlockedDecrement((PLONG)&ObjectType->TotalNumberOfHandles);

    return;
}

  为了突出主题,我把注释给删掉了,重点看这块代码:

    if (ObjectType->TypeInfo.CloseProcedure) {

#if DBG
        KIRQL SaveIrql;
#endif

        ObpBeginTypeSpecificCallOut( SaveIrql );

        (*ObjectType->TypeInfo.CloseProcedure)( Process,
                                                Object,
                                                GrantedAccess,
                                                ProcessHandleCount,
                                                SystemHandleCount );

        ObpEndTypeSpecificCallOut( SaveIrql, "Close", ObjectType, Object );

    }

  明白这块地方的作用了吧?其他的我就不逐个列举了。

下一篇

  羽夏看Win系统内核—— APC 篇

posted @ 2022-02-05 21:24  寂静的羽夏  阅读(1161)  评论(0编辑  收藏  举报