Windows 对象管理
😄
1 对象目录
1.1 对象目录管理
对象分为内核对象、用户对象。用户对象主要用于 GUI 下(没有对象头)。
- 内核对象:包含对象头、对象体。
- 无名对象,其他进程不能通过对象名打开对象,对象不可以共享。不命名的对象一般意义不大。
- 命名对象,可以通过对象名与其他进程共享内核对象。
- 用户对象:不具有对象头,参见《GUI Subsystem 用户对象》。
在系统中,通过对象目录对命名对象进行组织管理,对象目录是树状结构而非网状结构。
- 根节点: 目录对象(
_OBJECT_DIRECTORY)。根节点对象名为\。 - 中间节点:目录对象(
_OBJECT_DIRECTORY)、符号链接对象(_OBJECT_SYMBOLIC_LINK)。 - 叶节点:普通内核对象、目录对象(
_OBJECT_DIRECTORY)。
说明:根节点是一个数据目录类型,节点名为 “\”,其为 37 个元素的哈希数组,每个元素串起一个单链表队列,同一个链表上的所有节点对象名的 Hash 值相同。

目录对象:可以是根节点、中间节点、叶节点。
1 | //0x158 bytes (sizeof) |
主要关注第一个成员,可以看到它是一个含有 37 = NUMBER_HASH_BUCKETS 个成员的指针数组,每个成员都指向 _OBJECT_DIRECTORY_ENTRY 结构。
1 | //0x18 bytes (sizeof) |
- ChainLink:指向下一个
_OBJECT_DIRECTORY_ENTRY,是个单向链表。同一条链表上的所有节点对象名的 Hash 值相同。 - Object:指向该 Entry 所管理的那个对象,比如
EPROCESS,ETHREAD等等。 - HashValue:该 Entry 的 Hash 值
注意:目录项 _OBJECT_DIRECTORY_ENTRY 并非内核对象,但是每一个内核对象插入到对象目录树时,必须有目录项来进行承接才行。
符号链接 _OBJECT_SYMBOLIC_LINK:
1 | //0x28 bytes (sizeof) |
其中 LinkTarget 就是其关联的对象名,关于该对象的解析可看 DKOM – Now with Symbolic Links!。
节点遍历:
如果从根节点或某个中间节点开始遍历某个对象,在路径上的每个节点前加 “\” 进行分隔,就形成一个路径名。
- 绝对路径:根节点\中间节点\叶节点。
- 相对路径:中间节点\叶节点。

