一种注册表沙箱的思路、实现——研究Reactos中注册表函数的实现1

        因为我们沙箱注入了一个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;
}

        总的来说,该函数的实现流程是:

  1. 参数合法性判断
  2. 用MapDefaultKey将HKEY转换成HANDLE
  3. 组装ObjectAttributes
  4. 调用Nt式函数
  5. 关闭第一步获得的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,我们不用去执行这个转换过程。但是作为知识,既然研究到这儿,就得好好研究透。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券