Skip to content
Published at:

第17章:注册表

注册表概述

Windows 注册表(Registry)是一个分层数据库,用于存储系统配置和用户相关信息。从数据持久性角度,注册表数据分为两类:

  • 易失性数据(Volatile Data):运行时生成,系统关机后即删除,仅存在于内存中。
  • 非易失性数据(Non-volatile Data):持久保存在磁盘文件中,重启后依然存在。

操作注册表的主要工具有 Regedit.exe(GUI 图形界面)、reg.exe(命令行工具)和 PowerShell。微软官方建议:"建议不要使用注册表来存储应用程序或与用户相关的数据",推荐使用 INI、XML、JSON 或 YAML 等文件格式。注册表应主要由 Windows 自身使用。

注册表项(The Hives)

注册表被划分为多个 hive(项,或称配置单元)。从逻辑结构上看,注册表呈现出树状层次结构,但实际上只有两个"真正的"根项(Root Hive):HKEY_USERSHKEY_LOCAL_MACHINE,其余根键均由这两个组合或映射而成。

HKEY_LOCAL_MACHINE (HKLM)

HKEY_LOCAL_MACHINE(简称 HKLM)存储与计算机相关的信息,与具体登录用户无关。其中许多数据对系统启动至关重要,默认情况下仅管理员(Administrator)可修改。关键子键包括:

SOFTWARE:应用程序存储与用户无关的配置信息,常见的组织模式为 SOFTWARE\[公司名称]\[产品名称]\[版本号]

SYSTEM:系统核心参数,包含以下重要子键:

  • SYSTEM\CurrentControlSet\Services:服务(Service)与设备驱动程序(Device Driver)的信息。
  • SYSTEM\CurrentControlSet\Enum:硬件设备驱动程序的父键。
  • SYSTEM\CurrentControlSet\Control:内核(Kernel)、Smss.exe、csrss.exe、Services.exe 等系统关键进程读取的设置。
  • SYSTEM\BCD00000000:启动配置数据(BCD,Boot Configuration Data)。
  • SYSTEM\SECURITY:本地安全策略(Local Security Policy),默认仅 SYSTEM 账户可访问。
  • SYSTEM\SAM:本地用户和组信息,与 SECURITY 具有相同的访问限制。

SYSTEM\CurrentControlSet 实际是指向 ControlSet001 的符号链接(Symbolic Link),这与 Windows 的"最后一次正确配置"(Last Known Good Configuration)功能有关。SYSTEM\Select 键中保存了哪个控制集(Control Set)是"当前"的。

HKLM 的持久化文件存储在 %SystemRoot%\System32\Config\ 目录下:

子键对应文件名
SAMSAM
SecuritySECURITY
SoftwareSOFTWARE
SystemSYSTEM

完整的 hive 文件映射可以在 HKLM\System\CurrentControlSet\Control\hivelist 中找到。

HKEY_USERS (HKU)

HKEY_USERS(简称 HKU)存储所有曾在本地登录过的用户的特定信息。每个用户由其 SID(Security Identifier,安全标识符)唯一标识。其中 .DEFAULT 子键保存新用户的默认配置值。SYSTEM、本地服务(Local Service)、网络服务(Network Service)账户的短 SID 也在 HKU 中。普通用户的 SID 下包含桌面设置、控制台配置、环境变量、键盘布局、打印机设置等信息。

HKEY_CURRENT_USER (HKCU)

HKEY_CURRENT_USER(简称 HKCU)是指向 HKEY_USERS 中当前登录用户对应 SID 子键的链接。其持久化文件为 C:\Users\<用户名>\NtUser.dat

HKEY_CLASSES_ROOT (HKCR)

