句柄表篇——進程句柄表


寫在前面

  此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的復雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章后面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看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的情況。

句柄表索引查找

  既然我們了解了結構,我們來看看如何通過句柄查找對應的結構體地址,首先我們的測試代碼如下:

#include "stdafx.h"
#include <stdlib.h>
#include <windows.h>

int main(int argc, char* argv[])
{
   int pid;
   printf("請輸入程序的pid:");
   scanf("%d",&pid);
   HANDLE hprocess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
   if (hprocess==INVALID_HANDLE_VALUE)
   {
      puts("進程句柄無效!!!");
   }
   else
   {
      printf("打開進程獲得句柄成功!句柄為:%d\n",hprocess);
   }
   system("pause");
   CloseHandle(hprocess);
   return 0;
}

  然后我打開了一個notepad進程,從任務管理器找到它的PID,輸入進去,得到輸出十進制的2024
  然后我們輸入指令獲取我們寫代碼運行的進程的EPROCESS結構體:

kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
Failed to get VadRoot
PROCESS 89b6a7e8  SessionId: 0  Cid: 0110    Peb: 7ffd5000  ParentCid: 05f4
    DirBase: 13a40260  ObjectTable: e1cef910  HandleCount:  46.
    Image: notepad.exe
    
Failed to get VadRoot
PROCESS 89b84500  SessionId: 0  Cid: 0640    Peb: 7ffdf000  ParentCid: 06fc
    DirBase: 13a40320  ObjectTable: e1d84558  HandleCount:  22.
    Image: HandleTest.exe

  然后我們讀取HandleTest.exe,也就是我們調用OpenProcess函數的進程,找到它的句柄表( …… 表示省略):

