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






4.85/5 (49投票s)
一篇关于使用 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);
- 二进制 (`REG_BINARY`, `REG_RESOURCE_LIST`, `REG_FULL_RESOURCE_DECRIPTOR`, `REG_RESOURCE_REQUIREMENTS_LIST`, `REG_NONE`)
- 显示来自 `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。
|
|
|
NtCreateKey |
|
|
NtOpenKey |
|
|
NtDeleteKey |
RegDeleteKey |
|
NtFlushKey |
RegFlushKey |
|
NtSetInformationKey |
|
|
NtQueryKey |
RegQueryInfoKey |
|
NtEnumerateKey |
|
|
NtNotifyChangeKey |
RegNotifyChangeKeyValue |
|
NtDeleteValueKey |
RegDeleteValue |
|
NtSetValueKey |
|
|
NtQueryValueKey |
|
|
NtEnumerateValueKey |
RegEnumValue |
|
NtQueryMultipleValueKey |
RegQueryMultipleValues |
|
NtEnumerateKey |
|
|
|
RegSaveKey |
SeBackupPrivilege |
|
RegRestoreKey |
SeRestorePrivilege |
|
RegLoadKey |
SeRestorePrivilege |
|
|
SeRestorePrivilege |
|
RegReplaceKey |
SeRestorePrivilege |
|
RegUnloadKey |
SeRestorePrivilege |
NtClose |
CloseHandle |
|
NtCreateFile |
CreateFile |
|
NtOpenThread |
OpenThread |
|
NtOpenProcessToken |
|
SeCreateTokenPrivilege |
NtAdjustPrivilegesToken |
AdjustTokenPrivileges |
|
NtQueryInformationToken |
GetTokenInformation |
|
用于 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()
- 更改了“`CopyKeys()/CopyValues()`”函数的参数。
- 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 日 - 初始测试。