8.1 注册表的结构
注册表(Registry)是基本数据的中心,在系统的设置和管理方面扮演着重要的角色。注册表的结构类似于磁盘的逻辑结构,但是注册表的内容不是磁盘数据的静态组合,而是随系统的工作进程而动态改变。注册表由keys构成,键就像磁盘的目录。最上层的keys叫做root keys。keys本身就是一个容器,装着其它的keys,装在里面的这些keys叫做subkeys或是values,就像磁盘上的文件。values保存着实际数据。对注册表的操作与管理是由Configuration Manager负责的。
root keys有六个:
HKEY_USER -- 包含所有注册信息;
HKEY_CURRENT_USER -- 保存着当前用户的注册信息;
HKEY_LOCAL_MACHINE -- 保存着系统配置信息:硬件设备支持记录,安全策略,用户口令,应用程序设置和服务及驱动程序配置。
HKEY_CURRENT_CONFIG -- 包含当前硬件配置信息;
HKEY_CLASSES_ROOT -- 保存着文件关联和COM类的注册数据;
HKEY_PERFORMANCE_DATA -- 包含着性能信息。
HKEY_PERFORMANCE_DATA 是一个特殊的键,未必会直接用到。在用户模式下,关于性能统计的信息是通过Performance Data Helper库来访问的,这个库实现在模块pdh.dll里。标准程序Performance Monitor正是利用了这个库。除性能统计之外这个键还包含着许多补充信息。例如,用于枚举进程、线程和模块等等的Process Status函数(在psapi.dll里实现)正是从HKEY_PERFORMANCE_DATA键中获得的信息。注册表编辑器Regedit和Regedt32并不能显示出这个键的内容,因此无法编辑。
根键HKEY_CURRENT_USER、HKEY_CURRENT_CONFIG和HKEY_CLASSES_ROOT并不包含什么信息。它们只是链接到注册表其它的子键。
HKEY_CURRENT_USER -- 链接到子键HKEY_USER\ 系统用户中对应于当前用户的注册信息。
HKEY_CURRENT_CONFIG -- 链接到子键HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current.
HKEY_CLASSES_ROOT -- 链接到子键HKEY_LOCAL_MACHINE\SOFTWARE\Classes和HKEY_CURRENT_USER\SOFTWARE\Classes.
8.2 在驱动程序中访问注册表
如何在内核模式下访问注册表?对注册表的访问和访问其它命名对象完全相同,即通过对象管理器的名字空间(详细方法见第三章)来访问。为了将注册表名字空间与对象管理器名字空间集成起来,配置管理器(Configuration Manager)在建立了一个名为“Registry”注册键类型的对象(key object)并将其放在对象管理器名字空间的根目录下。对于内核模式组件,有进入注册表的入口点。
所有的内核函数,对命名对象的访问要获得其名称,这个名称位于结构体OBJECT_ATRIBUTES的一个成员中,这个我们以前就知道了。如果对象类型为注册表键,则对象名字应该起始于“\Registry”。我要说的是,我不知道打开HKEY_PERFORMANCE_DATA根键要用什么名字,以及更一般的说,对某个键要用哪个名字。我在这方面作出过努力,但都是白费。如果您了解这方面的东西,还请您教我一下。有两个根键还算简单。
键名称
HKEY_USER "\Registry\User"
HKEY_LOCAL_MACHINE "\Registry\Machine"
对于三个链接到其它地方的键还需要额外的操作。比如说,操作HKEY_CURRENT_CONFIG键需要知道它链接到了子键HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current上并用这个根键名字代替。这样就得到了\Registry\Machine\SYSTEM\CurrentControlSet\Hardware Profiles\Current这个名字。
8.3 RegistryWorks驱动程序源代码
现在,当我们熟知根键名字的时候,理解它们就不会太困难。
驱动程序的代码由几个独立的函数构成:CreateKey、SetValueKey、QueryValueKey、DeleteKey和EnumerateKey,每一个都是“从零开始”操作注册表的,对于学习来说,例子总是最直观的。
8.3.1 注册表键的创建与打开
在CreateKey函数中调用了函数ZwCreateKey,建立新的Registry\Machine\Software\CoolApp键。
标志REG_OPTION_VOLATILE不准将建立的子键写入hive——磁盘上的一个注册表文件。这样这个子键只存在到系统下一次加载。在本例中,并不一定非要使用这个标志,我们可以自己删除所有子键。如果想在注册表中较长时间注册这个子键,就不要用这个标志了。
在成功调用ZwCreateKey后,变量dwDisposition的值定义了是否已创建新的子键(REG_CREATED_NEW_KEY),要是这个子键已在注册表中存在(REG_OPENED_EXISTING_KEY),就已被打开。
在剩下的函数中,我们调用ZwOpenKey函数来打开已经存在的键,对相应的访问类型只使用相应的标志。
8.3.2 创建注册表键值
现在,我们来在我们的子键里创建一个字符串键值“SomeData”。
常量REG_SZ定义了所创建键值的类型:以零结尾的unicode字符串。系统还有许多其它的类型——DDK中有详细描述。
8.3.3 访问注册表键值
我们来获取我们的SomeData的值。
函数ZwQueryValueKey的第三个参数定义了请求信息的类型。在DDK中定义了三种值:KeyValueBasicInformation、KeyValueFullInformation 和KeyValuePartialInformation,每一个值都有自己相应的结构体:KEY_VALUE_BASIC_INFORMATION、KEY_VALUE_FULL_INFORMATION和KEY_VALUE_PARTIAL_INFORMATION。在本例中,我们想获得键值的内容。对此KeyValuePartialInformation完全适合。
我们事先不知道所获取信息的大小,所以我们调用ZwQueryValueKey时,第四第五个参数分别使用nil(缓冲区指针)和0(缓冲区大小)。函数ZwQueryValueKey计并算出缓冲区所需的大小并将这个值返回到变量cb中(很多但非全部的Zw*函数都是这个样子处理的)。
分配必需的内存空间,再次调用ZwQueryValueKey——现在已经有缓冲区指针了。
在任何情况下,我们都要检查键值类型。
如果是REG_SZ类型的键值,则KEY_VALUE_PARTIAL_INFORMATION结构体的Data域就包含了以零结尾的unicode字符串。我们需要在调试信息中输出这个字符串,所以要将其转换成AnsiString,函数RtlUnicodeStringToAnsiString将unicode字符串转换为ansi字符串。如果最后的参数设置为TRUE,则函数自己分配缓冲区,向那里写入转换的字符串并填充ANSI_STRING结构体(在我们这里是变量_as)。ANSI_STRING的Buffer域将指向所分配的装有转换好的ansi字符串的缓冲区。如果最后一个参数为FALSE,则用于保存ansi字符串的缓冲区就需要事先分配好并将指针放在我们的ANSI_STRING结构体的Buffer域中。在本例中,我们让RtlUnicodeStringToAnsiString函数为我们分配缓冲区。
8.3.4 删除注册表键
我想,这部分不用我讲您也能搞定。
8.3.5 更新注册表键
现在我们来看\Registry\User键下都有些什么。
要组织键的内容就需要知道其下子键/键值的数量。对此我们要使用KeyFullInformation信息类。分配必需的内存并将它传给ZwQueryKey,我们就在变量dwSubKeys中得到了子键/键值的数量。
我们在一个循环中使用KeyBasicInformation信息类来调用ZwEnumerateKey。就像前面那样,我们对其调用两次,第一次是为了得知信息的大小,第二次是为了获得这项信息本身。
在KEY_BASIC_INFORMATION结构体的_Name域返回的是子键/键值的名称,其形式为unicode字符串,但是这个字符串不是以零结尾的。为了在后面将其转换为ansi字符串(为了在调试信息中输出),我们应该将其完善——为其分配一个临时缓冲区。
将临时缓冲区清零,并向其中拷贝子键/键值的名称。
我们来做必要的处理并在调试信息中输出子键/键值的名称。
注册表(Registry)是基本数据的中心,在系统的设置和管理方面扮演着重要的角色。注册表的结构类似于磁盘的逻辑结构,但是注册表的内容不是磁盘数据的静态组合,而是随系统的工作进程而动态改变。注册表由keys构成,键就像磁盘的目录。最上层的keys叫做root keys。keys本身就是一个容器,装着其它的keys,装在里面的这些keys叫做subkeys或是values,就像磁盘上的文件。values保存着实际数据。对注册表的操作与管理是由Configuration Manager负责的。
root keys有六个:
HKEY_USER -- 包含所有注册信息;
HKEY_CURRENT_USER -- 保存着当前用户的注册信息;
HKEY_LOCAL_MACHINE -- 保存着系统配置信息:硬件设备支持记录,安全策略,用户口令,应用程序设置和服务及驱动程序配置。
HKEY_CURRENT_CONFIG -- 包含当前硬件配置信息;
HKEY_CLASSES_ROOT -- 保存着文件关联和COM类的注册数据;
HKEY_PERFORMANCE_DATA -- 包含着性能信息。
HKEY_PERFORMANCE_DATA 是一个特殊的键,未必会直接用到。在用户模式下,关于性能统计的信息是通过Performance Data Helper库来访问的,这个库实现在模块pdh.dll里。标准程序Performance Monitor正是利用了这个库。除性能统计之外这个键还包含着许多补充信息。例如,用于枚举进程、线程和模块等等的Process Status函数(在psapi.dll里实现)正是从HKEY_PERFORMANCE_DATA键中获得的信息。注册表编辑器Regedit和Regedt32并不能显示出这个键的内容,因此无法编辑。
根键HKEY_CURRENT_USER、HKEY_CURRENT_CONFIG和HKEY_CLASSES_ROOT并不包含什么信息。它们只是链接到注册表其它的子键。
HKEY_CURRENT_USER -- 链接到子键HKEY_USER\ 系统用户中对应于当前用户的注册信息。
HKEY_CURRENT_CONFIG -- 链接到子键HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current.
HKEY_CLASSES_ROOT -- 链接到子键HKEY_LOCAL_MACHINE\SOFTWARE\Classes和HKEY_CURRENT_USER\SOFTWARE\Classes.
8.2 在驱动程序中访问注册表
如何在内核模式下访问注册表?对注册表的访问和访问其它命名对象完全相同,即通过对象管理器的名字空间(详细方法见第三章)来访问。为了将注册表名字空间与对象管理器名字空间集成起来,配置管理器(Configuration Manager)在建立了一个名为“Registry”注册键类型的对象(key object)并将其放在对象管理器名字空间的根目录下。对于内核模式组件,有进入注册表的入口点。
所有的内核函数,对命名对象的访问要获得其名称,这个名称位于结构体OBJECT_ATRIBUTES的一个成员中,这个我们以前就知道了。如果对象类型为注册表键,则对象名字应该起始于“\Registry”。我要说的是,我不知道打开HKEY_PERFORMANCE_DATA根键要用什么名字,以及更一般的说,对某个键要用哪个名字。我在这方面作出过努力,但都是白费。如果您了解这方面的东西,还请您教我一下。有两个根键还算简单。
键名称
HKEY_USER "\Registry\User"
HKEY_LOCAL_MACHINE "\Registry\Machine"
对于三个链接到其它地方的键还需要额外的操作。比如说,操作HKEY_CURRENT_CONFIG键需要知道它链接到了子键HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current上并用这个根键名字代替。这样就得到了\Registry\Machine\SYSTEM\CurrentControlSet\Hardware Profiles\Current这个名字。
8.3 RegistryWorks驱动程序源代码
现在,当我们熟知根键名字的时候,理解它们就不会太困难。
unit RegistryWorks; interface uses nt_status, ntoskrnl, native, winioctl, fcall, macros; function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall; implementation var g_usMachineKeyName: UNICODE_STRING; g_usValueName: UNICODE_STRING; g_wszStringData: PWideChar = 'It''s just a string'; procedure CreateKey; var oa: OBJECT_ATTRIBUTES; hKey: THANDLE; dwDisposition: DWORD; RtnCode: NTSTATUS; begin DbgPrint(#13#10'RegistryWorks: *** Creating registry key'#13#10); InitializeObjectAttributes(oa, @g_usMachineKeyName, OBJ_CASE_INSENSITIVE, 0, nil); RtnCode := ZwCreateKey(hKey, KEY_WRITE, @oa, 0, nil, REG_OPTION_VOLATILE, dwDisposition); if RtnCode = STATUS_SUCCESS then begin if dwDisposition = REG_CREATED_NEW_KEY then begin DbgPrint('RegistryWorks: Registry key \Registry\Machine\Software\CoolApp created'#13#10); end else if dwDisposition = REG_OPENED_EXISTING_KEY then begin DbgPrint('RegistryWorks: Registry key \Registry\Machine\Software\CoolApp opened'#13#10); end; ZwClose(hKey); DbgPrint('RegistryWorks: Registry key handle closed'#13#10); end else begin DbgPrint('RegistryWorks: Can''t create registry key. Status: %08X'#13#10, RtnCode); end; end; procedure SetValueKey; var oa: OBJECT_ATTRIBUTES; hKey: THANDLE; RtnCode: NTSTATUS; begin DbgPrint(#13#10'RegistryWorks: *** Opening registry key to set new value'#13#10); InitializeObjectAttributes(oa, @g_usMachineKeyName, OBJ_CASE_INSENSITIVE, 0, nil); RtnCode := ZwOpenKey(hKey, KEY_SET_VALUE, @oa); if RtnCode = STATUS_SUCCESS then begin DbgPrint('RegistryWorks: Registry key openeded'#13#10); RtnCode := ZwSetValueKey(hKey, @g_usValueName, 0, REG_SZ, g_wszStringData, (wcslen(g_wszStringData) + 1) * sizeof(WideChar)); if RtnCode = STATUS_SUCCESS then begin DbgPrint('RegistryWorks: Registry key value added'#13#10); end else begin DbgPrint('RegistryWorks: Can''t set registry key value. Status: %08X'#13#10, RtnCode); end; ZwClose(hKey); DbgPrint('RegistryWorks: Registry key handle closed'#13#10); end else begin DbgPrint('RegistryWorks: Can''t open registry key. Status: %08X'#13#10, RtnCode); end; end; procedure QueryValueKey; var oa: OBJECT_ATTRIBUTES; hKey: THANDLE; cb: DWORD; ppi: PKEY_VALUE_PARTIAL_INFORMATION; _as: ANSI_STRING; us: UNICODE_STRING; RtnCode: NTSTATUS; begin DbgPrint(#13#10'RegistryWorks: *** Opening registry key to read value'#13#10); InitializeObjectAttributes(oa, @g_usMachineKeyName, OBJ_CASE_INSENSITIVE, 0, nil); RtnCode := ZwOpenKey(hKey, KEY_QUERY_VALUE, @oa); if RtnCode = STATUS_SUCCESS then begin DbgPrint('RegistryWorks: Registry key openeded'#13#10); RtnCode := ZwQueryValueKey(hKey, @g_usValueName, KeyValuePartialInformation, nil, 0, cb); if cb <> 0 then begin ppi := ExAllocatePool(PagedPool, cb); if ppi <> nil then begin RtnCode := ZwQueryValueKey(hKey, @g_usValueName, KeyValuePartialInformation, ppi, cb, cb); if (RtnCode = STATUS_SUCCESS ) and ( cb <> 0) then begin if ppi^._Type = REG_SZ then begin RtlInitUnicodeString(us, PWideChar(@ppi^.Data)); if RtlUnicodeStringToAnsiString(@_as, @us, true) = STATUS_SUCCESS then begin DbgPrint('RegistryWorks: Registry key value is: %s'#13#10, _as.Buffer); RtlFreeAnsiString(@_as); end; end; end else begin DbgPrint('RegistryWorks: Can''t query registry key value. Status: %08X'#13#10, RtnCode); end; ExFreePool(ppi); end else begin DbgPrint('RegistryWorks: Can''t allocate memory. Status: %08X'#13#10, ppi); end; end else begin DbgPrint('RegistryWorks: Can''t get bytes count needed for key partial information. Status: %08X'#13#10, RtnCode); end; ZwClose(hKey); DbgPrint('RegistryWorks: Registry key handle closed'#13#10); end else begin DbgPrint('RegistryWorks: Can''t open registry key. Status: %08X'#13#10, RtnCode); end; end; procedure DeleteKey; var oa: OBJECT_ATTRIBUTES; hKey: THANDLE; RtnCode: NTSTATUS; begin DbgPrint(#13#10'RegistryWorks: *** Deleting registry key'#13#10); InitializeObjectAttributes(oa, @g_usMachineKeyName, OBJ_CASE_INSENSITIVE, 0, nil); RtnCode := ZwOpenKey(hKey, KEY_ALL_ACCESS, @oa); if RtnCode = STATUS_SUCCESS then begin DbgPrint('RegistryWorks: Registry key opened'#13#10); RtnCode := ZwDeleteKey(hKey); if RtnCode = STATUS_SUCCESS then begin DbgPrint('RegistryWorks: Registry key deleted'#13#10); end else begin DbgPrint('RegistryWorks: Can''t delete registry key. Status: %08X'#13#10, RtnCode); end; ZwClose(hKey); DbgPrint('RegistryWorks: Registry key handle closed'#13#10); end else begin DbgPrint('RegistryWorks: Can''t open registry key. Status: %08X'#13#10, RtnCode); end; end; procedure EnumerateKey; var oa: OBJECT_ATTRIBUTES; hKey: THANDLE; cb: DWORD; pbi: PKEY_BASIC_INFORMATION; pfi: PKEY_FULL_INFORMATION; _as: ANSI_STRING; us: UNICODE_STRING; dwSubKeys, iCnt: DWORD; pwszKeyName: PWideChar; g_usUserKeyName: UNICODE_STRING; RtnCode: NTSTATUS; lpTemp: PVOID; begin lpTemp := nil; DbgPrint(#13#10'RegistryWorks: *** Opening \Registry\User key to enumerate'#13#10); RtlInitUnicodeString(g_usUserKeyName, '\Registry\User'); InitializeObjectAttributes(oa, @g_usUserKeyName, OBJ_CASE_INSENSITIVE, 0, nil); RtnCode := ZwOpenKey(hKey, KEY_ENUMERATE_SUB_KEYS, @oa); if RtnCode = STATUS_SUCCESS then begin DbgPrint('RegistryWorks: Registry key openeded'#13#10); ZwQueryKey(hKey, KeyFullInformation, lpTemp, 0, cb); if cb <> 0 then begin pfi := ExAllocatePool(PagedPool, cb); if pfi <> nil then begin RtnCode := ZwQueryKey(hKey, KeyFullInformation, PVOID(pfi), cb, cb); if (RtnCode = STATUS_SUCCESS ) and ( cb <> 0) then begin dwSubKeys := pfi^.SubKeys; DbgPrint('RegistryWorks: ---------- Starting enumerate subkeys ----------'#13#10); iCnt := 0; while iCnt < dwSubKeys do begin ZwEnumerateKey(hKey, iCnt, KeyBasicInformation, lpTemp, 0, cb); if cb <> 0 then begin pbi := ExAllocatePool(PagedPool, cb); if pbi <> nil then begin RtnCode := ZwEnumerateKey(hKey, iCnt, KeyBasicInformation, PVOID(pbi), cb, cb); if (RtnCode = STATUS_SUCCESS ) and ( cb <> 0) then begin cb := pbi^.NameLength + sizeof(WCHAR); pwszKeyName := ExAllocatePool(PagedPool, cb); if pwszKeyName <> nil then begin memset(pwszKeyName, 0, cb); wcsncpy(pwszKeyName, PWideChar(@pbi^._Name), pbi^.NameLength div 2); RtlInitUnicodeString(us, pwszKeyName); if RtlUnicodeStringToAnsiString(@_as, @us, TRUE) = STATUS_SUCCESS then begin DbgPrint('RegistryWorks: %s'#13#10, _as.Buffer); RtlFreeAnsiString(@_as); end; ExFreePool(pwszKeyName); end; end else begin DbgPrint('RegistryWorks: Can''t enumerate registry keys. Status: %08X'#13#10, RtnCode); end; ExFreePool(pbi); end; end; inc(iCnt); end; DbgPrint('RegistryWorks: ------------------------------------------------'#13#10); end else begin DbgPrint('RegistryWorks: Can''t query registry key information. Status: %08X'#13#10, RtnCode); end; ExFreePool(pfi); end else begin DbgPrint('RegistryWorks: Can''t allocate memory. Status: %08X'#13#10, pfi); end; end; ZwClose(hKey); DbgPrint('RegistryWorks: Registry key handle closed'#13#10); end else begin DbgPrint('RegistryWorks: Can''t open registry key. Status: %08X'#13#10, RtnCode); end; end; {驱动程序进入点} function _DriverEntry(pDriverObject:PDRIVER_OBJECT; pusRegistryPath:PUNICODE_STRING): NTSTATUS; begin DbgPrint(#13#10'RegistryWorks: Entering DriverEntry'#13#10); {初始化相关unicode字符串} RtlInitUnicodeString(g_usMachineKeyName, '\Registry\Machine\Software\CoolApp'); RtlInitUnicodeString(g_usValueName, 'SomeData'); CreateKey; SetValueKey; QueryValueKey; DeleteKey; EnumerateKey; DbgPrint(#13#10'RegistryWorks: Leaving DriverEntry'#13#10); result := STATUS_DEVICE_CONFIGURATION_ERROR; end; end. |
驱动程序的代码由几个独立的函数构成:CreateKey、SetValueKey、QueryValueKey、DeleteKey和EnumerateKey,每一个都是“从零开始”操作注册表的,对于学习来说,例子总是最直观的。
8.3.1 注册表键的创建与打开
在CreateKey函数中调用了函数ZwCreateKey,建立新的Registry\Machine\Software\CoolApp键。
RtnCode := ZwCreateKey(hKey, KEY_WRITE, @oa, 0, nil,REG_OPTION_VOLATILE, dwDisposition); |
标志REG_OPTION_VOLATILE不准将建立的子键写入hive——磁盘上的一个注册表文件。这样这个子键只存在到系统下一次加载。在本例中,并不一定非要使用这个标志,我们可以自己删除所有子键。如果想在注册表中较长时间注册这个子键,就不要用这个标志了。
if RtnCode = STATUS_SUCCESS then begin if dwDisposition = REG_CREATED_NEW_KEY then begin DbgPrint('RegistryWorks: Registry key \Registry\Machine\Software\CoolApp created'#13#10); end else if dwDisposition = REG_OPENED_EXISTING_KEY then begin DbgPrint('RegistryWorks: Registry key \Registry\Machine\Software\CoolApp opened'#13#10); end; |
在成功调用ZwCreateKey后,变量dwDisposition的值定义了是否已创建新的子键(REG_CREATED_NEW_KEY),要是这个子键已在注册表中存在(REG_OPENED_EXISTING_KEY),就已被打开。
ZwOpenKey(hKey, KEY_SET_VALUE, @oa); ZwOpenKey(hKey, KEY_QUERY_VALUE, @oa); ZwOpenKey(hKey, KEY_ALL_ACCESS, @oa); ZwOpenKey(hKey, KEY_ENUMERATE_SUB_KEYS, @oa); |
在剩下的函数中,我们调用ZwOpenKey函数来打开已经存在的键,对相应的访问类型只使用相应的标志。
8.3.2 创建注册表键值
现在,我们来在我们的子键里创建一个字符串键值“SomeData”。
RtnCode := ZwSetValueKey(hKey, @g_usValueName, 0, REG_SZ,@g_wszStringData,sizeof(g_wszStringData)); |
常量REG_SZ定义了所创建键值的类型:以零结尾的unicode字符串。系统还有许多其它的类型——DDK中有详细描述。
8.3.3 访问注册表键值
我们来获取我们的SomeData的值。
RtnCode := ZwQueryValueKey(hKey, @g_usValueName,KeyValuePartialInformation,nil, 0, cb); |
函数ZwQueryValueKey的第三个参数定义了请求信息的类型。在DDK中定义了三种值:KeyValueBasicInformation、KeyValueFullInformation 和KeyValuePartialInformation,每一个值都有自己相应的结构体:KEY_VALUE_BASIC_INFORMATION、KEY_VALUE_FULL_INFORMATION和KEY_VALUE_PARTIAL_INFORMATION。在本例中,我们想获得键值的内容。对此KeyValuePartialInformation完全适合。
我们事先不知道所获取信息的大小,所以我们调用ZwQueryValueKey时,第四第五个参数分别使用nil(缓冲区指针)和0(缓冲区大小)。函数ZwQueryValueKey计并算出缓冲区所需的大小并将这个值返回到变量cb中(很多但非全部的Zw*函数都是这个样子处理的)。
if cb <> 0 then begin ppi := ExAllocatePool(PagedPool, cb); if ppi <> nil then begin RtnCode := ZwQueryValueKey(hKey, @g_usValueName,KeyValuePartialInformation,ppi, cb, cb); |
分配必需的内存空间,再次调用ZwQueryValueKey——现在已经有缓冲区指针了。
if (RtnCode = STATUS_SUCCESS ) and ( cb <> 0) then begin if ppi^._Type = REG_SZ then begin |
在任何情况下,我们都要检查键值类型。
如果是REG_SZ类型的键值,则KEY_VALUE_PARTIAL_INFORMATION结构体的Data域就包含了以零结尾的unicode字符串。我们需要在调试信息中输出这个字符串,所以要将其转换成AnsiString,函数RtlUnicodeStringToAnsiString将unicode字符串转换为ansi字符串。如果最后的参数设置为TRUE,则函数自己分配缓冲区,向那里写入转换的字符串并填充ANSI_STRING结构体(在我们这里是变量_as)。ANSI_STRING的Buffer域将指向所分配的装有转换好的ansi字符串的缓冲区。如果最后一个参数为FALSE,则用于保存ansi字符串的缓冲区就需要事先分配好并将指针放在我们的ANSI_STRING结构体的Buffer域中。在本例中,我们让RtlUnicodeStringToAnsiString函数为我们分配缓冲区。
if RtlUnicodeStringToAnsiString(@_as, @ppi^.Data, true) = STATUS_SUCCESS then begin DbgPrint('RegistryWorks: Registry key value is: %s'#13#10,_as.Buffer); RtlFreeAnsiString(@_as); end; |
8.3.4 删除注册表键
我想,这部分不用我讲您也能搞定。
8.3.5 更新注册表键
现在我们来看\Registry\User键下都有些什么。
ZwQueryKey(hKey, KeyFullInformation, lpTemp, 0, cb); if cb <> 0 then begin pfi := ExAllocatePool(PagedPool, cb); if pfi <> nil then begin RtnCode := ZwQueryKey(hKey, KeyFullInformation, PVOID(pfi),cb, cb); if (RtnCode = STATUS_SUCCESS ) and ( cb <> 0) then begin dwSubKeys := pfi^.SubKeys; |
要组织键的内容就需要知道其下子键/键值的数量。对此我们要使用KeyFullInformation信息类。分配必需的内存并将它传给ZwQueryKey,我们就在变量dwSubKeys中得到了子键/键值的数量。
iCnt := 0; while iCnt < dwSubKeys do begin ZwEnumerateKey(hKey, iCnt, KeyBasicInformation,lpTemp, 0, cb); if cb <> 0 then begin pbi := ExAllocatePool(PagedPool, cb); if pbi <> nil then begin RtnCode := ZwEnumerateKey(hKey, iCnt,KeyBasicInformation,PVOID(pbi), cb, cb); if (RtnCode = STATUS_SUCCESS ) and ( cb <> 0) then begin |
我们在一个循环中使用KeyBasicInformation信息类来调用ZwEnumerateKey。就像前面那样,我们对其调用两次,第一次是为了得知信息的大小,第二次是为了获得这项信息本身。
cb := pbi^.NameLength + sizeof(WCHAR); pwszKeyName := ExAllocatePool(PagedPool, cb); |
在KEY_BASIC_INFORMATION结构体的_Name域返回的是子键/键值的名称,其形式为unicode字符串,但是这个字符串不是以零结尾的。为了在后面将其转换为ansi字符串(为了在调试信息中输出),我们应该将其完善——为其分配一个临时缓冲区。
if pwszKeyName <> nil then begin memset(pwszKeyName, 0, cb); wcsncpy(pwszKeyName, PWideChar(@pbi^._Name), pbi^.NameLength div 2); |
将临时缓冲区清零,并向其中拷贝子键/键值的名称。
RtlInitUnicodeString(us, pwszKeyName); if RtlUnicodeStringToAnsiString(@_as, @us, TRUE) = STATUS_SUCCESS then begin DbgPrint('RegistryWorks: %s'#13#10, _as.Buffer); RtlFreeAnsiString(@_as); end; |
我们来做必要的处理并在调试信息中输出子键/键值的名称。
ExFreePool(pwszKeyName); end; end else begin DbgPrint('RegistryWorks: Can''t enumerate registry keys. Status: %08X'#13#10,RtnCode); end; ExFreePool(pbi); end; end; inc(iCnt); end; |