Windows内核对象
Windows内核中外物皆对象,并通过对象管理器(内核组件)管理这些对象。例如进程内核对象,线程内核对象,文件内核对象,设备内核对象,互斥量内核对象等,每一个对象都有其所属的对象类型_OBJECT_TYPE。
内核对象的内存布局
内核对象的内存布局包含了POOL_HEADER,一些可选的标头,OBJECT_HEADER和Object主体。其中OBJECT_HEADER.boby与实际的Object主体重叠。
0: kd> dt nt!_OBJECT_HEADER
+0x000 PointerCount : Int8B //对象的引用计数
+0x008 HandleCount : Int8B //对象的句柄计数
+0x008 NextToFree : Ptr64 Void
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : UChar //通过计算能得到在ObTypeIndexTable中的索引
+0x019 TraceFlags : UChar //标记是否启动了对象跟踪
+0x019 DbgRefTrace : Pos 0, 1 Bit
+0x019 DbgTracePermanent : Pos 1, 1 Bit
+0x01a InfoMask : UChar //可选头掩码,表示是否存在某个可选头,通过计算能得到可选头的偏移
+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
//可选头类型以及其对象的掩码
name mask
_OBJECT_HEADER_EXTENDED_INFO (0x80)
_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的InfoMask为可选头掩码,通过InfoMask的值可以确定存在哪些可选头。可选头的偏移通过对应可选头的掩码作为索引,在nt!ObpInfoMaskToOffset表中得到对应的值(一个字节)得负数就是此可选头相对于_OBJECT_HEADER的偏移。
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
+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_HEADER_CREATOR_INFO.TypeList中保存了系统中所有的内核对象类型。
内核对象类型
每一个内核对象都有其所属的对象类型,通过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)
命令可以得到计算机系统的所有内核对象类型的名称。(通过sysinternals工具包的WinObj也可以)
0: kd> dt nt!_OBJECT_TYPE
+0x000 TypeList : _LIST_ENTRY //此对象类型对应的所有对象示例
+0x010 Name : _UNICODE_STRING
+0x020 DefaultObject : Ptr64 Void
+0x028 Index : UChar //在ObTypeIndexTable中的索引
+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 //对象对应的回调地址链表
每个对象类型都已一个_OBJECT_TYPE来描述它的基本信息, nt!ObTypeIndexTable中保存了所有对象类型对应的地址(通过_OBJECT_HEADER.TypeIndex经过计算后可以得到相对于这个表的索引值 == _OBJECT_TYPE.Index)。
0: kd> dt 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
+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 //对于文件对象来说此函数为nt!IopQueryName
+0x068 OkayToCloseProcedure : Ptr64 unsigned char //每次关闭句柄前都会调用这个函数检查可否关闭
+0x070 WaitObjectFlagMask : Uint4B
+0x074 WaitObjectFlagOffset : Uint2B
+0x076 WaitObjectPointerOffset : Uint2B
_OBJECT_TYPE包含了一个重要的字段_OBJECT_TYPE_INITIALIZER, _OBJECT_TYPE_INITIALIZER的SupportsObjectCallbacks表示此对象是否支持设置回调函数。
0: kd> dt nt!_OBJECT_TYPE_INITIALIZER ffffbb0caccc60c0+40 -b -y SupportsObjectCallbacks
+0x002 SupportsObjectCallbacks : 0y1
0: kd> dt nt!_OBJECT_TYPE_INITIALIZER ffffbb0caccc7140+40 -b -y SupportsObjectCallbacks
+0x002 SupportsObjectCallbacks : 0y1
0: kd> dt nt!_OBJECT_TYPE_INITIALIZER ffffbb0caccc7f00+40 -b -y SupportsObjectCallbacks
+0x002 SupportsObjectCallbacks : 0y1
0: kd> dt nt!_OBJECT_TYPE ffffbb0caccc60c0 -y Name
+0x010 Name : _UNICODE_STRING "Process"
0: kd> dt nt!_OBJECT_TYPE ffffbb0caccc7140 -y Name
+0x010 Name : _UNICODE_STRING "Thread"
0: kd> dt nt!_OBJECT_TYPE ffffbb0caccc7f00 -y Name
+0x010 Name : _UNICODE_STRING "Desktop"
通过遍历ObTypeIndexTable中所有的内核对象类型并得到其对应_OBJECT_TYPE的_OBJECT_TYPE_INITIALIZER对应的SupportsObjectCallbacks发现:只有"Process","Thread","Desktop"这三个对象支持回调。
查看microsoft的文档也可以发现确实是只支持进程,线程和桌面这三种对象的回调。
PsProcessType,PsThreadType和ExDesktopObjectType同时也是内核导出的指针,分别对应Process,Thread和Desktop对象的OBJECT_TYPE
typedef struct OB_CALLBACK_ENTRY_t {
LIST_ENTRY CallbackList; // linked element tied to _OBJECT_TYPE.CallbackList
OB_OPERATION Operations; // bitfield : 1 for Creations, 2 for Duplications
BOOL Enabled; // self-explanatory
OB_CALLBACK* Entry; // points to the structure in which it is included
POBJECT_TYPE ObjectType; // points to the object type affected by the callback
POB_PRE_OPERATION_CALLBACK PreOperation; // callback function called before each handle operation
POB_POST_OPERATION_CALLBACK PostOperation; // callback function called after each handle operation
KSPIN_LOCK Lock; // lock object used for synchronization
} OB_CALLBACK_ENTRY;
如果对象支持回调,那么回调函数就会保存在_OBJECT_TYPE.CallbackList的一个双向链表中。其节点是一个OB_CALLBACK_ENTRY类型,其中PreOperation和PostOperation保存了对应对象的回调函数(ObRegisterCallbacks注册的)
设置对象回调实现进程保护
通过调用ObRegisterCallbacks设置PsProcessType类型的回调,在回调函数中对尝试打开被保护进程的行为进程句柄的降权处理可以实现进程的保护。(与之前枚举所有被保护进程的句柄并进行降权处理类似,只不过这种是windows提供的接口更方便和稳定)
例如某个进程试图通过OpenProcess打开被保护的进程,OpenProcess-->NtOpenProcess-->PsOpenProcess->ObOpenObjectByPointer->ObpCreateHandle当调用ObpCreateHandle时就会判断此需要打开的对象对应的OBJECT_TYPE是否支持回调以及其对应的回调函数列表CallbackList是否为空,然后调用ObpCallPreOperationCallbacks,这个函数负责调用ObRegisterCallbacks注册的pre回调(保存在CallbackList列表中)。调用完之后就会尝试创建一个与被打开进程对象相关联的句柄。(如果在回调函数中对待创建句柄的权限进行降权处理,那么创建的句柄对应的GrantedAccessBits就会被降权)
ObpCreateHandle创建完句柄后会调用ObpPostInterceptHandleCreate,此函数负责调用post类型的回调。
绕过通过设置对象回调的进程保护
- 设置进程回调函数链表CallbackList中的OB_CALLBACK_ENTRY_t.Entry为0,这样此回调函数就无效了
- 设置进程回调函数链表CallbackList为空,头尾指针相同达到清空回调函数的目的。
- 通过设置进程_OBJECT_TYPE的_OBJECT_TYPE_INITIALIZER.SupportsObjectCallbacks 字段为0,使进程对象不支持回调函数。
- 通过重新给被降权的句柄进行提权,OpenProcess后得到降权的句柄,然后驱动程序通过枚举句柄表修改此被降权句柄的GrantedAccessBits 达到提权的目的。
参考:
https://github.com/wavestone-cdt/EDRSandblast
https://codemachine.com/articles/object_headers.html
https://rayanfam.com/topics/reversing-windows-internals-part1/#handle-creation-process