65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 NT 原生 API 进行注册表操作

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (49投票s)

2006 年 6 月 19 日

CPOL

9分钟阅读

viewsIcon

285488

downloadIcon

5340

一篇关于使用 NT 原生 API 操作注册表文章。

这是我写的关于使用 `CNtRegistry` 类 的 原生注册表编辑器 (NtRegEdit) 文章。

重要提示:任何注册表操作都可能对您的系统造成损害,导致系统无法启动或运行。在使用此类之前,请备份您的注册表。我对由此造成的任何损害概不负责

引言

市面上有很多注册表类,但我所知没有一个使用 NT 原生 API 调用来操作注册表的。通常,我们使用 Microsoft API 来完成工作,但它们的易用性(恕我直言)稍显不足,尤其是在复制、搜索和删除键和值时。我还喜欢 SysInternals (SysInternals) 的 RegHide 的简单示例,它可以(某种程度上)隐藏注册表键。

现在,我总是在编写任何访问注册表的东西时使用 Robert Pittenger 的 `CRegistry` 类(可以在 CodeProject 的 这里 找到),因为它很简单。我也喜欢隐藏注册表键/值不被 Microsoft 的注册表编辑器(RegEdit)看到的主意,所以我想结合这两点,编写一个可以同时做到这两点但只使用 NT 原生注册表 API 的类。这就是 `CNtRegistry` 类的由来。

隐藏注册表键,您说?

