因为我们沙箱注入了一个DLL到了目标进程,并且Hook了一系列NtXX(NtOpenKey)函数,所以我们在注入的代码中是不能使用RegXX(RegOpenKey等)这类函数的。因为RegXX系列函数在底层使用了NtXX系列函数,如果在注入DLL执行Hook后的逻辑中使用了RegXX系列函数,将会导致递归调用的问题,就让程序产生“蛋生鸡,鸡生蛋”这样的“思考”,可是程序不知道停止,最终脑袋用完了就挂了。于是使用Nt函数实现我们曾经习惯使用的RegXX函数是必要的。(转载请指明出处)
编写这块代码时,我参考了reactos注册表相关的源码。它的源码写的很好,但是也存在一定的漏洞,我会在之后介绍。
列出一个比较简单的RegXX函数的实现:
LONG WINAPI RegOpenKeyExW(HKEY hKey,
LPCWSTR lpSubKey,
DWORD ulOptions,
REGSAM samDesired,
PHKEY phkResult)
{
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING SubKeyString;
HANDLE KeyHandle;
NTSTATUS Status;
LONG ErrorCode = ERROR_SUCCESS;
if (!phkResult)
{
return ERROR_INVALID_PARAMETER;
}
Status = MapDefaultKey(&KeyHandle, hKey);
if (!NT_SUCCESS(Status))
{
return RtlNtStatusToDosError(Status);
}
if (lpSubKey != NULL)
RtlInitUnicodeString(&SubKeyString, (LPWSTR)lpSubKey);
else
RtlInitUnicodeString(&SubKeyString, (LPWSTR)L"");
InitializeObjectAttributes(&ObjectAttributes,
&SubKeyString,
OBJ_CASE_INSENSITIVE,
KeyHandle,
NULL);
Status = NtOpenKey((PHANDLE)phkResult,
samDesired,
&ObjectAttributes);
if (!NT_SUCCESS(Status))
{
ErrorCode = RtlNtStatusToDosError(Status);
}
ClosePredefKey(KeyHandle);
return ErrorCode;
}
总的来说,该函数的实现流程是:
我们发现其他很多Reg函数都是走这个套路的,比如RegSetKeyValue和RegEnumKeyEx等。
因为我们Hook的是Nt式函数,我们在函数中可以获取键对应的HANDLE,而不会得到HKEY。于是我们关心的是HKEY和HANDLE转换的过程。我们查看MapDefaultKey
#define MAX_DEFAULT_HANDLES 6
static HANDLE DefaultHandleTable[MAX_DEFAULT_HANDLES];
#define IsPredefKey(HKey) \
(((ULONG_PTR)(HKey) & 0xF0000000) == 0x80000000)
#define GetPredefKeyIndex(HKey) \
((ULONG_PTR)(HKey) & 0x0FFFFFFF)
static NTSTATUS MapDefaultKey(OUT PHANDLE RealKey, IN HKEY Key)
{
PHANDLE Handle;
ULONG Index;
BOOLEAN DoOpen, DefDisabled;
NTSTATUS Status = STATUS_SUCCESS;
if (!IsPredefKey(Key)) {
*RealKey = (HANDLE)((ULONG_PTR)Key & ~0x1);
return STATUS_SUCCESS;
}
/* Handle special cases here */
Index = GetPredefKeyIndex(Key);
if (Index >= MAX_DEFAULT_HANDLES) {
return STATUS_INVALID_PARAMETER;
}
RegInitialize(); /* HACK until delay-loading is implemented */
RtlEnterCriticalSection (&HandleTableCS);
if (Key == HKEY_CURRENT_USER)
DefDisabled = DefaultHandleHKUDisabled;
else
DefDisabled = DefaultHandlesDisabled;
if (!DefDisabled) {
Handle = &DefaultHandleTable[Index];
DoOpen = (*Handle == NULL);
}
else {
Handle = RealKey;
DoOpen = TRUE;
}
if (DoOpen) {
/* create/open the default handle */
Status = OpenPredefinedKey(Index,
Handle);
}
if (NT_SUCCESS(Status)) {
if (!DefDisabled)
*RealKey = *Handle;
else
*(PULONG_PTR)Handle |= 0x1;
}
RtlLeaveCriticalSection (&HandleTableCS);
return Status;
}
我来解释下上述代码,我们先来看一些定义
#define HKEY_CLASSES_ROOT (( HKEY ) (ULONG_PTR)((LONG)0x80000000) )
#define HKEY_CURRENT_USER (( HKEY ) (ULONG_PTR)((LONG)0x80000001) )
#define HKEY_LOCAL_MACHINE (( HKEY ) (ULONG_PTR)((LONG)0x80000002) )
#define HKEY_USERS (( HKEY ) (ULONG_PTR)((LONG)0x80000003) )
#define HKEY_PERFORMANCE_DATA (( HKEY ) (ULONG_PTR)((LONG)0x80000004) )
#define HKEY_PERFORMANCE_TEXT (( HKEY ) (ULONG_PTR)((LONG)0x80000050) )
#define HKEY_PERFORMANCE_NLSTEXT (( HKEY ) (ULONG_PTR)((LONG)0x80000060) )
#if(WINVER >= 0x0400)
#define HKEY_CURRENT_CONFIG (( HKEY ) (ULONG_PTR)((LONG)0x80000005) )
#define HKEY_DYN_DATA (( HKEY ) (ULONG_PTR)((LONG)0x80000006) )
看到这些主键的值都是0x80000000+,IsPredefKey(Key)函数就是判断hKey是否为这些主键。如果不是主键,则可能是我们通过RegOpenKey等函数打开的主键下的子键(RegOpenKey(HKEY_CLASS_ROOT,L".txt",&hKey); hKey可能是形如0x000003AB这样的值),对于这样的子键Hkey,其通过(HANDLE)((ULONG_PTR)Key & ~0x1)就可以转换为HANDLE了。
如果是以上主键,这些主键的后28位是一个数组的Index,该数组保存的其对应的HANDLE。但是这个版本的Reactos只保存了MAX_DEFAULT_HANDLES(6)个元素,于是HKEY_PERFORMANCE_TEXT、HKEY_DYN_DATA和HKEY_PERFORMANCE_NLSTEXT这样主键就会被认为不合法了。
if (Index >= MAX_DEFAULT_HANDLES){
//HKEY_PERFORMANCE_TEXT、HKEY_PERFORMANCE_NLSTEXT、HKEY_DYN_DATA
return STATUS_INVALID_PARAMETER;
}
拿到Index后使用OpenPredefinedKey打开这些主键的HANDLE
static NTSTATUS
OpenPredefinedKey(IN ULONG Index,
OUT HANDLE Handle)
{
NTSTATUS Status;
switch (Index)
{
case 0: /* HKEY_CLASSES_ROOT */
Status = OpenClassesRootKey (Handle);
break;
case 1: /* HKEY_CURRENT_USER */
Status = RtlOpenCurrentUser (MAXIMUM_ALLOWED,
Handle);
break;
case 2: /* HKEY_LOCAL_MACHINE */
Status = OpenLocalMachineKey (Handle);
break;
case 3: /* HKEY_USERS */
Status = OpenUsersKey (Handle);
break;
#if 0
case 4: /* HKEY_PERFORMANCE_DATA */
Status = OpenPerformanceDataKey (Handle);
break;
#endif
case 5: /* HKEY_CURRENT_CONFIG */
Status = OpenCurrentConfigKey (Handle);
break;
case 6: /* HKEY_DYN_DATA */
Status = STATUS_NOT_IMPLEMENTED;
break;
default:
WARN("MapDefaultHandle() no handle creator\n");
Status = STATUS_INVALID_PARAMETER;
break;
}
return Status;
这些获取HANDLE的方法在底层都是通过NtOpenKey实现的,如获取HKEY_CURRENT_USER键的路径的函数
NTSTATUS
NTAPI
RtlOpenCurrentUser(IN ACCESS_MASK DesiredAccess,
OUT PHANDLE KeyHandle)
{
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING KeyPath;
NTSTATUS Status;
PAGED_CODE_RTL();
/* Get the user key */
Status = RtlFormatCurrentUserKeyPath(&KeyPath);
if (NT_SUCCESS(Status))
{
/* Initialize the attributes and open it */
InitializeObjectAttributes(&ObjectAttributes,
&KeyPath,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
Status = ZwOpenKey(KeyHandle, DesiredAccess, &ObjectAttributes);
/* Free the path and return success if it worked */
RtlFreeUnicodeString(&KeyPath);
if (NT_SUCCESS(Status)) return STATUS_SUCCESS;
}
/* It didn't work, so use the default key */
RtlInitUnicodeString(&KeyPath, RtlpRegPaths[RTL_REGISTRY_USER]);
InitializeObjectAttributes(&ObjectAttributes,
&KeyPath,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
Status = ZwOpenKey(KeyHandle, DesiredAccess, &ObjectAttributes);
/* Return status */
return Status;
}
经过以上各步的转换,我们就可以获得HKEY和HANDLE对应的关系了。但是实际我们却不会去关心这个过程,因为我们在Nt式函数里获得就是HANDLE,我们不用去执行这个转换过程。但是作为知识,既然研究到这儿,就得好好研究透。