進程句柄表與創建句柄表


 

我們編寫Windows程序中經常使用到內核對象,特別是句柄這個概念,通過句柄可以對內核對象進行訪問,那句柄到底是什么?本文將會從內核來說明這個概念。

Windows采取了面向對象設計,內核中有一個的模塊來管理內核對象,有很多資料都是說是“對象管理器”,本文也采用這個概念。對象管理器用來管理內核對象信息和記錄內核對象的使用情況,包括引用計數。

每個進程都要創建一個句柄列表,這些句柄指向各種系統資源,比如信號量,線程,和文件等,進程中的所有線程都可以訪問這些資源性),如下圖所示,進程和資源:

 

 

 

1.進程與句柄表數據關系

在用戶模式下如果調用CloseHanele( )表示不再使用這個對象,在內核中進程便會刪除句柄(釋放對象引用);對象管理器也會將內核對象的引用計數也會減一,當對象的句柄引用為0時,對象管理器便會釋放這個對象。

句柄表最基本作用就是句柄與目標對象之間的映射表,下圖是進程與句柄的簡化模型圖(有些數據域要經過處理):

 

 

       _HANDLE_TABLE是句柄表的信息的結構體,在內核中句柄是句柄表中表項的索引,在這里可以簡單的理解,由索引(句柄)在句柄表中查找到進程引用的內核對象.

在Windbg中查看_HANDLE_TABLE(這里例出部分有意義的項)

kd> dt _HANDLE_TABLE

nt!_HANDLE_TABLE

   +0x000 TableCode        : Uint4B           //指向第一層局部表,並記錄層數

   +0x004 QuotaProcess     : Ptr32 _EPROCESS  //指向進程_EPROCESS塊

   +0x008 UniqueProcessId  : Ptr32 Void      //進程ID

+0x03c HandleCount      : Int4B             //句柄計數,當前使用句柄個數

kd> dt _EPROCESS        //進程_EPROCESS塊信息

nt!_EPROCESS

+0x084 UniqueProcessId  : Ptr32 Void             //進程ID

+0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE  //指向_HANDLE_TABLE結構

 

2.句柄的數據結構

內核與SDK中定義句柄都為:  typedef void *HANDLE;   表明句柄是一個無符號整數,實際上有效句柄的值時有范圍的,大家想想如果采用數組來存儲句柄需要耗費很大的內存,Windows句柄表使用了稀疏數組.

2.1XP/2003句柄表項:

先看下句柄表中存放的是什么?句柄表主要是存放的是對象的地址與屬性信息,當然還要存放句柄表相關一些信息(審計,空閑項),每個句柄表項是由_HANDLE_TABLE_ENTRY 描述的,_HANDLE_TABLE_ENTRY占8字節,定義如下:

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

由於_HANDLE_TABLE_ENTRY有些聯合體,不好理解,源碼定義如下:

typedef struct _HANDLE_TABLE_ENTRY {

 union {

    PVOID Object;                    //對象指針

    ULONG ObAttributes;              //對象屬性

    PHANDLE_TABLE_ENTRY_INFO InfoTable;

    ULONG_PTR Value;          //值

    };

union {

   union {

      ACCESS_MASK GrantedAccess;  //訪問掩碼

        struct {

            USHORT GrantedAccessIndex;

            USHORT CreatorBackTraceIndex;

            };

        };

     LONG NextFreeTableEntry;//下一個空閑的句柄表項,空閑鏈表索引

  };

} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;

表示的意義:

  1. 對象指針Object有效則第二個域為訪問掩碼GrantedAccess
  2. 第一個域為0,第二個域可能是NextFreeTableEntry,也可能為審計,后面會有相關算法用到這個域,要根據上下文來判斷。

這里的Object並不是“真正”的對象指針,而是包括了對象的指針域對象的屬性域,由於在內核中對象總是8字節對齊的,那么指向對象的指針最低3位總是0,微軟把這3位也利用上,Object的最低3位做為對象的屬性,看下面的一組宏定義:

#define OBJ_HANDLE_ATTRIBUTES (OBJ_PROTECT_CLOSE | OBJ_INHERIT | OBJ_AUDIT_OBJECT_CLOSE)

第0位 OBJ_PROTECT_CLOSE:句柄表項是否被鎖定,1鎖定,0未鎖定

第1位 OBJ_INHERIT:指向該進程所創建的子進程是否可以繼承該句柄,既是否將該句柄項    拷貝到它的句柄表中

第2位 OBJ_AUDIT_OBJECT_CLOSE:關閉該對象時是否產生一個審計事件

2.2XP/2003句柄表項:

Windows為了節省空間采用動態擴展結構,類似於頁表結構,最大可擴展3層表._HANDLE_TABLE.TableCode存放了第一層局部表的基址指針和層數,微軟在這里設計很精妙,由於效率32位地址都以4對齊,最低2位為0,微軟把_HANDLE_TABLE. TableCode的最低兩位作為句柄表層數的紀錄,即00 一層表,

01 二層表  10 三層表.句柄表的結構圖如下:

一層表:                           

 

_HANDLE_TABLE_ENTRY

 

 

 

 

 

 

 

 

 

 

_HANDLE_TABLE_ENTRY

 

兩層表時:

_HANDLE_TABLE_ENTRY

 

 

_HANDLE_TABLE_ENTRY

 

三層表時:

 

_HANDLE_TABLE_ENTRY

 

 

_HANDLE_TABLE_ENTRY

 

 

   有上圖所示,最低層局部表都是是存放着_HANDLE_TABLE_ENTTY結構,中間層和最高層都是存放着頁表指針,當句柄增加時,便會判斷是否需要擴展。

 

2.3XP/2003句柄表表項計數:

句柄表是動態擴展,當引用資源足夠多時,句柄的數目也在增加,當到一定數目時,句柄表便會擴展,擴展的標准是什么?下面一系列宏給出了定義

(1)   最低層存放句柄表項數:

每個最底層頁表存放的是_HANDLE_TABLE_ENTRY結構, 即4096/8 = 512,其中第一項做審計用,最多有511個有效項

#define LOWLEVEL_COUNT (TABLE_PAGE_SIZE / sizeof(HANDLE_TABLE_ENTRY))  

(2)   中間層可以存放的項數

中間層存放的頁表指針,最多有4028 / 4 =1024

#define MIDLEVEL_COUNT (PAGE_SIZE / sizeof(PHANDLE_TABLE_ENTRY))            

(3)可分配的最大句柄值,不是我們想象的無符號整數最大值

#define MAX_HANDLES (1<<24)                   //224

(4)最高層最大項數:

#define HIGHLEVEL_COUNT MAX_HANDLES / (LOWLEVEL_COUNT * MIDLEVEL_COUNT)

即224/(1024*512) = 25 =32,在句柄表結構圖已經說明3層表第一層表最大有32項

   通過上面計算:二級表最大可以存放511 * 1024 = 523264個對象引用,沒有特殊情況一般來說已經夠了,所以我們一般只能觀察到一層,兩層句柄表

3. nt!PspCreateProcess中創建進程句柄表

3.1在創建進程時初始化進程對象並創建進程句柄表

創建進程時先創建進程對象,再創建進程句柄表,即nt!PspCreateProcess->nt!ObInitProcess->nt!ExCreateHandleTable,創建句柄表的核心流程圖如下:

 

     分配進程句柄表例程步驟:

  1. 調用ExpAllocateHandleTable分配句柄表及_HANDLE_TABLE結構
  2. 插入到進程句柄表鏈表

函數描述:

; Routine Description:

;     This function allocate and initialize a new new handle table

;     這個例程分配並初始化一個新的句柄表(_HANDLE_TABLE)

; Arguments:

;     Process - Supplies an optional pointer to the process against which quota

;     will be charged.

;     提供一個將要記錄相關信息(對象)的進程的指針

; Return Value:

