上一篇博文中主要介绍了Reactos中大部分函数的思路和HKEY和HANDLE之间的关系,本文将介绍一些Reactos中有意思的函数和存在bug的函数。(转载请指明出处)
CreateNestedKey是一个辅助创建键的函数,比如我们要创建\Regsitry\User\3\2\1,而我们只是存在\Regsitry\User\,我们最终将调用此函数将在User下创建键3,然后在3键下创建键2,最后在2键下创建键1。假如你是这个函数的实现者,你要思考这个函数的实现思路。最容易的办法是将\Regsitry\User\3\2\1路径拆成一系列键名(\Regsitry\User、3、2、1),然后从根键一步一步的调用NtCreateKey,这样就可以保证路径上的所有键都被创建。这个方法的优点是简单。但是Reactos实现的思路却不是这样的。我列一下我改写的该函数。
NTSTATUS OriCreateNestedKey( PHANDLE pKeyHandle, POBJECT_ATTRIBUTES ObjectAttributes, PUNICODE_STRING ClassString, DWORD dwOptions, REGSAM samDesired, DWORD *lpdwDisposition ) { UNICODE_STRING LocalKeyName; NTSTATUS lRes = STATUS_INVALID_PARAMETER; do { if ( NULL == ObjectAttributes || NULL == ObjectAttributes->ObjectName ) { break; } lRes = RtlCreateUnicodeString_( &LocalKeyName, ObjectAttributes->ObjectName->Buffer ); CHECKRESULT(lRes); lRes = GETORIFUNC(CreateKey)( pKeyHandle, samDesired, ObjectAttributes, 0, ClassString, dwOptions, (PULONG)lpdwDisposition ); if ( STATUS_OBJECT_NAME_NOT_FOUND != lRes ) { return lRes; } OBJECT_ATTRIBUTES LocalObjectAttributes; RtlCopyMemory( &LocalObjectAttributes, ObjectAttributes, sizeof(OBJECT_ATTRIBUTES)); LocalObjectAttributes.ObjectName = &LocalKeyName; ULONG FullNameLength = LocalKeyName.Length / sizeof(WCHAR); HANDLE LocalKeyHandle = NULL; PWCHAR Ptr = NULL; ULONG Disposition = 0; // 通过将\\改成结尾符,逐个去掉最后一个键名,查看什么路径的注册表键存在 while ( STATUS_OBJECT_NAME_NOT_FOUND == lRes ) { Ptr = wcsrchr(LocalKeyName.Buffer, '\\'); if ( NULL == Ptr || Ptr == LocalKeyName.Buffer ) { lRes = STATUS_UNSUCCESSFUL; break; } // 通过将\\改成结尾符,通过长度去掉键名 *Ptr = (WCHAR)0; LocalKeyName.Length = wcslen(LocalKeyName.Buffer) * sizeof(WCHAR); lRes = GETORIFUNC(CreateKey)( &LocalKeyHandle, KEY_CREATE_SUB_KEY, &LocalObjectAttributes, 0, NULL, 0, &Disposition ); } CHECKRESULT(lRes); // 通过将结尾符改成\\,重新拼接上键名,逐个创建键 ULONG Length = wcslen(LocalKeyName.Buffer); while ( TRUE ) { if ( NULL != LocalKeyHandle ) { GETORIFUNC(Close) (LocalKeyHandle); } // 通过将结尾符改成\\,重新拼接上键名 LocalKeyName.Buffer[Length] = L'\\'; Length = wcslen (LocalKeyName.Buffer); LocalKeyName.Length = Length * sizeof(WCHAR); if ( Length == FullNameLength ) { // 如果已经拼接回以前的键名了,则执行完后就退出 lRes = GETORIFUNC(CreateKey)( pKeyHandle, samDesired, ObjectAttributes, 0, ClassString, dwOptions, (PULONG)lpdwDisposition); break; } // 创建祖宗键 lRes = GETORIFUNC(CreateKey)( &LocalKeyHandle, KEY_CREATE_SUB_KEY, &LocalObjectAttributes, 0, NULL, 0, &Disposition); if ( NT_FAILED(lRes) ) { break; } } } while (0); RtlFreeUnicodeString_(&LocalKeyName); return lRes; }大致说下这个函数的流程:
1 使用NtCreateKey函数调用传入的参数。如果不是返回STATUS_OBJECT_NAME_NOT_FOUND,则说明要创建的路径上的键的父键是存在的,直接返回创建的结果;否则说明父键就不存在,得依赖之后的步骤将父键创建起来。
2 声明个变量OBJECT_ATTRIBUTES LocalObjectAttributes;将作为参数传入的ObjectAttributes全部拷贝到LocalObjectAttributes,同时记录下路径的长度FullNameLength。
3 将LocalObjectAttributes.LocalKeyName中的记录路径的Buffer字段,从后向前寻找“\”符号,找到一个,就将其改为空,同时再计算LocalObjectAttributes中LocalKeyName的长度。得到修改后的LocalObjectAttributes后,再调用NtCreateKey。以上面的例子为例,就是用NtCreateKey创建\Regsitry\User\3\2。查看返回结果,如果结果还是STATUS_OBJECT_NAME_NOT_FOUND,说明父键还是不存在的,于是要重复3的步骤,一直到NtCreateKey执行成功。如例子中,一直到用NtCreateKey创建\Regsitry\User\3就停止这个循环,因为3键可以创建成功。
4 比较LocalObjectAttributes.LocalKeyName的长度和FullNameLength,如果相等,则说明我们应该创建的键都创建完了。如果不等,则我们将LocalObjectAttributes.LocalKeyName的最前一个空改成“\”,得到修改后的LocalObjectAttributes,再调用NtCreateKey。在我们的例子中,就是创建\Regsitry\User\3\2。一直重复4,一直到LocalObjectAttributes.LocalKeyName的长度和FullNameLength相等。
我很欣赏这样的思路,通过改改内存达到目的,效率方面会好很多。