HKEY_CLASSES_ROOT(简称 HKCR)由 HKLM\Software\ClassesHKCU\Software\Classes 组合而成,其中用户设置会覆盖计算机默认设置。HKCR 包含两类信息:

  • 资源管理器外壳数据(Explorer Shell Data):文件类型关联(File Type Association)和外壳扩展(Shell Extension)。例如搜索 .txt 会找到默认值 txtfile,该键下的 shell\open\command 值为 %SystemRoot%\System32\NOTEPAD.EXE %1,表示用记事本打开 .txt 文件。
  • COM 相关信息:将在第 21 章详细讨论。

HKEY_CURRENT_CONFIG (HKCC)

HKEY_CURRENT_CONFIG(简称 HKCC)是指向 HKLM\SYSTEM\CurrentControlSet\Hardware Profiles\Current 的链接,包含当前硬件配置文件的信息。

HKEY_PERFORMANCE_DATA

HKEY_PERFORMANCE_DATA 在 Regedit.exe 中不可见,是使用性能计数器(Performance Counter)的旧有机制。从 Windows 2000 起,已有更新的 API 替代了它。

32 位特定的注册表项

在 64 位 Windows 系统上,注册表的某些部分必须对 32 位进程和 64 位进程加以区分。当 32 位进程尝试打开 HKLM\Software 时,会被透明重定向到 HKLM\Software\Wow6432Node。这种机制称为注册表虚拟化(Registry Virtualization)。

其他重定向示例:

  • HKCR\CLSIDHKCR\Wow6432Node\CLSID(影响进程内 DLL COM 组件)
  • HKCR\AppIDHKCR\InterfaceHKCR\TypeLib 同样被重定向

32 位进程可通过在 RegCreateKeyExRegOpenKeyEx 中指定 KEY_WOW64_64KEY 标志选择不进行重定向,直接访问 64 位视图;64 位进程也可通过 KEY_WOW64_32KEY 标志访问 32 位视图。

操作键和值

打开键:RegOpenKeyEx

打开一个已存在的注册表键:

cpp
LSTATUS RegOpenKeyEx(
    _In_ HKEY hKey,
    _In_opt_ LPCTSTR lpSubKey,
    _In_opt_ DWORD ulOptions,
    _In_ REGSAM samDesired,
    _Out_ PHKEY phkResult);

参数说明:

  • hKey:已打开的键的句柄,可以是预定义根键(如 HKEY_CURRENT_USER)或其他 API 返回的句柄。
  • lpSubKey:要打开的子键名称,不区分大小写。
  • ulOptions:保留参数,通常设为 0;也可指定 REG_OPTION_OPEN_LINK 以打开符号链接本身而非其目标。
  • samDesired:访问掩码(Access Mask),常用值包括 KEY_READ(查询和枚举)和 KEY_WRITE(写入和修改)。
  • phkResult:输出参数,接收打开的键的句柄。

函数的返回值是错误码(如 ERROR_SUCCESS 即 0 表示成功),调用失败时不应使用 GetLastError 获取错误信息。使用完毕后需调用 RegCloseKey 关闭句柄。

创建键:RegCreateKeyEx

创建或打开一个注册表键:

cpp
LSTATUS RegCreateKeyEx(
    _In_ HKEY hKey, _In_ LPCTSTR lpSubKey,
    _Reserved_ DWORD Reserved, _In_opt_ LPTSTR lpClass,
    _In_ DWORD dwOptions, _In_ REGSAM samDesired,
    _In_opt_ CONST LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    _Out_ PHKEY phkResult, _Out_opt_ LPDWORD lpdwDisposition);

参数说明:

  • hKey / lpSubKey:与 RegOpenKeyEx 相同。如果中间的子键不存在,该函数会自动创建。
  • dwOptions:控制键的行为:
    • REG_OPTION_NON_VOLATILE:默认值,键持久保存到磁盘。
    • REG_OPTION_VOLATILE:键仅存在于内存中,重启后消失。
    • REG_OPTION_CREATE_LINK:创建符号链接而非普通键。
    • REG_OPTION_BACKUP_RESTORE:使用备份/恢复权限打开,此时忽略 samDesired 参数。
  • lpSecurityAttributes:可选的安全属性,指定键的安全描述符。
  • lpdwDisposition:输出参数,指示是新建还是打开已有键:
    • REG_CREATED_NEW_KEY:键是新创建的。
    • REG_OPENED_EXISTING_KEY:键已存在,被打开。