;     If a handle table is successfully created, then the address of the

;     handle table is returned as the function value. Otherwise, a value

;     NULL is returned.

;     如果成功函數返回handle table的地址,負責返回0

_HANDLE_TABLE *__stdcall ExCreateHandleTable(_EPROCESS *pProcess)

 

核心算法分析:

由於進程句柄表是一個雙向鏈表結構,是系統很重要的數據結構,所以必須考慮同步問題,只有在加鎖的情況下才能修改

 通過ExpAllocateHandleTable分配進程句柄表:

   push 1 ; DoInit

    push [ebp+pProcess] ; pProcess

    call _ExpAllocateHandleTable@8 ; 創建句柄表例程

    mov ebx, eax

    test ebx, ebx ; 判斷ExpAllocateHandleTable是否成功

    jz  short ALLOC_HANDLE_TABLE_UNSUCCESS

句柄表是進程句柄鏈表是內核重要結構,有同步問題存在,這里給句柄表上鎖

mov eax, 0

; 系統句柄鏈表的改變必須要實現同步操作,所以要使用鎖

mov ecx, offset _HandleTableListLock

lock bts [ecx], eax ; 加鎖

加入進程句柄表鏈表

mov ecx, _HandleTableListHead.Blink

    lea eax, [ebx+_HANDLE_TABLE.HandleTableList.Flink

;取_Handle_TABLE.HandleTableList的Flink指針

    mov [eax+_LIST_ENTRY.Flink], ecx

;_HANDLE_TABLE.HandleTableList.Flink= HADLETABLELIST.BLINK

mov dword ptr [eax], offset _HandleTableListHead.Flink HandleTalbeListHead

; 取得句柄表鏈表頭節點的頭指針地址

    mov [ecx], eax

mov _HandleTableListHead.Blink, eax

;設置,HandleTableListHead.BLink =_HANDLE_TABLE.HandleTableList.Flink

 

nt!ExpAllocateHandleTable的核心流程:

 

       ExpAllocateHandleTable例程:

  1. 分配_HANDLE_TABLE內存池與分配一頁內存池作為第一層句柄表
  2. 初始化句柄表
  3. 建立進程與句柄表的映射關系

函數描述:

; Routine Description:

;

;     This worker routine will allocate and initialize a new handle table

;     structure.  The new structure consists of the basic handle table

;     struct plus the first allocation needed to store handles.  This is

;     really one page divided up into the top level node, the first mid

;     level node, and one bottom level node.

;     例程分配並初始化一個新的句柄表結構,加入一些存儲句柄的必要的基本結構信息

;     到新分配的句柄表結構中.這里准備一個頁內存分割給高層節點,第一個中間層節點和一個

;    低層節點

; Arguments:

;     Process - Optionally supplies the process to charge quota for the

;         handle table

;              提供審計配額信息的進程(並不是指當前進程)的指針

;     DoInit - If FALSE then we are being called by duplicate and we don't need

;              the free list built for the caller

;     如果FALSE(copy)時同樣會被調用,並且調用者不需要釋放創建建的表

; Return Value:

;     A pointer to the new handle table or NULL if unsuccessful at getting

;     pool.

;     一個指向句柄表(HANDLE_TABLE)的指針,如果NULL表示獲取內核內存池失敗

; _HANDLE_TABLE *__stdcall ExpAllocateHandleTable(_EPROCESS *pProcess,

char DoInit)

核心算法分析:

分配_HANDLE_TABLE結構內存池:

  push  6274624Fh   ; Tag

  push  44h         ; sizeof(_HANDLE_TABLE)

  push  1           ; PoolType

  call  _ExAllocatePoolWithTag@12 ;分配一個大小為sizeof(HANDLE_TABLE)的內核內存

                                                     ;池

  mov   esi, eax

  xor   ebx, ebx

  cmp   esi, ebx    ; 判斷內存池是否分配成功

  jz    short AllOC_POOL_UNSUCCESS

分配一頁內存池作為第一層句柄表

  push  edi

  push  11h

  pop   ecx

  push  1000h       ; 一個頁表大小

  push  [ebp+pProcess] ; _ERPOCESS指針

  xor   eax, eax

  mov   edi, esi

  rep stosd         ; 分配一頁內存

  call  _ExpAllocateTablePagedPoolNoZero@8 ; 分配一頁內存池,作為第一級句柄表

  cmp   eax, ebx    ; 判斷是否分配成功

  jnz   short AllOC_PAGE_SUCCESS ; 判斷DoInit參數是否為FALSE

  push  ebx         ; TagToFree

  push  esi         ; P

  call  _ExFreePoolWithTag@8 ; 釋放內存分配的內存池

cmp   [ebp+DoInit], bl ; 判斷DoInit參數是否為FALSE

  mov   [esi+_HANDLE_TABLE.TableCode], eax ; 將分配的頁表基地址賦值給

;HANDLT_TABLE第一項TableCode,建立一級表的映射關系

初始句柄表,設置空閑句柄鏈表:

  mov   dword ptr [eax+_HANDLE_TABLE_ENTRY.NextFreeTableEntry], 0FFFFFFFEh

 ; 句柄頁表的第一個句柄項作為審計用,

                 ; NextFreeTableEntry設置為EX_ADDITIONAL_INFO_SIGNATURE標志

  mov   [eax+_HANDLE_TABLE_ENTRY.___u0.Value], ebx ; 設置第一項Value為0

  mov   edx, 800h

  jz    short DOINIT_FALSE

  push  8

  pop   ecx         ; ecx = 8

  push  4

  add   eax, 8      ; 指向第二項HANDLE_TABLE_ENTRY指針

  pop   edi         ; 記錄空閑項鏈表,當前為第二項HANDLE_TABLE_ENTRY值為4

 

END_LOOP:    

  ; 設置當前項的下一個空閑句柄索引

mov   [eax+_HANDLE_TABLE_ENTRY.___u1.NextFreeTableEntry], ecx   mov   [eax+_HANDLE_TABLE_ENTRY.___u0.Value], ebx ; 設置句柄值為0

  add   ecx, edi    ; sizeof(HANDLE_TABLE_ENTRY) = 8,步長為8

  add   eax, 8      ; 指向下個HANDLE_TABLE_ENTRY項

  cmp   ecx, edx    ; 判斷是否到頁表倒數第二項

  jb    short END_LOOP ; 設置當前項的下一個空閑句柄索引

  mov   [eax], ebx

  ; 頁表的最后一項NextFreeTableEntry設置為0,即無下一個空閑句柄

  mov   [eax+_HANDLE_TABLE_ENTRY.___u1.NextFreeTableEntry], ebx   mov   [esi+_HANDLE_TABLE.FirstFree], edi   ;設置當前項的下一個空閑項

建立進程與句柄表的映射關系

DOINIT_FALSE:      

  mov   eax, [ebp+pProcess]

  ;初始化HANDLE_TABLE的QuotaProcess為傳入的EPROCESS指針

  mov   [esi+_HANDLE_TABLE.QuotaProcess], eax

mov   eax, large fs:124h   ; 獲取當前線程指針

;設置NextHandleNeedingPool句柄表擴展的起始頁句柄索,

; NextHandleNeedingPool記錄的是以頁為單位

mov [esi+_HANDLE_TABLE.NextHandleNeedingPool],edx

;獲取到當前進程EPROCESS指針

mov   eax, [eax+_KTHREAD.___u6.ApcState.Process]

  mov   eax, [eax+_EPROCESS.UniqueProcessId] ; 獲取進程ID

  mov   [esi+_HANDLE_TABLE.UniqueProcessId], eax ; 填充HADLE_TABLE

;的UniqueProcessId域

  xor   eax, eax

  mov   [esi+_HANDLE_TABLE.___u12.Flags], ebx ; 設置標記為0

下圖反映初始化的句柄表示意圖:

 


免責聲明!

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



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