一、 背景:Windows NT 的对象机制
Windows NT系统将各种资源以对象的方式进行组织和管理。虽然Windows NT内核使用C语言和汇编语言编写的,本身并未使用到C++中的面向对象机制。但依然通过抽象化的对象概念来对各类资源进行管理。
对象分为对象头和对象体两部分,对象头又分为标准的对象头和可选头部,后者这里不介绍。标准头部的定义如下:
可以看到,标准的对象头的前面部分长度是固定的,长度正好为0x18,紧随其后就是对象体,并且对象体长度不定。
在_OBJECT_HEADER 结构中,POBJECT_TYPE Type是对象类型的指针。Windows已经预先定义好了基本的对象类型。例如ProcessType。(进程对象),其对象体就是EPROCESS结构体(包含KPROCESS)。内核在PsCreatProcess()中建立新进程时会同时建立一个新的对象。
下面看一下对象类型相关的OBJECT_TYPE结构体:
其中包含的一个特殊结构体OBJECT_TYPE_INITIALIZER:
可以看到,其中包含了许多OB_XXXX_METHOD类型的成员,它们是一些函数指针。潘爱民老师在《Windows内核原理与实现》一书中说这里定义的是一些对象操作的方法基本方法。莫非打开进程时使用的就是OB_OPEN_METHOD OpenProcedure指定的方法(例程)么?下面就拿Explorer.exe验证一下。
二、实战
先在WinDBG中通过!procress 0 0命令得到Explorer.exe的PEPROCESS:0x8ad84720,这也是该进程的对象体的开始。那么减掉紧挨着的对象头的大小0x18,就可以得到对象头的起始地址:0x8ad84708。之后执行dt_OBJECT_HEADER 0x8ad84708命令观察explorer.exe进程的对象头:
直接得到ProcessType的指针:0x8af7cca0,然后观察:
展开TypeInfo:
显然,这一堆的Procedure中,只有DeleteProcedure和SecurityProcedure为非零值,显然系统不会在内核去call 0x00000000。难道系统对于这些Procedure还有特殊的处理方式?
到这里,似乎得去翻WRK源码查找这些成员的所有引用了。但这似乎工作量不小,在这我们采取比较偷懒的方式:下断点。这里只选取了两个:OpenProcedure和SecurityProcedure。它们一个为NULL一个为非NULL指,并且从命名上来看一个是在打开对象时执行的,一个是在安全检查时执行的。
似乎系统在登录后会调用NtOpenProcess()遍历一遍进程,随后我们静静地等待断点命中……
很快:
对应的源码:
其中ObpCentralizedSecurity(ObjectType)是个宏:
看起来没啥意思,只是做了一下检查就返回成功了。
本来想直接GO掉继续等待,但是接着往下看代码会发现:
可以看到,在ObjectType->TypeInfo.SecurityProcedure!= SeDefaultObjectMethod时,内核最终会去调用SecurityProcedure所指定的例程。同时也可以看到,内核对SecurityProcedure指定的地址未作任何检查,所以假如我们要修改它的话,必须保证其有效性。
那么这个NTSTATUS ObGetObjectSecurity ()会在什么时候被调用呢?莫非翻翻NtDesignedBook看看么?还是查看一下调用层次吧:
其中仅对ObCheckCreateObjectAccess()深入追踪,在扒开几层外壳之后我们可以看到一个熟悉的函数:ObOpenObjectByName()。调用它的函数实在太多,这里就不列了,NtOpenProcess、NtOpenThread均在其中。此处可谓牵一发而动全身。
继续追踪,GO之后断点0命中:
这里很简洁明了,检查一下ObjectType->TypeInfo.OpenProcedure不是NULL就直接去调用OpenProcedure了(这样还是不够严格,好歹检查一下是否在内核地址范围内吧)。ObpIncrementHandleCount()看上去是增加句柄计数的,还是查看下调用层次看看最终谁会用到它:
这里也仅列出对ObpCreateHandle()深入追踪的结果,可以看到两个熟悉的函数:ObOpenObjectByName()、ObOpenObjectByPointer(),其意义和前面的ObCheckCreateObjectAccess()完全是一个水平,这里就不再重复了。
三、总结
小小的OBJECT_METHOD暗藏玄机,通过分析我们可以发现:这些OBJECT_METHOD虽然卡上去是“可选”的,甚至某些情况下默认值为NULL,,但这并不意味着它们不重要:一旦它们被设置为特殊的函数指针,其被调用的机会相当高,可以用来HOOK的事情也非常多。本文只是初窥,至于具体的应用,有兴趣的童鞋可以找出所有可能调用的地方,然后加以利用。
Windows NT系统将各种资源以对象的方式进行组织和管理。虽然Windows NT内核使用C语言和汇编语言编写的,本身并未使用到C++中的面向对象机制。但依然通过抽象化的对象概念来对各类资源进行管理。
对象分为对象头和对象体两部分,对象头又分为标准的对象头和可选头部,后者这里不介绍。标准头部的定义如下:
代码:
typedef struct _OBJECT_HEADER { LONG_PTR PointerCount; union { LONG_PTR HandleCount; PVOID NextToFree; }; POBJECT_TYPE Type; UCHAR NameInfoOffset; UCHAR HandleInfoOffset; UCHAR QuotaInfoOffset; UCHAR Flags; union { POBJECT_CREATE_INFORMATION ObjectCreateInfo; PVOID QuotaBlockCharged; }; PSECURITY_DESCRIPTOR SecurityDescriptor; QUAD Body; } OBJECT_HEADER, *POBJECT_HEADE
在_OBJECT_HEADER 结构中,POBJECT_TYPE Type是对象类型的指针。Windows已经预先定义好了基本的对象类型。例如ProcessType。(进程对象),其对象体就是EPROCESS结构体(包含KPROCESS)。内核在PsCreatProcess()中建立新进程时会同时建立一个新的对象。
下面看一下对象类型相关的OBJECT_TYPE结构体:
代码:
typedef struct _OBJECT_TYPE { ERESOURCE Mutex; LIST_ENTRY TypeList; UNICODE_STRING Name; // Copy from object header for convenience PVOID DefaultObject; ULONG Index; ULONG TotalNumberOfObjects; ULONG TotalNumberOfHandles; ULONG HighWaterNumberOfObjects; ULONG HighWaterNumberOfHandles; OBJECT_TYPE_INITIALIZER TypeInfo; #ifdef POOL_TAGGING ULONG Key; #endif //POOL_TAGGING ERESOURCE ObjectLocks[ OBJECT_LOCK_COUNT ]; } OBJECT_TYPE, *POBJECT_TYPE;
代码:
typedef struct _OBJECT_TYPE_INITIALIZER { USHORT Length; BOOLEAN UseDefaultObject; BOOLEAN CaseInsensitive; ULONG InvalidAttributes; GENERIC_MAPPING GenericMapping; ULONG ValidAccessMask; BOOLEAN SecurityRequired; BOOLEAN MaintainHandleCount; BOOLEAN MaintainTypeList; POOL_TYPE PoolType; ULONG DefaultPagedPoolCharge; ULONG DefaultNonPagedPoolCharge; OB_DUMP_METHOD DumpProcedure; OB_OPEN_METHOD OpenProcedure; OB_CLOSE_METHOD CloseProcedure; OB_DELETE_METHOD DeleteProcedure; OB_PARSE_METHOD ParseProcedure; OB_SECURITY_METHOD SecurityProcedure; OB_QUERYNAME_METHOD QueryNameProcedure; OB_OKAYTOCLOSE_METHOD OkayToCloseProcedure; } OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;
二、实战
先在WinDBG中通过!procress 0 0命令得到Explorer.exe的PEPROCESS:0x8ad84720,这也是该进程的对象体的开始。那么减掉紧挨着的对象头的大小0x18,就可以得到对象头的起始地址:0x8ad84708。之后执行dt_OBJECT_HEADER 0x8ad84708命令观察explorer.exe进程的对象头:
代码:
0: kd> dt_OBJECT_HEADER 0x8ad84708 nt!_OBJECT_HEADER +0x000 PointerCount : 0n108 +0x004 HandleCount : 0n4 +0x004 NextToFree : 0x00000004 Void +0x008 Type : 0x8af7cca0 _OBJECT_TYPE +0x00c NameInfoOffset : 0 '' +0x00d HandleInfoOffset : 0 '' +0x00e QuotaInfoOffset : 0 '' +0x00f Flags : 0x20 ' ' +0x010 ObjectCreateInfo : 0x8a5a7008 _OBJECT_CREATE_INFORMATION +0x010 QuotaBlockCharged : 0x8a5a7008 Void +0x014 SecurityDescriptor : 0xe16cd8c5 Void +0x018 Body : _QUAD
代码:
0: kd> dt_OBJECT_TYPE 0x8af7cca0 nt!_OBJECT_TYPE +0x000 Mutex : _ERESOURCE +0x038 TypeList : _LIST_ENTRY [ 0x8af7ccd8 - 0x8af7ccd8 ] +0x040 Name : _UNICODE_STRING "Process" +0x048 DefaultObject : (null) +0x04c Index : 5 +0x050 TotalNumberOfObjects : 0x1c +0x054 TotalNumberOfHandles : 0x7d +0x058 HighWaterNumberOfObjects : 0x20 +0x05c HighWaterNumberOfHandles : 0x82 +0x060 TypeInfo : _OBJECT_TYPE_INITIALIZER +0x0ac Key : 0x636f7250 +0x0b0 ObjectLocks : [4] _ERESOURCE
代码:
0: kd> dt _OBJECT_TYPE_INITIALIZER 0x8af7cd00 nt!_OBJECT_TYPE_INITIALIZER +0x000 Length : 0x4c +0x002 UseDefaultObject : 0 '' +0x003 CaseInsensitive : 0 '' +0x004 InvalidAttributes : 0xb0 +0x008 GenericMapping : _GENERIC_MAPPING +0x018 ValidAccessMask : 0x1f0fff +0x01c SecurityRequired : 0x1 '' +0x01d MaintainHandleCount : 0 '' +0x01e MaintainTypeList : 0 '' +0x020 PoolType : 0 ( NonPagedPool ) +0x024 DefaultPagedPoolCharge : 0x1000 +0x028 DefaultNonPagedPoolCharge : 0x2a8 +0x02c DumpProcedure : (null) +0x030 OpenProcedure : (null) +0x034 CloseProcedure : (null) +0x038 DeleteProcedure : 0x80932460 void nt!PspProcessDelete+0 +0x03c ParseProcedure : (null) +0x040 SecurityProcedure : 0x8091ce64 long nt!SeDefaultObjectMethod+0 +0x044 QueryNameProcedure : (null) +0x048 OkayToCloseProcedure : (null)
到这里,似乎得去翻WRK源码查找这些成员的所有引用了。但这似乎工作量不小,在这我们采取比较偷懒的方式:下断点。这里只选取了两个:OpenProcedure和SecurityProcedure。它们一个为NULL一个为非NULL指,并且从命名上来看一个是在打开对象时执行的,一个是在安全检查时执行的。
代码:
0: kd> ba r4 0x8af7cd30 0: kd> ba r4 0x8af7cd40 3: kd> bl 0 e 8af7cd30 r 4 0001 (0001) 1 e 8af7cd40 r 4 0001 (0001)
很快:
代码:
Breakpoint 1 hit nt!ObGetObjectSecurity+0x22: 80924f0a 7518 jne nt!ObGetObjectSecurity+0x3c (80924f24)
代码:
if (ObpCentralizedSecurity(ObjectType)) { *SecurityDescriptor = ObpReferenceSecurityDescriptor( ObjectHeader ); *MemoryAllocated = FALSE; return( STATUS_SUCCESS ); }
代码:
#define ObpCentralizedSecurity(_ObjectType) \ ((_ObjectType)->TypeInfo.SecurityProcedure == SeDefaultObjectMethod) // // Declare a global table of object types. //
本来想直接GO掉继续等待,但是接着往下看代码会发现:
代码:
// // The security method will return an absolute format // security descriptor that just happens to be in a self // contained buffer (not to be confused with a self-relative // security descriptor). // ObpBeginTypeSpecificCallOut( SaveIrql ); Status = (*ObjectType->TypeInfo.SecurityProcedure)( Object, QuerySecurityDescriptor, &SecurityInformation, *SecurityDescriptor, &Length, &ObjectHeader->SecurityDescriptor, ObjectType->TypeInfo.PoolType, &ObjectType->TypeInfo.GenericMapping ); ObpEndTypeSpecificCallOut( SaveIrql, "Security", ObjectType, Object );
那么这个NTSTATUS ObGetObjectSecurity ()会在什么时候被调用呢?莫非翻翻NtDesignedBook看看么?还是查看一下调用层次吧:
代码:
ObCheckCreateObjectAccess(); - ObpLookupObjectName(); - ObInsertObject(); - ObOpenObjectByName(); -ObReferenceObjectByName(); ObCheckObjectAccess(); ObInsertObject(); ObpCheckObjectReference(); ObpCheckTraverseAccess(); PsCreateProcess() PsCreateThread(); PspSetPrimaryToken();
继续追踪,GO之后断点0命中:
代码:
Breakpoint 0 hit nt!ObpIncrementHandleCount+0x2b9: 80921d3f 85c0 test eax,eax 对应的代码: if (ObjectType->TypeInfo.OpenProcedure != NULL) { #if DBG KIRQL SaveIrql; #endif // // Leave the object type mutex when call the OpenProcedure. If an exception // while OpenProcedure the HoldObjectTypeMutex disable leaving the mutex // on finally block // ObpBeginTypeSpecificCallOut( SaveIrql ); Status = (*ObjectType->TypeInfo.OpenProcedure)( OpenReason, Process, Object, AccessState ? AccessState->PreviouslyGrantedAccess : 0, ProcessHandleCount ); ObpEndTypeSpecificCallOut( SaveIrql, "Open", ObjectType, Object );
代码:
ObDupHandleProcedure(); ObDuplicateObject(); ObpCreateHandle(); - ObInsertObject(); - ObOpenObjectByName(); -ObOpenObjectByPointer();
三、总结
小小的OBJECT_METHOD暗藏玄机,通过分析我们可以发现:这些OBJECT_METHOD虽然卡上去是“可选”的,甚至某些情况下默认值为NULL,,但这并不意味着它们不重要:一旦它们被设置为特殊的函数指针,其被调用的机会相当高,可以用来HOOK的事情也非常多。本文只是初窥,至于具体的应用,有兴趣的童鞋可以找出所有可能调用的地方,然后加以利用。
转载于:https://blog.51cto.com/whatday/1382276