读取值:RegQueryValueEx

从指定键中读取一个命名值的数据:

cpp
LSTATUS RegQueryValueEx(
    _In_ HKEY hKey, _In_opt_ LPCTSTR lpValueName,
    _Reserved_ LPDWORD lpReserved, _Out_opt_ LPDWORD lpType,
    _Out_ LPBYTE lpData, _Inout_opt_ LPDWORD lpcbData);

参数说明:

  • lpValueName:要读取的值的名称。传入 NULL 或空字符串时读取默认值(Default Value)。
  • lpType:输出参数,返回值的类型(可为 NULL 忽略)。
  • lpData:接收数据的缓冲区。可先传入 NULL 以获取所需缓冲区大小。
  • lpcbData:输入时为缓冲区大小,输出时为实际数据大小。

注册表值支持的数据类型如下:

常量描述
REG_NONE0无类型
REG_SZ1以 NULL 结尾的 Unicode 字符串
REG_EXPAND_SZ2含未展开环境变量(用 % 包围)的 Unicode 字符串
REG_BINARY3任意二进制数据
REG_DWORD432 位无符号整数(小端序,Little-Endian)
REG_DWORD_BIG_ENDIAN532 位无符号整数(大端序,Big-Endian)
REG_LINK6符号链接(Unicode 字符串)
REG_MULTI_SZ7多个以 NULL 分隔的 Unicode 字符串,以双 NULL 结尾
REG_RESOURCE_LIST8仅内核模式使用
REG_FULL_RESOURCE_DESCRIPTOR9仅内核模式使用
REG_RESOURCE_REQUIREMENTS_LIST10仅内核模式使用
REG_QWORD1164 位无符号整数(小端序)

读取示例——获取控制台窗口中使用的字体名称:

cpp
HKEY hKey;
::RegOpenKeyEx(HKEY_CURRENT_USER, L"Console", 0, KEY_READ, &hKey);

DWORD type, size;
::RegQueryValueEx(hKey, L"FaceName", nullptr, &type, nullptr, &size);
assert(type == REG_SZ);

auto value = std::make_unique<BYTE[]>(size);
::RegQueryValueEx(hKey, L"FaceName", nullptr, &type, value.get(), &size);

::RegCloseKey(hKey);
printf("Value: %ws\n", (PCWSTR)value.get());

增强读取:RegGetValue

RegGetValue 是对 RegQueryValueEx 的增强版本,通过 dwFlags 参数可以直接限制允许的数据类型:

cpp
LSTATUS RegGetValue(
    _In_ HKEY hkey, _In_opt_ LPCSTR lpSubKey, _In_opt_ LPCSTR lpValue,
    _In_ DWORD dwFlags, _Out_opt_ LPDWORD pdwType,
    _Out_ PVOID pvData, _Inout_opt_ LPDWORD pcbData);

常用的 dwFlags 标志:

  • RRF_RT_REG_SZ / RRF_RT_REG_EXPAND_SZ / RRF_RT_REG_BINARY / RRF_RT_REG_DWORD / RRF_RT_REG_MULTI_SZ / RRF_RT_REG_QWORD:仅接受对应类型。
  • RRF_RT_DWORD:接受 REG_DWORDREG_BINARY 组合。
  • RRF_RT_QWORD:接受 REG_QWORDREG_BINARY 组合。
  • RRF_RT_ANY:不限制类型。
  • RRF_SUBKEY_WOW6464KEY / RRF_SUBKEY_WOW6432KEY:控制 WoW64 重定向。
  • RRF_NOEXPAND:不展开 REG_EXPAND_SZ 中的环境变量。
  • RRF_ZEROONFAILURE:失败时将缓冲区清零。

