[Windows] 内核对象
什么是内核对象
Windows 中一切皆为 对象,Linux 中一切皆为 文件。内核对象是 Windows 用来管理资源的一种 数据结构
内核对象分类
- 访问标记对象
- 注册表键对象
- 调试对象
- 目录对象
- 符号链接对象
- 事件对象
- 文件对象
- 文件映射对象
- IO完成端口对象
- LPC端口对象
- 作业对象
- 邮槽对象
- 进程对象
- 线程对象
- 令牌对象
- 时钟对象
- 线程池对象
- 互斥对象
- 管道对象
- 信号量对象
内核对象和句柄的关系
内核对象的所有者是操作系统内核,句柄的所有者是进程。在 r3 调用创建内核对象的函数时会返回一个索引该内核对象的句柄。
并不是所有的句柄都指向内核对象,菜单、窗口、鼠标光标等,这些属于用户对象 或 GDI对象,而非内核对象。要判断一个对象是不是内核对象,最简单的方法就是查看创建这个对象的函数,内核对象在创建的时候都要传入一个 SECURITY_ATTRIBUTES 类型的参数(安全描述符)。
内核对象的结构
根据内核对象种类的不同,其结构也不同,但都有一个相同的 _OBJECT_HEADER 结构,这个结构位于 object - 0x30 处,(因为_OBJECT_HEADER 大小为0x30)
nt!_OBJECT_HEADER
+0x000 PointerCount : Int8B // 对象的引用个数
+0x008 HandleCount : Int8B // 指向该对象的句柄数
+0x008 NextToFree : Ptr64 Void
+0x010 Lock : _EX_PUSH_LOCK // 针对每个对象的锁, 当修改该对象头部的域或者任何子头的域时, 需要用到该锁
+0x018 TypeIndex⭐ : UChar // 指向类型对象的索引,所有的类型对象都存储在 nt!ObTypeIndexTable 表中
+0x019 TraceFlags : UChar // 追踪开启标志, 用来调试引用计数问题
+0x019 DbgRefTrace : Pos 0, 1 Bit
+0x019 DbgTracePermanent : Pos 1, 1 Bit
+0x01a InfoMask⭐ : UChar // 可选头掩码, 除了创建者信息子头外, 只要存在, 就位于对象的前面. 该掩码通过 nt!ObpInfoMaskToOffset 表被转换成一个负的偏移;每个可选头都有一个与之关联的 1 字节索引, 将该可选头置于相对其他子头出现的位置
+0x01b Flags : UChar // 对象的特征和对象属性
+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 // 对象体的位置
TypeIndex
在系统中,所有的 _OBJECT_TYPE 类型都保存在一个叫 ObTypeIndexTable 的数组中 ,这个数组的地址被导出,TypeIndex 就是这个表中的索引。
InfoMask
因为并不是所有的对象都初始化了全部的信息,所以 InfoMask 代表了指定结构是否存在的掩码:
// 从上到下依次为
name mask size
_OBJECT_HEADER_EXTENDED_INFO (0x80) (0x8)
_OBJECT_HEADER_PADDING_INFO (0x40) ()
_OBJECT_HEADER_AUDIT_INFO (0x20)
_OBJECT_HEADER_PROCESS_INFO (0x10)
_OBJECT_HEADER_QUOTA_INFO (0x08)
_OBJECT_HEADER_HANDLE_INFO (0X04)
_OBJECT_HEADER_NAME_INFO (0x02)
_OBJECT_HEADER_CREATOR_INFO (0x01)
...
_OBJECT_HEADER
比如,我的 windbg.exe 的 eprocess = 0xFFFFB105F8625580:
那么它的 _OBJECT_HEADER = 0xFFFFB105F8625550:
InfoMask = 0x8 说明只初始化了 _OBJECT_HEADER_QUOTA_INFO 结构,查看 ObpInfoMaskToOffset[8] 的值:
0x20 !,再看下 _OBJECT_HEADER_QUOTA_INFO 结构:
大小刚好为 0x18+0x8 = 0x20! 验证成功!所以 _OBJECT_HEADER_QUOTA_INFO 的地址为 0xFFFFB105F8625550 - 0x20:
可选头数据结构:
nt!_OBJECT_HEADER_CREATOR_INFO
+0x000 TypeList : _LIST_ENTRY // 用来挂入所属对象类型中的链表
+0x010 CreatorUniqueProcess : Ptr64 Void // 本对象是由哪个进程创建的
+0x018 CreatorBackTraceIndex : Uint2B
+0x01a Reserved1 : Uint2B
+0x01c Reserved2 : Uint4B
nt!_OBJECT_HEADER_NAME_INFO
+0x000 Directory : Ptr64 _OBJECT_DIRECTORY // 对象目录中的父目录
+0x008 Name : _UNICODE_STRING // 相对于 Directory 的路径或者全路径
+0x018 ReferenceCount : Int4B
+0x01c Reserved : Uint4B
nt!_OBJECT_HEADER_HANDLE_INFO
+0x000 HandleCountDataBase : Ptr64 _OBJECT_HANDLE_COUNT_DATABASE // 句柄信息
+0x000 SingleEntry : _OBJECT_HANDLE_COUNT_ENTRY
nt!_OBJECT_HEADER_QUOTA_INFO
+0x000 PagedPoolCharge : Uint4B // 分页负载
+0x004 NonPagedPoolCharge : Uint4B // 非分页负载
+0x008 SecurityDescriptorCharge : Uint4B // 安全描述符负载
+0x00c Reserved1 : Uint4B
+0x010 SecurityDescriptorQuotaBlock : Ptr64 Void
+0x018 Reserved2 : Uint8B
nt!_OBJECT_HEADER_PROCESS_INFO
+0x000 ExclusiveProcess : Ptr64 _EPROCESS // 所属进程
+0x008 Reserved : Uint8B
nt!_OBJECT_HEADER_AUDIT_INFO
+0x000 SecurityDescriptor : Ptr64 Void // 安全描述符
+0x008 Reserved : Uint8B
nt!_OBJECT_HEADER_PADDING_INFO
+0x000 PaddingAmount : Uint4B // 填充总量
nt!_OBJECT_HEADER_EXTENDED_INFO
+0x000 Footer : Ptr64 _OBJECT_FOOTER // 扩展
+0x008 Reserved : Uint8B
_OBJECT_TYPE
TypeIndex 为 ObTypeIndexTable 数组的索引,而 ObTypeIndexTable 数组的类型为 _OBJECT_TYPE。
nt!_OBJECT_TYPE
+0x000 TypeList : _LIST_ENTRY // 本类对象的链表,记录所有同类对象
+0x010 Name : _UNICODE_STRING // 类型名
+0x020 DefaultObject : Ptr64 Void // 本类对象默认使用的同步事件对象
+0x028 Index : UChar // 类型的索引,也即表示这是系统中第几个注册的对象类型
+0x02c TotalNumberOfObjects : Uint4B // 对象链表中总的对象个数
+0x030 TotalNumberOfHandles : Uint4B // 所有同类对象的打开句柄总数
+0x034 HighWaterNumberOfObjects : Uint4B // 历史本类对象个数峰值
+0x038 HighWaterNumberOfHandles : Uint4B // 历史本类对象的句柄个数峰值
+0x040 TypeInfo⭐ : _OBJECT_TYPE_INITIALIZER // 对象类型信息
+0x0b8 TypeLock : _EX_PUSH_LOCK // 对象类型锁
+0x0c0 Key : Uint4B // 内存块标记
+0x0c8 CallbackList : _LIST_ENTRY // 回调函数链表
nt!_OBJECT_TYPE_INITIALIZER
+0x000 Length : Uint2B // 本结构体本身的长度
+0x002 ObjectTypeFlags : Uint2B
+0x002 CaseInsensitive : Pos 0, 1 Bit // 本类对象的对象名是否大小写不敏感
+0x002 UnnamedObjectsOnly : Pos 1, 1 Bit
+0x002 UseDefaultObject : Pos 2, 1 Bit // 是否使用全局默认的同步事件对象
+0x002 SecurityRequired : Pos 3, 1 Bit // 本类对象是否需要安全控制
+0x002 MaintainHandleCount : Pos 4, 1 Bit // 对象头中是否维护句柄统计信息
+0x002 MaintainTypeList : Pos 5, 1 Bit // 是否维护创建者信息
+0x002 SupportsObjectCallbacks : Pos 6, 1 Bit // 支持的对象回调
+0x002 CacheAligned : Pos 7, 1 Bit // 隐藏对齐
+0x003 UseExtendedParameters : Pos 0, 1 Bit // 启用扩展参数
+0x003 Reserved : Pos 1, 7 Bits
+0x004 ObjectTypeCode : Uint4B
+0x008 InvalidAttributes : Uint4B
+0x00c GenericMapping : _GENERIC_MAPPING // 通用映射
+0x01c ValidAccessMask : Uint4B // 本类对象支持的属性集合
+0x020 RetainAccess : Uint4B
+0x024 PoolType : _POOL_TYPE // 本类对象位于分页池还是非分页池(一般内核对象都分配在非分页池中)
+0x028 DefaultPagedPoolCharge : Uint4B // 对象占用的分页池总体大小
+0x02c DefaultNonPagedPoolCharge : Uint4B // 对象占用的非分页池总体大小
// object hook 就是改变这几个成员指向的函数地址 =======================================
>>>+0x030 DumpProcedure : Ptr64 void // dump 对象时调用
>>>+0x038 OpenProcedure : Ptr64 long // 打开对象时调用
>>>+0x040 CloseProcedure : Ptr64 void // 关闭句柄时调用
>>>+0x048 DeleteProcedure : Ptr64 void // 销毁对象时调用
>>>+0x050 ParseProcedure : Ptr64 long // 解析路径时调用(设备、文件、键)
>>>+0x050 ParseProcedureEx : Ptr64 long
>>>+0x058 SecurityProcedure : Ptr64 long // 查询、设置对象安全描述符时调用
>>>+0x060 QueryNameProcedure : Ptr64 long // 文件对象提供了自定义的QueryNameString函数
>>>+0x068 OkayToCloseProcedure : Ptr64 unsigned char // 每次关闭句柄前都会调用这个函数检查可否关闭
+0x070 WaitObjectFlagMask : Uint4B
+0x074 WaitObjectFlagOffset : Uint2B
+0x076 WaitObjectPointerOffset : Uint2B
下一篇: 内核启动早期的打印