1.2 创建目录对象
创建目录对象使用 ZwCreateDirectoryObject 函数。
1 | NTSYSAPI NTSTATUS ZwCreateDirectoryObject( |
DesiredAccess:该参数除了可以使用
ACCESS_MASK值进行定义外,还可以指定以下一个或多个权限:ACCESS_MASK标志 访问类型 DIRECTORY_QUERY 查询 DIRECTORY_TRAVERSE 名称查找 DIRECTORY_CREATE_OBJECT 名称创建 DIRECTORY_CREATE_SUBDIRECTORY 创建子目录 DIRECTORY_ALL_ACCESS 上述所有类型 使用
ObjectAttributes结构指针前,必须先调用 InitializeObjectAttributes 对其进行初始化。
该函数核心是通过 ObCreateObject 创建目录对象,然后再用 ObInsertObject 将对象插入到句柄表中。
对象层次目录中插入,查询和删除目录或目录项常用的函数如下:
- ObpLookupDirectoryEntry:在一个指定的目录中查找一个指定名称的对象。
- ObpInsertDirectoryEntry:把一个对象插入到一个目录中。
- ObpDeleteDirectoryEntry:删除指定目录中的指定对象。
1.2 对象遍历
查看/遍历内核对象主要有三种方法:
- 通过 WinObj.exe 工具(以管理员权限运行)。
- 通过 Windbg(使用全局指针
ObpRootDirectoryObject)。 - 通过代码遍历。
一、WinObj.exe 工具查看内核对象(以管理员权限运行)
可以从微软官方 SysinternalsSuite 下载该工具。

可以看到根节点是 \,然后下面的中间节点是目录对象或符号链接对象。如子目录 Device 二级目录下还有 4 个三级目录。
二、通过 Windbg 查看
通过全局指针
ObpRootDirectoryObject找到根目录节点。1
2
3kd> dq ObpRootDirectoryObject
fffff800`588440a8 ffff9e02`12423ce0 ffffaf8f`846b1380
fffff800`588440b8 00000000`00000000 ffffaf8f`846b1380可以看到根节点的地址为
0xffff9e02’12423ce0。查看根节点目录。
1
2
3
4
5
6
7
8
9
10kd> dt _OBJECT_DIRECTORY ffff9e02`12423ce0
ntdll!_OBJECT_DIRECTORY
+0x000 HashBuckets : [37] (null)
+0x128 Lock : _EX_PUSH_LOCK
+0x130 DeviceMap : (null)
+0x138 ShadowDirectory : (null)
+0x140 NamespaceEntry : (null)
+0x148 SessionObject : (null)
+0x150 Flags : 0
+0x154 SessionId : 0xffffffff查看
HashBuckets数组。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39kd> dx -id 0,0,ffffaf8f846a1040 -r1 (*((nt!_OBJECT_DIRECTORY_ENTRY * (*)[37])0xffff9e0212423ce0))
(*((nt!_OBJECT_DIRECTORY_ENTRY * (*)[37])0xffff9e0212423ce0)) [Type: _OBJECT_DIRECTORY_ENTRY * [37]]
[0] : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[1] : 0xffff9e0212bbbd00 [Type: _OBJECT_DIRECTORY_ENTRY *]
[2] : 0xffff9e02183524e0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[3] : 0xffff9e0212547550 [Type: _OBJECT_DIRECTORY_ENTRY *]
[4] : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[5] : 0xffff9e02124b45d0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[6] : 0xffff9e0215b52f60 [Type: _OBJECT_DIRECTORY_ENTRY *]
[7] : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[8] : 0xffff9e02124b43c0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[9] : 0xffff9e0218351d90 [Type: _OBJECT_DIRECTORY_ENTRY *]
[10] : 0xffff9e021883a0e0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[11] : 0xffff9e0212bbbdc0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[12] : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[13] : 0xffff9e0212bbeaf0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[14] : 0xffff9e02125450f0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[15] : 0xffff9e0212cfe6e0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[16] : 0xffff9e02124af630 [Type: _OBJECT_DIRECTORY_ENTRY *]
[17] : 0xffff9e0212bbc330 [Type: _OBJECT_DIRECTORY_ENTRY *]
[18] : 0xffff9e02124b49f0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[19] : 0xffff9e0212cfe860 [Type: _OBJECT_DIRECTORY_ENTRY *]
[20] : 0xffff9e0212b66be0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[21] : 0xffff9e0217efd440 [Type: _OBJECT_DIRECTORY_ENTRY *]
[22] : 0xffff9e0212bbc090 [Type: _OBJECT_DIRECTORY_ENTRY *]
[23] : 0xffff9e0212537580 [Type: _OBJECT_DIRECTORY_ENTRY *]
[24] : 0xffff9e02125472e0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[25] : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[26] : 0xffff9e0215b52360 [Type: _OBJECT_DIRECTORY_ENTRY *]
[27] : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[28] : 0xffff9e02183523f0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[29] : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[30] : 0xffff9e0212547130 [Type: _OBJECT_DIRECTORY_ENTRY *]
[31] : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[32] : 0xffff9e0212565a40 [Type: _OBJECT_DIRECTORY_ENTRY *]
[33] : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[34] : 0xffff9e0215b52db0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[35] : 0xffff9e0218352870 [Type: _OBJECT_DIRECTORY_ENTRY *]
[36] : 0xffff9e0217ef8a00 [Type: _OBJECT_DIRECTORY_ENTRY *]可以看到,如下标 0、4、7、12 的目录项下面没有数据。
以下标
1为例查看其链表队列。1
2
3
4
5
6
7
8
9
10
11kd> dx -id 0,0,ffffaf8f846a1040 -r1 ((ntkrnlmp!_OBJECT_DIRECTORY_ENTRY *)0xffff9e0212bbbd00)
((ntkrnlmp!_OBJECT_DIRECTORY_ENTRY *)0xffff9e0212bbbd00) : 0xffff9e0212bbbd00 [Type: _OBJECT_DIRECTORY_ENTRY *]
[+0x000] ChainLink : 0xffff9e02124af3c0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[+0x008] Object : 0xffffaf8f87c66e50 [Type: void *]
[+0x010] HashValue : 0xb8888f8d [Type: unsigned long]
kd> dx -id 0,0,ffffaf8f846a1040 -r1 ((ntkrnlmp!_OBJECT_DIRECTORY_ENTRY *)0xffff9e02124af3c0)
((ntkrnlmp!_OBJECT_DIRECTORY_ENTRY *)0xffff9e02124af3c0) : 0xffff9e02124af3c0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[+0x000] ChainLink : 0x0 [Type: _OBJECT_DIRECTORY_ENTRY *]
[+0x008] Object : 0xffff9e0212426230 [Type: void *]
[+0x010] HashValue : 0x6c1c47fb [Type: unsigned long]可以看到根节点
OBJECT_DIRECTORY.HashBuckets[1]链表上挂着两个节点,下面分别查看一下节点类型。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21kd> !object 0xffffaf8f87c66e50
Object: ffffaf8f87c66e50 Type: (ffffaf8f8468e4e0) Mutant
ObjectHeader: ffffaf8f87c66e20 (new version)
HandleCount: 0 PointerCount: 1
Directory Object: ffff9e0212423ce0 Name: PendingRenameMutex
kd> !object 0xffff9e0212426230
Object: ffff9e0212426230 Type: (ffffaf8f846b14e0) Directory
ObjectHeader: ffff9e0212426200 (new version)
HandleCount: 0 PointerCount: 69
Directory Object: ffff9e0212423ce0 Name: ObjectTypes
Hash Address Type Name
---- ------- ---- ----
00 ffffaf8f846b9380 Type TmTm
01 ffffaf8f8468ed20 Type Desktop
ffffaf8f8468f140 Type Process
02 ffffaf8f846b9d20 Type EnergyTracker
ffffaf8f846b9220 Type RegistryTransaction
03 ffffaf8f8468ff00 Type DebugObject
...第一个节点是
Mutant、第二个节点为Directory。
1.3 对象名哈希值计算
待办:需要逆向分析 ObpLookupDirectoryEntry 函数,解析出函数名的 Hash 过程。https://bbs.kanxue.com/thread-262316.htm。
- https://blog.csdn.net/feihuadao/article/details/46620877
- https://www.cnblogs.com/oneway1990/p/8496775.html
- https://developer.aliyun.com/article/920442
- https://www.4hou.com/posts/wkBg
- https://www.pediy.com/kssd/pediy11/114734/820481/44303.pdf
- http://mykings.me/2017/04/06/反检测技术三:Shellcode-炼金术/
- https://paper.seebug.org/1743/
- https://blog.csdn.net/qq_37232329/article/details/127268313
ObpLookupDirectoryEntry:
1 | PAGE:000000014078122C // PVOID ObpLookupDirectoryEntry( |
ObpLookupDirectoryEntryEx:
1 | PAGE:00000001406E4C98 // __int64 __usercall ObpLookupDirectoryEntryEx@<rax>(PVOID Object@<rcx>, char, int, __int64) |
代码还原如下:
1 | ULONG64 ObpLookupDirectoryEntryEx( |
1.4 符号链接
DKOM – Now with Symbolic Links!
2 对象结构
一个内核对象由以下五部分构成:
- 对象内存头
- 可选信息头
- 对象头
- 对象体
- 对象脚

1.1 POOL_HEADER
在每块系统内存池被分配的时候,都会默认分配该结构(在申请返回的内存前)。系统内存池即内核 System 进程的堆。
1 | kd> dt nt!_POOL_HEADER |
POOL_HEADER 结构用来描述每一块已经分配的内核内存块属性。该结构从 Windows 10 1909开始发生了使用上的变化。具体需要研究分页/非分页内存池的分配相关内容。以下文章较详细描述了 POOL_HEADER 的意义和用途:
- Scoop the Windows 10 pool ! 翻译 & 复现
- Windows-kernel-SegmentHeap-Aligned-Chunk-Confusion
- Scoop the Windows 10 pool!
- Windows内核池风水利用工具研究
- windows内核之池溢出(七)
- Data-only Pwning Microsoft Windows Kernel: Exploitation of Kernel Pool Overflows on Microsoft Windows 8.1
- Discovering and exploiting a kernel pool overflow On modern Windows 10
- Windows 7 Kernel Pool Internals

1.2 可选信息头
目前,可选信息头共有 8 个,理论上已经全部使用,因为 InfoMask 是 UCHAR 类型,共 8 Bit(每个 Bit 对应一种可选信息头)。

特别说明:扩展信息可选头如果存在,就意味着有对象脚存在。_OBJECT_HEADER_EXTENDED_INFO 中有成员指向对象脚结构。
每个可选信息头的大小、位置顺序都是固定的。如何确定一个可选信息头是否存在:
OBJECT_HEADER_CREATOR_INFO:由OBJECT_HEADER.Flags决定是否存在。- 其他 7 个可选信息头:由
OBJECT_HEADER.InfoMask决定是否存在。
每个可选信息头对应 OBJECT_HEADER.InfoMask 1 Byte 大小数据中的一位掩码(如下表8-17),然后使用 ObpInfoMaskToOffset[InfoMask & (DesiredHeaderBit | (DesiredHeaderBit-1))] 找到期望的可选信息头的偏移,最后用 OBJECT_HEADER 起始地址减去偏移即可得到可选信息头的起始地址。
该公式参考 CodeMachine - Article - Windows Object Headers。该文章指出,这个数组应该足够大,才能容纳 InfoMask(UCHAR) 8 Bits 共 $2^8$ 种可能性偏移。

可选信息头是否存在,是在系统初始化或者对象创建时决定的,存在的条件如表 8-18。

简单总结可选信息头存在的条件:
- 名称信息头
_OBJECT_HEADER_NAME_INFO:当一个对象是有名称对象时,该信息头存在。 - 配额信息头
_OBJECT_HEADER_QUOTA_INFO:如果对象是被系统初始化进程或空闲系统进程创建的,该信息头存在。 - 进程信息头
_OBJECT_HEADER_PROCESS_INFO:如果_OBJECT_HEADER.ExclusiveObject == 1,则信息头存在。 - 句柄信息头
_OBJECT_HEADER_HANDLE_INFO:如果句柄类型的_OBJECT_TYPE_INITIALIZER.MaintainHandleCount == 1,则信息头存在。 - 创建信息头
_OBJECT_HEADER_CREATOR_INFO:如果句柄类型的_OBJECT_TYPE_INITIALIZER.MaintainTypeList == 1,则信息头存在。 - Audit 信息头
_OBJECT_HEADER_AUDIT_INFO:只有文件对象才具有该信息头。 - 需要信息脚
OBJECT_FOOTER的对象,才具有_OBJECT_HEADER_EXTENDED_INFO扩展信息头。 - 填充信息头
_OBJECT_HEADER_PADDING_INFO:如果句柄类型的_OBJECT_TYPE_INITIALIZER.CacheAligned == 1,则信息头存在。
参考:
计算可选信息头偏移示例:
查看根目录下的所有对象
1 | kd> !object \ |
下面以对象名 PendingRenameMutex、ObjectTypes 进行举例。
PendingRenameMutex。
查看对象头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25kd> dt _OBJECT_HEADER 0xffffaf8f87c66e50-0x30
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n1
+0x008 HandleCount : 0n0
+0x008 NextToFree : (null)
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : 0xab ''
+0x019 TraceFlags : 0 ''
+0x019 DbgRefTrace : 0y0
+0x019 DbgTracePermanent : 0y0
+0x01a InfoMask : 0xa ''
+0x01b Flags : 0x10 ''
+0x01b NewObject : 0y0
+0x01b KernelObject : 0y0
+0x01b KernelOnlyAccess : 0y0
+0x01b ExclusiveObject : 0y0
+0x01b PermanentObject : 0y1
+0x01b DefaultSecurityQuota : 0y0
+0x01b SingleHandleEntry : 0y0
+0x01b DeletedInline : 0y0
+0x01c Reserved : 0x20
+0x020 ObjectCreateInfo : 0xfffff800`58872840 _OBJECT_CREATE_INFORMATION
+0x020 QuotaBlockCharged : 0xfffff800`58872840 Void
+0x028 SecurityDescriptor : 0xffff9e02`12b5fbae Void
+0x030 Body : _QUAD可以看到
InfoMask = 0xa,说明该对象有_OBJECT_HEADER_NAME_INFO和_OBJECT_HEADER_QUOTA_INFO可选信息头存在。计算
_OBJECT_HEADER_NAME_INFO头的偏移。这里期望的可选头
DesiredHeaderBit == 0x2,根据公示ObpInfoMaskToOffset[0xa & (0x2 | 0x1)] == ObpInfoMaskToOffset[2]。1
2
3
4
5
6kd> ? 0xa & (0x2 | (0x2 - 1))
Evaluate expression: 2 = 00000000`00000002
kd> db ObpInfoMaskToOffset+0x2
fffff800`58844dc2 20 40 10 30 30 50 20 40-40 60 30 50 50 70 10 30 @.00P @@`0PPp.0
fffff800`58844dd2 30 50 20 40 40 60 30 50-50 70 40 60 60 80 10 30 0P @@`0PPp@``..0可以看到偏移为
0x20。1
2
3
4
5
6kd> dt _OBJECT_HEADER_NAME_INFO 0xffffaf8f87c66e50-0x30-0x20
nt!_OBJECT_HEADER_NAME_INFO
+0x000 Directory : 0xffff9e02`12423ce0 _OBJECT_DIRECTORY // 根对象
+0x008 Name : _UNICODE_STRING "PendingRenameMutex"
+0x018 ReferenceCount : 0n0
+0x01c Reserved : 0x1f01ff计算
_OBJECT_HEADER_QUOTA_INFO头的偏移。期望的可选头
DesiredHeaderBit == 0x8,计算如下:1
2
3
4
5
6kd> ? 0xa & (0x8 | (0x8 - 1))
Evaluate expression: 10 = 00000000`0000000a
kd> db ObpInfoMaskToOffset+0xa
fffff800`58844dca 40 60 30 50 50 70 10 30-30 50 20 40 40 60 30 50 @`0PPp.00P @@`0P
fffff800`58844dda 50 70 40 60 60 80 10 30-30 50 20 40 40 60 30 50 Pp@``..00P @@`0P可以看到偏移为
0x40。1
2
3
4
5
6
7
8kd> dt _OBJECT_HEADER_QUOTA_INFO 0xffffaf8f87c66e50-0x30-0x40
nt!_OBJECT_HEADER_QUOTA_INFO
+0x000 PagedPoolCharge : 0 // 分页内存开支为 0,说明该对象是在非分页内存中申请的
+0x004 NonPagedPoolCharge : 0x90 // 非分页内存开支为 0x90
+0x008 SecurityDescriptorCharge : 0x3c
+0x00c Reserved1 : 0x14
+0x010 SecurityDescriptorQuotaBlock : 0xfffff800`58872840 Void
+0x018 Reserved2 : 0x00140011`00000001查看该对象所在内存页分配情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38kd> !pool 0xffffaf8f87c66e50
Pool page ffffaf8f87c66e50 region is Nonpaged pool
ffffaf8f87c66000 size: 50 previous size: 0 (Free) ....
ffffaf8f87c66050 size: c0 previous size: 0 (Allocated) FMsl
ffffaf8f87c66110 size: c0 previous size: 0 (Allocated) FMsl
ffffaf8f87c661d0 size: c0 previous size: 0 (Allocated) FMsl
ffffaf8f87c66290 size: c0 previous size: 0 (Allocated) FMsl
ffffaf8f87c66350 size: c0 previous size: 0 (Allocated) FMsl
ffffaf8f87c66410 size: c0 previous size: 0 (Allocated) FMsl
ffffaf8f87c664d0 size: c0 previous size: 0 (Allocated) FMsl
ffffaf8f87c66590 size: c0 previous size: 0 (Allocated) NDvb
ffffaf8f87c66650 size: c0 previous size: 0 (Allocated) FMsl
ffffaf8f87c66710 size: c0 previous size: 0 (Allocated) FMsl
ffffaf8f87c667d0 size: c0 previous size: 0 (Allocated) FMsl
ffffaf8f87c66890 size: c0 previous size: 0 (Allocated) HDAu
ffffaf8f87c66950 size: c0 previous size: 0 (Allocated) FMsl
ffffaf8f87c66a10 size: c0 previous size: 0 (Allocated) XHCW
ffffaf8f87c66ad0 size: c0 previous size: 0 (Allocated) XHCW
ffffaf8f87c66b90 size: c0 previous size: 0 (Allocated) cdro
ffffaf8f87c66c50 size: c0 previous size: 0 (Allocated) XHCW
ffffaf8f87c66d10 size: c0 previous size: 0 (Allocated) HDAu
*ffffaf8f87c66dd0 size: c0 previous size: 0 (Allocated) *Muta
Pooltag Muta : Mutant objects
ffffaf8f87c66e90 size: c0 previous size: 0 (Allocated) NDvb
*******************************************************************
*ffffaf8f87c66dd0 size: c0 previous size: 0 (Allocated) *Muta <=== POOL_HEADER
Pooltag Muta : Mutant objects
ffffaf8f87c66de0 00000000 00000090 0000003c 00000014 <=== OBJECT_HEADER_QUOTA_INFO
ffffaf8f87c66df0 58872840 fffff800 00000001 00140011
ffffaf8f87c66e00 12423ce0 ffff9e02 00240024 00000000 <=== OBJECT_HEADER_NAME_INFO
ffffaf8f87c66e10 12bda250 ffff9e02 00000000 001f01ff
ffffaf8f87c66e20 00000001 00000000 00000000 00000000 <=== OBJECT_HEADER
ffffaf8f87c66e30 00000000 00000000 100a00ab 00000020
ffffaf8f87c66e40 58872840 fffff800 12b5fbae ffff9e02
ffffaf8f87c66e50 00000002 00000001 87c66e58 ffffaf8f <=== PendingRenameMutex OBJECT_BODY
ffffaf8f87c66e60 87c66e58 ffffaf8f 00000000 00000000
ffffaf8f87c66e70 00000000 00000000 00000000 00000000
ffffaf8f87c66e80 00000000 00000000 880fc085 00000097可以看到该内核对象
POOL_HEADER == 0xffffaf8f87c66dd0,且Pooltag == Muta:1
2
3
4
5
6
7
8
9
10
11
12
13
14kd> dt _POOL_HEADER ffffaf8f87c66dd0
nt!_POOL_HEADER
+0x000 PreviousSize : 0y00000000 (0)
+0x000 PoolIndex : 0y10000011 (0x83)
+0x002 BlockSize : 0y00001100 (0xc)
+0x002 PoolType : 0y00000010 (0x2)
+0x000 Ulong1 : 0x20c8300
+0x004 PoolTag : 0x6174754d
+0x008 ProcessBilled : 0x0000010a`870f38f8 _EPROCESS
+0x008 AllocatorBackTraceIndex : 0x38f8
+0x00a PoolTagHash : 0x870f
kd> db ffffaf8f87c66dd0+0x4
ffffaf8f`87c66dd4 4d 75 74 61 f8 38 0f 87-0a 01 00 00 00 00 00 00 Muta.8..........
ffffaf8f`87c66de4 90 00 00 00 3c 00 00 00-14 00 00 00 40 28 87 58 ....<.......@(.X
ObjectTypes。
查看对象头。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25kd> dt nt!_OBJECT_HEADER 0xffff9e0212426230-0x30
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n69
+0x008 HandleCount : 0n0
+0x008 NextToFree : (null)
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : 0xb5 ''
+0x019 TraceFlags : 0 ''
+0x019 DbgRefTrace : 0y0
+0x019 DbgTracePermanent : 0y0
+0x01a InfoMask : 0x2 ''
+0x01b Flags : 0x12 ''
+0x01b NewObject : 0y0
+0x01b KernelObject : 0y1
+0x01b KernelOnlyAccess : 0y0
+0x01b ExclusiveObject : 0y0
+0x01b PermanentObject : 0y1
+0x01b DefaultSecurityQuota : 0y0
+0x01b SingleHandleEntry : 0y0
+0x01b DeletedInline : 0y0
+0x01c Reserved : 0x1f62f
+0x020 ObjectCreateInfo : 0x00000000`00000001 _OBJECT_CREATE_INFORMATION
+0x020 QuotaBlockCharged : 0x00000000`00000001 Void
+0x028 SecurityDescriptor : 0xffff9e02`1242662e Void
+0x030 Body : _QUAD可以看到
InfoMask == 0x2,说明该对象有_OBJECT_HEADER_NAME_INFO可选信息头存在。计算
_OBJECT_HEADER_NAME_INFO头的偏移。期望的可选头
DesiredHeaderBit == 0x2,计算如下:1
2
3
4
5
6kd> ? 0x2 & (0x2 | (0x2 - 1))
Evaluate expression: 2 = 00000000`00000002
kd> db ObpInfoMaskToOffset+0x2
fffff800`58844dc2 20 40 10 30 30 50 20 40-40 60 30 50 50 70 10 30 @.00P @@`0PPp.0
fffff800`58844dd2 30 50 20 40 40 60 30 50-50 70 40 60 60 80 10 30 0P @@`0PPp@``..0可以看到偏移为
0x20。1
2
3
4
5
6kd> dt nt!_OBJECT_HEADER_NAME_INFO 0xffff9e0212426230-0x30-0x20
nt!_OBJECT_HEADER_NAME_INFO
+0x000 Directory : 0xffff9e02`12423ce0 _OBJECT_DIRECTORY
+0x008 Name : _UNICODE_STRING "ObjectTypes"
+0x018 ReferenceCount : 0n0
+0x01c Reserved : 0x9f1fb
1.3 OBJECT_HEADER
对象头结构如下:
1 | kd> dt nt!_OBJECT_HEADER |

| 成员 | 偏移 | 解释 |
|---|---|---|
| PointerCount | +0x00 | 通过对象指针访问对象,表示对对象的引用总数,并且每次引用对象时都会增加,例如在查询有关对象的更多信息时。PreviousMode == 0 访问对象时使用。 |
| HandleCount NextToFree |
+0x08 | 以对象句柄访问对象,对象句柄的引用计数。PreviousMode == 1 访问对象时使用。 |
| Lock | +0x10 | 对 OBJECT_HEADER 或可选信息头进行更改前,需要上锁。Windows 7 以前只能对 OBJECT_TYPE.TypeLock 进行上锁,但是这样的弊端就是在整个系统中,在对象类型上锁期间,无法操作该类型的其他对象。为了更细粒度上锁,就有了 OBJECT_HEADER.Lock。 |
| TypeIndex | +0x18 | 指定 OBJECT_BODY 所属的类型。 |
| TraceFlags | +0x19 | 与跟踪和调试工具特别相关的附加标志。 |
| InfoMask | +0x1a | 用来指定哪些可选信息头存在。 |
| Flags PermanentObject |
+0x1b | 标志位,描述当前对象的安全属性、访问权限等。创建对象时,Flags 的信息来自于 _OBJECT_ATTRIBUTES。PermanentObject:如果 PermanentObject == 1,当该对象的引用计数减为 0 时不应该删除该对象。如果 PermanentObject == 0,在非分页内存申请的内核对象,如果 IRQL >= DISPATCH_LEVEL 且引用计数为 0 时不会将内核对象删除,降低 IRQL 后才可以删除该对象。 |
| ObjectCreateInfo | +0x20 | 在对象完全插入命名空间之前,存储有关对象创建的临时信息。 该字段在创建后转换为指向配额块的指针。 |
| SecurityDescriptor | +0x28 | 安全描述符,确定谁可以使用该对象以及他们可以用它做什么操作。 请注意,根据定义,未命名对象不能具有安全性。该字段是指向 _SECURITY_DESCRIPTOR 结构的指针,该结构可能包含安全标识符(SID)、DACL 和系统访问控制列表(SACL)。对象创建时,安全描述符来自于 OBJECT_ATTRIBUTES.SecurityDescriptor。 |
参考内容:
- Windows Internals 7 Part2 Chapter8 Object manager
- CodeMachine - Article - Windows Object Headers
- Object Headers, Handles and Types
- Object Headers, Handles and Types
- Journey Into the Object Manager Executive Subsystem: Object Header and Object Type
- Windows内核对象管理全景解析前奏
- Win7可变对象头结构之InfoMask解析
1.4 OBJECT_HEADER.Flags
OBJECT_HEADER.Flags 是一些标志位(UCHAR 类型仅 8 位),描述当前对象的属性、访问权限等。创建对象时,Flags 的信息来自于 _OBJECT_ATTRIBUTES。
当通过 Windows 子系统中的 APl(例如
CreateEvent或CreateFile)创建对象时,调用者不指定任何对象属性——子系统 DLL 在幕后执行工作。 因此,通过 Win32 创建的所有命名对象都位于BaseNamedObjects目录中,无论是全局对象还是每个会话中的对象,因为这个目录是Kernelbase.dll指定为对象指定的。 《Windows Internals 7 Part2 Chapter 8》
在创建对象时,先使用 InitializeObjectAttributes 将对象属性进行初始化赋值。其中指定的以 OBJ_ 开头的属性就是传递给 OBJECT_HEADER.Flags 的属性。
1 | VOID InitializeObjectAttributes( |
OBJ_ 开头属性和 OBJECT_HEADER.Flags 对照如下:
| Bitmask Flag | Attributes Flags | Header Flags Bit | Description |
|---|---|---|---|
| NewObject | N/A | OB_FLAG_NEW_OBJECT (0x01) | Define that the object was created but not stored into the namespace of the object.指定对象已创建但尚未插入到对象名称空间中。 |
| KernelObject | OBJ_KERNEL_HANDLE | OB_FLAG_KERNEL_OBJECT (0x02) | 指定此对象是内核对象,只能在 PreviousMode == 0 时才能访问。 |
| KernelOnlyAccess | OBJ_KERNEL_EXCLUSIVE | OB_FLAG_KERNEL_ONLY_ACCESS (0x04) | Define that only the kernel can open an handle to the object.只能在 PreviousMode == 0 时才能访问。 |
| ExclusiveObject | OBJ_EXCLUSIVE | OB_FLAG_EXCLUSIVE_OBJECT (0x08) | Define that only the process that created the object can access and use the object.定义只有创建该对象的进程才能访问和使用该对象。 |
| PermanentObject | OBJ_PERMANENT | OB_FLAG_PERMANENT_OBJECT(0x10) | Define that the object will not be disposed when the handle count drop to 0, if named object.如果是命名对象,当句柄计数下降到 0 时不会释放该对象。如果 PermanentObject == 1,当该对象的引用计数减为 0 时不应该删除该对象。如果 PermanentObject == 0,在非分页内存申请的内核对象,如果 IRQL >= DISPATCH_LEVEL 且引用计数为 0 时不会将内核对象删除,降低 IRQL 后才可以删除该对象。 |
| DefaultSecurityQuota | N/A | OB_FLAG_DEFAULT_SECURITY_QUOTA (0x20) | Define that the security descriptor use the default 2-KB quota.定义安全描述符使用默认的 2 KB 配额。 |
| SingleHandleEntry | N/A | OB_FLAG_SINGLE_HANDLE_ENTRY(0x40) | Define that the optional header OBJECT_HEADER_HANDLE_INFO structure only contains one entry and not a list of handle.定义可选头 OBJECT_HEADER_HANDLE_INFO 结构仅包含一个条目而不是句柄列表。 |
| DeletedInline | N/A | OB_FLAG_DELETED_INLINE (0x80) | Specifies that the object is not being de leted through the deferred deletion worker thread but rather inline through a call to ObDereferenceObject(Ex).指定不通过延迟删除工作线程删除对象,而是通过调用 ObDereferenceObject(Ex) 内联删除对象。 |
参考内容:
- Windows Internals 7 Part2 Chapter8 Object manager
- Journey Into the Object Manager Executive Subsystem: Object Header and Object Type
- OBJECT_ATTRIBUTES Structure
3 对象类型
对象是进行分类的,每个对象都有所属的类型。为了做到这一点,每个对象都需要对应到一个类型对象(OBJECT_TYPE)上去,类型对象用来描述该对象所属的类型及其一些属性。对象类型中的描述的属性是该类型下所有对象共有的属性。
系统为每一种对象类型创建了一个内核对象,用该对象来对某一类的对象进行描述,这个对象结构为 OBJECT_TYPE。系统中约有 64 种类型的对象,那就会有 64 个 OBJECT_TYPE 结构存在,每一个 OBJECT_TYPE 结构的 Name 成员指出当前类型对象所描述的对象类型。需要理清楚几个概念:
- 系统使用类型对象
OBJECT_TYPE来将各种对象类型进行分类,对相同类型的对象属性进行定义。 - 同一类内核对象对应的
OBJECT_TYPE都是同一个,如EPROCESS 1、EPROCESS 2对应的类型对象OBJECT_TYPE是同一个,且OBJECT_TYPE.Name == Process。 - 并不是每个内核对象创立时都会新建一个类型对象。类型对象是在系统初始化时创建好的,后面每当新建内核对象时,都要指定对象对所属的全局类型对象指针(如
PsProcessType)。 - 同一类型的内核对象和类型对象
OBJECT_TYPE的关系是:多对一,如下图所有进程对象对应的类型对象都是同一个。

系统预定义的对象类型约有 64 种,但是系统可以开放出来让开发者自己定义想要的对象类型。
系统中有全局指针指向这个 64 个类型对象(指针类型为 POBJECT_TYPE),在这个类型对象下面的双链表指针串着相同类型的所有对象。如下是 WRK 中列出的 31 个指向类型对象的指针(每个指针类型为 POBJECT_TYPE)。

1 | kd> dq PsProcessType |
全局变量 ObpObjectTypes 数组记录了所有已创建的类型,但是 OBJECT_TYPE.TypeIndex 经过运算之后为 ObTypeIndexTable 数组的索引。两个数组的区别如下(Windows 7及之后就没有使用 ObpObjectTypes):
1 | kd> dq ObTypeIndexTable l50 |
就是数组前几个成员不相同,索引位置也不同。Windows 7 开始使用的是 ObTypeIndexTable 数组。
3.1 OBJECT_TYPE 类型对象
注意,通过 OBJECT_TYPE.TypeList 并不能遍历当前类型下的所有对象。Type 对象一般都具有 _OBJECT_HEADER_CREATOR_INFO 可选信息头,OBJECT_HEADER_CREATOR_INFO.TypeList 一般都串着同一类型下的所有对象。
1 | kd> dt nt!_OBJECT_TYPE |
系统预定义的对象类型约有 64 种,但是系统可以开放出来让开发者自己定义想要的对象类型。通过 ObCreateObjectType 函数创建新的对象类型。以下通过 Windbg 枚举对象类型实际上不一定全,查看对应系统版本有多少种对象类型的方法(全部对象)可参考Reversing Windows Internals (Part 1) - Digging Into Handles, Callbacks & ObjectTypes。
1 | kd> dx Debugger.Utility.Collections.FromListEntry( (*(nt!_OBJECT_TYPE **)&nt!ObpTypeObjectType)->TypeList, "nt!_OBJECT_TYPE", "TypeList").Select(o => (nt!_OBJECT_HEADER*)((unsigned char *)&o + 0x20)).Select( o => o->ObjectName) |
创建新类型使用 ObCreateObjectType 函数:
1 | NTSTATUS ObCreateObjectType( |
其中 ObjectTypeInitializer 指定新类型相关属性,以及该类型下相关的操作函数,这些属性随后会复制到 OBJECT_TYPE.TypeInfo 中。
Typelnfo 字段指向 _OBJECT_TYPE_INITIALIZER 数据结构,该数据结构保存对象类型的所有对象共有的属性、标志和设置,以及指向对象类型的自定义方法的指针。
1 | kd> dt nt!_OBJECT_TYPE_INITIALIZER |
下面介绍同类型下面内核对象的操作函数,如在创建、删除或修改对象时,应该调用什么函数等。
| 例程方法 | 什么时候被调用 | 举例(进程为例) |
|---|---|---|
| DumpProcedure | 转储例程,没有使用。 | / |
| OpenProcedure | 当一个对象句柄被创建、打开、复制或继承时。 | nt!PspProcessOpen |
| CloseProcedure | 当一个对象句柄被关闭时。 | nt!PspProcessClose |
| DeleteProcedure | 在对象管理器删除一个对象前进行调用。 | nt!PspProcessDelete |
| ParseProcedure | 对象管理器搜索对象名时调用,如 ObpLookupObjectName。 |
/ |
| ParseProcedureEx | 如果 OBJECT_TYPE_INITIALIZER.Flags 中的标志位 UseExtendedParameters == 1,则调用该方法而不调用 ParseProcedure 方法。 |
/ |
| SecurityProcedure | 当一个进程读取或改变对象的保护属性时调用。 | nt!SeDefaultObjectMethod |
| QueryNameProcedure | 当一个线程查询对象名称时,调用该方法。主要用在 ObQueryNameString 函数。 |
/ |
| OkayToCloseProcedure | 当一个对象句柄被关闭时。该函数会优先于 CloseProcedure 之前被调用。对象类型为 WindowStation 时对应的函数 ExpWindowStationOkToClose。对象类型为 Desktop 时对应的函数 ExpDesktopOkToClose。 |
/ |
如下示例:
1 | kd> dt nt!_OBJECT_TYPE ffffaf8f`8468f140 |
该类型下对象共有的权限说明:
1 | kd> dt nt!_OBJECT_TYPE_INITIALIZER 0xffffaf8f8468f180 |
该类型下多有对象通用权限枚举:
1 | kd> dt _GENERIC_MAPPING 0xffffaf8f8468f180+0xC |
可对当前类型对象进行的访问权限:
1 | +0x01c ValidAccessMask : 0x1fffff // GenericAll,全部 |
不允许其他进程修改的权限 Object retention:
1 | +0x020 RetainAccess : 0x101000 // 暂未查到 |
3.2 OBJECT_HEADER.TypeIndex
一、Windows XP
Windows XP 中使用 Type 直接指向 OBJECT_TYPE 结构。
1 | kd> dt nt!_OBJECT_HEADER |
二、Windows 7/8/8.1
Windows 7/8/8.1 下,使用 TypeIndex 索引来取代 Type 成员。
此时的 TypeIndex 是全局数组 ObTypeIndexTable 的索引,该数组中的每个成员都是 POBJECT_TYPE 指针类型,指向其对应的类型对象。
三、Windows 10
Windows 10 下 TypeIndex 不再直接作为全局数组 ObTypeIndexTable 的索引,需要经过运算。运算过程通过逆向 ObGetObjectType 函数得:

1 | POBJECT_HEADER ObGetObjectType(PVOID Object) |
计算过程如下:
1 | kd> !object ffffaf8f`87c66e50 |
可以看到 OBJECT_TYPE.Index == 0x11。
1 | PAGE:00000001407002E0 public ObGetObjectType |
参考内容:
- Windows Object Headers
- A Light on Windows 10’s “OBJECT_HEADER->TypeIndex”
- Object Headers, Handles and Types
- Windows Process Internals: A few Concepts to know before jumping on Memory Forensics [Part 5] — A Journey in to the Undocumented Process Handle Structures
- Reversing Windows Internals (Part 1) - Digging Into Handles, Callbacks & ObjectTypes
- Journey Into the Object Manager Executive Subsystem: Object Header and Object Type
- Windows内核对象
- Automating Windows Kernel Pool Overflow/Corruption Exploits Development
- Windows内核对象