写入值:RegSetValueEx

向指定键写入一个命名值:

cpp
LSTATUS RegSetValueEx(
    _In_ HKEY hKey, _In_opt_ LPCTSTR lpValueName,
    _Reserved_ DWORD Reserved, _In_ DWORD dwType,
    _In_ CONST BYTE* lpData, _In_ DWORD cbData);

注意事项:

  • hKey 需要具有 KEY_SET_VALUE 访问掩码。
  • 字符串类型(REG_SZREG_EXPAND_SZ)必须以 NULL 结尾;REG_MULTI_SZ 需以双 NULL 结尾。
  • cbData 始终以字节为单位,对于字符串类型,大小必须包含终止 NULL 字符。

写入示例——修改控制台字体名称:

cpp
HKEY hKey;
::RegOpenKeyEx(HKEY_CURRENT_USER, L"Console", 0, KEY_WRITE, &hKey);

WCHAR value[] = L"Arial";
::RegSetValueEx(hKey, L"FaceName", 0, REG_SZ, (const BYTE*)value, sizeof(value));

::RegCloseKey(hKey);

替代函数 RegSetKeyValue 允许直接指定子键路径进行写入,无需预先显式打开子键,简化了调用流程。

删除键和值

删除键:RegDeleteKey / RegDeleteKeyEx

cpp
LSTATUS RegDeleteKey(_In_ HKEY hKey, _In_ LPCTSTR lpSubKey);

LSTATUS RegDeleteKeyEx(_In_ HKEY hKey, _In_ LPCTSTR lpSubKey,
    _In_ REGSAM samDesired, _Reserved_ DWORD Reserved);

注意事项:

  • 键的安全描述符决定删除权限,而非打开句柄时使用的访问掩码。
  • 键被标记为删除(Marked for Deletion),但直到所有打开的句柄被关闭后才真正从磁盘移除。
  • 只能删除不含任何子键的键,否则返回 ERROR_ACCESS_DENIED (5)

删除包含子键的树:RegDeleteTree

cpp
LSTATUS RegDeleteTree(_In_ HKEY hKey, _In_opt_ LPCTSTR lpSubKey);

该函数递归删除指定键下所有子键和值。hKey 需要具有 DELETEKEY_ENUMERATE_SUB_KEYSKEY_QUERY_VALUEKEY_SET_VALUE 权限。当 lpSubKeyNULL 时,删除 hKey 下的所有键和值。

删除值:RegDeleteValue / RegDeleteKeyValue

cpp
LSTATUS RegDeleteValue(_In_ HKEY hKey, _In_opt_ LPCTSTR lpValueName);

LSTATUS RegDeleteKeyValue(_In_ HKEY hKey, _In_opt_ LPCTSTR lpSubKey,
    _In_opt_ LPCTSTR lpValueName);

hKey 需要具有 KEY_SET_VALUE 访问掩码。在性能方面,RegDeleteValue 优于 RegDeleteKeyValue,因为后者内部需要额外打开子键。

创建注册表链接

注册表中的链接(Registry Link)是指向其他键的键。典型的例子是 HKLM\System\CurrentControlSet,它是一个指向 ControlSet001 的符号链接。

创建符号链接的步骤:

  1. 调用 RegCreateKeyEx 时指定 REG_OPTION_CREATE_LINK 标志。
  2. 在创建的键中写入名为 SymbolicLinkValue、类型为 REG_LINK 的值。
  3. 值的路径必须是内核视角的绝对路径,格式如 \REGISTRY\USER\{SID}\...

获取当前用户 SID,用于构造内核绝对路径:

cpp
HANDLE hToken;
::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &hToken);

BYTE buffer[sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE];
DWORD len;
::GetTokenInformation(hToken, TokenUser, buffer, sizeof(buffer), &len);
::CloseHandle(hToken);

