Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.htmlhtml
句柄表(私有句柄表)编程
咱们在R3环编程中,会接触到句柄HANDLE的概念。app
好比OPENPROCESS,打开进程获取其进程句柄,这些被称为“内核句柄”。ide
注意,与GUI图形界面不一样,那些 画刷句柄 被称为“用户句柄”,不在咱们讨论范围之列。
spa
1、 句柄表的基本概念线程
句柄表分为私有私有句柄表和全局句柄表,咱们这一节只讨论私有句柄表。指针
每个进程都有本身的私有句柄表,在一个进程中使用OpenProcess打开另外一个进程时,则会将被打开进程在内核对象的 _EPROCESS 结构体完整的映射到打开进程的私有句柄表中。
code
注意:是映射,不是建立,被打开进程的_EPROCESS在建立时就已经存储到全局句柄表中了,其余进程打开时直接从这里映射一份便可。orm
2、句柄表有关的结构htm
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
3、经过实验来验证句柄表有关信息:
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 }
2)实验原理:
1. 先将计算器打开。
2. handle_test.exe 会先查找计算器的窗口获取PID,而后打开该进程。
3. 这样就会在 handle_test.exe 进程内部建立关于计算器的句柄的值,咱们来研究该值。
3)实验流程:
1)获取 句柄值 640,按照 【2、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)按照【2、4】所介绍的,取得的值 816899cb,变为 816899c8,这指向 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"