kd> dt _EPROCESS 89b84500
ntdll!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x01d80d3a`c4dffca0
   +0x078 ExitTime         : _LARGE_INTEGER 0x0
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : 0x00000640 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x899782d8 - 0x89b24470 ]
   +0x090 QuotaUsage       : [3] 0x460
   +0x09c QuotaPeak        : [3] 0x650
   +0x0a8 CommitCharge     : 0x41
   +0x0ac PeakVirtualSize  : 0x824000
   +0x0b0 VirtualSize      : 0x7b2000
   +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x89978304 - 0x89b2449c ]
   +0x0bc DebugPort        : 0x89b16020 Void
   +0x0c0 ExceptionPort    : 0xe13a3f08 Void
   +0x0c4 ObjectTable      : 0xe1d84558 _HANDLE_TABLE
   ……

kd> dx -id 0,0,805539a0 -r1 ((ntdll!_HANDLE_TABLE *)0xe1d84558)
((ntdll!_HANDLE_TABLE *)0xe1d84558)                 : 0xe1d84558 [Type: _HANDLE_TABLE *]
    [+0x000] TableCode        : 0xe1f97000 [Type: unsigned long]
    [+0x004] QuotaProcess     : 0x89b84500 [Type: _EPROCESS *]
    [+0x008] UniqueProcessId  : 0x640 [Type: void *]
    [+0x00c] HandleTableLock  [Type: _EX_PUSH_LOCK [4]]
    [+0x01c] HandleTableList  [Type: _LIST_ENTRY]
    [+0x024] HandleContentionEvent [Type: _EX_PUSH_LOCK]
    [+0x028] DebugInfo        : 0x0 [Type: _HANDLE_TRACE_DEBUG_INFO *]
    [+0x02c] ExtraInfoPages   : 0 [Type: long]
    [+0x030] FirstFree        : 0x7dc [Type: unsigned long]
    [+0x034] LastFree         : 0x0 [Type: unsigned long]
    [+0x038] NextHandleNeedingPool : 0x800 [Type: unsigned long]
    [+0x03c] HandleCount      : 22 [Type: long]
    [+0x040] Flags            : 0x0 [Type: unsigned long]
    [+0x040 ( 0: 0)] StrictFIFO       : 0x0 [Type: unsigned char]

  我們看看這里句柄表存的是什么:

kd> dq 0xe1f97000
ReadVirtual: e1f97000 not properly sign extended
e1f97000  fffffffe`00000000 00000000`00000000
e1f97010  00000004`00000000 00000008`00000000
e1f97020  0000000c`00000000 00000010`00000000
e1f97030  00000014`00000000 00000018`00000000
e1f97040  0000001c`00000000 00000020`00000000
e1f97050  00000024`00000000 00000028`00000000
e1f97060  0000002c`00000000 00000030`00000000
e1f97070  00000034`00000000 00000038`00000000

  如何根據句柄查找對應的結構體地址呢?首先我們對得到的句柄進行除4就是索引。即我們偏移就是得到的句柄乘2,我們來看看示例:

kd> dq 0xe1f97000+FD0
ReadVirtual: e1f97fd0 not properly sign extended
e1f97fd0  001f0fff`89b6a7d1 021f0001`e1d4a1f9
e1f97fe0  000f000f`e14134b1 000007b8`00000000
e1f97ff0  00000003`e14114e9 000f0003`e1009681

  注意,最后兩位是句柄表的屬性,我們需要清零才能得到結構體的地址,也就是0x89b6a7d0就是結構體的地址,但是,這個結構體指向的不是EPROCESS,而是_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

  也就是說,我們還需要對這個地址+0x18的偏移,我們來看看:

kd> dt _EPROCESS 89b6a7d0+0x018
ntdll!_EPROCESS
   ……
   +0x170 Session          : 0xbadca000 Void
   +0x174 ImageFileName    : [16]  "notepad.exe"
   +0x184 JobLinks         : _LIST_ENTRY [ 0x0 - 0x0 ]

  正好就是我們的notepad進程。
  句柄表不是僅僅只有進程結構體句柄,還有線程、文件等句柄,我們如何判斷呢?就是通過_OBJECT_HEADER這個結構體判斷的,如下指令所示:

kd> dt _OBJECT_HEADER 89b6a7d0
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n16
   +0x004 HandleCount      : 0n3
   +0x004 NextToFree       : 0x00000003 Void
   +0x008 Type             : 0x89db0040 _OBJECT_TYPE
   +0x00c NameInfoOffset   : 0 ''
   +0x00d HandleInfoOffset : 0 ''
   +0x00e QuotaInfoOffset  : 0 ''
   +0x00f Flags            : 0x20 ' '
   +0x010 ObjectCreateInfo : 0x89cbdb98 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x89cbdb98 Void
   +0x014 SecurityDescriptor : 0xe17b15f6 Void
   +0x018 Body             : _QUAD
kd> dx -id 0,0,805539a0 -r1 ((ntkrnlpa!_OBJECT_TYPE *)0x89db0040)
((ntkrnlpa!_OBJECT_TYPE *)0x89db0040)                 : 0x89db0040 [Type: _OBJECT_TYPE *]
    [+0x000] Mutex            : Unowned Resource [Type: _ERESOURCE]
    [+0x038] TypeList         [Type: _LIST_ENTRY]
    [+0x040] Name             : "Process" [Type: _UNICODE_STRING]
    [+0x048] DefaultObject    : 0x0 [Type: void *]
    [+0x04c] Index            : 0x5 [Type: unsigned long]
    [+0x050] TotalNumberOfObjects : 0x1a [Type: unsigned long]
    [+0x054] TotalNumberOfHandles : 0x68 [Type: unsigned long]
    [+0x058] HighWaterNumberOfObjects : 0x1b [Type: unsigned long]
    [+0x05c] HighWaterNumberOfHandles : 0x69 [Type: unsigned long]
    [+0x060] TypeInfo         [Type: _OBJECT_TYPE_INITIALIZER]
    [+0x0ac] Key              : 0x636f7250 [Type: unsigned long]
    [+0x0b0] ObjectLocks      [Type: _ERESOURCE [4]]

  我們通過Type里面的Name屬性,很容易判斷出它是Process類型,也就是進程結構體。

本節練習

本節的答案將會在下一節進行講解,務必把本節練習做完后看下一個講解內容。不要偷懶,實驗是學習本教程的捷徑。

  俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到后面,不做練習的話容易夾生了,開始還明白,后來就真的一點都不明白了。本節練習不多,請保質保量的完成。

1️⃣ 思考當我用函數打開一個內核對象時,如果用CloseHandle,這個內核對象一定會被銷毀嗎?假設我用OpenProcess打開了一個現有的進程,但打開后,這個進程被關閉了,這個內核對象還存在嗎?
2️⃣ 使用循環打開100次某個內核對象,分析句柄表的結構;然后打開1000次某個內核對象,繼續分析上述操作。

下一篇

  句柄表篇——全局句柄表


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM