-
-
[原创]Windows内核学习笔记之对象管理
-
发表于: 2021-12-8 18:23 8645
-
一.对象概述
对象是一种特殊的数据结构,用于定于受保护的实体。为了让系统高效稳定的运行,在Windows内核中空间中保存了多种不同的对象。例如:在用户层使用CreateProcess创建进程以后,Windows系统就会在内核中创建一个进程对象用来保存进程的信息(进程名,PID等等),有了这些信息Windows就可以方便的对进程进行管理。这些内核对象是保存在内核空间中的,用户层的程序是无法通过地址直接访问这些对象。想要访问这些内核对象,就需要使用Windows提供的句柄。在用户层如果想做什么样的操作,就可以通过句柄进行操作,因为在内核层会通过该句柄找到相应的内核对象完成操作。通过这样的设计,用户层的代码就无法直接内核层的数据,对重要的数据的一些关键操作就会由内核来完成,增强了系统的健壮性。
对象管理器是执行体的组件,主要管理执行体对象。但是,执行体对象也可能封装了一个或多个内核对象。因此,Windows对象管理器的作用可以认为是对内核空间中多种多样的不同内核对象进行控制。
Windows对象管理器基本设计意图:
为执行体的数据结构提供统一而又可扩展的定义和控制机制
提供统一的安全访问机制
在无需修改已有系统代码的情况下,加入新的对象类型
提供一组标准的API来对对象执行各种操作
提供一种命名机制,与文件系统的命名机制集成在一起
二.对象目录
在Windows内核中的对象可以是有名对象,也可以是无名对象。当创建一个对象并返回句柄之后,创建该对象的进程就可以通过句柄访问它,这样的对象可以是无名对象。但是,如下几种情况下需要创建的对象是有名对象:
一个进程创建一个对象之后,别的进程需要共享这个对象。可是别的进程并不知道这个对象在其创建进程里的句柄,即使知道也没办法共享,因为句柄是属于特定进程而不是全局。为了共享,别的对象需要“打开”同一对象,这个时候就需要对象名了。
同一个进程可能需要有访问同一个对象的多个上下文(例如文件的读/写位置)。所谓“创建“对象,是在创建一个目标对象的同时创建一个上下文,此后每当需要增加一个新的上下文时就得再”打开“一次同一个目标对象,此时往往需要使用对象名。
有些对象的内容是永久性的,例如文件就是这样,这一次写入的数据可能要到将来的某个时候再来读出
所以,一般而言对象是需要命名的。当系统中存在着许多命名对象的时候,就需要有一种手段可以对这些命名对象进行同一的组织和管理。而对象的本身结构并没有提供构成对象目录的手段,因此,Windows内部维护了一个对象层次目录(即系统全局名字空间)来对这些命名对象进行管理。
对象目录是由多个"节点"连接而成的树状结构,数的根是一个“目录”对象,即类型为OBJECT_DIRECTORY的对象。树中的每个节点都是对象,所以"节点名"就是"对象名"。除根节点之外,树中的所有中间节点都必须是目录对象或“符号连接”对象(即类型为OBJECT_SYMBOLIC_LINK的对象),而普通的对象则只能成为"叶节点"。对于对象目录中的任何节点,如果从根节点或某个中间节点开始逐节向此节点前进,记下沿途各个节点的节点名,并以分隔符"\"加以分隔,就形成一个"路径名"。如果路径名中的第一个节点是根节点,就是全路径名或称绝对路径。根节点的节点名是"\",内核中全局指针ObpRootDirectoryObject指向的就是对象目录的根节点。
显然,目录节点是对象目录的骨干,没有目录节点就形不成对象目录。另一方面,目录节点本身也是一种对象,其数据结构为OBJECT_DIRECTORY。该结构再不同版本的系统中定义不同,在Win7 x86版本中的定义如下:
3: kd> dt _OBJECT_DIRECTORY nt!_OBJECT_DIRECTORY +0x000 HashBuckets : [37] Ptr32 _OBJECT_DIRECTORY_ENTRY +0x094 Lock : _EX_PUSH_LOCK +0x098 DeviceMap : Ptr32 _DEVICE_MAP +0x09c SessionId : Uint4B +0x0a0 NamespaceEntry : Ptr32 Void +0x0a4 Flags : Uint4B
最关键的是第一个成员HashBuckets,该成员是OBJECT_DITRCTORY_ENTRY结构指针数组。这是个散列表,数组中的每个指针都可以用来维持一个"(对象)目录项"即OBJECT_DIRECTORY_ENTRY结构的队列。目录项结构本身并非对象,但是除根节点以外的所有节点都要靠目录项结构才能插入目录,所以这种结构起着类似螺丝钉的作用,其定义如下:
3: kd> dt _OBJECT_DIRECTORY_ENTRY nt!_OBJECT_DIRECTORY_ENTRY +0x000 ChainLink : Ptr32 _OBJECT_DIRECTORY_ENTRY +0x004 Object : Ptr32 Void +0x008 HashValue : Uint4B
指针ChanLink用来构成队列,而指针Object则指向其所连接的对象。除了根目录节点以外,对象目录中的每个节点(对象)都必须挂在某个目录节点的某个散列队列中,具体挂在哪一个队列中则取决于节点名(对象名)的Hash值。下图是一张对象目录的示意图
先看根节点,这是个目录对象,其主体是个Hash表,这里只画出了Hash表中的两个队列。上面的队列里有4个节点,这4个节点的对象名都有相同的Hash值,所以挂在同一队列中。每一个节点都是一个对象,但是需要有个目录项作为连接件才能进入对象目录。在这4个节点中,对象"M"是个目录节点,但是此刻这个目录还是空的,其余3个节点则是普通对象,所以这4个节点都是叶节点。注意节点A和H直接并没有层次的关系,它们属于同一层次,都是根目录下的节点。下面的这个队列中有两个节点,即"J"和"D"。其中,"J"是个目录节点,而且该目录不是空白的,所以这是个中间节点。如前所述,只有两种对象可以充任中间节点,一种是目录,一种是符号连接。图中的节点"Y"就是个符号连接对象,这个对象指向了上面的目录节点"M"。不过,符号连接对象的数据结构中其实并没有这样的指针,而只是用"M"的全路径来说明连接的目标。符号连接的目录可以是目录节点,也可是叶节点,还可以是另一个符号连接节点。所以,从某种意义上说,符号连接的作用是为一个路径起了一个别名。
注意图中有两个节点都名为"D",但是它们在不同的目录节点下面,它们的全路径名不同,所以不会混淆。如前所述,根节点的节点名是"\",所以其中之一的全路径名为"\D",而另一个则是"\J\D"。再看节点"M",由于符号链接的存在,这个节点有两个全路径名,一个是"M",另一个是"\J\Y"。
WinObj就是通过对该结构的解析得到了系统中的对象
创建目录对象所使用的函数是NtCreateDirectoryObject,该函数的定义如下
NTSTATUS NtCreateDirectoryObject( OUT PHANDLE DirectoryHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes );
参数 | 含义 |
DirectoryHandle | 指向接收对象目录句柄的句柄变量的指针 |
DesiredAccess | 指定访问掩码值,该值确定对对象的请求访问。除了为所有类型的对象定义的访问权限(请参见access_MASK),调用方还可以指定以下一个或多个特定于对象目录的访问权限:
|
ObjectAttributes | 指向包含对象属性的对象属性结构的指针,必须已通过调用InitializeObjectAttributes初始化该对象属性 |
该函数的功能实现如下,核心是通过ObCreateObject创建目录对象,在用ObInsertObject将对象插入到句柄表中,关于对象创建会在下一小节说明。
NTSTATUS NtCreateDirectoryObject( OUT PHANDLE DirectoryHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes ) { POBJECT_DIRECTORY Directory; HANDLE Handle; KPROCESSOR_MODE PreviousMode; NTSTATUS Status; PAGED_CODE(); ObpValidateIrql( "NtCreateDirectoryObject" ); // 检测当前模式 PreviousMode = KeGetPreviousMode(); if (PreviousMode != KernelMode) { try { ProbeForWriteHandle( DirectoryHandle ); // 如果是用户模式,则探测目录对象指针是否可写 } except( EXCEPTION_EXECUTE_HANDLER ) { return( GetExceptionCode() ); } } // 创建对象 Status = ObCreateObject( PreviousMode, ObpDirectoryObjectType, //指定创建对象类型为目录对象 ObjectAttributes, PreviousMode, NULL, sizeof( *Directory ), 0, 0, (PVOID *)&Directory ); if (!NT_SUCCESS( Status )) { return( Status ); } RtlZeroMemory( Directory, sizeof( *Directory ) ); // 将创建的目录对象插入到句柄表中 Status = ObInsertObject( Directory, NULL, DesiredAccess, 0, (PVOID *)NULL, &Handle ); try { // 将句柄返回给调用者 *DirectoryHandle = Handle; } except( EXCEPTION_EXECUTE_HANDLER ) { // // Fall through, since we do not want to undo what we have done. // } return( Status ); }
在初始化对象系统的函数ObInitSystem中,会使用下列代码创建L"\",L"\KernelObjects",L"\ObjectTypes"。
BOOLEAN ObInitSystem ( VOID ) { RtlInitUnicodeString( &RootDirectoryName, L"\\" ); InitializeObjectAttributes( &ObjectAttributes, &RootDirectoryName, OBJ_CASE_INSENSITIVE | OBJ_PERMANENT, NULL, EffectiveSd ); Status = NtCreateDirectoryObject( &RootDirectoryHandle, DIRECTORY_ALL_ACCESS, &ObjectAttributes ); if (!NT_SUCCESS( Status )) { return( FALSE ); } RtlInitUnicodeString( &TypeDirectoryName, L"\\KernelObjects" ); InitializeObjectAttributes( &ObjectAttributes, &TypeDirectoryName, OBJ_CASE_INSENSITIVE | OBJ_PERMANENT, NULL, &SecurityDescriptor ); Status = NtCreateDirectoryObject( &TypeDirectoryHandle, DIRECTORY_ALL_ACCESS, &ObjectAttributes ); if (!NT_SUCCESS( Status )) { return( FALSE ); } RtlInitUnicodeString( &TypeDirectoryName, L"\\ObjectTypes" ); InitializeObjectAttributes( &ObjectAttributes, &TypeDirectoryName, OBJ_CASE_INSENSITIVE | OBJ_PERMANENT, NULL, NULL ); Status = NtCreateDirectoryObject( &TypeDirectoryHandle, DIRECTORY_ALL_ACCESS, &ObjectAttributes ); if (!NT_SUCCESS( Status )) { return( FALSE ); } }
对象管理器提供了一些基本的操作用于在对象层次目录中插入,查询和删除目录或目录项。例如,ObpLookupDirectoryEntry函数的功能是在一个指定的目录中查找一个名称;ObpInsertDirectoryEntry函数把一个对象插入到一个目录中;ObpDeleteDirectoryEntry函数用来删除刚刚找到的那一项。这三个函数都直接在一个子目录中进行操作。另外还有一个重要的操作是ObpLookupObjectName,它可以从指定的目录或根目录,递归地根据名称来找到一个对象,该函数的定义如下:
NTSTATUS ObpLookupObjectName( IN HANDLE RootDirectoryHandle, IN PUNICODE_STRING ObjectName, IN ULONG Attributes, IN POBJECT_TYPE ObjectType, IN KPROCESSOR_MODE AccessMode, IN PVOID ParseContext OPTIONAL, IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL, IN PVOID InsertObject OPTIONAL, IN OUT PACCESS_STATE AccessState, OUT PBOOLEAN DirectoryLocked, OUT PVOID *FoundObject )
对象管理器的两个接口函数ObOpenObjectByName和ObReferenceObjectByName,正是通过ObpLookupObjectName来完成其打开对象或引用对象的功能的。将对象插入到一个进程的句柄表中的ObInsertObject函数,也通过ObpLookupObjectName来验证待插入的对象是否在全局名字空间中并不存在,当然,对于无名称的对象并不需要这样的检测,该函数位于obdir.c中,代码很长,以下是基本的执行逻辑
参数检查
如果调用者指定了RootDirectoryHandle参数,则利用此RootDirectory的Parse方法来解析对象名称,直到解析成功或者不成功,或者指示从头解析
如果调用者没有指定RootDirectoryHandle参数,则系统从全局的根目录ObpRootDirctoryObject开始解析。在这种情况下,传递进来的对象名称必须以"\"开始,如果待查找的名称仅仅是"\",则执行特殊处理。否则,执行下面的逻辑
首先判断名称是否以"\??\"开头,如果是的话,需要拿到当前进程的DeviceMap(设备表),以进一步查询
如果名称正好是"\??\",则直接返回当前进程的DeviceMap作为结果
调用ObpLookupDirectoryEntry函数,层层递进,或者碰到具有Parse方法的对象,由它来解析余下的名称字符串,或者碰到子目录对象,从而可以在子目录对象中进一步查询下一级名称
内核代码在创建对象类型时,可以为新的对象类型指定Parse方法。每一种对象都可以有它自己的名称解析方法,这使得Windows的名称系统非常强大,即允许以基本的目录方法来管理名称的层次结构,也允许特定类型的对象有它自己的命名和解析策略。譬如,文件(File)对象有它自己的Parse方法,从而可以方便地支持我们所熟悉地文件系统中的目录结构。
三.对象
在Windows内核中,"对象"实质上就是数据结构,就是一些带有"对象头(OBJECT_HEADER)"的特殊数据结构。对象的总体结构包括:附加信息头,对象头和对象体。
附加信息头是可选的,它包括了以下信息头的一个或多个
名称 | 结构 | 作用 |
创建信息头 | OBJECT_HEADER_CREATOR_INFO | 包含创建者信息,用来将创建的对象挂入其创建者的对象队列 |
命名信息头 | OBJECT_HEADER_NAME_INFO | 载有对象名和目录节点的指针 |
句柄信息头 | OBJECT_HEADER_HANDLE_INFO | 关于句柄的信息 |
配额信息头 | OBJECT_HEADER_QUOTA_INFO | 关于耗用内存配额的信息 |
附加信息头和对象头的结构大小都是固定的,只有对象体的大小会根据对象类型的不同而不同。对象头包括对象扩展,对象扩展一般在对象体之后,它们共同组成对象头的空间。一个有效的内核对象至少要有对象头和对象体,附加信息头可以不需要。其实对于对象来说,只要有对象头就可以成立,因为对象头才是真正的对象本身,只不过这样的对象,因为缺乏对象头没法进行管理。对象头包含的信息描述了对象头,对象管理器使用对象头管理对象。如果各部分都存在,顺序应该如下图所示,按内存地址高低顺序,依次是附加信息头,对象头,对象体。
其中对象头的结构如下
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_HEADER;
成员 | 含义 |
PointerCount | 对象的引用计数,包括每个句柄的引用。内核模式组件可以通过ObReferenceObjectByPointer引用一个对象,增加计数,而无需使用一个句柄。如果对象计数为0,对象将会被管理器删除 |
HandleCount | 对象的句柄计数 |
Type | 指向对象的类型信息 |
NameInfoOffset | 名称信息的内存偏移,如果为0,表示不存在命名信息 |
HandleInfoOffset | 句柄信息的内存偏移,如果为0,表示不存在句柄信息 |
QuotaInfoOffset | 配额信息的内存偏移,如果为0,表示不存在配额信息 |
Flags | 对象标志 |
ObjectCreateInfo | 对象创建信息 |
SecurityDescriptor | 安全描述符,决定谁可以使用这个对象以及可以对这个对象做什么操作 |
Body | 指向对象体 |
对象头中的Body成员不应该算是OBJECT_HEADER,因为紧跟对象头的就是对象体,所以&Header->Body的地址就是对象头的本身。
对象体的地址减去0x18就得到了对象头的地址,在对象头中NameInfoOffset,HandleInfoOffset和QuotaInfoOffset分别保存了命名信息头,句柄信息头,配额信息头相对于对象头的偏移,所以使用对象头减去这些偏移就可以得到相应的附加信息头的地址。对于创建信息头,在对象头中并没有保存它的偏移,这是因为它离对象头最近,只需要减去OBJECT_HEADER_CREATOR_INFO的大小就可以得到相应的地址,只不过在减去之前要先判断OB_FLAG_CREATOR_INFO标志。
为了方便用户使用,内核提供了以下的宏来方便的获得相应的头地址
#define CONTAINING_RECORD(address, type, field) \ ((type *)(((ULONG_PTR)address) - (ULONG_PTR)(&(((type *)0)->field)))) #define OBJECT_TO_OBJECT_HEADER( o ) \ CONTAINING_RECORD( (o), OBJECT_HEADER, Body ) #define OBJECT_HEADER_TO_NAME_INFO(h) \ ((POBJECT_HEADER_NAME_INFO)(!(h)->NameInfoOffset ? \ NULL : ((PCHAR)(h) - (h)->NameInfoOffset))) #define OBJECT_HEADER_TO_HANDLE_INFO(h) \ ((POBJECT_HEADER_HANDLE_INFO)(!(h)->HandleInfoOffset ? \ NULL : ((PCHAR)(h) - (h)->HandleInfoOffset))) #define OJBECT_HEADER_TO_QUOTA_INFO(h) \ ((POBJECT_HEADER_QUOTA_INFO)(!(h)->QuotaInfoOffset ? \ NULL : ((PCHAR)(h) - (h)->QuotaInfoOffset))) #define OBJECT_HEADER_TO_CREATE_INFO(h) \ ((POBJECT_HEADER_CREATOR_INFO)(!((h)->Flags & \ OB_FLAG_CREATOR_INFO) ? NULL ? ((PCHAR)(h) - \ sizeof(OBJECT_HEADER_CREATOR_INFO))))
想要在内核中创建并使用一个对象,需要经过下面的三个步骤:
通过ObCreateObject函数来创建目标对象
目标对象本身的初始化
通过ObInsertObject将目标对象插入对象目录和句柄表,并返回句柄
其中创建目标对象的ObCreateObject函数定义如下:
NTSTATUS ObCreateObject( IN KPROCESSOR_MODE ProbeMode, IN POBJECT_TYPE ObjectType, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN KPROCESSOR_MODE OwnershipMode, IN OUT PVOID ParseContext OPTIONAL, IN ULONG ObjectBodySize, IN ULONG PagedPoolCharge, IN ULONG NonPagedPoolCharge, OUT PVOID *Object )
其中第二个参数ObjectType是一个类型对象,在创建每一种对象之前都需要创建这个对象对应的类型对象,关于类型对象具体内容在下一小节,这里先看看ObCreateObject函数的具体实现
NTSTATUS ObCreateObject( IN KPROCESSOR_MODE ProbeMode, IN POBJECT_TYPE ObjectType, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN KPROCESSOR_MODE OwnershipMode, IN OUT PVOID ParseContext OPTIONAL, IN ULONG ObjectBodySize, IN ULONG PagedPoolCharge, IN ULONG NonPagedPoolCharge, OUT PVOID *Object ) { UNICODE_STRING CapturedObjectName; POBJECT_CREATE_INFORMATION ObjectCreateInfo; POBJECT_HEADER ObjectHeader; NTSTATUS Status; PAGED_CODE(); // 分配对象创建信息内存 ObjectCreateInfo = ObpAllocateObjectCreateInfoBuffer(); if (ObjectCreateInfo == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; } else { // 捕获对象创建信息 Status = ObpCaptureObjectCreateInformation(ObjectType, ProbeMode, ObjectAttributes, &CapturedObjectName, ObjectCreateInfo, FALSE); if (NT_SUCCESS(Status)) { // 校验属性 if (ObjectType->TypeInfo.InvalidAttributes & ObjectCreateInfo->Attributes) { Status = STATUS_INVALID_PARAMETER; } else { // 检查是否拥有分页池 if (PagedPoolCharge == 0) { PagedPoolCharge = ObjectType->TypeInfo.DefaultPagedPoolCharge; } // 检查是否拥有非分页池 if (NonPagedPoolCharge == 0) { NonPagedPoolCharge = ObjectType->TypeInfo.DefaultNonPagedPoolCharge; } // 保存在对象创建信息头上 ObjectCreateInfo->PagedPoolCharge = PagedPoolCharge; ObjectCreateInfo->NonPagedPoolCharge = NonPagedPoolCharge; // 分配对象内存空间 Status = ObpAllocateObject(ObjectCreateInfo, OwnershipMode, ObjectType, &CapturedObjectName, ObjectBodySize, &ObjectHeader); if (NT_SUCCESS(Status)) { // 指向对象体 *Object = &ObjectHeader->Body; // 检查是否是一个永久对象 if (ObjectHeader->Flags & OB_FLAG_PERMANENT_OBJECT) { // 做特权级别检查 if (!SeSinglePrivilegeCheck(SeCreatePermanentPrivilege, ProbeMode)) { ObpFreeObject(*Object); Status = STATUS_PRIVILEGE_NOT_HELD; } } return Status; } } // 释放创建者信息 ObpReleaseObjectCreateInformation(ObjectCreateInfo); if (CapturedObjectName.Buffer != NULL) { ObpFreeObjectNameBuffer(&CapturedObjectName); } } ObpFreeObjectCreateInfoBuffer(ObjectCreateInfo); } return Status; }
ObpAllocateObjectCreateInfoBuffer分配出来的对象创建信息是用来设置对象头成员ObjectCreateInfo的。如果ObpAllocateObject分配对象内存成功,就会通过*Object返回对象体地址,返回成功状态,所以对象分配的具体实现在ObpAllocateObject实现,代码如下:
NTSTATUS ObpAllocateObject( IN POBJECT_CREATE_INFORMATION ObjectCreateInfo, IN KPROCESSOR_MODE OwnershipMode, IN POBJECT_TYPE ObjectType OPTIONAL, IN PUNICODE_STRING ObjectName, IN ULONG ObjectBodySize, OUT POBJECT_HEADER *ReturnedObjectHeader ) { ULONG HeaderSize; POBJECT_HEADER ObjectHeader; NTSTATUS Status; PVOID ZoneSegment; USHORT CreatorBackTraceIndex = 0; ULONG QuotaInfoSize; ULONG HandleInfoSize; ULONG NameInfoSize; ULONG CreatorInfoSize; POBJECT_HEADER_QUOTA_INFO QuotaInfo; POBJECT_HEADER_HANDLE_INFO HandleInfo; POBJECT_HEADER_NAME_INFO NameInfo; POBJECT_HEADER_CREATOR_INFO CreatorInfo; POOL_TYPE PoolType; PAGED_CODE(); // 每次创建对象都会增加该全局变量的创建计数 ObpObjectsCreated += 1; // 是否提供了创建信息 // 如创建的是对象类型(ObCreateObjectType调用),此参数为NULL if (ObjectCreateInfo == NULL) { QuotaInfoSize = 0; HandleInfoSize = 0; NameInfoSize = sizeof( OBJECT_HEADER_NAME_INFO ); CreatorInfoSize = sizeof( OBJECT_HEADER_CREATOR_INFO ); } else { //检查是否提供配额信息 if (ObjectCreateInfo->PagedPoolCharge != ObjectType->TypeInfo.DefaultPagedPoolCharge || ObjectCreateInfo->NonPagedPoolCharge != ObjectType->TypeInfo.DefaultNonPagedPoolCharge || ObjectCreateInfo->SecurityDescriptorCharge > SE_DEFAULT_SECURITY_QUOTA || (ObjectCreateInfo->Attributes & OBJ_EXCLUSIVE) ) { QuotaInfoSize = sizeof( OBJECT_HEADER_QUOTA_INFO ); ObpObjectsWithPoolQuota += 1; } else { QuotaInfoSize = 0; } // 是否包含句柄信息 if (ObjectType->TypeInfo.MaintainHandleCount) { HandleInfoSize = sizeof( OBJECT_HEADER_HANDLE_INFO ); ObpObjectsWithHandleDB += 1; } else { HandleInfoSize = 0; } // 检查是否有对象名 if (ObjectName->Buffer != NULL) { NameInfoSize = sizeof( OBJECT_HEADER_NAME_INFO ); ObpObjectsWithName += 1; } else { NameInfoSize = 0; } // 检查是否包含类型链表 if (ObjectType->TypeInfo.MaintainTypeList) { CreatorInfoSize = sizeof( OBJECT_HEADER_CREATOR_INFO ); ObpObjectsWithCreatorInfo += 1; } else { CreatorInfoSize = 0; } } // 计算文件头的空间大小 HeaderSize = QuotaInfoSize + HandleInfoSize + NameInfoSize + CreatorInfoSize + FIELD_OFFSET( OBJECT_HEADER, Body ); // 检查是否提供了对象类型以及是否指定了分配池类型 if ((ObjectType == NULL) || (ObjectType->TypeInfo.PoolType == NonPagedPool)) { PoolType = NonPagedPool; } else { PoolType = PagedPool; } // 分配对象头和对象体内存,从这里可以看到对象头根对象头是连在一起的 ObjectHeader = ExAllocatePoolWithTag( PoolType, HeaderSize + ObjectBodySize, (ObjectType == NULL ? 'TjbO' : ObjectType->Key) | PROTECTED_POOL ); if (ObjectHeader == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } #if i386 && !FPO CreatorBackTraceIndex = ExGetPoolBackTraceIndex( ObjectHeader ); #else CreatorBackTraceIndex = 0; #endif // i386 && !FPO // 是否有配额信息 if (QuotaInfoSize != 0) { QuotaInfo = (POBJECT_HEADER_QUOTA_INFO)ObjectHeader; QuotaInfo->PagedPoolCharge = ObjectCreateInfo->PagedPoolCharge; QuotaInfo->NonPagedPoolCharge = ObjectCreateInfo->NonPagedPoolCharge; QuotaInfo->SecurityDescriptorCharge = ObjectCreateInfo->SecurityDescriptorCharge; QuotaInfo->ExclusiveProcess = NULL; ObjectHeader = (POBJECT_HEADER)(QuotaInfo + 1); } // 是否有句柄信息 if (HandleInfoSize != 0) { HandleInfo = (POBJECT_HEADER_HANDLE_INFO)ObjectHeader; HandleInfo->SingleEntry.HandleCount = 0; ObjectHeader = (POBJECT_HEADER)(HandleInfo + 1); } // 是否有命名信息 if (NameInfoSize != 0) { NameInfo = (POBJECT_HEADER_NAME_INFO)ObjectHeader; NameInfo->Name = *ObjectName; NameInfo->Directory = NULL; ObjectHeader = (POBJECT_HEADER)(NameInfo + 1); } // 是否有创建信息 if (CreatorInfoSize != 0) { CreatorInfo = (POBJECT_HEADER_CREATOR_INFO)ObjectHeader; CreatorInfo->CreatorBackTraceIndex = CreatorBackTraceIndex; CreatorInfo->CreatorUniqueProcess = PsGetCurrentProcess()->UniqueProcessId; InitializeListHead( &CreatorInfo->TypeList ); ObjectHeader = (POBJECT_HEADER)(CreatorInfo + 1); } // 计算为对象头赋值偏移 if (QuotaInfoSize != 0) { ObjectHeader->QuotaInfoOffset = (UCHAR)(QuotaInfoSize + HandleInfoSize + NameInfoSize + CreatorInfoSize);(UCHAR)(QuotaInfoSize + HandleInfoSize + NameInfoSize + CreatorInfoSize); } else { ObjectHeader->QuotaInfoOffset = 0; } if (HandleInfoSize != 0) { ObjectHeader->HandleInfoOffset = (UCHAR)(HandleInfoSize + NameInfoSize + CreatorInfoSize); } else { ObjectHeader->HandleInfoOffset = 0; } if (NameInfoSize != 0) { ObjectHeader->NameInfoOffset = (UCHAR)(NameInfoSize + CreatorInfoSize); } else { ObjectHeader->NameInfoOffset = 0; } // 新对象标志 ObjectHeader->Flags = OB_FLAG_NEW_OBJECT; // 创建信息标志 if (CreatorInfoSize != 0) { ObjectHeader->Flags |= OB_FLAG_CREATOR_INFO; } // 句柄信息标志 if (HandleInfoSize != 0) { ObjectHeader->Flags |= OB_FLAG_SINGLE_HANDLE_ENTRY; } // 初始化对象头 ObjectHeader->PointerCount = 1; ObjectHeader->HandleCount = 0; ObjectHeader->Type = ObjectType; // 是否为内核对象 if (OwnershipMode == KernelMode) { ObjectHeader->Flags |= OB_FLAG_KERNEL_OBJECT; } // 是否为独占对象 if (ObjectCreateInfo != NULL && ObjectCreateInfo->Attributes & OBJ_PERMANENT ) { ObjectHeader->Flags |= OB_FLAG_PERMANENT_OBJECT; } if ((ObjectCreateInfo != NULL) && (ObjectCreateInfo->Attributes & OBJ_EXCLUSIVE) ) { ObjectHeader->Flags |= OB_FLAG_EXCLUSIVE_OBJECT; } ObjectHeader->ObjectCreateInfo = ObjectCreateInfo; ObjectHeader->SecurityDescriptor = NULL; // 检查是否为指定对象类型 if (ObjectType != NULL) { ObjectType->TotalNumberOfObjects += 1; if (ObjectType->TotalNumberOfObjects > ObjectType->HighWaterNumberOfObjects) { ObjectType->HighWaterNumberOfObjects = ObjectType->TotalNumberOfObjects; } } // 返回对象头地址 *ReturnedObjectHeader = ObjectHeader; return STATUS_SUCCESS; }
可以看到,该函数的主要作用就是分配初始化一块需要的对象头内存并返回回去,内核就是通过这种方式来创建一个对象。目标对象创建成功以后,还需要通过ObInsertObject函数插入到句柄表中,根据对象属性ObjectAttributes->Attributes是否带有OBJ_KERNEL_HANDLE标志,如果有则插入到内核句柄表(ObpKernelHandleTable),否则则插入到当前进程的句柄表。不过对于Idle,System这样的内核进程的句柄表,也是内核句柄表。
ObInsertObject函数比较长,具体代码在obinsert.c中,该函数的功能分为以下三步:
检查是否是无名对象且不存在安全要求,调用ObpCreateUnnamedHandle创建无名句柄
如果存在对象名,先调用ObpLookupObjectName查找是否存在同名对象,如果存在同名对象,返回失败状态
如果没有同名对象,且参数Handle不为NULL,调用ObpCreateHandle创建句柄
四.对象类型
在Windows中每一种对象都需要有一个与其对应的类型对象(即OBJECT_TYPE对象),其定义如下:
#define OBJECT_LOCK_COUNT 4 typedef struct _OBJECT_TYPE { ERESOURCE Mutex; LIST_ENTRY TypeList; UNICODE_STRING Name; 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;
其中的重要成员即含义如下:
成员 | 含义 |
TypeList | 类型链表,确切地说是类型对象的链表,所有类型相同的对象通过该双向链表连接 |
Name | 类型名称,如:"Type","Directory" |
DefaultObject | 默认对象,比如指向全局的默认对象ObpDefaultObject。它是事件对象,没有对象头,或者说它本身就是一个头。这个字段的初始化是根据TypeInfo字段UseDefaultObject=TRUE设置的 |
Index | 类型索引,指向的是ObpObjectTypes全局对象类型数组的下标,索引时需要减去1,从创建此类型的类型对象总量得到。比如:对象类型"Type",Index等于1;对象类型"Directory",Index等于2;对象类型"SymbolicLink",Index等于3 |
TotalNumberOfObjects | 对象数量,主要在ObpAllocateObject函数中增加,可见主要是对分配对象的计数 |
TotalNumberOfHandles | 句柄数量,记录对象上限,主要在ObpIncrementHandleCount函数和ObpIncrementUnnamedHandleCount函数中增加,而在ObpDecrementHandleCount函数中减少 |
TypeInfo | 对象类型初始化信息 |
Key | 标签,如:'TjbO' |
系统定义的对象种类是有限的,以下是常见的几种对象,每一种对象都有一个全局的POBJECT_TYPE指针来指向其类型对象以供用户使用。
对象类型 | 系统调用 | Win32 API函数 | 全局类型对象变量 |
Process | NtCreateProcess/NtOpenProcess | CreateProcess,CreateProcessAsUser/OpenProcess | PsProcessType |
Thread | NtCreateThread/NtOpenThread | CreateThread,CreateRemoteThread/OpenThread | PsThreadType |
Token | NtCreateToken/NtOpenProcessToken,NtOpenThreadToken | None/OpenProcessToken,OpenThreadToken | SeTokenObjectType |
Key | NtCreateKey/NtOpenKey | RegCreateKey/RegOpenKey | CmpKeyObjectType |
File | NtCreateFile/NtOpenFile | CreateFile/OpenFile | IoFileObjectType |
Directory | NtCreateDirectoryObject/NtOpenDirectoryObject | None/None | ObpDirectoryObjectType |
SymbolicLink | NtCreateSymbolicLinkObject/NtOpenSymbolicLinkObject | DefineDosDeivce/None | ObpSymbolicLinkObjectType |
Section | NtCreateSection/NtOpenSection | CreateFileMapping/OpenFileMapping | MmSectionObjectType |
IoCompletion | NtCreateIoCompletion/NtOpenIoCompletion | CreateIoCompletionPort/None | IoCompletionObjectType |
Callback | ExCreateCallback | None | ExCallbackObjectType |
Semaphore | NtCreateSemaphore/NtOpenSemaphore | CreateSemphore/OpenSemphore | ExSemphoreObjectType |
Event | NtCreateEvent/NtOpenEvent | CreateEvent/OpenEvent | ExEventObjectType |
Debug | NtCreateDebugObject/NtCreatePagingFile | None | DbgkDebugObjectType |
类似Device,Driver等用于设备驱动的对象类型,这些对象并没有专门的系统调用,而是使用NtOpenFile作为创建/打开的方法。此外,对象类型并不是一个封闭的集合,内核可安装模块(.sys模块)可以创建新的对象类型,这些新的类型也只能借用NtOpenFile作为创建/打开的方法。事实上,NtOpenFile是创建/打开对象的通用方法,凡是没有专门为其配备系统调用的对象类型,就都以NtOpenFile作为创建/打开的方法,并借用NtReadFile,NtWriteFile,NtDeiviceIoControlFile等本来用于文件操作的方法作为各种操作的方法。
Windows对象类型的集合对于内核来说是开放的,内核函数可以向这个集合中加入新的对象类型。一旦增加了对象类型之后,用户程序就可以通过(借用文件对象的)系统调用创建或打开并操作属于此种类型的对象。而对于用户空间,Windows并没有提供系统调用来创建对象,所以用户层程序需要将内核模块,也就是.sys模块动态加载安装到内核中,使其成为内核的一部分。这样,用户就有了增加新对象的自由。
在内核层中,想要增加一个新的类型对象就需要通过调用ObCreateObjectType来实现,该函数定义如下:
NTSTATUS ObCreateObjectType( IN PUNICODE_STRING TypeName, IN POBJECT_TYPE_INITIALIZER ObjectTypeInitializer, IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL, OUT POBJECT_TYPE *ObjectType )
其中第二个参数ObjectTypeInitializer是一个指向OBJECT_TYPE_INITIALIZER的指针,和OBJECT_TYPE中的TypeInfo的类型是一样的,该参数用来指定新创建的类型的各种信息和特性等等,定义如下
typedef struct _OBJECT_TYPE_INITIALIZER { USHORT Length; // 结构长度 BOOLEAN UseDefaultObject; // 使用默认对象(ObpDefaultObject) BOOLEAN CaseInsensitive; // 是否区分大小写(TRUE为不区分,FALSE为区分) ULONG InvalidAttributes; // 有效属性 GENERIC_MAPPING GenericMapping; // 通用映射 ULONG ValidAccessMask; // 有效访问掩码 BOOLEAN SecurityRequired; // 安全需求 BOOLEAN MaintainHandleCount; // 保持句柄计数 BOOLEAN MaintainTypeList; // 保持类型链表 POOL_TYPE PoolType; // 池类型(PagePool, NonPagePool) 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;
可以看到,在调用ObCreateObjectType函数来构建一种新的对象类型时,调用者除了可以指定此种类型对象的一些数据特性以外,还可以指定该类型对象的一些基本操作方法,包括Dump, Open, Close, Delete, Parse, Security, QueryName和OkayToClose。对象管理器正是通过这些方法来同一管理各种类型的对象的。系统有一个全局变量ObpObjectTypes数组记录了所有已创建的类型,这是一个静态数组,WRK限定不超过48种对象类型。OBJECT_TYPE中的Index成员记录了一个类型对象在此数组中的索引。
当完成了ObjectTypeInitializer的填写,就可以使用ObCreateObjectType来创建对象类型,初始化对象系统的函数ObInitSystem中可以看到,通过这种方法创建了L"Type", L"Directory", L"SymbolicLink"三种对象类型。
BOOLEAN ObInitSystem ( VOID ) { RtlZeroMemory( &ObjectTypeInitializer, sizeof( ObjectTypeInitializer ) ); ObjectTypeInitializer.Length = sizeof( ObjectTypeInitializer ); ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK; ObjectTypeInitializer.PoolType = NonPagedPool; RtlInitUnicodeString( &TypeTypeName, L"Type" ); ObjectTypeInitializer.ValidAccessMask = OBJECT_TYPE_ALL_ACCESS; ObjectTypeInitializer.GenericMapping = ObpTypeMapping; ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof( OBJECT_TYPE ); ObjectTypeInitializer.MaintainTypeList = TRUE; ObjectTypeInitializer.UseDefaultObject = TRUE; ObjectTypeInitializer.DeleteProcedure = &ObpDeleteObjectType; ObCreateObjectType( &TypeTypeName, &ObjectTypeInitializer, (PSECURITY_DESCRIPTOR)NULL, &ObpTypeObjectType ); // // Create the object type for the "Directory" object. // ObjectTypeInitializer.PoolType = OB_NAMESPACE_POOL_TYPE; RtlInitUnicodeString( &DirectoryTypeName, L"Directory" ); ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof( OBJECT_DIRECTORY ); ObjectTypeInitializer.ValidAccessMask = DIRECTORY_ALL_ACCESS; ObjectTypeInitializer.CaseInsensitive = TRUE; ObjectTypeInitializer.GenericMapping = ObpDirectoryMapping; ObjectTypeInitializer.UseDefaultObject = TRUE; ObjectTypeInitializer.MaintainTypeList = FALSE; ObjectTypeInitializer.DeleteProcedure = NULL; ObCreateObjectType( &DirectoryTypeName, &ObjectTypeInitializer, (PSECURITY_DESCRIPTOR)NULL, &ObpDirectoryObjectType ); // // Clear SYNCHRONIZE from the access mask to not allow // synchronization on directory objects // ObpDirectoryObjectType->TypeInfo.ValidAccessMask &= ~SYNCHRONIZE; // // Create the object type for the "SymbolicLink" object. // RtlInitUnicodeString( &SymbolicLinkTypeName, L"SymbolicLink" ); ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof( OBJECT_SYMBOLIC_LINK ); ObjectTypeInitializer.ValidAccessMask = SYMBOLIC_LINK_ALL_ACCESS; ObjectTypeInitializer.CaseInsensitive = TRUE; ObjectTypeInitializer.GenericMapping = ObpSymbolicLinkMapping; ObjectTypeInitializer.DeleteProcedure = ObpDeleteSymbolicLink; ObjectTypeInitializer.ParseProcedure = ObpParseSymbolicLink; ObCreateObjectType( &SymbolicLinkTypeName, &ObjectTypeInitializer, (PSECURITY_DESCRIPTOR)NULL, &ObpSymbolicLinkObjectType ); }
而ObCreateObjectType的源码则在obtype.c中,该函数的执行的代码如下:
校验参数
通过while循环检测类型名,确保不包含分隔符'\'。因为'\'被用于根目录对象
检查我们是否已经创建类型目录对象。如果已创建,则根据类型名调用ObpLookupEntryDirectory查找。如果找到类型对象,就返回类型名冲突状态(STATUS_OBJECT_NAME_COLLISION)。如果第一次调用ObCreateObjectType创建类型对象,类型目录对象还没有创建,会跳过查找代码块
使用ObpAllocateObject分配对象空间,使用非分页内存。如果分配成功,返回对象头
从类型对象的对象头中获得对象体,设置对象类型名,标志
如果ObpTypeObjectType未初始化,现在就初始化,因为我们第一次创建的是"Type"本身,ObpTypeObject等于LocalObjectType
如果提供了创建信息,那么把创建的类型对象插入到类型对象链表
如果创建的类型对象数小于32,那么把创建的类型对象插入到ObpObjectTypes全局变量数组中。我们从这个数组可以得到系统创建的前32个类型对象
如果创建类型目录对象,则把创建的类型对象插入到类型目录中
在校验参数的时候,只要属于下面任何一种情况都返回无效参数(STATUS_INVALID_PARAMETER)
如果类型名为NULL
如果类型名长度为0
如果类型名长度不为sizeof(WCHAR)的倍数
如果类型初始化结果ObjectTypeInitializer为NULL
如果类型初始化结构体长度不匹配
如果属性值无效&~OBJ_VALID_ATTRIBUTES
如果存在句柄数,OpenProcedure和CloseProcedure都为NULL
如果使用默认对象,池类型不等于非分页
成功创建了对象类型以后,就可以创建该类型对应的内核对象了。
五.常用的几个内核函数
1.ObReferenceObjectByHandle
对象管理器的对象是执行体对象,它们位于系统地址空间中,因而所有的进程都可以访问这些对象。但是,在进程地址空间中运行的用户模式代码不能用指针的方式来引用这些对象,它们在调用系统服务时只通过句柄来引用执行体对象。句柄是进程范畴的概念,它一定在特定的进程环境中才有意义。在内核中,给定一个句柄,ObReferenceObjectByHandle函数可以通过该句柄得到目标对象的指针,该函数具体代码如下:
NTSTATUS ObpReferenceProcessObjectByHandle ( IN HANDLE Handle, IN PEPROCESS Process, IN PHANDLE_TABLE HandleTable, IN KPROCESSOR_MODE AccessMode, OUT PVOID *Object, OUT POBJECT_HANDLE_INFORMATION HandleInformation, OUT PACCESS_MASK AuditMask ) { ACCESS_MASK GrantedAccess; POBJECT_HEADER ObjectHeader; PHANDLE_TABLE_ENTRY ObjectTableEntry; NTSTATUS Status; PETHREAD Thread; PHANDLE_TABLE_ENTRY_INFO ObjectInfo; ObpValidateIrql("ObReferenceObjectByHandle"); Thread = PsGetCurrentThread (); *Object = NULL; if ((LONG)(ULONG_PTR) Handle < 0) { // 是否是当前进程或线程的句柄,如果是返回相应的对象 if (Handle == NtCurrentProcess()) { GrantedAccess = Process->GrantedAccess; ObjectHeader = OBJECT_TO_OBJECT_HEADER(Process); HandleInformation->GrantedAccess = GrantedAccess; HandleInformation->HandleAttributes = 0; *AuditMask = 0; ObpIncrPointerCount(ObjectHeader); *Object = Process; ASSERT( *Object != NULL ); Status = STATUS_SUCCESS; return Status; } else if (Handle == NtCurrentThread()) { GrantedAccess = Thread->GrantedAccess; ObjectHeader = OBJECT_TO_OBJECT_HEADER(Thread); HandleInformation->GrantedAccess = GrantedAccess; HandleInformation->HandleAttributes = 0; *AuditMask = 0; ObpIncrPointerCount(ObjectHeader); *Object = Thread; ASSERT( *Object != NULL ); Status = STATUS_SUCCESS; return Status; } else if (AccessMode == KernelMode) { Handle = DecodeKernelHandle( Handle ); // 获取内核句柄表 HandleTable = ObpKernelHandleTable; } else { return STATUS_INVALID_HANDLE; } }else { // 获取进程句柄表 HandleTable = PsGetCurrentProcessByThread(Thread)->ObjectTable; } ASSERT(HandleTable != NULL); // 进入临界区 KeEnterCriticalRegionThread(&Thread->Tcb); // 通过句柄将句柄映射成相应的结构体指针 ObjectTableEntry = ExMapHandleToPointer ( HandleTable, Handle ); if (ObjectTableEntry != NULL) { // 获取对象头 ObjectHeader = (POBJECT_HEADER)(((ULONG_PTR)(ObjectTableEntry->Object)) & ~OBJ_HANDLE_ATTRIBUTES); ObjectInfo = ExGetHandleInfo(HandleTable, Handle, TRUE); HandleInformation->GrantedAccess = GrantedAccess; HandleInformation->HandleAttributes = ObpGetHandleAttributes(ObjectTableEntry); // // Return handle audit information to the caller // if (ObjectInfo != NULL) { *AuditMask = ObjectInfo->AuditMask; } else { *AuditMask = 0; } ObpIncrPointerCount(ObjectHeader); ExUnlockHandleTableEntry( HandleTable, ObjectTableEntry ); KeLeaveCriticalRegionThread(&Thread->Tcb); // 获取对象体 *Object = &ObjectHeader->Body; ASSERT( *Object != NULL ); return STATUS_SUCCESS; } else { Status = STATUS_INVALID_HANDLE; } // 离开临界区 KeLeaveCriticalRegionThread(&Thread->Tcb); return Status; }
该函数的执行流程如下:
判断句柄值是否小于0,如果小于0,则判断是否是获取当前进程或者当前线程,如果是,则返回想要的内核对象。如果不是,则获取内核句柄表
如果句柄值大于0,则获取当前进程的句柄表
从获取到的句柄表中查找句柄对应的内核对象,将内核对象的对象体返回
2.ObReferenceObjectByPointer
前面讲的是给定句柄找到并引用目标对象。所谓引用目标对象,是递增其引用计数PointerCount。但是有的时候已经取得了目标对象的指针而要递增其引用计数的要求。这通常发生在为某种目的而要赋值指针的时候,因为每复制一个指针目标对象就多一个引用者。这样的操作由ObReferenceObjectPointer完成,该函数的实现如下:
NTSTATUS ObReferenceObjectByPointer ( __in PVOID Object, __in ACCESS_MASK DesiredAccess, __in_opt POBJECT_TYPE ObjectType, __in KPROCESSOR_MODE AccessMode ) { POBJECT_HEADER ObjectHeader; UNREFERENCED_PARAMETER (DesiredAccess); // 根据对象获得对象头 ObjectHeader = OBJECT_TO_OBJECT_HEADER( Object ); if ((ObjectHeader->Type != ObjectType) && (AccessMode != KernelMode || ObjectType == ObpSymbolicLinkObjectType)) { return( STATUS_OBJECT_TYPE_MISMATCH ); } // 增加引用计数 ObpIncrPointerCount( ObjectHeader ); return( STATUS_SUCCESS ); }
3.ObDeferenceObject
该函数的作用是撤销对内核对象的引用。所谓撤销对对象的引用,实际上就是递减其引用计数。由于给定的是对象指针,所以递减的是指针计数而不是句柄计数,该函数的代码如下:
LONG_PTR ObDereferenceObject ( IN PVOID Object ) { return ObfDereferenceObject (Object) ; }
可以看到该函数是通过ObfDereferenceObject来实现的,此函数的代码如下:
LONG_PTR FASTCALL ObfDereferenceObject ( __in PVOID Object ) { POBJECT_HEADER ObjectHeader; POBJECT_TYPE ObjectType; KIRQL OldIrql; LONG_PTR Result; // 获取对象头 ObjectHeader = OBJECT_TO_OBJECT_HEADER( Object ); #if DBG { POBJECT_HEADER_NAME_INFO NameInfo; NameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectHeader ); if (NameInfo) { InterlockedDecrement(&NameInfo->DbgDereferenceCount) ; } } #endif // // Decrement the point count and if the result is now then // there is extra work to do // ObjectType = ReadForWriteAccess(&ObjectHeader->Type); // 减少引用计数并返回结果 Result = ObpDecrPointerCount( ObjectHeader ); // 如果引用计数为0,则删除对象 if (Result == 0) { // // Find out the level we're at and the object type // OldIrql = KeGetCurrentIrql(); ASSERT(ObjectHeader->HandleCount == 0); // // If we're at the passive level then go ahead and delete the // object now. // if ( !KeAreAllApcsDisabled() ) { #ifdef POOL_TAGGING // // The object is going away, so we deregister it. // if (ObpTraceEnabled && !ObpTraceNoDeregister) { ObpDeregisterObject( ObjectHeader ); } #endif //POOL_TAGGING ObpRemoveObjectRoutine( Object, FALSE ); return Result; } else { // 删除对象 ObpDeferObjectDeletion (ObjectHeader); } } return Result; }
可以看到该函数就是通过递减对象头的引用数来实现,如果引用数为0的时候,还会通过ObpDeferObjectDeletion来删除对象。
六.参考资料
《Windows内核原理与实现》
《Windows内核情景分析》(上)
《Windows内核设计思想》
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
- [原创]CVE-2022-21882提权漏洞学习笔记 16382
- [原创]CVE-2021-1732提权漏洞学习笔记 19489
- [原创]CVE-2014-1767提权漏洞学习笔记 15192
- [原创]CVE-2018-8453提权漏洞学习笔记 18526
- [原创]CVE-2020-1054提权漏洞学习笔记 13542