看过了查找指定目录下是否有指定名称的对象的函数后再来看看一个功能更强大的函数ObpLookupObjectName它可以根据路径来查找对象, 路径里还可以包含符号链接等。。。总之很复杂很强大。。。
NTSTATUSObpLookupObjectName ( IN HANDLE RootDirectoryHandle OPTIONAL, // 父目录句柄 IN PUNICODE_STRING ObjectName, // 路径 IN ULONG Attributes, // 属性 IN POBJECT_TYPE ObjectType, // 对象类型 IN KPROCESSOR_MODE AccessMode, IN PVOID ParseContext OPTIONAL, // 传递给_OBJECT_TYPE_INITIALIZER.ParseProcedure 的参数 IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL, // 不管.... IN PVOID InsertObject OPTIONAL, IN OUT PACCESS_STATE AccessState, OUT POBP_LOOKUP_CONTEXT LookupContext, // 查询对象使用的结构, 查询详细信息通过它返回 OUT PVOID *FoundObject // 找到的对象体 ) { ..... // 初始化 ObpInitializeLookupContext(LookupContext); *FoundObject = NULL; Status = STATUS_SUCCESS; Object = NULL; // 全局的ObpCaseInsensitive 和 对象类型决定了对象是否区分大小写 if ( ObpCaseInsensitive ) { if ( (ObjectType == NULL) || (ObjectType->TypeInfo.CaseInsensitive) ) { Attributes |= OBJ_CASE_INSENSITIVE; } } // 指定了OBJ_FORCE_ACCESS_CHECK属性后要强制检测参数, 所以AccessCheckMode设置为UserMode if (Attributes & OBJ_FORCE_ACCESS_CHECK) { AccessCheckMode = UserMode; } else { AccessCheckMode = AccessMode; } if (ARGUMENT_PRESENT( RootDirectoryHandle )) { .... // 这里处理RootDirectoryHandle不为空的情况, 先略过 } else { // 没有指定RootDirectoryHandle, 上级目录是根目录 RootDirectory = ObpRootDirectoryObject; // 以为没有指定上级目录, 对象名要合法必须以 '/' 开头 if ((ObjectName->Length == 0) || (ObjectName->Buffer == NULL) || (*(ObjectName->Buffer) != OBJ_NAME_PATH_SEPARATOR)) { return( STATUS_OBJECT_PATH_SYNTAX_BAD ); } if (ObjectName->Length == sizeof( OBJ_NAME_PATH_SEPARATOR )) { // 对象名是 '/' 表示打开根目录, 暂时略过 ...... } else { // 如果不是打开根目录或指定了上级目录, 这里不会成立的, 略...ParseFromRoot: if (DeviceMap != NULL) { ObfDereferenceDeviceMap(DeviceMap); DeviceMap = NULL; } // 如果ObjectName->Buffer 是以8字节为分配粒度的 // 应该检测对象名是否在 /??/ 目录下, 我们需要重定位它到 /GLOBAL??/ 目录 // 不过为什么要根据分配力度决定是否检测?????? if (!((ULONG_PTR)(ObjectName->Buffer) & (sizeof(ULONGLONG)-1))) { // 如果对象目录是 /??/ if ((ObjectName->Length >= ObpDosDevicesShortName.Length) && (*(PULONGLONG)(ObjectName->Buffer) == ObpDosDevicesShortNamePrefix.Alignment.QuadPart)) { // 获取设备表 if ((DeviceMap = ObpReferenceDeviceMap()) != NULL) { if (DeviceMap->DosDevicesDirectory != NULL ) { // 设置父目录是根目录, 当前目录是 /GLOBAL??/ 更新对象名 开始解析 ParentDirectory = RootDirectory; Directory = DeviceMap->DosDevicesDirectory; RemainingName = *ObjectName; RemainingName.Buffer += (ObpDosDevicesShortName.Length / sizeof( WCHAR )); RemainingName.Length = (USHORT)(RemainingName.Length - ObpDosDevicesShortName.Length);
goto quickStart;
} } } // 对象名是/?? 说明它打开的是GLOBAL?? 目录 else if ((ObjectName->Length == ObpDosDevicesShortName.Length - sizeof( WCHAR )) && (*(PULONG)(ObjectName->Buffer) == ObpDosDevicesShortNameRoot.Alignment.LowPart) && (*((PWCHAR)(ObjectName->Buffer)+2) == (WCHAR)(ObpDosDevicesShortNameRoot.Alignment.HighPart))) { // 我们直接返回 GLOBAL?? 目录给用户就可以了 if ((DeviceMap = ObpReferenceDeviceMap()) != NULL) { if (DeviceMap->DosDevicesDirectory != NULL ) { Status = ObReferenceObjectByPointer( DeviceMap->DosDevicesDirectory, 0, ObjectType, AccessMode ); if (NT_SUCCESS( Status )) { *FoundObject = DeviceMap->DosDevicesDirectory; } ObfDereferenceDeviceMap(DeviceMap); return( Status ); } } } } // /??/到/GLOBAL??/ 的转换处理完毕 } } // 没有指定RootDirectoryHandle, 上级目录是根目录 处理完毕 // 晕了吧... 幸好WRK在这里有个注释 // // At this point either (在这里会有三种情况) // // (这个没看懂....) // the user specified a directory that is not the object // type directory and got reparsed back to the root directory // // (用户指定了父目录, 并且给定了一个路径用作查找) // the user specified the object type directory and gave us // a name to actually look up // // (用户没有指定父目录, 并且给定的路径不是以/??/开头的。) // (以/??/开头的路径会直接跳到下面的quickStart, 或直接返回) // the user did not specify a search directory (default // to root object directory) and if the name did start off // with the dos device prefix we've munged outselves back to // it to the dos device directory for the process // // 第一次是执行到这里时ReparsedSymbolicLink一定为FALSE // 之所以会再到这里是因为下面的某些代码使用了goto ParseFromRoot ....烂风格 if( ReparsedSymbolicLink == FALSE ) { Reparse = TRUE; MaxReparse = OBJ_MAX_REPARSE_ATTEMPTS; } while(Reparse) { // 这才是这个函数的精髓 ...... } // 一些资源释放 return Status;}
///华丽的分割线//
ObpLookupObjectName函数的逻辑太复杂了, 里面又goto来goto去的很乱, 所以我们把解析的部分提取出来。while 这个循环里面的东西才是我们想学的, 下面仔细看看这个循环
while (TRUE) { Object = NULL; // /aaaa/bbbbb/cccc // 路径起始为 '/' 跳过这个字符 变为 aaaa/bbbbb/cccc if ( (RemainingName.Length != 0) && (*(RemainingName.Buffer) == OBJ_NAME_PATH_SEPARATOR) ) {
RemainingName.Buffer++; RemainingName.Length -= sizeof( OBJ_NAME_PATH_SEPARATOR ); } // 分离出第一个对象名 // ComponentName指向aaaa RemainingName指向/bbbbb/cccc ComponentName = RemainingName; while (RemainingName.Length != 0) { if (*(RemainingName.Buffer) == OBJ_NAME_PATH_SEPARATOR) { break; } RemainingName.Buffer++; RemainingName.Length -= sizeof( OBJ_NAME_PATH_SEPARATOR ); } ComponentName.Length = (USHORT)(ComponentName.Length - RemainingName.Length); // 非法文件名 if (ComponentName.Length == 0) { Status = STATUS_OBJECT_NAME_INVALID; break; } // 如果当前文件名为空说明是根目录 if ( Directory == NULL ) { Directory = RootDirectory; } if ( (AccessCheckMode != KernelMode) && !(AccessState->Flags & TOKEN_HAS_TRAVERSE_PRIVILEGE) ) { // 略过权限检测 ...... } // 这里注释写的比较清楚了 if (RemainingName.Length == 0) {
// // If we are searching the last name, take an additional reference // to the directory. We need this to either insert a new object into // this directory, or to check for traverse access if an insert object // is not specified. If not referenced the directory could go // away since ObpLookupDirectoryEntry releases the existing reference // if (ReferencedDirectory == NULL) {
ObReferenceObject( Directory ); ReferencedDirectory = Directory; }
if (InsertObject != NULL) { // // We lock the context exclusively before the lookup if we have an object // to insert. An insertion is likely to occur after this lookup if it fails, // so we need to protect this directory to be changed until the // ObpInsertDirectoryEntry call // ObpLockLookupContext( LookupContext, Directory ); } } // 从Directory(当前目录)里查找名字为ComponentName的对象 Object = ObpLookupDirectoryEntry( Directory, &ComponentName, Attributes, InsertObject == NULL ? TRUE : FALSE, LookupContext ); if (!Object) { // 如果对象不存在, 并且用户指定了InsertObject, 这里面把InsertObject插入到目录里 // 注意只有当前已经没有剩余路径时才插入 // 之后直接退出 代码很简单 不过这里的逻辑已经够复杂了 略过 ....... } ReparseObject: // 找到了路径中的对象aaaa 获得它的ParsePrecedure ObjectHeader = OBJECT_TO_OBJECT_HEADER( Object ); ParseProcedure = ObjectHeader->Type->TypeInfo.ParseProcedure; if (ParseProcedure && (!InsertObject || (ParseProcedure == ObpParseSymbolicLink))) { // 参考一下ObInitSystem可以发现其实目录对象是没有ParseProcedure的 // 如果存在ParseProcedure并且不需要插入对象 或者 // 存在ParseProcedure并且当前对象是一个符号链接 // 直接调用ParsePrecedure并把剩余的路径传递进去 然后就结束了 // 略过 ...... } else { // 如果当前对象是目录就会到这里 // 已经到了路径的尽头, 说明已经解析完成了, 返回当前的Object if (RemainingName.Length == 0) { if (!InsertObject) { if ( (AccessCheckMode != KernelMode) && !(AccessState->Flags & TOKEN_HAS_TRAVERSE_PRIVILEGE) ) { // 权限检测 ..... } Status = ObReferenceObjectByPointer( Object, 0, ObjectType, AccessMode ); if (!NT_SUCCESS( Status )) { Object = NULL; } } break;
} else { // 如果当前对象是个目录, 继续循环查找 if (ObjectHeader->Type == ObpDirectoryObjectType) { if (ReferencedParentDirectory != NULL) { ObDereferenceObject( ReferencedParentDirectory ); } ReferencedParentDirectory = ReferencedDirectory; ReferencedDirectory = NULL; ParentDirectory = Directory; Directory = (POBJECT_DIRECTORY)Object; } else { // 不是目录就出错 Status = STATUS_OBJECT_TYPE_MISMATCH; Object = NULL; break; } } } }
到这里while循环也结束了。总结一下ObpLookupObjectName函数的功能1。根据指定路径搜索对象, 可以处理开头为/??/、包含符号链接等特殊路径2。若路径中的某个对象不是目录, 则调用它的ParseProcedure并把剩余路径名传递进去 (注册表、文件路径的解析就是这方法)3。InsertObject参数可以用来在目录中插入一个对象, 但全路径必须都是目录, 最后一个对象是InsertObject插入后的名字。(如 aaa/bbb/ccc aaa、bbb都是目录对象 ccc不存在, 若指定了InsertObject, 这个对象便会插入到aaa/bbb/中, 名字叫ccc)
