在Windows的内核管理层(Executive),对象管理是个很重要的机制。Windows对象管理中,有些著作喜欢将其称为"子系统",其实也就是内核管理层。下面讲介绍几个常用的内核中对象管理的函数。
这个函数根据Handle,返回一个该Handle对应的对象。
// // Converts to and from a Kernel Handle to a normal handle // #define ObKernelHandleToHandle(Handle) \ //将内核句柄的相应标志位去掉 (HANDLE)((ULONG_PTR)(Handle) & ~KERNEL_HANDLE_FLAG) #define ObMarkHandleAsKernelHandle(Handle) \ (HANDLE)((ULONG_PTR)(Handle) | KERNEL_HANDLE_FLAG) #define ObpGetHandleObject(x) \ //获取的是对象头 ((POBJECT_HEADER)((ULONG_PTR)x->Object & ~OBJ_HANDLE_ATTRIBUTES)) NTSTATUS NTAPI ObReferenceObjectByHandle(IN HANDLE Handle, IN ACCESS_MASK DesiredAccess, IN POBJECT_TYPE ObjectType, IN KPROCESSOR_MODE AccessMode, OUT PVOID* Object, OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL) { PHANDLE_TABLE_ENTRY HandleEntry; POBJECT_HEADER ObjectHeader; ACCESS_MASK GrantedAccess; ULONG Attributes; PEPROCESS CurrentProcess; PVOID HandleTable; PETHREAD CurrentThread; NTSTATUS Status; PAGED_CODE(); /* Assume failure */ *Object = NULL; /* Check if the caller wants the current process */ if ((Handle == NtCurrentProcess()) && //当Handle值是NtCurrentProcess时 特殊对待 ((ObjectType == PsProcessType) || !(ObjectType))) { /* Get the current process */ CurrentProcess = PsGetCurrentProcess(); //调用PsGetCurrentProcess获取EPROCESS结构 /* Check if the caller wanted handle information */ if (HandleInformation) //如果要求返回句柄信息的话 填写相应的字段 { /* Return it */ HandleInformation->HandleAttributes = 0; HandleInformation->GrantedAccess = PROCESS_ALL_ACCESS; } /* Reference ourselves */ ObjectHeader = OBJECT_TO_OBJECT_HEADER(CurrentProcess); //获取对象头 InterlockedExchangeAdd(&ObjectHeader->PointerCount, 1); //获取对象头的目的是对 Header的PointerCount 即对该对象的引用计数加1 /* Return the pointer */ *Object = CurrentProcess;//EPROCESS结构即要找的对象 return STATUS_SUCCESS; } else if (Handle == NtCurrentProcess()) //若Handle值是NtCurrentProcess,但类型不匹配 说明传入的类型出现错误 { /* The caller used this special handle value with a non-process type */ return STATUS_OBJECT_TYPE_MISMATCH; } /* Check if the caller wants the current thread */ if ((Handle == NtCurrentThread()) && //对于Handle值为当前线程而言,同样如此 ((ObjectType == PsThreadType) || !(ObjectType))) { /* Get the current thread */ CurrentThread = PsGetCurrentThread(); /* Check if the caller wanted handle information */ if (HandleInformation) { /* Return it */ HandleInformation->HandleAttributes = 0; HandleInformation->GrantedAccess = THREAD_ALL_ACCESS; } /* Reference ourselves */ ObjectHeader = OBJECT_TO_OBJECT_HEADER(CurrentThread); InterlockedExchangeAdd(&ObjectHeader->PointerCount, 1); /* Return the pointer */ *Object = CurrentThread; return STATUS_SUCCESS; } else if (Handle == NtCurrentThread()) { /* The caller used this special handle value with a non-thread type */ return STATUS_OBJECT_TYPE_MISMATCH; } //下面的是一般情况 /* Check if this is a kernel handle */ if (ObIsKernelHandle(Handle, AccessMode)) //若该句柄是内核句柄 { /* Use the kernel handle table and get the actual handle value */ Handle = ObKernelHandleToHandle(Handle); //也就是将最高位(表示此时是kernel_handle)的标志位清零,因为之后要查找 HandleTable = ObpKernelHandleTable; //选用内核句柄表 } else { /* Otherwise use this process's handle table */ HandleTable = PsGetCurrentProcess()->ObjectTable; //选用本进程的句柄表 } /* Enter a critical region while we touch the handle table */ ASSERT(HandleTable != NULL); KeEnterCriticalRegion(); //进入临界区 /* Get the handle entry */ HandleEntry = ExMapHandleToPointer(HandleTable, Handle); //调用ExMapHandleToPointer根据句柄表和对应的句柄获取对应的句柄表项 非常重要的一个函数 if (HandleEntry) //如果找到了对应的句柄表项 { /* Get the object header and validate the type*/ ObjectHeader = ObpGetHandleObject(HandleEntry); //根据句柄表项获取对应的对象头 if (!(ObjectType) || (ObjectType == ObjectHeader->Type)) { /* Get the granted access and validate it */ GrantedAccess = HandleEntry->GrantedAccess; if ((AccessMode == KernelMode) || //若访问机制不冲突 !(~GrantedAccess & DesiredAccess)) { /* Reference the object directly since we have its header */ InterlockedIncrement(&ObjectHeader->PointerCount); //对该对象的引用计数加一 /* Mask out the internal attributes */ Attributes = HandleEntry->ObAttributes & OBJ_HANDLE_ATTRIBUTES; /* Check if the caller wants handle information */ if (HandleInformation) //若需要句柄信息 进行填写 { /* Fill out the information */ HandleInformation->HandleAttributes = Attributes; HandleInformation->GrantedAccess = GrantedAccess; } /* Return the pointer */ *Object = &ObjectHeader->Body; //返回对应对象的主体 即具体对象的数据结构 /* Unlock the handle */ ExUnlockHandleTableEntry(HandleTable, HandleEntry); KeLeaveCriticalRegion(); //离开临界区 /* Return success */ ASSERT(*Object != NULL); return STATUS_SUCCESS; } else { /* Requested access failed */ Status = STATUS_ACCESS_DENIED; } } else { /* Invalid object type */ Status = STATUS_OBJECT_TYPE_MISMATCH; } /* Unlock the entry */ ExUnlockHandleTableEntry(HandleTable, HandleEntry); } else { /* Invalid handle */ Status = STATUS_INVALID_HANDLE; } /* Return failure status */ KeLeaveCriticalRegion(); *Object = NULL; return Status; }对于当前进程和当前线程的句柄值而言,他们是很特殊的,Windows将它们也视为一个对象!!!。其对应的当前进程和当前线程的句柄值通过NtCurrentProcess和NtCurrentThread可以获取,注意这里要跟获取EPROCESS的函数 PsGetCurrentProcess进行区分。
#define NtCurrentProcess ( (HANDLE)(ULONG_PTR) -1 ) #define NtCurrentThread ( (HANDLE)(ULONG_PTR) -2 )NtCurrentProcess是句柄值!!!,而PsGetCurrentProcess()则是函数IoGetCurrentProcess,它返回的是当前进程的EPROCESS数据结构的地址。 但是-1 和 -2 并不是真正的句柄,所以需要特殊处理。于是如果ObReferenceObjectByHandle 给定的句柄是-1,并且ObjectType是PsProcessType或者是默认ObjectType(即传入0),那么就会进入这条控制流,通过PsGetCurrentProcess来获取对象的数据结构。
当然大部分情况是下面的情况。也就是从句柄表获取相应的句柄表项,然后获取对象地址。
其流程是先根据是否是内核句柄选择相应的句柄表,若是内核句柄,则选择内核句柄表的时候,需要将KERNEL_HANDLE_FLAG的值设置为0。然后调用ExMapHandleToPointer,根据选择的句柄表和我们传入的Handle找到相应的句柄表,这是一个很重要的函数,我们等会会去看它。获取句柄表项后,其句柄表项就对应着相应的对象,通过ObpGetHandleObject宏可以获取到相应的对象,注意的是这里的对象Object,其实是ObjectHeader对象头。成功获取后,需要对其对象头字段的PointerCount,即引用计数要加一,最后返回的时候要返回它的Body,也就是ObjectHeader->Body。看下它根据句柄表和句柄寻找句柄表项的主要逻辑。
ObReferenceObjectByHandle->ExMapHandleToPointer
PHANDLE_TABLE_ENTRY NTAPI ExMapHandleToPointer(IN PHANDLE_TABLE HandleTable, IN HANDLE Handle) { EXHANDLE ExHandle; PHANDLE_TABLE_ENTRY HandleTableEntry; PAGED_CODE(); /* Set the handle value */ ExHandle.GenericHandleOverlay = Handle; //可以看出这里将Handle用ExHandle包装了起来 /* Fail if we got an invalid index */ if (!(ExHandle.Index & (LOW_LEVEL_ENTRIES - 1))) return NULL; //如果最低层的索引为0 表明出现问题 /* Do the lookup */ HandleTableEntry = ExpLookupHandleTableEntry(HandleTable, ExHandle); //最终调用ExpLookupHandleTableEntry完成查找 if (!HandleTableEntry) return NULL; /* Lock it */ if (!ExpLockHandleTableEntry(HandleTable, HandleTableEntry)) return NULL; /* Return the entry */ return HandleTableEntry; }这里将Handle用ExHandle包装了起来,然后判断该Handle是否合法,最后将查找的工作留给了ExpLookupHandleTableEntry
ExHandle的结构如下
#define LOW_LEVEL_ENTRIES (PAGE_SIZE / sizeof(HANDLE_TABLE_ENTRY)) #define MID_LEVEL_ENTRIES (PAGE_SIZE / sizeof(PHANDLE_TABLE_ENTRY)) #define HIGH_LEVEL_ENTRIES (16777216 / (LOW_LEVEL_ENTRIES * MID_LEVEL_ENTRIES)) #define HANDLE_LOW_BITS (PAGE_SHIFT - 3) #define HANDLE_HIGH_BITS (PAGE_SHIFT - 2) #define HANDLE_TAG_BITS (2) #define HANDLE_INDEX_BITS (HANDLE_LOW_BITS + 2*HANDLE_HIGH_BITS) // 9 + 20 #define KERNEL_FLAG_BITS (sizeof(PVOID)*8 - HANDLE_INDEX_BITS - HANDLE_TAG_BITS) typedef union _EXHANDLE { struct { ULONG_PTR TagBits : HANDLE_TAG_BITS; //2位标签 代表的是HandleTable的层数 ULONG_PTR Index : HANDLE_INDEX_BITS; //29位 ULONG_PTR KernelFlag : KERNEL_FLAG_BITS; //1位 最高位 代表是否是内核句柄 }; struct { ULONG_PTR TagBits2: HANDLE_TAG_BITS; ULONG_PTR LowIndex: HANDLE_LOW_BITS; ULONG_PTR MidIndex: HANDLE_HIGH_BITS; ULONG_PTR HighIndex: HANDLE_HIGH_BITS; ULONG_PTR KernelFlag2: KERNEL_FLAG_BITS; }; HANDLE GenericHandleOverlay; ULONG_PTR Value; } EXHANDLE, *PEXHANDLE;可以看到EXHANDLE类型是个Union结构,最低两位是个Tag标签,剩下的29位是Index代表是一个查找的下标,其实也是30位,因为最高位在之前已经被清零了。因为Tag代表的是层数,当最低9位为0的时候,说明只有一层,那么此时索引为0显然是有问题的。
看下ExpLookupHandleTableEntry
ObReferenceObjectByHandle->ExMapHandleToPointer->ExpLookupHandleTableEntry
PHANDLE_TABLE_ENTRY NTAPI ExpLookupHandleTableEntry(IN PHANDLE_TABLE HandleTable, IN EXHANDLE LookupHandle) { ULONG i, j, k, TableLevel, NextHandle; ULONG_PTR TableBase; PHANDLE_TABLE_ENTRY Entry = NULL; EXHANDLE Handle = LookupHandle; PUCHAR Level1, Level2, Level3; /* Clear the tag bits and check what the next handle is */ Handle.TagBits = 0; //清空层数 NextHandle = HandleTable->NextHandleNeedingPool; if (Handle.Value >= NextHandle) return NULL; //若Handle的Value落在了下一个HandleTable的区间 说明有问题 /* Get the table code */ TableBase = (ULONG_PTR)HandleTable->TableCode; //获取句柄表对应的下一级表地址(伪地址) /* Extract the table level and actual table base */ TableLevel = (ULONG)(TableBase & 3); //TableCode不是严格的一个地址 其最低两位代表的是句柄表的层级 TableBase = TableBase - TableLevel; //去掉层级后才是真正的句柄表地址 /* Check what level we're running at */ switch (TableLevel) //根据层级来 { /* Direct index */ case 0: //为0时表示此时的TableBase指向的是一个一维数组 每一个数组元素都是一个HANDLE_TABLE_ENTRY结构 一个元素8 Bytes大小 /* Use level 1 and just get the entry directlry */ Level1 = (PUCHAR)TableBase; //此时TableBase指向的是最低层的一维数组 Entry = (PVOID)&Level1[Handle.Value * //获取HandleTableEntry (sizeof(HANDLE_TABLE_ENTRY) / SizeOfHandle(1))]; break; /* Nested index into mid level */ case 1: //为1时代表句柄表有两层 /* Get the second table and index into it */ Level2 = (PUCHAR)TableBase; //此时指向的是中间层 即Level2 i = Handle.Value % SizeOfHandle(LOW_LEVEL_ENTRIES); //i获取的是最低层的下标 即HANDLE_TABLE_ENTRY的一维数组下标 /* Substract this index, and get the next one */ Handle.Value -= i; //去掉低位 j = Handle.Value / //获取level2的相应下标 注意此时的j的单位是PHANDLE_TABLE_ENTRY (SizeOfHandle(LOW_LEVEL_ENTRIES) / sizeof(PHANDLE_TABLE_ENTRY)); /* Now get the next table and get the entry from it */ Level1 = (PUCHAR)*(PHANDLE_TABLE_ENTRY*)&Level2[j]; //获取level1的地址 Entry = (PVOID)&Level1[i * //在level1中获取句柄表项 (sizeof(HANDLE_TABLE_ENTRY) / SizeOfHandle(1))]; break; /* Nested index into high level */ case 2: //此时有三层 /* Start with the 3rd level table */ Level3 = (PUCHAR)TableBase; //此时的TableBse指向的是最顶层 i = Handle.Value % SizeOfHandle(LOW_LEVEL_ENTRIES); //获取level1中的下标 /* Subtract this index and get the index for the next lower table */ Handle.Value -= i; k = Handle.Value / //去掉最低层的i后得到的结果 (SizeOfHandle(LOW_LEVEL_ENTRIES) / sizeof(PHANDLE_TABLE_ENTRY)); /* Get the remaining index in the 2nd level table */ j = k % (MID_LEVEL_ENTRIES * sizeof(PHANDLE_TABLE_ENTRY)); //此时j是level2中的下标 此时乘是因为要乘sizeof(PHANDLE_TABLE_ENTRY)是因为以这个为单位 /* Get the remaining index, which is in the third table */ k -= j; k /= MID_LEVEL_ENTRIES; //此时的k是level3的下标 此时k的单位是PHANDLE_TABLE_ENTRY /* Extract the table level for the handle in each table */ Level2 = (PUCHAR)*(PHANDLE_TABLE_ENTRY*)&Level3[k]; Level1 = (PUCHAR)*(PHANDLE_TABLE_ENTRY*)&Level2[j]; /* Get the handle table entry */ Entry = (PVOID)&Level1[i * (sizeof(HANDLE_TABLE_ENTRY) / SizeOfHandle(1))]; default: /* All done */ break; } /* Return the handle entry */ return Entry; }其三级表结构如下
// // Number of entries in each table level // #define LOW_LEVEL_ENTRIES (PAGE_SIZE / sizeof(HANDLE_TABLE_ENTRY)) #define MID_LEVEL_ENTRIES (PAGE_SIZE / sizeof(PHANDLE_TABLE_ENTRY)) #define HIGH_LEVEL_ENTRIES (16777216 / (LOW_LEVEL_ENTRIES * MID_LEVEL_ENTRIES))其流程就是根据层数来采取不同策略进行索引。 当然,首先要获取表的地址,表的地址在HandleTable->TableCode中,我们看下HandleTable的结构
typedef struct _HANDLE_TABLE { #if (NTDDI_VERSION >= NTDDI_WINXP) ULONG_PTR TableCode; //这里的TableCode就是表的地址 #else PHANDLE_TABLE_ENTRY **Table; #endif PEPROCESS QuotaProcess; PVOID UniqueProcessId; #if (NTDDI_VERSION >= NTDDI_WINXP) EX_PUSH_LOCK HandleTableLock[4]; LIST_ENTRY HandleTableList; EX_PUSH_LOCK HandleContentionEvent; #else ERESOURCE HandleLock; LIST_ENTRY HandleTableList; KEVENT HandleContentionEvent; #endif PHANDLE_TRACE_DEBUG_INFO DebugInfo; LONG ExtraInfoPages; #if (NTDDI_VERSION >= NTDDI_LONGHORN) union { ULONG Flags; UCHAR StrictFIFO:1; }; LONG FirstFreeHandle; PHANDLE_TABLE_ENTRY LastFreeHandleEntry; LONG HandleCount; ULONG NextHandleNeedingPool; #else ULONG FirstFree; ULONG LastFree; ULONG NextHandleNeedingPool; LONG HandleCount; union { ULONG Flags; UCHAR StrictFIFO:1; }; #endif } HANDLE_TABLE, *PHANDLE_TABLE;当然TableCode并不是直接表示地址的,最低两位代表的是层数level,因为肯定是以4对齐的,所以最低两位就被空了出来
一个HANDLE_TABLE_ENTRY结构的大小是8字节,而它是以页对齐的,所以一页有512个,所以在ExHandle中需要9bit来表示它在level1中的下标中层数组是一个PHANDLE_TABLE_ENTRY结构,它的一个元素大小是4bit,所以一页可表示1024个元素,所以在ExHandle中需要10bit来表示在level2中的下标最顶层即level3,它并不是以页对齐的,它的大小是32个指针大小,也就是说需要5位来寻址。所以在HIGH_LEVEL_ENTRIES 是12777216,其实也就是24 - 9 - 10 == 5 也就是ExHandle的5bit这个函数的实质就是对相应对象的引用计数加一,也就是对Header头中的PointerCount字段加一,这个字段是记录着对象被引用的次数,当然,前提是要类型相匹配且不能是符号链接对象。
这个函数很长,分段来看。该函数给定一个RootHandle,表示搜索的起点,ObjectName是路径名,InsertName是否存在决定当搜索不到相应的Object的时候是否进行插入。FoundObject是最后若找到该对象后所返回的对象。
NTSTATUS NTAPI ObpLookupObjectName(IN HANDLE RootHandle OPTIONAL, IN PUNICODE_STRING ObjectName, IN ULONG Attributes, IN POBJECT_TYPE ObjectType, IN KPROCESSOR_MODE AccessMode, IN OUT PVOID ParseContext, IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL, IN PVOID InsertObject OPTIONAL, IN OUT PACCESS_STATE AccessState, OUT POBP_LOOKUP_CONTEXT LookupContext, OUT PVOID *FoundObject) { PVOID Object; POBJECT_HEADER ObjectHeader; UNICODE_STRING ComponentName, RemainingName; BOOLEAN Reparse = FALSE, SymLink = FALSE; PDEVICE_MAP DeviceMap = NULL; POBJECT_DIRECTORY Directory = NULL, ParentDirectory = NULL, RootDirectory; POBJECT_DIRECTORY ReferencedDirectory = NULL, ReferencedParentDirectory = NULL; KIRQL CalloutIrql; OB_PARSE_METHOD ParseRoutine; NTSTATUS Status; KPROCESSOR_MODE AccessCheckMode; PWCHAR NewName; POBJECT_HEADER_NAME_INFO ObjectNameInfo; ULONG MaxReparse = 30; PAGED_CODE(); OBTRACE(OB_NAMESPACE_DEBUG, "%s - Finding Object: %wZ. Expecting: %p\n", __FUNCTION__, ObjectName, InsertObject); /* Initialize starting state */ ObpInitializeDirectoryLookup(LookupContext); //初始化上下文结构 记录着运行时候的一些参数 *FoundObject = NULL; Status = STATUS_SUCCESS; Object = NULL; /* Check if case-insensitivity is checked */ if (ObpCaseInsensitive) //若开启了大小写敏感 对应的属性中进行勾选 { /* Check if the object type requests this */ if (!(ObjectType) || (ObjectType->TypeInfo.CaseInsensitive)) { /* Add the flag to disable case sensitivity */ Attributes |= OBJ_CASE_INSENSITIVE; } } /* Check if this is a access checks are being forced */ AccessCheckMode = (Attributes & OBJ_FORCE_ACCESS_CHECK) ? //若需要进行访问控制检查 强制转换成用户模式 这样就一定会进行检查 UserMode : AccessMode;首先进行一些初始化工作,即是否勾选大小写敏感和是否进行访问控制检查。
这段对应于ObpLookupObjectName的第二段,主要是当给出起点目录时候的情况,流程如下
首先判断是否根据起点目录句柄获取起点对象结构,这个起点对象或许不是对象目录结构对于不是对象目录结构而言,它也许是个符号链接对象或是文件对象。找到的这个节点对象需要提供解析函数Parse函数解析,这个解析函数是在创建该对象类型的时候填写到“提交单”上的那堆函数之一。通过设定一个最大解析次数为30次的循环次数进行解析。每次解析完后有三种可能,第一是不需要再次解析,此时有可能是解析失败,或者成功解析完但是未找到目标对象,或是成功解析完,并且找到了目标对象,那么自然是大吉大利,返回该对象到FoundObject即可。第二是需要进行再次解析,且此时的路径名为空,或者从根节点出发,那么此时解除对根据RootHandle找到的RootDirectory的引用,转而引用ObpRootDirectoryObject,并跳到ParseFromRoot处,从根节点开始解析。第三是需要再次解析,但是30次的解析已经用完,此时只能放弃返回。若无上述情况且需要ReParse,那么解析次数减少一次继续循环此时对于RootHandle获得的RootDirectory对象排除了不是目录对象的可能,那么还有一种特例就是寻找的就是目录节点对象本身,此时的ObjectName显然为空,当是这种情况的时候,返回该目录节点对象即可下面看下第三部分
else //此时未给出起点目录,即RootHandle { /* We did not get a Root Directory, so use the root */ RootDirectory = ObpRootDirectoryObject; //那么默认从根节点出发 /* It must start with a path separator */ if (!(ObjectName->Length) || //此时对应的ObjectName应该是以"\"为开头的 若不是自然路径名参数无效 !(ObjectName->Buffer) || (ObjectName->Buffer[0] != OBJ_NAME_PATH_SEPARATOR)) { /* This name is invalid, so fail */ return STATUS_OBJECT_PATH_SYNTAX_BAD; } /* Check if the name is only the path separator */ if (ObjectName->Length == sizeof(OBJ_NAME_PATH_SEPARATOR)) //若此时未给出起点目录 并且路径名就是“\” 那么所要找的正是该根目录对象 { /* So the caller only wants the root directory; do we have one? */ if (!RootDirectory) //若根目录尚未建立 { /* This must be the first time we're creating it... right? */ if (InsertObject) //此时要插入的节点自然是没法插入的 直接返回 { /* Yes, so return it to ObInsert so that it can create it */ Status = ObReferenceObjectByPointer(InsertObject, //递增其引用计数 0, ObjectType, AccessMode); if (NT_SUCCESS(Status)) *FoundObject = InsertObject; return Status; } else { /* This should never really happen */ ASSERT(FALSE); return STATUS_INVALID_PARAMETER; } } else //若根目录建立 显然就是要获得根目录节点的 { /* We do have the root directory, so just return it */ Status = ObReferenceObjectByPointer(RootDirectory, //所以增加根目录的引用计数 0, ObjectType, AccessMode); if (NT_SUCCESS(Status)) *FoundObject = RootDirectory; //返回根目录 return Status; } }此时未给出起始目录,那么就将起始目录设置为从根目录开始,若此时的ObjectName不是以“\”出发的,那么显然就出现错误了。与要查找目录对象本身相似,这里也有一种特殊的情况那就是假如寻找的是根节点本身,那么就要先判断根节点本身是否建立起来,若未建立起来则只能返回InsertObject,建立起来的话说明寻找的正是根结点,那么返回根节点目录对象即可。
这里由于历史原因,对象的绝对路径名有可能是“DOS路径名”,其特点是根节点以 “\??\” 或者是 “\??” 表示。由于他们是宽字符,所以前者是8个字节,后者是6个字节。所以对于前者就相当于与一个ULL大小的数进行比较。后者则分两次,按一个32位的整数和一个16位的整数进行比较。但是这里ReactOS代码还没有实现这两种情况。实际上,用户空间的 DLL的库函数负责将DOS路径转换成全路径。
这里就是逐层查找了。ComponentName是本次查找的中间节点名。该循环首先获取本次搜索的中间节点名,然后检查是否有穿越目录的权限,若是有的话那么就可以调用ObpLookupEntryDirectory查找当前目录下是否有该中间节点,对于找不到该节点,主要有以下情况:
此时RemainingName的Length还没有穷尽,说明刚刚查找的是一个中间节点,而中间节点查找不到说明有问题,失败退出循环经过上一步的check,现在找的正是最后一个节点,此时最后找的节点不存在,并且InsertObject为空,那么状态码设置成查找不到即可此时InsertObject存在,那么就插入到该目录下,并进行初始化,当然创建的时候需要检查该目录下是否有创建对象的权限如果通过ObLookupEntryDirectory找到了目标对象,那么先看看该目标对象是否有解析函数,若存在解析函数则交给对应的解析函数来处理,这个时候可能会返回STATUS_REPARSE或者是STATUS_REPARSE_OBJECT,前者是重新解析路径名,后者是从当前节点开始解析。若不存在解析函数,则判断是否到达了最后一个节点,若到达了最后一个节点,并且不是进行插入,说明已经找到了,此时检查一次是否能进行穿越,若可以 其访问控制就是合法的,那么递增一次引用计数即可,因为已经打开过了。若并没有到达最后一次,判断是否是目录节点,如果是的话更替目录为Object对应的目录,即ComponentName对应的目录。当然,如果既不是目录对象且没有提供解析函数的话,自然该循环是无法进行解析的,只好退出循环了。
下面自然就是扫尾工作了
/* Check if we failed */ if (!NT_SUCCESS(Status)) //若失败了 清除context { /* Cleanup after lookup */ ObpCleanupDirectoryLookup(LookupContext); } /* Check if we have a device map and dereference it if so */ //if (DeviceMap) ObfDereferenceDeviceMap(DeviceMap); /* Check if we have a referenced directory and dereference it if so */ if (ReferencedDirectory) ObDereferenceObject(ReferencedDirectory); /* Check if we have a referenced parent directory */ if (ReferencedParentDirectory) { /* We do, dereference it */ ObDereferenceObject(ReferencedParentDirectory); } /* Set the found object and check if we got one */ *FoundObject = Object; //返回找到的对象 如果找到的话 if (!Object) { /* Nothing was found. Did we reparse or get success? */ if ((Status == STATUS_REPARSE) || (NT_SUCCESS(Status))) { /* Set correct failure */ Status = STATUS_OBJECT_NAME_NOT_FOUND; } } /* Check if we had a root directory */ if (RootHandle) ObDereferenceObject(RootDirectory); //如果存在目录句柄的话 解除之前对RootDirectory的引用 /* Return status to caller */ OBTRACE(OB_NAMESPACE_DEBUG, "%s - Found Object: %p. Expected: %p\n", __FUNCTION__, *FoundObject, InsertObject); return Status; }扫尾工作 就是清除一些空间 解除引用。 总结一下该函数的功能:
当Object存在,InsertObject为空时,表示找到了该Object 实际上相当于打开该对象,返回该对象的指针当Object不存在时,InsertObject不为空且权限允许时(即拥有在该目录创建对象的权限),此时会在指定的目录下创建该对象当Object不存在时,InsertObject为空时,此时表示未找到该对象 ,自然返回NULL首先分配了一个OB_TEMP_BUFFER的空间 OB_TEMP_BUFFER结构如下
typedef struct _OB_TEMP_BUFFER { ACCESS_STATE LocalAccessState; OBJECT_CREATE_INFORMATION ObjectCreateInfo; //创建信息 即传入的Attributes OBP_LOOKUP_CONTEXT LookupContext; AUX_ACCESS_DATA AuxData; } OB_TEMP_BUFFER, *POB_TEMP_BUFFER;OB_TEMP_BUFFER的主要目的是 然后调用ObpCaptureObjectAttributes将对象属性进行分离,大部分到TempBuffer->ObjectCreateInfo里,然后属性ObjectAttributes中的ObjectName也被提取了出来用于之后的操作,根据Name打开对象最终是由ObpLookupObjectName来实现的,最后为该对象创建一个句柄,即调用了ObpCreateHandle将对象安装到句柄表或找到相对应的对象句柄。 这里的打开,是指因打开而创建句柄,也就是OpenReason为OpenHandle这个枚举值,打开和创建其实质都是一样的,是为了创建并获取句柄。
通过对象名获取对象指针,对于用户而言,是无法直接操纵对象本身的,所以这个函数显然不是给用户用的,而是内核本身为了操作有名对象而设计方便的。
NTSTATUS NTAPI ObReferenceObjectByName(IN PUNICODE_STRING ObjectPath, IN ULONG Attributes, IN PACCESS_STATE PassedAccessState, IN ACCESS_MASK DesiredAccess, IN POBJECT_TYPE ObjectType, IN KPROCESSOR_MODE AccessMode, IN OUT PVOID ParseContext, OUT PVOID* ObjectPtr) { PVOID Object = NULL; UNICODE_STRING ObjectName; NTSTATUS Status; OBP_LOOKUP_CONTEXT Context; AUX_DATA AuxData; ACCESS_STATE AccessState; PAGED_CODE(); /* Fail quickly */ if (!ObjectPath) return STATUS_OBJECT_NAME_INVALID; /* Capture the name */ Status = ObpCaptureObjectName(&ObjectName, ObjectPath, AccessMode, TRUE); //获取对象名 if (!NT_SUCCESS(Status)) return Status; /* We also need a valid name after capture */ if (!ObjectName.Length) return STATUS_OBJECT_NAME_INVALID; /* Check if we didn't get an access state */ if (!PassedAccessState) { /* Use our built-in access state */ PassedAccessState = &AccessState; Status = SeCreateAccessState(&AccessState, &AuxData, DesiredAccess, &ObjectType->TypeInfo.GenericMapping); if (!NT_SUCCESS(Status)) goto Quickie; } /* Find the object */ *ObjectPtr = NULL; Status = ObpLookupObjectName(NULL, //依旧调用的是ObpLookupObjectName查找相关的Object &ObjectName, Attributes, ObjectType, AccessMode, ParseContext, NULL, NULL, PassedAccessState, &Context, &Object); /* Cleanup after lookup */ ObpCleanupDirectoryLookup(&Context); /* Check if the lookup succeeded */ if (NT_SUCCESS(Status)) //若成功找到了Object { /* Check if access is allowed */ if (ObpCheckObjectReference(Object, PassedAccessState, FALSE, AccessMode, &Status)) { /* Return the object */ *ObjectPtr = Object; //直接将Object返回即可 } } /* Free the access state */ if (PassedAccessState == &AccessState) { SeDeleteAccessState(PassedAccessState); } Quickie: /* Free the captured name if we had one, and return status */ ObpFreeObjectNameBuffer(&ObjectName); return Status; }这里很简单的先将ObjectName用ObpCaptureObjectName()进行提取,然后调用ObpLookupObjectName来获取对应的对象若成功将该Object放到对应参数中得以返回,最后进行扫尾,清除掉分配的pool等空间。