看过了查找指定目录下是否有指定名称的对象的函数后
再来看看一个功能更强大的函数ObpLookupObjectName
它可以根据路径来查找对象, 路径里还可以包含符号链接等。。。总之很复杂很强大。。。
NTSTATUS
ObpLookupObjectName (
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)
发表评论 取消回复