32位参考:https://blog.youkuaiyun.com/Kwansy/article/details/109801722
一、内核对象,句柄
这次课讨论的内核对象是指创建时需要指定 LPSECURITY_ATTRIBUTES 参数的对象,例如 Mutex, Thread。
调用 CreateThread 等函数会返回一个 HANDLE 类型值,这种就叫句柄,它对应一个内核对象;
调用 CloseHandle 函数对某个内核对象计数减一,当内核对象计数为0,这个对象就被销毁了。
内核对象在内核存储,直接把地址给3环用很不安全,所以微软设计了句柄(HANDLE)给3环使用,句柄是一个整数,它的值除以4是句柄表的下标,通过下标能找到存储在句柄表里的句柄表项,每个占8字节。
二、句柄表,句柄表项
1.句柄表结构
句柄表存储在 EPROCESS.ObjectTable.TableCode 里:
kd> dt _ePROCESS fffffa801adab060
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x160 ProcessLock : _EX_PUSH_LOCK
+0x168 CreateTime : _LARGE_INTEGER 0x01d77e09`56302ac0
+0x170 ExitTime : _LARGE_INTEGER 0x0
+0x178 RundownProtect : _EX_RUNDOWN_REF
+0x180 UniqueProcessId : 0x00000000`00000be4 Void
+0x188 ActiveProcessLinks : _LIST_ENTRY [ 0xfffff800`0402bb90 - 0xfffffa80`1ac42cb8 ]
+0x198 ProcessQuotaUsage : [2] 0x3c10
+0x1a8 ProcessQuotaPeak : [2] 0x3df0
+0x1b8 CommitCharge : 0x6a3
+0x1c0 QuotaBlock : 0xfffffa80`1a37f400 _EPROCESS_QUOTA_BLOCK
+0x1c8 CpuQuotaBlock : (null)
+0x1d0 PeakVirtualSize : 0x8fc2000
+0x1d8 VirtualSize : 0x7bfe000
+0x1e0 SessionProcessLinks : _LIST_ENTRY [ 0xfffff880`0526a010 - 0xfffffa80`1ac4a580 ]
+0x1f0 DebugPort : (null)
+0x1f8 ExceptionPortData : 0xfffffa80`1a2c5730 Void
+0x1f8 ExceptionPortValue : 0xfffffa80`1a2c5730
+0x1f8 ExceptionPortState : 0y000
+0x200 ObjectTable : 0xfffff8a0`010b3c50 _HANDLE_TABLE !!在这里
+0x208 Token : _EX_FAST_REF
+0x210 WorkingSetPage : 0x73cbe
+0x218 AddressCreationLock : _EX_PUSH_LOCK
((ntdll!_HANDLE_TABLE *)0xfffff8a0010b3c50) : 0xfffff8a0010b3c50 [Type: _HANDLE_TABLE *]
[+0x000] TableCode : 0xfffff8a0015b5000 [] 第2位代表几级句柄表
[+0x008] QuotaProcess : 0xfffffa801adab060 [Type: _EPROCESS *] 当前进程结构体
[+0x010] UniqueProcessId : 0xbe4 [Type: void *] 进程ID
[+0x018] HandleLock [Type: _EX_PUSH_LOCK]
[+0x020] HandleTableList [Type: _LIST_ENTRY]
[+0x030] HandleContentionEvent [Type: _EX_PUSH_LOCK]
[+0x038] DebugInfo : 0x0 [Type: _HANDLE_TRACE_DEBUG_INFO *]
[+0x040] ExtraInfoPages : 0 [Type: long]
[+0x044] Flags : 0x0 [Type: unsigned long]
[+0x044 ( 0: 0)] StrictFIFO : 0x0 [Type: unsigned char]
[+0x048] FirstFreeHandle : 0x248 [Type: unsigned long]
[+0x050] LastFreeHandleEntry : 0xfffff8a0015b5ff0 [Type: _HANDLE_TABLE_ENTRY *]
[+0x058] HandleCount : 0x90 [Type: unsigned long] 句柄数量
[+0x05c] NextHandleNeedingPool : 0x400 [Type: unsigned long] //分配下一层句柄表之前可以容纳的最大句柄的值
[+0x060] HandleCountHighWatermark : 0x94 [Type: unsigned long]
特别留意 TableCode 的第2位,它表明了句柄表的结构,如果低两位是0,则一级表大小4096/16= 256,如果低两位是01,表示现在句柄表有两级, TableCode 指向的表存储了 4096/8256个句柄表的地址,每个地址指向一个句柄表。如果低两位是02,则存储 4096/84096/8*256
举个例子
先用一层表举例子()
1: kd> dq 0xfffff8a0015b5000 l14
fffff8a0`015b5000 00000000`00000000 fffff8a0`fffffffe
fffff8a0`015b5010 fffff8a0`0155b8b1 00000000`00000009
fffff8a0`015b5020 fffff8a0`02e979b1 00000000`00000003
fffff8a0`015b5030 fffffa80`1a3684a1 00000000`00100020
fffff8a0`015b5040 fffffa80`1ad22531 00000000`00100020
fffff8a0`015b5050 fffffa80`1a6efc81 00000000`00100020
fffff8a0`015b5060 fffffa80`18e96e31 00000000`001f0001
fffff8a0`015b5070 fffff8a0`00c05f71 00000000`00020019
fffff8a0`015b5080 fffff8a0`00eea1d1 00000000`00000001
fffff8a0`015b5090 fffffa80`1ada24e1 00000000`021f0003
由图可知,句柄表内结构是这样
struct handle
{
LONG64 Objress //对象低三位为属性,需要清空,另外需要补上_OBJECT_HEADER的大小
LONG64 属性
}
但是肉眼看的可以看见对象地址和ARK地址是不一致的,主要原因是属性位和_OBJECT_HEADER的原因
句柄表内:1.对象低三位为属性,需要清空
2.所有对象前面都有一个_OBJECT_HEADER结构,而句柄结构在这个结构的后面,64位下这个结构大小为0X30
nt!_OBJECT_HEADER
+0x000 PointerCount : Int8B
+0x008 HandleCount : Int8B
+0x008 NextToFree : Ptr64 Void
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : UChar
+0x019 TraceFlags : UChar
+0x019 DbgRefTrace : Pos 0, 1 Bit
+0x019 DbgTracePermanent : Pos 1, 1 Bit
+0x01a InfoMask : UChar
+0x01b Flags : UChar // 0x71; //进程退出
+0x01b NewObject : Pos 0, 1 Bit
+0x01b KernelObject : Pos 1, 1 Bit
+0x01b KernelOnlyAccess : Pos 2, 1 Bit
+0x01b ExclusiveObject : Pos 3, 1 Bit
+0x01b PermanentObject : Pos 4, 1 Bit
+0x01b DefaultSecurityQuota : Pos 5, 1 Bit
+0x01b SingleHandleEntry : Pos 6, 1 Bit
+0x01b DeletedInline : Pos 7, 1 Bit
+0x01c Reserved : Uint4B
+0x020 ObjectCreateInfo : Ptr64 _OBJECT_CREATE_INFORMATION
+0x020 QuotaBlockCharged : Ptr64 Void
+0x028 SecurityDescriptor : Ptr64 Void
+0x030 Body : _QUAD //句柄的对象在这个位置
FFFFFA801A449603 & FFFFFFFFFFFFFFF8 + 0X30 = 0XFFFF FA80 1A44 9630
现在WINDBG的对象就和ARK一致了,windbg访问对象结构也没问题了
dt _eprocess 0XFFFF FA801A44963
二、查看一个二级句柄表
现在进程大多数是一级和二级句柄表,拿个二级表举个例子
让我们来找找这个句柄吧 0x468
句柄是需要/4的,0x468/4取值,如果小于0x100就在第一张表,0x100<值<0x200就在第二张表,依次类推
0x468/4=11A,
11A-0X100=1A,
可知该句柄在第二张表的1A位置
公式:第二张表地址1A0x10=句柄对象结构
//和ARK工具一致,差0x30还是_OBJECT_HEADER结构问题,前面已经解释了
3: kd> dq fffff8a0`01e0e000+1a*10
fffff8a0`01e0e1a0 fffffa80`1a5ea411 00000000`00100020
fffff8a0`01e0e1b0 fffffa80`1a490161 00000000`00000804
fffff8a0`01e0e1c0 fffffa80`194df771 00000000`00000804
fffff8a0`01e0e1d0 fffffa80`194df6b1 00000000`00000804
fffff8a0`01e0e1e0 fffffa80`194df5f1 00000000`00000804
fffff8a0`01e0e1f0 fffffa80`194df531 00000000`00000804
fffff8a0`01e0e200 fffffa80`194df471 00000000`00000804
fffff8a0`01e0e210 fffffa80`1a97f431 00000000`00100003
我们看到fffffa80`1a5ea411 最后一个字节为1,其实最后三位是属性位,使用时需要清0
那么我们看看这三位代表什么吧
至此,已经知道句柄表的寻找方式了,那么取到的对象可以来查看这些信息
首先是查看句柄对象的信息,用到这个结构体
3: kd> dt _object_header fffffa80`1a5ea410 //第三位要清0,上面讲了
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n1
+0x008 HandleCount : 0n1
+0x008 NextToFree : 0x00000000`00000001 Void
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : 0x1c '' //和ARK一致为28 因为加密了所以不做参考,下节课有解密讲解
+0x019 TraceFlags : 0 ''
+0x01a InfoMask : 0xc ''
+0x01b Flags : 0x40 '@'
+0x020 ObjectCreateInfo : 0xfffffa80`1a374040 _OBJECT_CREATE_INFORMATION
+0x020 QuotaBlockCharged : 0xfffffa80`1a374040 Void
+0x028 SecurityDescriptor : (null)
+0x030 Body : _QUAD
下面看看句柄的权限结构信息,和遍历句柄号一样
3: kd> dt _handle_table_entry fffff8a0`01e0e000+1a*10
ntdll!_HANDLE_TABLE_ENTRY
+0x000 Object : 0xfffffa80`1a5ea411 Void
+0x000 ObAttributes : 0x1a5ea411
+0x000 InfoTable : 0xfffffa80`1a5ea411 _HANDLE_TABLE_ENTRY_INFO
+0x000 Value : 0xfffffa80`1a5ea411
+0x008 GrantedAccess : 0x100020
+0x008 GrantedAccessIndex : 0x20
+0x00a CreatorBackTraceIndex : 0x10
+0x008 NextFreeTableEntry : 0x100020
dt _eprocess fffffa80`1a5ea410+30