非常安全地保存和还原注册表的方法






4.18/5 (9投票s)
2004年8月24日
7分钟阅读

75180

990
本文提供了一种非常安全的保存和恢复注册表项的方法。它提供了命令行和图形用户界面(UI)模式下可直接使用的工具。
目录
引言
在先前的文章《如何保存和恢复注册表项》中,我提供了一个命令行工具,用于将注册表项保存/恢复到/从数据文件中。因此,如果您不熟悉此主题,我邀请您先阅读前一篇文章,以了解该主题。该文章中提供的工具存在一个问题,即从数据文件恢复注册表项根本不安全,除非我们确切知道以下两个条件都满足:
- 数据文件与我们要恢复的注册表项完全对应。
- 数据文件与保存时完全相同,即未被修改或损坏。
本文不仅满足了上述两个条件,还提供了一个更通用的工具,该工具可以在两种模式下使用:作为脚本中的命令行工具,以及如上图所示的 UI 模式。为了满足这两个条件,我们需要提供一个注册表配置文件(不要将其与后续内容中提到的程序配置文件混淆,它们不一定相同),在保存阶段,我们在其中添加两个重要信息:
- 数据文件位置与注册表项路径之间的对应关系。
- 与已保存数据文件对应的 CRC32 值(32 位数字的循环冗余校验和)。
在恢复数据时,我们需要检查数据完整性,即两项内容:
- 注册表项/文件对应关系:我们在配置文件中查找与我们要恢复的注册表项对应的文件(在保存阶段已完成)。
- 文件完整性检查:我们计算其 CRC32,并将其与报告的 CRC32(在保存阶段)进行比较。如果两个 CRC 相同,则继续执行恢复操作,否则不恢复数据文件。
基本上,项目中使用了两个 Windows API:RegSaveKey
和 RegRestoreKey
。使用这两个 API 函数无法确保保存和恢复注册表项的安全性。MSDN 中有如下说明:
“如果
RegSaveKey
在其操作过程中途失败,则文件将损坏,后续对该文件的RegLoadKey
、RegReplaceKey
或RegRestoreKey
调用将失败。”
要使用本文附带的工具,调用进程必须使用管理员组中的账户。该工具添加了保存和恢复注册表所需的必要权限,分别是 SE_BACKUP_NAME
(SeBackupPrivilege
)或/和 SE_RESTORE_NAME
(SeRestorePrivilege
)。如果在不属于管理员组的情况下完成这两项任务,将是一个很好的实际权限测试练习。
我在此感谢:
- Brian Friesen,感谢他撰写的优秀文章《CRC32: Generating a checksum for a file》,我从中选取了所有函数来提供项目中使用的
CCRC32
类。 - Pavel Antonov,感谢他撰写的优秀文章《Command line parser》,其中使用的
CCmdLineParser
类用于解析工具的命令行。
如何使用提供的工具
UI 模式 | 命令行模式 | |||||||||
RegSR /UI | RegSR /S|/R /H:ROOT /K:KEY /P:FILE [/C:CONFIG_FILE] | |||||||||
/S |
保存注册表项到文件。
|
注意:程序配置文件 CONF_FILE
是一个文件,它指示注册表保存/恢复配置文件应位于的服务器名称(作为 ServerPath
键的值)以及配置文件的名称(作为 ConfigFile
键的值)。以下是这类文件的两个示例:
示例 1 | 示例 2 |
[SETTINGS] |
[SETTINGS] |
在示例 1 中,程序会将注册表项和文件数据(包括文件的 CRC32 信息)保存在 .\RegConfig.ini 文件中。
在示例 2 中,程序会将注册表项和文件数据(包括文件的 CRC32 信息)保存在 \\MyServer\MyShare\RegConfig.ini 文件中。
请注意,程序配置文件可以与注册表保存/恢复配置文件相同。在这种情况下,我们将参数 CONFIG_FILE
设置为 ServerPath\ConfigFile
。
何时可以使用提供的工具
提供的工具可以在许多场景中使用,例如:
- 管理与注册表相关的用户配置文件。
- 在任何时候配置软件与注册表相关的设置,不一定仅限于安装过程。
- 配置硬件,如打印机、扫描仪等。
上下文使用
如果您在当前用户以外的上下文中运行该工具,例如在服务上下文中,则无法使用 HKCU(HKEY_CURRENT_USER),因为您无法访问已登录用户的注册表可见性。在这种情况下,您可以通过其 SID 的主键在 HKUSERS(HKEY_USERS)注册表蜂巢下访问 HKCU,前提是能够获取 SID 值。这是一个示例:
RegSr /R /H:HKUSERS /K:S-1-5-21-861567501-842925246-854245398-1004\Microsoft\Office /P:C:\Office.dat
在这里,我们将数据文件 C:\Office.dat 恢复到 SID 为 S-1-5-21-861567501-842925246-854245398-1004 的用户的 Microsoft\Office 注册表项。上面的示例等同于以下行,但在当前用户的上下文中:
RegSr /R /H:HKCU /K:Microsoft\Office /P:C:\Office.dat
如何使用提供的工具
有一个通用的 VBScript RegSr.vbs,如下所示,它使用提供的工具来保存/恢复注册表项。您可以根据您的需求对其进行自定义。它会在临时目录中生成一个日志文件 RegSR.log,其中包含程序 RegSR.exe 的退出代码。返回的代码包括:
1 | 无效参数 |
2 | 文件未找到 |
else | 来自 RegSR.exe 的返回代码。也可能是 2。 |
要使用该脚本,您只需使用与工具相同的语法,即:
RegSR.vbs /S|/R /H:ROOT /K:KEY /P:FILE [/C:CONFIG_FILE]
示例: RegSR.vbs /S /H:HKCU /K:software\test /P:c:\test.dat
RegSr.vbs 列表
'////////////////////////////////////////////////////////
' Purpose: Registry Save/Restore
' Author: A. YEZZA ' Date: August 2004
'////////////////////////////////////////////////////////
Option Explicit
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'CONSTANTS
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Const LOG_FILE="RegSR.LOG"
Const REG_SR="RegSR.exe"
Const SEPARATOR="==================================================="
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'Global variables
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Dim ScriptArgs: Set ScriptArgs = WScript.Arguments Dim WSHShell : _
Set WSHShell = WScript.CreateObject("WScript.Shell")
Dim fso: Set fso = CreateObject("Scripting.FileSystemObject")
Dim EnvObject: Set EnvObject = WshShell.Environment("PROCESS")
Dim TempDir: TEMPDir = EnvObject.Item("TEMP")
Dim WScriptJet: WScriptJet = _
EnvObject.Item("WINDIR") &"\System32\WScript.exe"
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'Command-line Arguments variables
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Dim Op
Dim Root
Dim Key
Dim InOutFile Dim ConfigFile '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'MAIN
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Call MAIN()
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'> PROCEDURES >
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Sub MAIN()
Dim Ret WriteToFile LOG_FILE,vbcrlf+SEPARATOR+vbcrlf+"BEGIN:<"
If GetParameters()=False Then
ExitScript(1)
Else
If ConfigFile<>"" Then
WriteToFile LOG_FILE,"Launching : "&_
CurrentDir()& REG_SR & " "& _
Op&" /H:"&Root&" /K:"&Key&_
" /P:"&InOutFile&" /C:" & _
ConfigFile Ret=LaunchEXE(CurrentDir() _
& REG_SR, Op&" /H:"&Root&_
" /K:"&Key& " /P:"&InOutFile&_
" /C:"&ConfigFile,True)
Else
WriteToFile LOG_FILE,"Launching : "& _
CurrentDir() & REG_SR & " "& _
Op&" /H:"&Root&" /K:"&Key&_
" /P:" & InOutFile Ret=LaunchEXE(CurrentDir() _
& REG_SR, Op&" /H:"&Root&_
" /K:"&Key& " /P:"&InOutFile,True)
End If
End If ExitScript(Ret)
End Sub
Function CurrentDir() CurrentDir=Mid(WScript.ScriptFullName,_
1,Len(WScript.ScriptFullName)-Len(WScript.ScriptName))
End Function
Sub ExitScript(ErrCode)
if ErrCode=0 Then WriteToFile LOG_FILE, _
"END:>Normal termination"+vbcrlf+_
SEPARATOR Else WriteToFile LOG_FILE, _
"END:>Error Code: " & CStr(ErrCode)+_
vbcrlf+SEPARATOR End If
Set fso=Nothing
Set WSHShell=Nothing
Set EnvObject=Nothing WScript.Quit(ErrCode)
End Sub
Sub WriteToFile(File, Text)
Dim TextFile File=TEMPDir+"\"+File
If Not IsFileExist(File) Then
Set TextFile=fso.CreateTextFile(File, True)
Else
Set TextFile=fso.OpenTextFile(File, 8)
End If
TextFile.WriteLine(Text)
TextFile.Close
Set TextFile=Nothing
End Sub
Function IsFileExist(File) IsFileExist=fso.FileExists(File)
End Function
Function IsDirExist(Fldr) IsDirExist=fso.FolderExists(Fldr)
End Function
Function LaunchEXE(EXE, Args, IsWait)
If (IsFileExist(EXE)) Then
If InStr(1, EXE, " ")>1 Then
LaunchEXE=WshShell.Run (Chr(34)& EXE _
& " " & Args&Chr(34), 1, IsWait)
Else
LaunchEXE=WshShell.Run (EXE & _
" " & Args, 1, IsWait)
End If
Else MsgBox(EXE & " Not found.")
ExitScript(2)
End If
End Function
Function GetArgument(Arg,TheSwitch,Value) _
GetArgument=False Arg=LCase(Arg):TheSwitch=LCase(TheSwitch)
If Mid(Arg,1,Len(TheSwitch))=TheSwitch Then
Value=Mid(Arg,Len(TheSwitch)+1,Len(Arg))
Value=Trim(Value) GetArgument=True
End If
End Function
Function GetParameters() GetParameters=False
Dim Value
If ScriptArgs.Count >=4 Then
'Get operation (save or restore)
If GetArgument(ScriptArgs(0),"/R",Value)=True Then
Op="/R"
ElseIf GetArgument(ScriptArgs(0), "/S",Value)=True Then
Op="/S"
Else GetParameters=False: Exit Function
End If
'Get Root
If GetArgument(ScriptArgs(1),"/H:",Value)=True Then
Root=Value
Else GetParameters=False: Exit Function
End If
'Get Key path
If GetArgument(ScriptArgs(2),"/K:",Value)=True Then
Key=Value
Else
GetParameters=False: Exit Function
End If
'Get InOutFile
If GetArgument(ScriptArgs(3),"/P:",Value)=True Then
InOutFile=Value
Else
GetParameters=False: Exit Function
End If
GetParameters=True
'Get ConfigFile
If ScriptArgs.Count=5 Then
If GetArgument(ScriptArgs(4),"/C:",Value)=True Then
ConfigFile=Value
End If
End If
End If
End Function
使用代码
该程序基于 WIN32 项目。它包含以下类:
CRegSRApp (RegSR.h) |
应用程序类。我将留给读者提取一个不一定基于应用程序类(CRegSRApp )的类,该类可以在任何项目中用于保存/恢复注册表。 |
CMainDlg (MainDlg.h) |
UI 模式下的主对话框类。 |
CCRC32 (CRC32.h) |
用于计算文件的 CRC32。 |
为了能够从对话框实现调用保存/恢复函数,诀窍在于使用中间的 CWinApp
成员 theApp
,通过它可以调用唯一的非构造函数应用程序的两个公共函数:
void SetParams(CmdParams &P, int &PNum) |
从命令行或从主对话框设置参数(如果程序以 UI 模式调用)。 |
void DoSaveRestore(DWORD &RetErr) |
这个函数实际上执行了注册表保存或恢复操作。 |
我邀请读者在项目中查看有关这两个函数的详细信息。一个值得解释的私有函数是恢复阶段用于检查数据完整性的函数,在那里我们确保要恢复的数据是正确的数据,并且我们不会冒险损坏注册表。
// This function checks 2 things: // 1.The fact that the file from which we restore the key // has the right CRC32 already reported to config file // 2.The fact that the file corresponds exactly to the key // to restore from the file // NOTE: These 2 information have been written in the save stage // Return TRUE if OKAY else FALSE BOOL CRegSRApp::CheckIntegrity(CString &Root, CString &SubKey, CString &InFile) { BOOL ret=TRUE; CString KeyPath=AddBackSlash(Root)+SubKey; if (Get_CRC32()!=FALSE) { CString RetCRC32; DWORD dw=GetPrivateProfileString(SEC_CRC32, SavResFile.FileName, "", RetCRC32.GetBuffer(128), 128, AddBackSlash(ServerPath)+ConfigFile); RetCRC32.ReleaseBuffer(); if (dw>0) { //Compare calculated CRC32 and the reported one if (RetCRC32.CompareNoCase(SavResFile.CRC32)==0){ //Get the corresponding key and compare CString strKeyPath; DWORD dw=GetPrivateProfileString( SEC_KEYS_FILES, SavResFile.FileName, "", strKeyPath.GetBuffer(255), 255, AddBackSlash(ServerPath)+ConfigFile); strKeyPath.ReleaseBuffer(); ret=(strKeyPath.CompareNoCase(KeyPath)==0) ?TRUE:FALSE; } else ret=FALSE; } else ret=FALSE; } else ret=FALSE; return ret; }
趣味点
本文展示了以下事实:
- 如何在同一个项目中以最少的工作量提供命令行和 UI 工具。
- 如何通过提供注册表配置文件,使注册表保存到数据文件,尤其是从数据文件恢复注册表的操作变得非常安全。
为了更好地利用这项工作,我们可以轻松提取一个与应用程序无关的类来保存/恢复注册表项。
历史
第一个版本:2004 年 8 月。