auto user = (TOKEN_USER*)buffer;
PWSTR stringSid;
::ConvertSidToStringSid(user->User.Sid, &stringSid);
// 使用 stringSid 构造路径...
::LocalFree(stringSid);

构造路径并写入链接(注意 cbData 不含 NULL 终止符):

cpp
std::wstring path = L"\\REGISTRY\\USER\\";
path += stringSid;
path += L"\\Control Panel\\Desktop\\Colors";

::RegSetValueEx(hKey, L"SymbolicLinkValue", 0, REG_LINK,
    (const BYTE*)path.c_str(), path.size() * sizeof(WCHAR));

删除符号链接:不能使用 RegDeleteKey(Ex)(否则会删除链接所指向的目标键),必须使用原生 API NtDeleteKey

cpp
extern "C" int NTAPI NtDeleteKey(HKEY);
#pragma comment(lib, "ntdll")

HKEY hKey;
::RegOpenKeyEx(HKEY_CURRENT_USER, L"DesktopColors", REG_OPTION_OPEN_LINK,
    DELETE, &hKey);
::NtDeleteKey(hKey);
::RegCloseKey(hKey);

打开现有链接时必须使用 RegOpenKeyEx 并指定 REG_OPTION_OPEN_LINK 标志,否则默认会跟随链接并打开目标键。

枚举键和值

枚举子键:RegEnumKeyEx

cpp
LSTATUS RegEnumKeyEx(
    _In_ HKEY hKey, _In_ DWORD dwIndex,
    _Out_ LPTSTR lpName, _Inout_ LPDWORD lpcchName,
    _Reserved_ LPDWORD lpReserved, _Out_ LPTSTR lpClass,
    _Inout_opt_ LPDWORD lpcchClass, _Out_opt_ PFILETIME lpftLastWriteTime);

参数说明:

  • hKey:需要具有 KEY_ENUMERATE_SUB_KEYS 访问掩码。
  • dwIndex:从 0 开始递增,直到返回 ERROR_NO_MORE_ITEMS
  • lpName / lpcchName:接收键名及其长度。键名最大长度为 255 个字符。
  • lpClass / lpcchClass:可选,接收类名。
  • lpftLastWriteTime:可选,接收最后修改时间。

枚举值:RegEnumValue

cpp
LSTATUS RegEnumValue(
    _In_ HKEY hKey, _In_ DWORD dwIndex,
    _Out_ LPTSTR lpValueName, _Inout_ LPDWORD lpcchValueName,
    _Reserved_ LPDWORD lpReserved, _Out_opt_ LPDWORD lpType,
    _Out_opt_ LPBYTE lpData, _Inout_opt_ LPDWORD lpcbData);

参数说明:

  • hKey:需要具有 KEY_QUERY_VALUE 访问掩码。
  • dwIndex:从 0 开始递增。
  • lpValueName / lpcchValueName:接收值名及其长度,值名最大长度为 16383 个字符。
  • lpType:可选,接收值的数据类型。
  • lpData / lpcbData:可选,接收值的数据。

获取键的元信息:RegQueryInfoKey

该函数可一次性获取子键数量、值的数量、最大值名长度、最大值数据大小等信息,用于在枚举前预先分配足够大小的缓冲区,含 13 个参数。

完整枚举示例——实现一个递归转储注册表键的 DumpKey 工具函数:

cpp
void DumpKey(HKEY hKey, bool dumpKeys, bool dumpValues, bool recurse) {
    DWORD nsubkeys, nvalues, maxValueSize, maxValueNameLen;
    FILETIME modified;
    ::RegQueryInfoKey(hKey, nullptr, nullptr, nullptr, &nsubkeys, nullptr,
        nullptr, &nvalues, &maxValueNameLen, &maxValueSize, nullptr, &modified);

    if (dumpValues) {
        auto value = std::make_unique<BYTE[]>(maxValueSize);
        auto name = std::make_unique<WCHAR[]>(maxValueNameLen + 1);
        for (DWORD i = 0; ; i++) {
            DWORD cname = maxValueNameLen + 1;
            DWORD size = maxValueSize;
            DWORD type;
            auto error = ::RegEnumValue(hKey, i, name.get(), &cname,
                nullptr, &type, value.get(), &size);
            if (error == ERROR_NO_MORE_ITEMS) break;
            // 显示名称、类型、大小和值
        }
    }

    // 递归枚举子键(略)...
}

此外,可借助 GetValueAsString 辅助函数将不同类型的数据转换为可读文本,支持 REG_DWORDREG_QWORDREG_SZREG_EXPAND_SZREG_BINARY 等多种类型。

注册表通知

RegNotifyChangeKeyValue

RegNotifyChangeKeyValue 用于监视注册表键的变化,但存在局限性:它不指明具体是什么发生了变化或变化发生在哪个确切位置。

cpp
LSTATUS RegNotifyChangeKeyValue(
    _In_ HKEY hKey, _In_ BOOL bWatchSubtree,
    _In_ DWORD dwNotifyFilter, _In_opt_ HANDLE hEvent,
    _In_ BOOL fAsynchronous);

参数说明:

  • hKey:必须用 KEY_NOTIFY(即 REG_NOTIFY)访问掩码打开。
  • bWatchSubtreeTRUE 监控整棵子树(包含所有后代键);FALSE 仅监控指定的单个键。
  • dwNotifyFilter:指定监控的变化类型,可以是以下标志的组合:
    • REG_NOTIFY_CHANGE_NAME (1):子键被添加或删除。
    • REG_NOTIFY_CHANGE_ATTRIBUTES (2):键属性发生变化。
    • REG_NOTIFY_CHANGE_LAST_SET (4):最后修改时间变化(值被添加、更改或删除)。
    • REG_NOTIFY_CHANGE_SECURITY (8):安全描述符发生变化。
    • REG_NOTIFY_THREAD_AGNOSTIC (0x10000000):Windows 8+,通知与调用线程无关。
  • hEvent:异步模式下使用的事件句柄。
  • fAsynchronousFALSE 时阻塞调用线程直到变化发生;TRUE 时立即返回,变化发生时触发 hEvent

同步模式监视示例

cpp
HKEY hKey;
::RegOpenKeyEx(root, path, 0, KEY_NOTIFY, &hKey);

DWORD notifyFlags = REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET;
while (ERROR_SUCCESS == ::RegNotifyChangeKeyValue(
    hKey, recurse, notifyFlags, nullptr, FALSE)) {
    printf("Change occurred.\n");
}
::RegCloseKey(hKey);

ETW 注册表监视(RegWatch2 示例)

通过 Windows 事件跟踪(ETW,Event Tracing for Windows)可以获取更详细的注册表活动信息,使用内核提供程序(Kernel Provider)。相比 RegNotifyChangeKeyValue,ETW 能够精确地告诉你哪个键被创建、打开、删除或修改。

核心设置是将 ETW 会话的启用标志设为 EVENT_TRACE_FLAG_REGISTRY

cpp
_properties->EnableFlags = EVENT_TRACE_FLAG_REGISTRY;

EventParser 负责解析事件记录,OnEvent 回调根据不同的操作码(Opcode)进行处理:

cpp
void OnEvent(PEVENT_RECORD rec) {
    EventParser parser(rec);
    switch (parser.GetEventHeader().EventDescriptor.Opcode) {
        case EVENT_TRACE_TYPE_REGCREATE:   printf("Create key"); break;
        case EVENT_TRACE_TYPE_REGOPEN:     printf("Open key"); break;
        case EVENT_TRACE_TYPE_REGDELETE:   printf("Delete key"); break;
        case EVENT_TRACE_TYPE_REGQUERY:    printf("Query key"); break;
        case EVENT_TRACE_TYPE_REGSETVALUE: printf("Set value"); break;
        // ... 还有更多操作码
    }
    auto prop = parser.GetProperty(L"KeyName");
    if (prop) printf(" %ws", prop->GetUnicodeString());
}

