句柄表篇——进程句柄表

发布于 1970年 01月 01日 08:00

写在前面

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

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

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


🔒 华丽的分割线 🔒


句柄

  句柄是一种内核对象,当一个进程创建或者打开一个内核对象时,将获得一个句柄,通过这个句柄可以访问内核对象。

HANDLE g_hMutex = ::CreateMutex( NULL , FALSE, "XYZ");
HANDLE g_hMutex = ::OpenMutex( MUTEX_ALL_ACCESSFALSE, "XYZ");
HANDLE g_hEvent = ::CreateEvent( NULL, TRUE, FALSE, NULL);
HANDLE g_hThread = ::CreateThread( NULL, 0, Proc,NULL, 0, NULL);

  那么,句柄到底是何方神圣?为什么操作系统要使用这个句柄?句柄存在的目的是为了避免在应用层直接修改内核对象,而句柄就是一个索引,通过这个索引,我就可以在内核轻松找到对应的内核对象结构体的地址。
  注意,我在这里强调一下 句柄是给3环用的,而不是给内核用的 。所以在写驱动的时候,不要搞句柄花里胡哨的东西。Windows所有涉及句柄的API,一旦到了真正函数实现的部分,就立刻使用ObReferenceObjectByHandle把它转化为真正的指向内核对象的指针,举个例子,由于篇幅,只列出开头部分:

; int __stdcall PspCreateProcess(int, ACCESS_MASK DesiredAccess, int, HANDLE Handle, int, HANDLE, HANDLE, HANDLE, int)
_PspCreateProcess@36 proc near          ; CODE XREF: NtCreateProcessEx(x,x,x,x,x,x,x,x,x)+72↓p
                                        ; PsCreateSystemProcess(x,x,x)+1B↓p ...

; __unwind { // __SEH_prolog
                push    11Ch
                push    offset stru_402EB0
                call    __SEH_prolog
                mov     eax, large fs:124h
                mov     [ebp+var_84], eax
                mov     cl, [eax+140h]
                mov     [ebp+AccessMode], cl
                mov     eax, [eax+44h]
                mov     [ebp+RunRef], eax
                xor     esi, esi
                mov     [ebp+var_1D], 0
                mov     [ebp+var_48], esi
                mov     [ebp+var_44], esi
                test    [ebp+arg_10], 0FFFFFFF0h
                jnz     short loc_4EFB0B
                cmp     [ebp+Handle], esi
                jz      short loc_4EFB1A
                push    esi             ; HandleInformation
                lea     eax, [ebp+Object]
                push    eax             ; Object
                push    dword ptr [ebp+AccessMode] ; AccessMode
                push    _PsProcessType  ; ObjectType
                push    80h ; '€'       ; DesiredAccess
                push    [ebp+Handle]    ; Handle
                call    _ObReferenceObjectByHandle@24 ; ObReferenceObjectByHandle(x,x,x,x,x,x)
                mov     ecx, [ebp+Object] ; Object
                mov     [ebp+var_1C], ecx
                cmp     eax, esi
                jl      loc_4F01FB
                cmp     [ebp+arg_20], esi
                jz      short loc_4EFB15
                cmp     [ecx+134h], esi
                jnz     short loc_4EFB15
                call    @ObfDereferenceObject@4 ; ObfDereferenceObject(x)

loc_4EFB0B:                             ; CODE XREF: PspCreateProcess(x,x,x,x,x,x,x,x,x)+3D↑j
                mov     eax, 0C000000Dh
                jmp     loc_4F01FB
; ---------------------------------------------------------------------------

  下面是微软官方对ObReferenceObjectByHandle的解释:

The ObReferenceObjectByHandle routine provides access validation on the object handle, and, if access can be granted, returns the corresponding pointer to the object's body.

句柄表

  上面介绍了句柄是什么,我们来介绍句柄表这个东西。不加特殊说明,我们所说的句柄表是进程句柄表。那么句柄表在哪里呢?看下面的结构体:

kd> dt _EPROCESS
ntdll!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER
   +0x078 ExitTime         : _LARGE_INTEGER
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : Ptr32 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY
   +0x090 QuotaUsage       : [3] Uint4B
   +0x09c QuotaPeak        : [3] Uint4B
   +0x0a8 CommitCharge     : Uint4B
   +0x0ac PeakVirtualSize  : Uint4B
   +0x0b0 VirtualSize      : Uint4B
   +0x0b4 SessionProcessLinks : _LIST_ENTRY
   +0x0bc DebugPort        : Ptr32 Void
   +0x0c0 ExceptionPort    : Ptr32 Void
   +0x0c4 ObjectTable      : Ptr32 _HANDLE_TABLE

  这个结构体我们之前接触过,由于这个结构体十分庞大,只显示了一部分。在0xc4这个偏移就是存放句柄表的。我们来看看它的结构是什么:

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

  TableCode就是所谓的句柄表,但句柄表是有结构的,下面我们来看看它的结构:

  ①:这一块共计两个字节,高位字节是给SetHandleInformation这个函数用的,比如写成如下形式,那么这个位置将被写入0x02

`SetHandleInformation(Handle,HANDLE_FLAG_PROTECT_FROM_CLOSE,HANDLE_FLAG_PROTECT_FROM_CLOSE);`

  HANDLE_FLAG_PROTECT_FROM_CLOSE宏的值为0x00000002,取最低字节,最终 ① 这块是0x0200
  ②:这块是访问掩码,是给OpenProcess这个函数用的,具体的存的值就是这个函数的第一个参数的值。
  ③ 和 ④ 这两个块共计四个字节,其中bit0-bit2存的是这个句柄的属性,其中bit2bit0默认为01; bit1表示的函数是该句柄是否可继承,OpenProcess的第二个参数与bit1有关,
bit31-bit3则是存放的该内核对象在内核中的具体的地址。

句柄表结构

  句柄表的结构还是比较复杂的,具体结构如下:

  TableCode从上图可知,指明句柄表的级数,如果低两位是0,那么指向的句柄表里面存放的就是真正的句柄;如果低两位是1,那么指向的第一张句柄表的每一个成员都是一个地址,每一个地址指向每一个真正的句柄表,以此类推低两位是2的情况。

本节练习

本节的答案将会在下一节进行讲解,务必把本节练习做完后看下一个讲解内容。不要偷懒,实验是学习本教程的捷径。

  俗话说得好,光说不练假把式,如下是本节相关的练习。如果练习没做好,就不要看下一节教程了,越到后面,不做练习的话容易夹生了,开始还明白,后来就真的一点都不明白了。本节练习不多,请保质保量的完成。

1️⃣ 思考当我用函数打开一个内核对象时,如果用CloseHandle,这个内核对象一定会被销毁吗?假设我用OpenProcess打开了一个现有的进程,但打开后,这个进程被关闭了,这个内核对象还存在吗?
2️⃣ 使用循环打开100次某个内核对象,分析句柄表的结构;然后打开1000次某个内核对象,继续分析上述操作。

下一篇

  句柄表篇——全局句柄表