对Windows对象管理有一定了解的人都知道,在固定对象头(OBJECT_HEADER)前面是一块可变区域,称为可变对象头,它所包含的结构内容并不固定。在Win7之前,可变区域实际有哪些结构,通常是由OBJECT_HEADER中的几个偏移值指出。如下:
lkd> dt _OBJECT_HEADER(WinXP SP2)
nt!_OBJECT_HEADER
+0x000 PointerCount : Int4B
+0x004 HandleCount : Int4B
+0x004 NextToFree : Ptr32 Void
+0x008 Type : Ptr32 _OBJECT_TYPE
+0x00c NameInfoOffset : UChar
+0x00d HandleInfoOffset : UChar
+0x00e QuotaInfoOffset : UChar
+0x00f Flags : UChar
+0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : Ptr32 Void
+0x014 SecurityDescriptor : Ptr32 Void
+0x018 Body : _QUAD
其中的NameInfoOffset、HandleInfoOffset、QuotaInfoOffset就是用于指出该结构相对于固定对象头(OBJECT_HEADER)的偏移。如果NtGlobalFlags设置了MaintainTypeList标志,那么由于CreatorInfo的存在,这个部分还会更复杂一点,还得依据OBJECT_HEADER->Flag中的标志位来作一些判断才能具体确定某个结构的具体偏移。在Win7中,这一部分显然经过了精心设计,一个InfoMask域再加一个ObpInfoMaskToOffset表就搞定了,显得更加简捷快速。
顾名思义,InfoMask就是一个掩码,它的每一位表示可变对象头中某个指定的结构是否存在。在Win7中,对象的结构大体上没有太大变化,固定对象头的前面仍然是可变对象头。根据不同类型的对象及实际情况,可变对象头可能包含以下5个结构中的一个或几个:
(Size=0x08)ntkrpamp!_OBJECT_HEADER_PROCESS_INFO
(Size=0x10)ntkrpamp!_OBJECT_HEADER_QUOTA_INFO
(Size=0x08)ntkrpamp!_OBJECT_HEADER_HANDLE_INFO
(Size=0x10)ntkrpamp!_OBJECT_HEADER_NAME_INFO
(Size=0x10)ntkrpamp!_OBJECT_HEADER_CREATOR_INFO
这些结构对应的掩码分别为:
#define OB_INFOMASK_PROCESS_INFO 0x10
#define OB_INFOMASK_QUOTA 0x08
#define OB_INFOMASK_HANDLE 0x04
#define OB_INFOMASK_NAME 0x02
#define OB_INFOMASK_CREATOR_INFO 0x01
因为可变对象头可能包含5种结构,每一种结构包括存在或不存在两种情况,那么一共是2^5=32种结果。所以,ObpInfoMaskToOffset表定义如下:
而存在的结构就要占据一定的空间,那么对象头(OBJECT_HEADER)距PoolHeader的偏移就会因结构存在与否产生对应的变化,该偏移实际上也就是可变对象头的总大小。该表的初始化是在ObInitSystem()中进行的。初始化代码如下:
初始化完成后表的内容如下:
可以看到,根据掩码不同,那么实际存在的可变头结构就不同,其大小是掩码所代表的有效结构的大小之和。可变头为空,那么偏移自然为0,若5个结构都包含了,那么偏移就是所有结构之和,也就是0x40。所以,根据对象头中的掩码InfoMask,就可以确定可变头部分的大小和具体包含的结构,及每一个结构相对于固定对象头(OBJECT_HEADER)的实际偏移。因为这个偏移是相对于固定对象头(OBJECT_HEADER)往前的偏移,而在初始化过程中,这个偏移的值却又是依次累加的,所以后来加上的结构大小,在整个可变对象头中反而比较靠上,掩码小的结构离固定对象头越近。我以一个明确的图来说明这些信息:
举个例子,比如某对象的ObjectHeader->InfoMask值为9,那么就说明它包含了_OBJECT_HEADER_CREATOR_INFO(对应掩码为1)和_OBJECT_HEADER_QUOTA_INFO(对应掩码为8)两个结构,ObpInfoMaskToOffset[9]的值为0x20,正是这两个结构的大小之和。并且,由于掩码小的结构离固定对象头越近,所以可以明确知道ObjectHeader-0x10是_OBJECT_HEADER_CREATOR_INFO结构,ObjectHeader->0x20是_OBJECT_HEADER_QUOTA_INFO结构。
下面我以一个实际的例子来详细说明。
可以看到,内存池的分配从8603a518开始。
根据对象的TypeIndex为0x14,及TypeIndex在OBJECT_HEADER中的偏移,不难看出8603a558就是对象头。
可以看到,InfoMask为0xF=8+4+2+1,也就是说包含了_OBJECT_HEADER_QUOTA_INFO(掩码为8)、_OBJECT_HEADER_HANDLE_INFO(掩码为4)、_OBJECT_HEADER_NAME_INFO(掩码为2)和_OBJECT_HEADER_CREATOR_INFO(掩码为1)这四个结构。掩码最大的结构_OBJECT_HEADER_QUOTA_INFO在最上面:
查看WinSta0的创建者,发现是wininit.exe~~
理解上以上结构,相信对于获取可变对象头中某个结构的位置已经不在话下。实际上,Win7的内核导出了一个函数专门用于获取对象头中的_OBJECT_HEADER_NAME_INFO结构。该函数就是ObQueryNameInfo(),还原成源码如下:
源码很好理解,取ObjectHeader->InfoMask,判断OB_INFOMASK_NAME标志位是否有效,若无效说明可变头中并没有这个结构。若有效,就根据掩码取_OBJECT_HEADER_NAME_INFO靠下的两个结构的大小(包括_OBJECT_HEADER_NAME_INFO和_OBJECT_HEADER_CREATOR_INFO两个结构),然后对象头减去这个偏移量,就是_OBJECT_HEADER_NAME_INFO结构的位置了,结合前面的结构图,相信不难理解。
实际上,根据掩码与结构大小的对应关系,完全可以用一个更一般的方式来获取相应的可变对象头结构信息。如下:
参数解释:
Object : 欲操作的对象
HeaderMask : 要获取的对象结构对应的掩码
函数中的ObpInfoMaskToOffset可以自己定义,反正内容和初始化方式已经清楚了,内容一样用起来没什么影响
这样,直接用GetSpecificObjectHeaderInfo(Object,OB_INFOMASK_NAME)就可以代替ObQueryNameInfo(Object)了。
如果要获取_OBJECT_HEADER_HANDLE_INFO结构,那么GetSpecificObjectHeaderInfo(Object,OB_INFOMASK_HANDLE)就可以了,获取其它结构信息也是如此,非常方便~~
关于Win7的可变对象头,就说到这里~~
lkd> dt _OBJECT_HEADER(WinXP SP2)
nt!_OBJECT_HEADER
+0x000 PointerCount : Int4B
+0x004 HandleCount : Int4B
+0x004 NextToFree : Ptr32 Void
+0x008 Type : Ptr32 _OBJECT_TYPE
+0x00c NameInfoOffset : UChar
+0x00d HandleInfoOffset : UChar
+0x00e QuotaInfoOffset : UChar
+0x00f Flags : UChar
+0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : Ptr32 Void
+0x014 SecurityDescriptor : Ptr32 Void
+0x018 Body : _QUAD
其中的NameInfoOffset、HandleInfoOffset、QuotaInfoOffset就是用于指出该结构相对于固定对象头(OBJECT_HEADER)的偏移。如果NtGlobalFlags设置了MaintainTypeList标志,那么由于CreatorInfo的存在,这个部分还会更复杂一点,还得依据OBJECT_HEADER->Flag中的标志位来作一些判断才能具体确定某个结构的具体偏移。在Win7中,这一部分显然经过了精心设计,一个InfoMask域再加一个ObpInfoMaskToOffset表就搞定了,显得更加简捷快速。
顾名思义,InfoMask就是一个掩码,它的每一位表示可变对象头中某个指定的结构是否存在。在Win7中,对象的结构大体上没有太大变化,固定对象头的前面仍然是可变对象头。根据不同类型的对象及实际情况,可变对象头可能包含以下5个结构中的一个或几个:
(Size=0x08)ntkrpamp!_OBJECT_HEADER_PROCESS_INFO
(Size=0x10)ntkrpamp!_OBJECT_HEADER_QUOTA_INFO
(Size=0x08)ntkrpamp!_OBJECT_HEADER_HANDLE_INFO
(Size=0x10)ntkrpamp!_OBJECT_HEADER_NAME_INFO
(Size=0x10)ntkrpamp!_OBJECT_HEADER_CREATOR_INFO
这些结构对应的掩码分别为:
#define OB_INFOMASK_PROCESS_INFO 0x10
#define OB_INFOMASK_QUOTA 0x08
#define OB_INFOMASK_HANDLE 0x04
#define OB_INFOMASK_NAME 0x02
#define OB_INFOMASK_CREATOR_INFO 0x01
因为可变对象头可能包含5种结构,每一种结构包括存在或不存在两种情况,那么一共是2^5=32种结果。所以,ObpInfoMaskToOffset表定义如下:
BYTE ObpInfoMaskToOffset[32];
而存在的结构就要占据一定的空间,那么对象头(OBJECT_HEADER)距PoolHeader的偏移就会因结构存在与否产生对应的变化,该偏移实际上也就是可变对象头的总大小。该表的初始化是在ObInitSystem()中进行的。初始化代码如下:
ULONG i = 0; ULONG offset = 0; do { offset = 0; if ( i & OB_INFOMASK_CREATOR_INFO ) offset = sizeof(_OBJECT_HEADER_CREATOR_INFO); if ( i & OB_INFOMASK_NAME ) offset += sizeof(_OBJECT_HEADER_NAME_INFO); if ( i & OB_INFOMASK_HANDLE ) offset += sizeof(_OBJECT_HEADER_HANDLE_INFO); if ( i & OB_INFOMASK_QUOTA ) offset += sizeof(_OBJECT_HEADER_QUOTA_INFO); if ( i & OB_INFOMASK_PROCESS_INFO ) offset += sizeof(_OBJECT_HEADER_PROCESS_INFO); ObpInfoMaskToOffset[i++] = offset; }while(i<32);
初始化完成后表的内容如下:
kd> db ObpInfoMaskToOffset 83b97e60 00 10 10 20 08 18 18 28-10 20 20 30 18 28 28 38 ... ...(. 0.((8 83b97e70 08 18 18 28 10 20 20 30-18 28 28 38 20 30 30 40 ...(. 0.((8 00@
可以看到,根据掩码不同,那么实际存在的可变头结构就不同,其大小是掩码所代表的有效结构的大小之和。可变头为空,那么偏移自然为0,若5个结构都包含了,那么偏移就是所有结构之和,也就是0x40。所以,根据对象头中的掩码InfoMask,就可以确定可变头部分的大小和具体包含的结构,及每一个结构相对于固定对象头(OBJECT_HEADER)的实际偏移。因为这个偏移是相对于固定对象头(OBJECT_HEADER)往前的偏移,而在初始化过程中,这个偏移的值却又是依次累加的,所以后来加上的结构大小,在整个可变对象头中反而比较靠上,掩码小的结构离固定对象头越近。我以一个明确的图来说明这些信息:
举个例子,比如某对象的ObjectHeader->InfoMask值为9,那么就说明它包含了_OBJECT_HEADER_CREATOR_INFO(对应掩码为1)和_OBJECT_HEADER_QUOTA_INFO(对应掩码为8)两个结构,ObpInfoMaskToOffset[9]的值为0x20,正是这两个结构的大小之和。并且,由于掩码小的结构离固定对象头越近,所以可以明确知道ObjectHeader-0x10是_OBJECT_HEADER_CREATOR_INFO结构,ObjectHeader->0x20是_OBJECT_HEADER_QUOTA_INFO结构。
下面我以一个实际的例子来详细说明。
kd> dt _OBJECT_TYPE 84e4aa38 //查看对象类型 nt!_OBJECT_TYPE +0x000 TypeList : _LIST_ENTRY [ [color=#FF0000]0x8603a548 [/color]- 0x863593e8 ] //为了便观察到同类型的所有对象,我设置了MaintainTypeList标志 +0x008 Name : _UNICODE_STRING "WindowStation" +0x010 DefaultObject : (null) +0x014 Index : 0x14 '' +0x018 TotalNumberOfObjects : 6 +0x01c TotalNumberOfHandles : 0x35 +0x020 HighWaterNumberOfObjects : 6 +0x024 HighWaterNumberOfHandles : 0x3f +0x028 TypeInfo : _OBJECT_TYPE_INITIALIZER +0x078 TypeLock : _EX_PUSH_LOCK +0x07c Key : 0x646e6957 +0x080 CallbackList : _LIST_ENTRY [ 0x84e4aab8 - 0x84e4aab8 ] kd> !pool 0x8603a548 //观察第一个对象的内存池分配 Pool page 8603a548 region is Nonpaged pool 8603a500 size: 18 previous size: 48 (Allocated) MmSi *[color=#FF0000]8603a518 [/color]size: b0 previous size: 18 (Allocated) *Wind (Protected) Owning component : Unknown (update pooltag.txt) 8603a5c8 size: 78 previous size: b0 (Allocated) EtwR (Protected) ...
可以看到,内存池的分配从8603a518开始。
kd> dd 8603a518 8603a518 04160003 e46e6957 00000000 00000068 8603a528 00000094 83b44c40 8a42edb8 00000002 8603a538 90ca2cd8 000e000e 90d95cd0 00000000 8603a548 86062f80 84e4aa38 0000016c 00000000 8603a558 0000000c 00000005 00000000 000f00[color=#FF0000]14[/color] 8603a568 83b44c40 90d9c23e 00000000 86022848 8603a578 8604d8c8 91badaa0 00000000 ffabb8e8 8603a588 00000000 00000000 00000000 00000000
根据对象的TypeIndex为0x14,及TypeIndex在OBJECT_HEADER中的偏移,不难看出8603a558就是对象头。
kd> dt _OBJECT_HEADER 8603a558 nt!_OBJECT_HEADER +0x000 PointerCount : 12 +0x004 HandleCount : 5 +0x004 NextToFree : 0x00000005 +0x008 Lock : _EX_PUSH_LOCK +0x00c TypeIndex : 0x14 '' +0x00d TraceFlags : 0 '' +0x00e InfoMask : 0xf '' +0x00f Flags : 0 '' +0x010 ObjectCreateInfo : 0x83b44c40 _OBJECT_CREATE_INFORMATION +0x010 QuotaBlockCharged : 0x83b44c40 +0x014 SecurityDescriptor : 0x90d9c23e +0x018 Body : _QUAD
可以看到,InfoMask为0xF=8+4+2+1,也就是说包含了_OBJECT_HEADER_QUOTA_INFO(掩码为8)、_OBJECT_HEADER_HANDLE_INFO(掩码为4)、_OBJECT_HEADER_NAME_INFO(掩码为2)和_OBJECT_HEADER_CREATOR_INFO(掩码为1)这四个结构。掩码最大的结构_OBJECT_HEADER_QUOTA_INFO在最上面:
kd> dt _OBJECT_HEADER_QUOTA_INFO 8603a520 nt!_OBJECT_HEADER_QUOTA_INFO +0x000 PagedPoolCharge : 0 +0x004 NonPagedPoolCharge : 0x68 +0x008 SecurityDescriptorCharge : 0x94 +0x00c SecurityDescriptorQuotaBlock : 0x83b44c40 //加上该结构的大小,就是下一个结构_OBJECT_HEADER_HANDLE_INFO kd> dt _OBJECT_HEADER_HANDLE_INFO 8603a520+10 nt!_OBJECT_HEADER_HANDLE_INFO +0x000 HandleCountDataBase : 0x8a42edb8 _OBJECT_HANDLE_COUNT_DATABASE +0x000 SingleEntry : _OBJECT_HANDLE_COUNT_ENTRY //同理,下一个结构是_OBJECT_HEADER_NAME_INFO kd> dt _OBJECT_HEADER_NAME_INFO 8603a520+10+8 nt!_OBJECT_HEADER_NAME_INFO +0x000 Directory : 0x90ca2cd8 _OBJECT_DIRECTORY +0x004 Name : _UNICODE_STRING "WinSta0" +0x00c ReferenceCount : 0 //然后是_OBJECT_HEADER_CREATOR_INFO kd> dt _OBJECT_HEADER_CREATOR_INFO 8603a520+10+8+10 nt!_OBJECT_HEADER_CREATOR_INFO +0x000 TypeList : _LIST_ENTRY [ 0x86062f80 - 0x84e4aa38 ] +0x008 CreatorUniqueProcess : 0x0000016c +0x00c CreatorBackTraceIndex : 0 +0x00e Reserved : 0 kd> !process 16c 0 Searching for Process with Cid == 16c Cid Handle table at 93365000 with 553 Entries in use PROCESS 84e39d40 SessionId: 0 Cid: 016c Peb: 7ffd4000 ParentCid: 0140 DirBase: 1ee8a0a0 ObjectTable: 90cae1f8 HandleCount: 75. Image: wininit.exe
查看WinSta0的创建者,发现是wininit.exe~~
理解上以上结构,相信对于获取可变对象头中某个结构的位置已经不在话下。实际上,Win7的内核导出了一个函数专门用于获取对象头中的_OBJECT_HEADER_NAME_INFO结构。该函数就是ObQueryNameInfo(),还原成源码如下:
PVOID ObQueryNameInfo(IN PVOID Object) { POBJECT_HEADER ObjectHeader=OBJECT_TO_OBJECT_HEADER(Object); BYTE InfoMask=ObjectHeader->InfoMask; ULONG NameInfo=0; if(InfoMask & OB_INFOMASK_NAME) { NameInfo=(ULONG)ObjectHeader - ObpInfoMaskToOffset[InfoMask & (OB_INFOMASK_NAME+OB_INFOMASK_CREATOR_INFO)]; } else { NameInfo=0; } return (PVOID)NameInfo; }
源码很好理解,取ObjectHeader->InfoMask,判断OB_INFOMASK_NAME标志位是否有效,若无效说明可变头中并没有这个结构。若有效,就根据掩码取_OBJECT_HEADER_NAME_INFO靠下的两个结构的大小(包括_OBJECT_HEADER_NAME_INFO和_OBJECT_HEADER_CREATOR_INFO两个结构),然后对象头减去这个偏移量,就是_OBJECT_HEADER_NAME_INFO结构的位置了,结合前面的结构图,相信不难理解。
实际上,根据掩码与结构大小的对应关系,完全可以用一个更一般的方式来获取相应的可变对象头结构信息。如下:
PVOID GetSpecificObjectHeaderInfo(PVOID Object,BYTE HeaderMask) { POBJECT_HEADER ObjectHeader=(POBJECT_HEADER)OBJECT_TO_OBJECT_HEADER(Object); BYTE InfoMask=ObjectHeader->InfoMask; BYTE MaxMask=0; ULONG HeaderInfo=0; if(InfoMask & HeaderMask) { MaxMask=2*HeaderMask-1; HeaderInfo=(ULONG)ObjectHeader - ObpInfoMaskToOffset[InfoMask & MaxMask]; } else { HeaderInfo=0; } return (PVOID)HeaderInfo; }
参数解释:
Object : 欲操作的对象
HeaderMask : 要获取的对象结构对应的掩码
函数中的ObpInfoMaskToOffset可以自己定义,反正内容和初始化方式已经清楚了,内容一样用起来没什么影响
这样,直接用GetSpecificObjectHeaderInfo(Object,OB_INFOMASK_NAME)就可以代替ObQueryNameInfo(Object)了。
如果要获取_OBJECT_HEADER_HANDLE_INFO结构,那么GetSpecificObjectHeaderInfo(Object,OB_INFOMASK_HANDLE)就可以了,获取其它结构信息也是如此,非常方便~~
关于Win7的可变对象头,就说到这里~~