该方式需要管理员权限运行。可通过 SetConsoleCtrlHandler 捕获 Ctrl+C 信号以优雅地停止 ETW 会话。

事务性注册表(Transactional Registry)

注册表操作可以作为事务(Transaction)的一部分执行,与文件事务操作配合使用。事务性注册表意味着:打开事务句柄后,在事务范围内进行的所有注册表操作要么全部成功提交(Commit),要么全部回滚(Rollback)。

创建/打开事务性键:RegCreateKeyTransacted / RegOpenKeyTransacted

cpp
LSTATUS RegCreateKeyTransacted(
    // ... 与 RegCreateKeyEx 相同的参数 ...
    _In_ HANDLE hTransaction, _Reserved_ PVOID pExtendedParemeter);

LSTATUS RegOpenKeyTransacted(
    // ... 与 RegOpenKeyEx 相同的参数 ...
    _In_ HANDLE hTransaction, _Reserved_ PVOID pExtendedParemeter);

这两个函数在原有函数的基础上增加了最后两个参数:

  • hTransaction:事务句柄,通过 CreateTransaction 创建。
  • pExtendedParemeter:保留参数,必须传入 NULL

使用返回的 hKey 句柄进行的所有后续注册表操作都属于该事务的范围。事务的详细内容将在第 9 章展开。

远程注册表(Remote Registry)

RegConnectRegistry

连接到远程计算机的注册表,前提是目标计算机上的远程注册表服务(Remote Registry Service)正在运行(默认为手动启动):

cpp
LSTATUS RegConnectRegistry(
    _In_opt_ LPCTSTR lpMachineName,  // 格式:\\computername
    _In_ HKEY hKey,                   // HKEY_USERS、HKEY_LOCAL_MACHINE 或 HKEY_PERFORMANCE_DATA
    _Out_ PHKEY phkResult);          // 返回的句柄可像本地句柄一样使用

获取远程句柄后,可以执行本地的常规注册表操作。操作完成后使用 RegCloseKey 关闭句柄。

RegConnectRegistryEx

RegConnectRegistryEx 是对 RegConnectRegistry 的扩展版本:

cpp
LSTATUS RegConnectRegistryEx(
    _In_opt_ LPCTSTR lpMachineName, _In_ HKEY hKey,
    _In_ ULONG Flags, _Out_ PHKEY phkResult);

目前唯一支持的标志是 REG_SECURE_CONNECTION (1),用于加密 RPC 调用,提升远程连接的安全性。

其他注册表函数

安全管理

cpp
LSTATUS RegGetKeySecurity(_In_ HKEY hKey,
    _In_ SECURITY_INFORMATION SecurityInformation,
    _Out_ PSECURITY_DESCRIPTOR pSecurityDescriptor,
    _Inout_ LPDWORD lpcbSecurityDescriptor);

LSTATUS RegSetKeySecurity(_In_ HKEY hKey,
    _In_ SECURITY_INFORMATION SecurityInformation,
    _In_ PSECURITY_DESCRIPTOR pSecurityDescriptor);

这两个函数用于获取和设置注册表键的安全描述符,比通用的 GetSecurityInfo / SetSecurityInfo 使用更简便。

保存和恢复注册表项

RegSaveKey / RegSaveKeyEx——将注册表键保存到文件(专有二进制格式,非 .REG 文本格式):

cpp
LSTATUS RegSaveKey(_In_ HKEY hKey, _In_ LPCTSTR lpFile,
    _In_opt_ CONST LPSECURITY_ATTRIBUTES lpSecurityAttributes);

lpFile 指定的文件不能已存在。可选的安全属性为输出文件提供安全描述符。

扩展版本 RegSaveKeyEx 增加 Flags 参数:

cpp
LSTATUS RegSaveKeyEx(_In_ HKEY hKey, _In_ LPCTSTR lpFile,
    _In_opt_ CONST LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    _In_ DWORD Flags);

标志取值:

  • REG_STANDARD_FORMAT (1):原始格式。
  • REG_LATEST_FORMAT (2):最新格式。
  • REG_NO_COMPRESSION (4):不压缩(仅适用于真正的配置单元 hive 文件)。

调用者需要具有 SeBackupPrivilege 权限。

RegRestoreKey——从文件恢复注册表键:

cpp
LSTATUS RegRestoreKey(_In_ HKEY hKey, _In_ LPCTSTR lpFile, _In_ DWORD dwFlags);

注意事项:

  • 保留根键名称,但替换所有其他属性、子键和值。
  • REG_FORCE_RESTORE (8):强制恢复,即使存在打开的句柄。
  • REG_WHOLE_HIVE_VOLATILE (1):仅在内存中创建,重启后消失。
  • 调用者需要具有 SeRestorePrivilege 权限。

加载和卸载配置单元

RegLoadAppKey——将配置单元加载到不可枚举的不可见根:

cpp
LSTATUS RegLoadAppKey(_In_ LPCTSTR lpFile, _Out_ PHKEY phkResult,
    _In_ REGSAM samDesired, _In_ DWORD dwOptions, _Reserved_ DWORD Reserved);

该函数加载的 hive 不属于标准注册表树,只能通过返回的根键句柄访问。调用者无需 SeRestorePrivilege 权限。REG_PROCESS_APPKEY 标志防止其他调用方同时加载同一文件。

RegLoadKey / RegUnloadKey——从文件加载配置单元作为新的 hive:

cpp
LSTATUS RegLoadKey(_In_ HKEY hKey, _In_opt_ LPCTSTR lpSubKey, _In_ LPCTSTR lpFile);

hKey 只能是 HKEY_LOCAL_MACHINEHKEY_USERS(两个"真正的"根键)。加载后,对子键的更改会持久保存到文件中。

cpp
LSTATUS RegUnLoadKey(_In_ HKEY hKey, _In_opt_ LPCTSTR lpSubKey);

卸载配置单元前必须关闭所有打开的句柄。

总结

注册表是 Windows 用于存储系统和用户设置的核心分层数据库。本章覆盖了以下内容:

  • 注册表的逻辑结构:六个主要 hive(HKLM、HKU、HKCU、HKCR、HKCC、HKEY_PERFORMANCE_DATA)及其相互关系——本质上只有 HKEY_USERSHKEY_LOCAL_MACHINE 两个真正的根键,其余均由这两个组合或映射而成。
  • 32/64 位注册表虚拟化:WoW64 重定向机制,以及 KEY_WOW64_64KEY / KEY_WOW64_32KEY 标志的使用。
  • 最常用的 API 操作:打开/创建键(RegOpenKeyEx / RegCreateKeyEx)、读取值(RegQueryValueEx / RegGetValue)、写入值(RegSetValueEx / RegSetKeyValue)、删除键和值(RegDeleteKey / RegDeleteTree / RegDeleteValue)、符号链接的创建和删除、键和值的枚举(RegEnumKeyEx / RegEnumValue / RegQueryInfoKey)。
  • 高级功能:使用 RegNotifyChangeKeyValue 和 ETW 实现注册表变化通知、事务性注册表(RegCreateKeyTransacted / RegOpenKeyTransacted)、远程注册表连接(RegConnectRegistry / RegConnectRegistryEx)。
  • 维护相关函数:保存/恢复/加载/卸载配置单元(RegSaveKey / RegRestoreKey / RegLoadKey / RegUnloadKey / RegLoadAppKey),以及安全描述符管理(RegGetKeySecurity / RegSetKeySecurity)。