SysInternals 网站对此的描述最到位(见下文 - 原样摘自其网站

Win32 API 和 Native API 之间一个微妙但显著的区别(有关这个很大程度上未公开的接口的更多信息,请参阅 *Inside the Native API*)是描述名称的方式。在 Win32 API 中,字符串被解释为以 NULL 结尾的 ANSI(8 位)或宽字符(16 位)字符串。在 Native API 中,名称被计算为 Unicode(16 位)字符串。虽然这个区别通常并不重要,但它留下了一个有趣的情况:存在一类可以使用 Native API 引用但无法使用 Win32 API 描述的名称。

这怎么可能呢?答案是,一个被计算为 Unicode 字符串的名称可以显式包含 NULL 字符(0)作为名称的一部分。例如,“Key\0”。为了包含末尾的 NULL,Unicode 字符串的长度被指定为 4。使用 Win32 API 绝对无法指定此名称,因为如果“Key\0”作为名称传递,API 将确定该名称是“Key”(长度为 3 个字符),因为“\0”表示名称的结束。

当使用这样的名称创建键(或任何其他带有名称的对象,如命名事件、信号量或互斥体)时,任何使用 Win32 API 的应用程序将无法打开该名称,即使它们似乎能看到它。

您可以在这里从 SysInternals 获取 RegHide 的副本。

它能做什么?

`CNtRegistry` 类具有一些有用的功能……您可以

  • 复制 键和值(包括隐藏的),在同一 RootKey 内或跨 RootKeys。
    • BOOL CopyKeys(CString csSource, CString csTarget, BOOL bRecursively);
    • BOOL CopyValues(CString csSource, CString csTarget, CString csValueName, CString csNewValueName);
  • 删除 键和值(包括隐藏的,可递归删除)。
    • BOOL DeleteKey (CString csKey);
    • BOOL DeleteKeysRecursive (CString csKey);
    • BOOL DeleteValue (CString csName);
  • 重命名 键和值。
    • BOOL RenameKey(CString csFullKey, CString csNewKeyName);
    • BOOL RenameValue(CString csOldName, CString csNewName);
  • 搜索 键、值名称和值。这允许您搜索某些“字符串”出现。它还可以查找隐藏的键。这两个函数(Search/Find)都可以递归执行,但只能在当前 RootKey 内。
    • BOOL Search (CString csString, CString csStartKey, CStringArray& csaResults, int nRegSearchType=3, BOOL bCaseSensitive = TRUE);
    • BOOL FindHiddenKeys (CString csKey, BOOL bRecursive, CStringArray& csaResults);
  • 读取和写入 值。可能的值/类型包括
    • 二进制 (`REG_BINARY`, `REG_RESOURCE_LIST`, `REG_FULL_RESOURCE_DECRIPTOR`, `REG_RESOURCE_REQUIREMENTS_LIST`, `REG_NONE`)
      • UCHAR* ReadBinary(CString csKey, CString csName, UINT& uiLength);
      • BOOL WriteBinary(CString csKey, CString csName, UCHAR* pValue, UINT uiLength);
    • DWORD (`REG_DWORD`)
      • DWORD ReadDword(CString csKey, CString csName, DWORD dwDefault);
      • BOOL WriteDword(CString csKey, CString csName, DWORD dwValue);
    • 字符串 (`REG_SZ`, `REG_EXPAND_SZ`, `REG_MULTI_SZ`)
      • CString ReadString(CString csKey, CString csName, CString csDefault);
      • BOOL ReadMultiString(CString csKey, CString csName, CStringArray& csaReturn);
      • BOOL WriteString(CString csKey, CString csName, CString csValue);
      • BOOL WriteExpandString(CString csKey, CString csName, CString csValue);
      • BOOL WriteMultiString(CString csKey, CString csName, CStringArray& csaValue);
    • int (`REG_DWORD`)
      • int ReadInt(CString csKey, CString csName, int nDefault);
      • BOOL WriteInt(CString csKey, CString csName, int nValue);
    • float (`REG_BINARY`)
      • double ReadFloat(CString csKey, CString csName, double fDefault);
      • BOOL WriteFloat(CString csKey, CString csName, double fValue);
    • BOOL (`REG_DWORD`)
      • BOOL ReadBool(CString csKey, CString csName, BOOL bDefault);
      • BOOL WriteBool(CString csKey, CString csName, BOOL bValue);
    • COleDateTime (`REG_BINARY`)
      • COleDateTime ReadDateTime(CString csKey, CString csName, COleDateTime dtDefault);
      • BOOL WriteDateTime(CString csKey, CString csName, COleDateTime dtValue);
    • COLORREF (`REG_BINARY`)
      • COLORREF ReadColor(CString csKey, CString csName, COLORREF rgbDefault);
      • BOOL WriteColor(CString csKey, CString csName, COLORREF rgbValue);
    • 对象 (`REG_BINARY`)
      • BOOL ReadFont(CString csKey, CString csName, CFont* pFont);
      • BOOL WriteFont(CString csKey, CString csName, CFont* pFont);
      • BOOL ReadPoint(CString csKey, CString csName, CPoint* pPoint);
      • BOOL WritePoint(CString csKey, CString csName, CPoint* pPoint);
      • BOOL ReadSize(CString csKey, CString csName, CSize* pSize);
      • BOOL WriteSize(CString csKey, CString csName, CSize* pSize);
      • BOOL ReadRect(CString csKey, CString csName, CRect* pRect);
      • BOOL WriteRect(CString csKey, CString csName, CRect* pRect);
  • 显示来自 `nt.dll` 中 Native API 调用的错误。
  • 提供一个默认值,以防操作失败。
  • 启用当前用户的权限,使其能够备份/恢复 Hives/Keys(如果尚不存在)。

使用代码

使用 `CNtRegistry` 类实际上非常简单。声明 `CNtRegistry` 后,通过调用 `InitNtRegistry()` 初始化类,调用 `SetRootKey`,然后调用 `SetKey` 设置“SOFTWARE\MyApp”。您还可以使用 `SetKey(HKEY hRoot, CString strKey, BOOL bCanCreate, BOOL bCanSaveCurrentKey)`,它只是合并了这两个函数(`SetRootKey` 和 `SetKey`)。现在可以调用几乎任何函数……例如 `CreateHiddenKey` 来创建一个隐藏的键(或尝试其他一些函数)。

这是一个创建隐藏键,然后在其(也隐藏的)中放入一些值的示例。

#define "NtRegistry.h"

void CMyApp::ReadRegistry()
{
    CNtRegistry ntReg;
    ntReg.InitNtRegistry(); 
    //
    if (ntReg.SetKey(HKEY_LOCAL_MACHINE, 
        _T("Software\\MyApp\\Settings"), FALSE, TRUE))
    {
        if (ntReg.CreateHiddenKey(_T("Software\\MyApp\\Settings\\Hidden")))
        {
            // Write some stuff
            ntReg.WriteInt(_T("Data1"), 777);
            ntReg.WriteFloat(_T("Pi"), 3.14159);
            ntReg.WriteString(_T("UserName"), _T("DMadden61"));

            // Read some stuff
            int nData = ntReg.ReadInt(_T("Data1"), 0);
            pi = ntReg.ReadFloat(_T("Pi"), 0.0);
            CString csUserName = ntReg.ReadString(_T("UserName"), _T("ERR"));
        }
    }
    else
    {
        TRACE("Failed to open/set key\n");
    }
}

很简单吧?嗯,是的,但所有这些是如何组合在一起的又是另一回事了。

Nt...() 调用和 Reg...() 调用之间的区别

我将向您展示 `CNtRegistry` 使用的一些 NT 原生注册表 API(或者至少是可以使用的),讨论它们的不同之处,以及我如何修改一个流行的函数“`EnablePrivileges`”来使用 NT 原生 API。

一些原生 API
相关的 Win32 API
所需权限
NtCreateKey
`RegCreateKey`, `RegCreateKeyEx`
N/A
NtOpenKey
`RegOpenKey`, `RegOpenKeyEx`
N/A
NtDeleteKey
RegDeleteKey
N/A
NtFlushKey
RegFlushKey
N/A
NtSetInformationKey
N/A
NtQueryKey
RegQueryInfoKey
N/A
NtEnumerateKey
`RegEnumerateKey`, `RegEnumerateKeyEx`
N/A
NtNotifyChangeKey
RegNotifyChangeKeyValue
N/A
NtDeleteValueKey
RegDeleteValue
N/A
NtSetValueKey
`RegSetValue`, `RegSetValueEx`
N/A
NtQueryValueKey
`RegQueryValue`, `RegQueryValueEx`
N/A
NtEnumerateValueKey
RegEnumValue
N/A
NtQueryMultipleValueKey
RegQueryMultipleValues
N/A
NtEnumerateKey
`RegEnumKey`, `RegEnumKeyEx`
N/A
*`NtSaveKey`
RegSaveKey
SeBackupPrivilege
*`NtRestoreKey`
RegRestoreKey
SeRestorePrivilege
*`NtLoadKey`
RegLoadKey
SeRestorePrivilege
*`NtLoadKey2`
SeRestorePrivilege
*`NtReplaceKey`
RegReplaceKey
SeRestorePrivilege
*`NtUnloadKey`
RegUnloadKey
SeRestorePrivilege
NtClose
CloseHandle
N/A
NtCreateFile
CreateFile
N/A
NtOpenThread
OpenThread
N/A
NtOpenProcessToken
SeCreateTokenPrivilege
NtAdjustPrivilegesToken
AdjustTokenPrivileges
N/A
NtQueryInformationToken
GetTokenInformation
N/A

Sample NtRegistryAPI Image

用于 NT 原生注册表 API 的参数与您熟悉的参数不同。您知道注册表中实际上只有两个(2)根(主)键吗?其余的只是符号链接。这两个根键是“\Registry\Machine (HKEY_LOCAL_MACHINE)”和“\Registry\User (HKEY_USERS)”。请看下面的 HKEY 和文本等效项。您通常为子键(`RegCreateKey`)编写路径,如“SOFTWARE\MyApp”,并且还包括 HKEY(HKEY_LOCAL_MACHINE),而这些原生 API(`NtCreateKey`)需要子键的“完整”路径,如下所示:“\Registry\Machine\SOFTWARE\MyApp”。`CNtRegistry` 类允许您像往常一样调用函数,但它(在内部)通过调用以下两个函数(或一个合并了两者的函数)为您将所有内容组合起来。

  • SetRootKey(HKEY_LOCAL_MACHINE);
  • SetKey(_T("SOFTWARE\\MyApp"),TRUE,TRUE);
  • 或者仅使用下面的一个,它合并了两者
  • SetKey(HKEY_LOCAL_MACHINE,_T("SOFTWARE\\MyApp"),TRUE,TRUE);
HKEY_USERS           \Registry\User
HKEY_CURRENT_USER    \Registry\User\<Users_SID>
HKEY_LOCAL_MACHINE   \Registry\Machine
HKEY_CLASSES_ROOT    \Registry\Machine\SOFTWARE\Classes
HKEY_CURRENT_CONFIG  \Registry\Machine\SYSTEM\CurrentControlSet\
                                       Hardware Profiles\Current

其他的是 `UNICODE_STRING (U_S)` 和 `OBJECT_ATTRIBUTES (O_A)` 结构。`U_S` 结构保存“完整”的键路径(Unicode 字符串)和长度。`O_A` 结构包含属性。`InitializeObjectAttributes(...)` 初始化 `O_A` 结构,该结构指定要打开的对象句柄的属性。然后将指向该结构的指针传递给实际打开句柄的例程(例如 `NtOpenKey(...)`)。

// Used to define Unicode strings.
typedef struct _UNICODE_STRING 
{
    // The length in bytes of the string stored in Buffer.
    USHORT Length;
    // The maximum length in bytes of Buffer.
    USHORT MaximumLength;
    // Points to a buffer used to contain
    // a string of wide characters.
    PWSTR  Buffer;
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;

// Specifies the properties of an object handle.
typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;
    // Handle to the root object directory             
    HANDLE RootDirectory;
    // Name of the object to open a handle for.
    PUNICODE_STRING ObjectName;
    // Specifies flags (e.g. OBJ_CASE_INSENSITIVE)
    ULONG Attributes;
    // Points to type SECURITY_DESCRIPTOR
    PVOID SecurityDescriptor;
    // Points to type SECURITY_QUALITY_OF_SERVICE
    PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;

#define InitializeObjectAttributes( p, n, a, r, s ) { \
    (p)->Length = sizeof( OBJECT_ATTRIBUTES );        \
    (p)->RootDirectory = r;                           \
    (p)->Attributes = a;                              \
    (p)->ObjectName = n;                              \
    (p)->SecurityDescriptor = s;                      \
    (p)->SecurityQualityOfService = NULL;             \
    }

使用上述结构创建注册表键(原生方式)的代码示例

...
//
// Initialize the Unicode String from an ANSI String
//
ANSI_STRING asName;
RtlZeroMemory(&asName,sizeof(asName));
RtlInitAnsiString(&asName,csName);

RtlZeroMemory(&usName,sizeof(usName));

RtlAnsiStringToUnicodeString(&usName,&asName,TRUE);


//
// Initialize the data/properties for the actual call
//
OBJECT_ATTRIBUTES ObjectAttributes;
InitializeObjectAttributes(&ObjectAttributes,
                           &usName, 
                           OBJ_CASE_INSENSITIVE, 
                           NULL,NULL);

//
// We are ready to create the key...
//
HANDLE hKey = NULL;
m_NtStatus = NtCreateKey(&hKey, 
                         KEY_ALL_ACCESS, 
                         &ObjectAttributes,
                         0, 
                         NULL, 
                         REG_OPTION_NON_VOLATILE, 
                         &m_dwDisposition);

if (!NT_SUCCESS(m_NtStatus)) {
    return FALSE;
}
else {
    NtClose(hKey);
}

...

由于我想使用一些 NT 注册表 Hive API(`NtSaveKey` 等),所以我不得不重写下面的 `EnablePrivileges` 函数来使用 NT 原生 API(除了一个找不到原生调用的函数 `LookupPrivilegeValue`)。

// The following code can be used to enable or disable the
// privilege. You can use this code to enable or disable privilege 
//
// Use the following to enable the privilege:
//   EnablePrivilege(SE_BACKUP_NAME, TRUE);
//
// Use the following to disable the privilege:
//   EnablePrivilege(SE_BACKUP_NAME, FALSE);
//
NTSTATUS CNtRegistry::EnablePrivilege(CString csPrivilege, BOOL bEnable)
{
    TOKEN_PRIVILEGES NewState;
    HANDLE           hToken   = NULL;
    NTSTATUS         NtStatus = STATUS_SUCCESS;

    // Open the process token for this process.
    NtStatus = NtOpenProcessToken(GetCurrentProcess(),
                                  TOKEN_ADJUST_PRIVILEGES|
                                  TOKEN_QUERY|TOKEN_QUERY_SOURCE,
                                  &hToken);
    if (!NT_SUCCESS(NtStatus)) {
        return NtStatus;
    }

    // Get the local unique id for the privilege. This
    // is a Win32 API :-\ I couldn't find a Native one.
    LUID luid;
    if ( !LookupPrivilegeValue(NULL,
                               (LPCTSTR)csPrivilege,
                               &luid))
    {
        NtClose( hToken );
        return (NTSTATUS) ERROR_FUNCTION_FAILED;
    }

    // Assign values to the TOKEN_PRIVILEGE structure.
    NewState.PrivilegeCount = 1;
    NewState.Privileges[0].Luid = luid;
    NewState.Privileges[0].Attributes = (bEnable ? SE_PRIVILEGE_ENABLED : 0);

    // Adjust the token privilege.
    NtStatus = NtAdjustPrivilegesToken( hToken, 
                                        FALSE, 
                                        &NewState, 
                                        sizeof(NewState), 
                                        (PTOKEN_PRIVILEGES)NULL, 
                                        0);
    // Close the handle.
    NtClose(hToken);

    return NtStatus;
}

总结...

作为一个“Intermediate”程序员,我遇到的问题主要是不同类型转换的学习曲线。我注释掉了 Hive 函数,因为我遇到了一些问题,并且不想让其他人弄坏他们的系统。我也不是在 `nt.dll` 中使用所有的原生注册表 API。

这就是我所有的内容,我不期望它完美,所以请给我您的想法来改进它。生活中的一件事情(以及随之而来的所有挑战)……如果你犯了一个“错误”并且从中吸取了教训,那么它一开始就不是一个错误……它是一个“教训”!

感谢...

  • CRegistry by Robert Pittenger. This class的灵感来源!!

待办事项

  • 将“Hive”函数集成到代码中。
  • 能够“重命名”键/值(由于已经具备复制/删除功能,这应该不难)。
  • 删除“不必要”的代码(编写它时使用的)。
  • 确保所有内容都已注释(已完成)。
  • 还有更多...

历史

这个类还有很多可以改进的地方,但是时间紧迫,我想也许有人会乐意帮忙 :-)

  • 2006 年 8 月 10 日 (0.0.0.37)
    • 为 `CopyKey` 函数添加了递归参数。
    • 添加了“`DeleteKeyRecursive()`”函数。
  • 2006 年 7 月 16 日 (0.0.0.36)
    • 添加了“`ShowPermissionsDlg()`”通用对话框。
    • 在状态栏中添加了键路径。
  • 2006 年 7 月 2 日 (0.0.0.35)
    • 更改了“`CopyKeys()/CopyValues()`”函数的参数。
      • 这使得复制任何东西/任何地方都更容易。
    • 更改了“`FindHiddenKeys()`”函数的参数。
      • 这样输出就到了 `CStringArray`(而不是消息框),这使得我能够将输出显示在 ListCtrl 中进行显示(感谢 CodeProject 的“Tcpip2005”的建议)!!
    • 添加了“`InitNtRegistry()`”函数,它完成了所有初始化工作。
    • 向“`Search()`”函数添加了 `CaseSensitive` 参数。
    • 在 `stdafx.h` 中添加了“`#pragma comment(linker...)`”以显示 XP 主题。
    • 添加了一些“`Rtl...()`”字符串函数。
      • RtlInitString()
      • RtlInitAnsiString()
      • RtlInitUnicodeString()
      • RtlAnsiStringToUnicodeString()
      • RtlUnicodeStringToAnsiString()
      • RtlFreeString()
      • RtlFreeAnsiString()
      • RtlFreeUnicodeString()
  • 2006 年 6 月 24 日 (0.0.0.34)
    • 添加了“`RenameKey()`”,它使用了“`NtRenameKey()`”。
    • 添加了“`RenameValue()`”,它使用了自制函数。
    • 重新格式化了头文件和源文件,以便头文件中的函数顺序与源文件中的顺序匹配。
  • 2006 年 6 月 22 日 (0.0.0.33)
    • 合并了“`SetRootKey()` 和 `SetKey`”,并在文章中添加了更多解释。
    • 添加了“`GetCurrentUsersTextualSid()`”,它返回私有变量“`m_csSID`”。
  • 2006 年 6 月 11 日 - 公开发布。
  • 2004 年 6 月 3 日 - 初始测试。
© . All rights reserved.