因为我们沙箱注入了一个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; }总的来说,该函数的实现流程是
0 参数合法性判断
1 用MapDefaultKey将HKEY转换成HANDLE
2 组装ObjectAttributes
3 调用Nt式函数
4 关闭第一步获得的HANDLE
我们发现其他很多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,我们不用去执行这个转换过程。但是作为知识,既然研究到这儿,就得好好研究透。