句柄表(私有句柄表)
- 2019 年 10 月 30 日
- 筆記
Windows內核分析索引目錄:https://www.cnblogs.com/onetrainee/p/11675224.html
句柄表(私有句柄表)
我們在R3環編程中,會接觸到句柄HANDLE的概念。
比如OPENPROCESS,打開進程獲取其進程句柄,這些被稱為“內核句柄”。
注意,與GUI圖形界面不同,那些 畫刷句柄 被稱為“用戶句柄”,不在我們討論範圍之列。
一、 句柄表的基本概念
句柄表分為私有私有句柄表和全局句柄表,我們這一節只討論私有句柄表。
每一個進程都有自己的私有句柄表,在一個進程中使用OpenProcess打開另一個進程時,則會將被打開進程在內核對象的 _EPROCESS 結構體完整的映射到打開進程的私有句柄表中。
注意:是映射,不是創建,被打開進程的_EPROCESS在創建時就已經存儲到全局句柄表中了,其他進程打開時直接從這裡映射一份即可。
二、句柄表有關的結構
1. 獲取_HANDLE_TABLE結構體
私有句柄表存儲在_EPROCESS+0x0c4 這個位置,其指針指向一個 _HANDLE_TABLE結構體
2. _HANDLE_TABLE 結構體
我們使用 dt _HANDLE_TABLE 0xe26cc488,該結構的第一個成員 TableCode就是句柄表存儲的位置。
kd> dt 0xe26cc488 _HANDLE_TABLE
ntdll!_HANDLE_TABLE
+0x000 TableCode : 0xe10af000
+0x004 QuotaProcess : 0x81eedb40 _EPROCESS
+0x008 UniqueProcessId : 0x000001e8 Void
3. 句柄表的結構
句柄表分為多級結構,一般為一級結構,當句柄值太大時則會展開多級結構,以後三位為準。
一個句柄值佔8個位元組,一頁4KB,故一頁能存儲 512 個句柄值。
如果採用多級結構(以兩級為例),第一級就會存儲第二級的地址。存儲地址則能存儲 1024 個地址,這樣算下來採用兩級結構可以存儲 512*1024 個值。
我們觀察是否採用多級結構,是看 TableCode的最後一位,比如 為 1,則採用二級結構,依次類推…
4. 句柄值結構
在句柄表中一個句柄值佔8位元組,故我們採用dq來查看。
kd> dq 0xe10af000+1d1*8
e10afe88 0000003a`816c800b 0000003a`816c800b
e10afe98 0000003a`816c800b 0000003a`816c800b
其前4個位元組是關於屬性,後面4位元組才是真正存儲句柄的值。
注意,後3位是關於屬性,要真正找到正確的地址,要變為0,比如 816c800b ,b = 1011 , 正確的應該為 1000,即 816c8008。
5. 如何使用R3環的句柄值查找R0中的句柄值
我們在三環,hPro = OpenProcess(),將 t = hPro / 4,所得到的結果就是在句柄表中的索引號t,其佔8位,故需要 t*8 得到地址。
6. 句柄的結構體
從結構體中來看,句柄可以看作 句柄頭+句柄體
句柄頭是 _OHJECT_HEADER;句柄體就是其句柄的本體,比如線程的句柄體為 _ETHREAD,進程的句柄體為 _EPROCESS
句柄體在句柄頭下方 +0x18 的位置。
kd> dt _OBJECT_HEADER
nt!_OBJECT_HEADER
+0x000 PointerCount : Int4B
····
+0x014 SecurityDescriptor : Ptr32 Void
+0x018 Body : _QUAD
因此,如果我們打算查看 _EPROCESS,就需要偏移 +0x18個位元組。
e2808380 81b1e5b3 0000003a 81b1e5b3 0000003a
kd> dt _EPROCESS 81b1e5b0+18
ntdll!_EPROCESS
+0x174 ImageFileName : [16] “calc.exe”
7. 句柄值的屬性
前面我們介紹前句柄值佔8位,前四個位元組代表屬性,下面我們就來介紹一下。
舉一個例子,我們使用下面代碼來修改最後一個的句柄的屬性
SetHandleInformation(hPro,HANDLE_FLAG_PROTECT_FROM_CLOSE,HANDLE_FLAG_PROTECT_FROM_CLOSE);
然後通過windbg查看,可以明顯看到與其他存在的不同:
e10fdc80 816899cb 0200003a 816899cb 0000003a
三、通過實驗來驗證句柄表有關信息:
1)實驗代碼 handle_test.exe

1 // handle_test.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 #include <Windows.h> 6 #include <stdlib.h> 7 #include <stdio.h> 8 void Test(){ 9 DWORD PID; 10 HANDLE hPro = NULL; 11 HWND hWnd = ::FindWindow(NULL,"計算器"); 12 ::GetWindowThreadProcessId(hWnd,&PID); 13 for(int i = 0; i<100;i++){ 14 hPro = ::OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,TRUE,PID); 15 printf("handle value: %x n", hPro); 16 } 17 // 修改屬性 18 SetHandleInformation(hPro,HANDLE_FLAG_PROTECT_FROM_CLOSE,HANDLE_FLAG_PROTECT_FROM_CLOSE); 19 } 20 int main(int argc, char* argv[]) 21 { 22 printf("Hello World!n"); 23 Test(); 24 system("pause"); 25 return 0; 26 }
View Code
2)實驗原理:
1. 先將計算器打開。
2. handle_test.exe 會先查找計算器的窗口獲取PID,然後打開該進程。
3. 這樣就會在 handle_test.exe 進程內部創建關於計算器的句柄的值,我們來研究該值。
3)實驗流程:
1)獲取 句柄值 640,按照 【二、5】 所描述的,地址在TableCode中的為 640/4*8 = C80.
2)通過 windbg 的!process 0 0來查找 handle_test.exe 的 _EPROCESS地址。
Failed to get VadRoot
PROCESS 81c45020 SessionId: 0 Cid: 07cc Peb: 7ffd3000 ParentCid: 00a8
DirBase: 1af46000 ObjectTable: e154d450 HandleCount: 123.
Image: handle_test.exe
3)先從 0x174 中驗證其是否是該進程
+0x174 ImageFileName : [16] “handle_test.exe”
之後從 0xc4位置找到句柄表地址
+0x0c4 ObjectTable : 0xe154d450 _HANDLE_TABLE
4)之後,我們從 句柄表地址來查看 TableCode 0xe10fd000,加上之前算的 C80 偏移得到句柄值。
kd> dt 0xe154d450 _HANDLE_TABLE
ntdll!_HANDLE_TABLE
+0x000 TableCode : 0xe10fd000
+0x004 QuotaProcess : 0x81c45020 _EPROCESS
+0x008 UniqueProcessId : 0x000007cc Void
+0x00c HandleTableLock : [4] _EX_PUSH_LOCK
+0x01c HandleTableList : _LIST_ENTRY [ 0xe2728354 – 0xe24faafc ]
+0x024 HandleContentionEvent : _EX_PUSH_LOCK
+0x028 DebugInfo : (null)
5)按照【二、4】所介紹的,取得的值 816899cb,變為 816899c1,這指向 OBJECT_HEAD,偏移+0x18位元組才真正指向 BODY(_EPROCESS)
kd> dq 0xe10fd000+c80
e10fdc80 0200003a`816899cb
6)驗證我們的 _EPROCESS,驗證通過,實驗成功。
kd> dt _EPROCESS 816899c8 + 18
ntdll!_EPROCESS
+0x174 ImageFileName : [16] “calc.exe”