nt内核里的对象管理[1]:HANDLE和对象头
有那么一段时间,“对象”基本上是当时IT届最流行的词语,无论什么东西都要搭上“对象”的概念才够体面。NT核就诞生在那个年代,所以在其设计概念中有“内核对象”这么一个牛逼的物件。几乎所有的windows内核组件,包括进程,线程,文件,设备等都属于内核对象,它们有一组共有的数据以及几个函数指针以提供抽象的访问,基本上c语言要玩“面向对象”的花都是这么干的(linux内核里也有类似的东西)。在系统内部所有已打开的对象的对象头都放在一个object table里,当你有某API打开某内核对象后,系统会返回给你一个HANDLE,其实它是该对象在object table里的索引。gussing.cnblogs.com
为了对HANDLE和object table有一个快速的了解,我们先用windbg玩一玩游戏:
lkd> !handle 4
processor number 0, process 89d09020
PROCESS 89d09020 SessionId: 0 Cid: 153c Peb: 7ffdf000 ParentCid: 0bc8
DirBase: 0b040a40 ObjectTable: e45e0ba8 HandleCount: 521.
Image: windbg.exe
Handle table at e1217000 with 521 Entries in use
0004: Object: e1009548 GrantedAccess: 000f0003 Entry: e4387008
Object: e1009548 Type: (8a7a6730) KeyedEvent
ObjectHeader: e1009530 (old version)
HandleCount: 78 PointerCount: 79
Directory Object: e1000088 Name: CritSecOutOfMemoryEvent
这里我们用!handle命令解析handle = 0x00000004。所有的HANDLE都是4的倍数,转换成索引很容易,就是除以一个4,所以0x00000004其实就是index=1的那个object。该object是KeyedEvent【Type: (8a7a6730) KeyedEvent】,对象头在e1009530 地址,对象身体在e1009548 地址。所有的对象头都是相同大小的,为0x18字节。再看object header的细节: gussing.cnblogs.com
lkd> dt _object_header e1009530
nt!_OBJECT_HEADER
+0x000 PointerCount : 79
+0x004 HandleCount : 78
+0x004 NextToFree : 0x0000004e
+0x008 Type : 0x8a7a6730 _OBJECT_TYPE
+0x00c NameInfoOffset : 0x10 ''
+0x00d HandleInfoOffset : 0 ''
+0x00e QuotaInfoOffset : 0 ''
+0x00f Flags : 0x32 '2'
+0x010 ObjectCreateInfo : 0x00000001 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : 0x00000001
+0x014 SecurityDescriptor : 0xe100b11c
+0x018 Body : _QUAD
可以看到该对象被79个其他对象引用【PointerCount : 79】,同时自己又保留有78个其他对象的引用【HandleCount : 78】。该对象的类型信息保留在Type字段里,让我们看看里面都有些什么: gussing.cnblogs.com
ntdll!_OBJECT_TYPE
+0x000 Mutex : _ERESOURCE
+0x038 TypeList : _LIST_ENTRY [ 0x8a7a6768 - 0x8a7a6768 ]
+0x040 Name : _UNICODE_STRING "KeyedEvent"
+0x048 DefaultObject : 0x80562ec0
+0x04c Index : 0x10
+0x050 TotalNumberOfObjects : 1
+0x054 TotalNumberOfHandles : 0x4e
+0x058 HighWaterNumberOfObjects : 1
+0x05c HighWaterNumberOfHandles : 0x50
+0x060 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x0ac Key : 0x6579654b
+0x0b0 ObjectLocks : [4] _ERESOURCE
该字段指向_object_type结构,里面保留了类型名和该类型的统计数据。我们可以很清楚的看到类型名是KeyedEvent
【Name:_UNICODE_STRING "KeyedEvent"】,该结构里还有个TypeInfo字段让我们继续往下看:
lkd> dt _OBJECT_TYPE_INITIALIZER 0x8a7a6730+0x60
ntdll!_OBJECT_TYPE_INITIALIZER
+0x000 Length : 0x4cgussing.cnblogs.com
+0x002 UseDefaultObject : 0x1 ''
+0x003 CaseInsensitive : 0 ''
+0x004 InvalidAttributes : 0
+0x008 GenericMapping : _GENERIC_MAPPING
+0x018 ValidAccessMask : 0x1f0003
+0x01c SecurityRequired : 0 ''
+0x01d MaintainHandleCount : 0 ''
+0x01e MaintainTypeList : 0 ''
+0x020 PoolType : 1 ( PagedPool )
+0x024 DefaultPagedPoolCharge : 0x30
+0x028 DefaultNonPagedPoolCharge : 0
+0x02c DumpProcedure : (null)
+0x030 OpenProcedure : (null)
+0x034 CloseProcedure : (null)
+0x038 DeleteProcedure : (null)
+0x03c ParseProcedure : (null)
+0x040 SecurityProcedure : 0x805f816e long nt!SeDefaultObjectMethod+0
+0x044 QueryNameProcedure : (null)
+0x048 OkayToCloseProcedure : (null)
可以看到这里保留了一个函数指针,提供一般性的操作,差不多有点虚函数的意思吧。再回头看_object_header结构,里面有一个NameInfoOffset字段,表明在_object_header所在内存前面NameInfoOffset个字节处保留了该对象的名字信息,这里NameInfoOffset=0x10,让我们去看个究竟:
lkd> dt _object_name_information e1009530-10
ntdll!_OBJECT_NAME_INFORMATIONgussing.cnblogs.com
+0x000 Name : _UNICODE_STRING "CritSecOutOfMemoryEvent"
事就这样成了,!handle命令所列出的所有信息,我们都可以手工收集到。
最后让我们找个大路货一点的对象来看看对象身体里都有什么东西吧。文件总归是老朋友了,就它吧。我手头这个测试程序的handle=0x12是一个文件:
lkd> !handle 12
processor number 0, process 89d09020
PROCESS 89d09020 SessionId: 0 Cid: 153c Peb: 7ffdf000 ParentCid: 0bc8
DirBase: 0b040a40 ObjectTable: e45e0ba8 HandleCount: 533.
Image: windbg.exe
Handle table at e1217000 with 533 Entries in use
0012: Object: 89c27d28 GrantedAccess: 00100020 Entry: e4387020
Object: 89c27d28 Type: (8a7dc730) File
ObjectHeader: 89c27d10 (old version)
HandleCount: 1 PointerCount: 1
Directory Object: 00000000 Name: \WINDOWS\WinSxS\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.2600.5512_x-ww_35d4ce83 {HarddiskVolume1}
插播一条广告:为了学习nt内核,我写了一个山寨ProcessExplorer,可以查看进程信息,线程信息,handle列表,已加载dll列表,已加载驱动列表等等信息,所有信息都是手工收集,用过的API只有DeviceIoControl一个,当然,内核导出函数还是用了不少的。能把指定dll注入指定进程,还能强杀某些杀毒软件。注意!目前只支持xp sp3,其他版本的windows未经测试。
该玩具源代码放在http://code.google.com/p/yasi/上,欢迎各位去取。另外,这是本人习作,高手不准笑。。。
如前面所讲,该对象的对象头在0x89c27d10处,那么对象身体就在0x89c27d10+0x18处,让我们仔细看看:
lkd> dt _file_object 89c27d10+18
ntdll!_FILE_OBJECT
+0x000 Type : 5
+0x002 Size : 112
+0x004 DeviceObject : 0x8a7d32b0 _DEVICE_OBJECT
+0x008 Vpb : 0x8a786910 _VPB
+0x00c FsContext : 0xe1bd5d20
+0x010 FsContext2 : 0xe39c9ba0
+0x014 SectionObjectPointer : (null)
+0x018 PrivateCacheMap : (null)
+0x01c FinalStatus : 0
+0x020 RelatedFileObject : (null)
+0x024 LockOperation : 0 ''
+0x025 DeletePending : 0 ''
+0x026 ReadAccess : 0x1 ''
+0x027 WriteAccess : 0 ''
+0x028 DeleteAccess : 0 ''gussing.cnblogs.com
+0x029 SharedRead : 0x1 ''
+0x02a SharedWrite : 0x1 ''
+0x02b SharedDelete : 0 ''
+0x02c Flags : 0x40002
+0x030 FileName : _UNICODE_STRING "\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.2600.5512_x-ww_35d4ce83"
+0x038 CurrentByteOffset : _LARGE_INTEGER 0x0
+0x040 Waiters : 0
+0x044 Busy : 0
+0x048 LastLock : (null)
+0x04c Lock : _KEVENT
+0x05c Event : _KEVENT
+0x06c CompletionContext : (null)
果然很顺利的拿到了FileObject。
由此我们可以看到,手握一个HANDLE就可以找到一个对应的object_header;而找到了一个object_header,则该对象几乎所有的信息都可以顺利拿到了。