注册表
10.1 注册表的结构
10.2 在驱动程序中访问注册表
10.3 RegistryWorks驱动程序源代码
10.3.1 注册表键的创建与打开
10.3.2 创建注册表键值
10.3.3 访问注册表键值
10.3.4 删除注册表键
10.3.5 更新注册表键
源代码: KmdKit/examples/basic/RegistryWorks
10.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/<SID_当前用户> 系统用户中对应于当前用户的注册信息。
HKEY_CURRENT_CONFIG
链接到子键HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Hardware Profiles/Current.
HKEY_CLASSES_ROOT
链接到子键HKEY_LOCAL_MACHINE/SOFTWARE/Classes和HKEY_CURRENT_USER/SOFTWARE/Classes.
10.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这个名字。很可惜,我们不能使用CTW0和$CTW0宏来定义这个长长的unicode字符串,这个字符串超出了47个符号的限制。对此您可以用自己的方案来解决。
10.3 RegistryWorks驱动程序源代码
现在,当我们熟知根键名字的时候,理解它们就不会太困难。
;@echo off
;goto make
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;
; RegistryWorks - Пример работы с реестром
;
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.386
.model flat, stdcall
option casemap:none
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; В К Л Ю Ч А Е М Ы Е Ф А Й Л Ы
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
include /masm32/include/w2k/ntstatus.inc
include /masm32/include/w2k/ntddk.inc
include /masm32/include/w2k/ntoskrnl.inc
includelib /masm32/lib/w2k/ntoskrnl.lib
include /masm32/Macros/Strings.mac
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; Н Е И З М Е Н Я Е М Ы Е Д А Н Н Ы Е
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.const
CCOUNTED_UNICODE_STRING "//Registry//Machine//Software//CoolApp", g_usMachineKeyName, 4
CCOUNTED_UNICODE_STRING "SomeData", g_usValueName, 4
CTW0 "It's just a string", g_wszStringData, 4
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; К О Д
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.code
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; CreateKey
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
CreateKey proc
local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
local dwDisposition:DWORD
invoke DbgPrint, $CTA0("/nRegistryWorks: *** Creating registry key/n")
lea ecx, oa
InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
invoke ZwCreateKey, addr hKey, KEY_WRITE, addr oa, 0, NULL, /
REG_OPTION_VOLATILE, addr dwDisposition
.if eax == STATUS_SUCCESS
.if dwDisposition == REG_CREATED_NEW_KEY
invoke DbgPrint, /
$CTA0("RegistryWorks: Registry key //Registry//Machine//Software//CoolApp created/n")
.elseif dwDisposition == REG_OPENED_EXISTING_KEY
invoke DbgPrint, /
$CTA0("RegistryWorks: Registry key //Registry//Machine//Software//CoolApp opened/n")
.endif
invoke ZwClose, hKey
invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed/n")
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't create registry key. Status: %08X/n"), eax
.endif
ret
CreateKey endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; SetValueKey
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
SetValueKey proc
local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
invoke DbgPrint, $CTA0("/nRegistryWorks: *** Opening registry key to set new value/n")
lea ecx, oa
InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
invoke ZwOpenKey, addr hKey, KEY_SET_VALUE, ecx
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: Registry key openeded/n")
invoke ZwSetValueKey, hKey, addr g_usValueName, 0, REG_SZ, /
addr g_wszStringData, sizeof g_wszStringData
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: Registry key value added/n")
.else
invoke DbgPrint, /
$CTA0("RegistryWorks: Can't set registry key value. Status: %08X/n"), eax
.endif
invoke ZwClose, hKey
invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed/n")
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X/n"), eax
.endif
ret
SetValueKey endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; QueryValueKey
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
QueryValueKey proc
local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
local cb:DWORD
local ppi:PKEY_VALUE_PARTIAL_INFORMATION
local as:ANSI_STRING
local us:UNICODE_STRING
invoke DbgPrint, $CTA0("/nRegistryWorks: *** Opening registry key to read value/n")
lea ecx, oa
InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
invoke ZwOpenKey, addr hKey, KEY_QUERY_VALUE, ecx
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: Registry key openeded/n")
invoke ZwQueryValueKey, hKey, addr g_usValueName, /
KeyValuePartialInformation, NULL, 0, addr cb
.if cb != 0
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov ppi, eax
invoke ZwQueryValueKey, hKey, addr g_usValueName, /
KeyValuePartialInformation, ppi, cb, addr cb
.if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
mov eax, ppi
.if [KEY_VALUE_PARTIAL_INFORMATION PTR [eax]]._Type == REG_SZ
lea eax, (KEY_VALUE_PARTIAL_INFORMATION PTR [eax]).Data
invoke RtlInitUnicodeString, addr us, eax
invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
.if eax == STATUS_SUCCESS
invoke DbgPrint, /
$CTA0("RegistryWorks: Registry key value is: /=%s/=/n"), as.Buffer
invoke RtlFreeAnsiString, addr as
.endif
.endif
.else
invoke DbgPrint, /
$CTA0("RegistryWorks: Can't query registry key value. Status: %08X/n"), eax
.endif
invoke ExFreePool, ppi
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't allocate memory. Status: %08X/n"), eax
.endif
.else
invoke DbgPrint, /
$CTA0("RegistryWorks: Can't get bytes count needed for key partial information. Status: %08X/n"), eax
.endif
invoke ZwClose, hKey
invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed/n")
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X/n"), eax
.endif
ret
QueryValueKey endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; DeleteKey
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DeleteKey proc
local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
invoke DbgPrint, $CTA0("/nRegistryWorks: *** Deleting registry key/n")
lea ecx, oa
InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
invoke ZwOpenKey, addr hKey, KEY_ALL_ACCESS, ecx
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: Registry key opened/n")
invoke ZwDeleteKey, hKey
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: Registry key deleted/n")
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't delete registry key. Status: %08X/n"), eax
.endif
invoke ZwClose, hKey
invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed/n")
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X/n"), eax
.endif
ret
DeleteKey endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; EnumerateKey
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
EnumerateKey proc
local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
local cb:DWORD
local pbi:PKEY_BASIC_INFORMATION
local pfi:PKEY_FULL_INFORMATION
local as:ANSI_STRING
local us:UNICODE_STRING
local dwSubKeys:DWORD
local pwszKeyName:PWCHAR
invoke DbgPrint, $CTA0("/nRegistryWorks: *** Opening //Registry//User key to enumerate/n")
CCOUNTED_UNICODE_STRING "//Registry//User", g_usUserKeyName, 4
lea ecx, oa
InitializeObjectAttributes ecx, offset g_usUserKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
invoke ZwOpenKey, addr hKey, KEY_ENUMERATE_SUB_KEYS, ecx
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: Registry key openeded/n")
invoke ZwQueryKey, hKey, KeyFullInformation, NULL, 0, addr cb
.if cb != 0
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov pfi, eax
invoke ZwQueryKey, hKey, KeyFullInformation, pfi, cb, addr cb
.if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
mov eax, pfi
push (KEY_FULL_INFORMATION PTR [eax]).SubKeys
pop dwSubKeys
invoke DbgPrint, /
$CTA0("RegistryWorks: ---------- Starting enumerate subkeys ----------/n")
push ebx
xor ebx, ebx
.while ebx < dwSubKeys
invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, NULL, 0, addr cb
.if cb != 0
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov pbi, eax
invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, pbi, cb, addr cb
.if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
mov eax, pbi
mov eax, (KEY_BASIC_INFORMATION PTR [eax]).NameLength
add eax, sizeof WCHAR
mov cb, eax
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov pwszKeyName, eax
invoke memset, pwszKeyName, 0, cb
mov ecx, pbi
mov eax, (KEY_BASIC_INFORMATION PTR [ecx]).NameLength
shr eax, 1
lea ecx, (KEY_BASIC_INFORMATION PTR [ecx])._Name
invoke wcsncpy, pwszKeyName, ecx, eax
invoke RtlInitUnicodeString, addr us, pwszKeyName
invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: /=%s/=/n"), as.Buffer
invoke RtlFreeAnsiString, addr as
.endif
invoke ExFreePool, pwszKeyName
.endif
.else
invoke DbgPrint, /
$CTA0("RegistryWorks: Can't enumerate registry keys. Status: %08X/n"), eax
.endif
invoke ExFreePool, pbi
.endif
.endif
inc ebx
.endw
pop ebx
invoke DbgPrint, /
$CTA0("RegistryWorks: ------------------------------------------------/n")
.else
invoke DbgPrint, /
$CTA0("RegistryWorks: Can't query registry key information. Status: %08X/n"), eax
.endif
invoke ExFreePool, pfi
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't allocate memory. Status: %08X/n"), eax
.endif
.endif
invoke ZwClose, hKey
invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed/n")
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X/n"), eax
.endif
ret
EnumerateKey endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; DriverEntry
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
invoke DbgPrint, $CTA0("/nRegistryWorks: Entering DriverEntry/n")
;:::::::::::::::::::::::::::::::::::::::
; Создаём новый подраздел ;
;:::::::::::::::::::::::::::::::::::::::
invoke CreateKey
;:::::::::::::::::::::::::::::::::::::::
; Создаем в этом подразделе параметр ;
;:::::::::::::::::::::::::::::::::::::::
invoke SetValueKey
;:::::::::::::::::::::::::::::::::::::::
; Получаем значение параметра ;
;:::::::::::::::::::::::::::::::::::::::
invoke QueryValueKey
;:::::::::::::::::::::::::::::::::::::::
; Удаляем подраздел ;
;:::::::::::::::::::::::::::::::::::::::
invoke DeleteKey
;:::::::::::::::::::::::::::::::::::::::
; Перечисляем содержимое раздела ;
;:::::::::::::::::::::::::::::::::::::::
invoke EnumerateKey
invoke DbgPrint, $CTA0("/nRegistryWorks: Leaving DriverEntry/n")
mov eax, STATUS_DEVICE_CONFIGURATION_ERROR
ret
DriverEntry endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
end DriverEntry
:make
set drv=RegistryWorks
/masm32/bin/ml /nologo /c /coff %drv%.bat
/masm32/bin/link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native %drv%.obj
del %drv%.obj
echo.
pause
驱动程序的代码由几个独立的函数构成:CreateKey、SetValueKey、QueryValueKey、DeleteKey和EnumerateKey,每一个都是“从零开始”操作注册表的,对于学习来说,例子总是最直观的。
10.3.1 注册表键的创建与打开
在CreateKey函数中调用了函数ZwCreateKey,建立新的Registry/Machine/Software/CoolApp键。
invoke ZwCreateKey, addr hKey, KEY_WRITE, addr oa, 0, NULL, REG_OPTION_VOLATILE, addr dwDisposition
标志REG_OPTION_VOLATILE不准将建立的子键写入hive——磁盘上的一个注册表文件。这样这个子键只存在到系统下一次加载。在本例中,并不一定非要使用这个标志,我们可以自己删除所有子键。如果想在注册表中较长时间注册这个子键,就不要用这个标志了。
.if eax == STATUS_SUCCESS
.if dwDisposition == REG_CREATED_NEW_KEY
.elseif dwDisposition == REG_OPENED_EXISTING_KEY
.endif
在成功调用ZwCreateKey后,变量dwDisposition的值定义了是否已创建新的子键(REG_CREATED_NEW_KEY),要是这个子键已在注册表中存在(REG_OPENED_EXISTING_KEY),就已被打开。
invoke ZwOpenKey, addr hKey, KEY_SET_VALUE, ecx
invoke ZwOpenKey, addr hKey, KEY_QUERY_VALUE, ecx
invoke ZwOpenKey, addr hKey, KEY_ALL_ACCESS, ecx
invoke ZwOpenKey, addr hKey, KEY_ENUMERATE_SUB_KEYS, ecx
在剩下的函数中,我们调用ZwOpenKey函数来打开已经存在的键,对相应的访问类型只使用相应的标志。
10.3.2 创建注册表键值
现在,我们来在我们的子键里创建一个字符串键值“SomeData”。
. . .
CCOUNTED_UNICODE_STRING "SomeData", g_usValueName, 4
CTW0 "It's just a string", g_wszStringData, 4
. . .
invoke ZwSetValueKey, hKey, addr g_usValueName, 0, REG_SZ, /
addr g_wszStringData, sizeof g_wszStringData
常量REG_SZ定义了所创建键值的类型:以零结尾的unicode字符串。系统还有许多其它的类型——DDK中有详细描述。
10.3.3 访问注册表键值
我们来获取我们的SomeData的值。
invoke ZwQueryValueKey, hKey, addr g_usValueName, /
KeyValuePartialInformation, NULL, 0, addr cb
函数ZwQueryValueKey的第三个参数定义了请求信息的类型。在DDK中定义了三种值:KeyValueBasicInformation、KeyValueFullInformation 和KeyValuePartialInformation,每一个值都有自己相应的结构体:KEY_VALUE_BASIC_INFORMATION、KEY_VALUE_FULL_INFORMATION和KEY_VALUE_PARTIAL_INFORMATION。在本例中,我们想获得键值的内容。对此KeyValuePartialInformation完全适合。
我们事先不知道所获取信息的大小,所以我们调用ZwQueryValueKey时,第四第五个参数分别使用NULL(缓冲区指针)和0(缓冲区大小)。函数ZwQueryValueKey计并算出缓冲区所需的大小并将这个值返回到变量cb中(很多但非全部的Zw*函数都是这个样子处理的)。
.if cb != 0
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov ppi, eax
invoke ZwQueryValueKey, hKey, addr g_usValueName, /
KeyValuePartialInformation, ppi, cb, addr cb
分配必需的内存空间,再次调用ZwQueryValueKey——现在已经有缓冲区指针了。
.if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
mov eax, ppi
.if [KEY_VALUE_PARTIAL_INFORMATION PTR [eax]]._Type == REG_SZ
在任何情况下,我们都要检查键值类型。
lea eax, (KEY_VALUE_PARTIAL_INFORMATION PTR [eax]).Data
如果是REG_SZ类型的键值,则KEY_VALUE_PARTIAL_INFORMATION结构体的Data域就包含了以零结尾的unicode字符串。我们需要在调试信息中输出这个字符串,所以要将它转换为ansi字符串。
invoke RtlInitUnicodeString, addr us, eax
函数RtlInitUnicodeString初始化unicode字符串,其地址在第二个参数中,并填充UNICODE_STRING结构体,其地址在第一个参数中。
invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
函数RtlUnicodeStringToAnsiString将unicode字符串转换为ansi字符串。如果最后的参数设置为TRUE,则函数自己分配缓冲区,向那里写入转换的字符串并填充ANSI_STRING结构体(在我们这里是变量as)。ANSI_STRING的Buffer域将指向所分配的装有转换好的ansi字符串的缓冲区。如果最后一个参数为FALSE,则用于保存ansi字符串的缓冲区就需要事先分配好并将指针放在我们的ANSI_STRING结构体的Buffer域中。在本例中,我们让RtlUnicodeStringToAnsiString函数为我们分配缓冲区。
.if eax == STATUS_SUCCESS
invoke DbgPrint, /
$CTA0("RegistryWorks: Registry key value is: /=%s/=/n"), as.Buffer
invoke RtlFreeAnsiString, addr as
.endif
DDK描述了RtlUnicodeStringToAnsiString函数,而我却昏了头,说得模模糊糊的。IFS DDK更好的描述了这个函数的操作。为了处理“i上面的一点”我们来看个简单的例子。
wsz db 'a', 0, 'b', 0, 'c', 0, 0, 0
us UNICODE_STRING <>
as ANSI_STRING <>
变量us和as最初没有定义。wsz为unicode字符串,要被转换为ANSI格式。
us._Length = ?
us.MaximumLength = ?
us.Buffer = ?
as._Length = ?
as.MaximumLength = ?
as.Buffer = ?
RtlInitUnicodeString 用字符串wsz的大小填充变量us。
invoke RtlInitUnicodeString, addr us, addr wsz
us._Length = 6
us.MaximumLength = 8
us.Buffer = offset wsz
as._Length = ?
as.MaximumLength = ?
as.Buffer = ?
从RtlUnicodeStringToAnsiString的最后一个参数可以看到,它分配的缓冲区大小应该为us.MaximumLength / sizeof WCHAR。分配缓冲区并将指针放在域as.Buffer中后,函数RtlUnicodeStringToAnsiString开始真正转换字符串。如果这个操作成功完成,则变量as将包含转换好的字符串的完整的描述。
invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
us._Length = 6
us.MaximumLength = 8
us.Buffer = offset wsz
as._Length = 3
as.MaximumLength = 4
as.Buffer = -> 'a', 'b', 'c', 0 ; 指向RtlUnicodeStringToAnsiString,,,TRUE函数分配的缓冲区的指针
; 缓冲区包含有转换为ANCI格式的wsz字符串的
在使用了RtlUnicodeStringToAnsiString函数分配的缓冲区之后,还需要调用RtlFreeAnsiString释放掉它。而且充当其参数的不是指向函数自己缓冲区的指针,而是指向ANSI_STRING结构体的指针。
RtlFreeAnsiString释放掉缓冲区as.Buffer并清零变量as。
invoke RtlFreeAnsiString, addr as
us._Length = 6
us.MaximumLength = 8
us.Buffer = offset wsz
as._Length = 0
as.MaximumLength = 0
as.Buffer = NULL
现在应该全明白了吧。
10.3.4 删除注册表键
我想,这部分不用我讲您也能搞定。
10.3.5 更新注册表键
现在我们来看/Registry/User键下都有些什么。
invoke ZwQueryKey, hKey, KeyFullInformation, NULL, 0, addr cb
.if cb != 0
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov pfi, eax
invoke ZwQueryKey, hKey, KeyFullInformation, pfi, cb, addr cb
.if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
mov eax, pfi
push (KEY_FULL_INFORMATION PTR [eax]).SubKeys
pop dwSubKeys
要组织键的内容就需要知道其下子键/键值的数量。对此我们要使用KeyFullInformation信息类。分配必需的内存并将它传给ZwQueryKey,我们就在变量dwSubKeys中得到了子键/键值的数量。
push ebx
xor ebx, ebx
.while ebx < dwSubKeys
invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, NULL, 0, addr cb
.if cb != 0
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov pbi, eax
invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, pbi, cb, addr cb
.if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
我们在一个循环中使用KeyBasicInformation信息类来调用ZwEnumerateKey。就像前面那样,我们对其调用两次,第一次是为了得知信息的大小,第二次是为了获得这项信息本身。
mov eax, pbi
mov eax, (KEY_BASIC_INFORMATION PTR [eax]).NameLength
add eax, sizeof WCHAR
mov cb, eax
invoke ExAllocatePool, PagedPool, cb
在KEY_BASIC_INFORMATION结构体的_Name域返回的是子键/键值的名称,其形式为unicode字符串,但是这个字符串不是以零结尾的。为了在后面将其转换为ansi字符串(为了在调试信息中输出),我们应该将其完善——为其分配一个临时缓冲区。
.if eax != NULL
mov pwszKeyName, eax
invoke memset, pwszKeyName, 0, cb
mov ecx, pbi
mov eax, (KEY_BASIC_INFORMATION PTR [ecx]).NameLength
shr eax, 1
lea ecx, (KEY_BASIC_INFORMATION PTR [ecx])._Name
invoke wcsncpy, pwszKeyName, ecx, eax
将临时缓冲区清零,并向其中拷贝子键/键值的名称。
invoke RtlInitUnicodeString, addr us, pwszKeyName
invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: /=%s/=/n"), as.Buffer
invoke RtlFreeAnsiString, addr as
.endif
我们来做必要的处理并在调试信息中输出子键/键值的名称。
invoke ExFreePool, pwszKeyName
.endif
.endif
invoke ExFreePool, pbi
.endif
.endif
inc ebx
.endw
pop ebx
.endif
invoke ExFreePool, pfi
.endif
.endif
进行必要的资源回收。
10.1 注册表的结构
10.2 在驱动程序中访问注册表
10.3 RegistryWorks驱动程序源代码
10.3.1 注册表键的创建与打开
10.3.2 创建注册表键值
10.3.3 访问注册表键值
10.3.4 删除注册表键
10.3.5 更新注册表键
源代码: KmdKit/examples/basic/RegistryWorks
10.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/<SID_当前用户> 系统用户中对应于当前用户的注册信息。
HKEY_CURRENT_CONFIG
链接到子键HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Hardware Profiles/Current.
HKEY_CLASSES_ROOT
链接到子键HKEY_LOCAL_MACHINE/SOFTWARE/Classes和HKEY_CURRENT_USER/SOFTWARE/Classes.
10.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这个名字。很可惜,我们不能使用CTW0和$CTW0宏来定义这个长长的unicode字符串,这个字符串超出了47个符号的限制。对此您可以用自己的方案来解决。
10.3 RegistryWorks驱动程序源代码
现在,当我们熟知根键名字的时候,理解它们就不会太困难。
;@echo off
;goto make
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;
; RegistryWorks - Пример работы с реестром
;
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.386
.model flat, stdcall
option casemap:none
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; В К Л Ю Ч А Е М Ы Е Ф А Й Л Ы
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
include /masm32/include/w2k/ntstatus.inc
include /masm32/include/w2k/ntddk.inc
include /masm32/include/w2k/ntoskrnl.inc
includelib /masm32/lib/w2k/ntoskrnl.lib
include /masm32/Macros/Strings.mac
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; Н Е И З М Е Н Я Е М Ы Е Д А Н Н Ы Е
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.const
CCOUNTED_UNICODE_STRING "//Registry//Machine//Software//CoolApp", g_usMachineKeyName, 4
CCOUNTED_UNICODE_STRING "SomeData", g_usValueName, 4
CTW0 "It's just a string", g_wszStringData, 4
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; К О Д
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.code
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; CreateKey
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
CreateKey proc
local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
local dwDisposition:DWORD
invoke DbgPrint, $CTA0("/nRegistryWorks: *** Creating registry key/n")
lea ecx, oa
InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
invoke ZwCreateKey, addr hKey, KEY_WRITE, addr oa, 0, NULL, /
REG_OPTION_VOLATILE, addr dwDisposition
.if eax == STATUS_SUCCESS
.if dwDisposition == REG_CREATED_NEW_KEY
invoke DbgPrint, /
$CTA0("RegistryWorks: Registry key //Registry//Machine//Software//CoolApp created/n")
.elseif dwDisposition == REG_OPENED_EXISTING_KEY
invoke DbgPrint, /
$CTA0("RegistryWorks: Registry key //Registry//Machine//Software//CoolApp opened/n")
.endif
invoke ZwClose, hKey
invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed/n")
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't create registry key. Status: %08X/n"), eax
.endif
ret
CreateKey endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; SetValueKey
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
SetValueKey proc
local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
invoke DbgPrint, $CTA0("/nRegistryWorks: *** Opening registry key to set new value/n")
lea ecx, oa
InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
invoke ZwOpenKey, addr hKey, KEY_SET_VALUE, ecx
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: Registry key openeded/n")
invoke ZwSetValueKey, hKey, addr g_usValueName, 0, REG_SZ, /
addr g_wszStringData, sizeof g_wszStringData
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: Registry key value added/n")
.else
invoke DbgPrint, /
$CTA0("RegistryWorks: Can't set registry key value. Status: %08X/n"), eax
.endif
invoke ZwClose, hKey
invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed/n")
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X/n"), eax
.endif
ret
SetValueKey endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; QueryValueKey
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
QueryValueKey proc
local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
local cb:DWORD
local ppi:PKEY_VALUE_PARTIAL_INFORMATION
local as:ANSI_STRING
local us:UNICODE_STRING
invoke DbgPrint, $CTA0("/nRegistryWorks: *** Opening registry key to read value/n")
lea ecx, oa
InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
invoke ZwOpenKey, addr hKey, KEY_QUERY_VALUE, ecx
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: Registry key openeded/n")
invoke ZwQueryValueKey, hKey, addr g_usValueName, /
KeyValuePartialInformation, NULL, 0, addr cb
.if cb != 0
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov ppi, eax
invoke ZwQueryValueKey, hKey, addr g_usValueName, /
KeyValuePartialInformation, ppi, cb, addr cb
.if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
mov eax, ppi
.if [KEY_VALUE_PARTIAL_INFORMATION PTR [eax]]._Type == REG_SZ
lea eax, (KEY_VALUE_PARTIAL_INFORMATION PTR [eax]).Data
invoke RtlInitUnicodeString, addr us, eax
invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
.if eax == STATUS_SUCCESS
invoke DbgPrint, /
$CTA0("RegistryWorks: Registry key value is: /=%s/=/n"), as.Buffer
invoke RtlFreeAnsiString, addr as
.endif
.endif
.else
invoke DbgPrint, /
$CTA0("RegistryWorks: Can't query registry key value. Status: %08X/n"), eax
.endif
invoke ExFreePool, ppi
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't allocate memory. Status: %08X/n"), eax
.endif
.else
invoke DbgPrint, /
$CTA0("RegistryWorks: Can't get bytes count needed for key partial information. Status: %08X/n"), eax
.endif
invoke ZwClose, hKey
invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed/n")
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X/n"), eax
.endif
ret
QueryValueKey endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; DeleteKey
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DeleteKey proc
local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
invoke DbgPrint, $CTA0("/nRegistryWorks: *** Deleting registry key/n")
lea ecx, oa
InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
invoke ZwOpenKey, addr hKey, KEY_ALL_ACCESS, ecx
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: Registry key opened/n")
invoke ZwDeleteKey, hKey
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: Registry key deleted/n")
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't delete registry key. Status: %08X/n"), eax
.endif
invoke ZwClose, hKey
invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed/n")
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X/n"), eax
.endif
ret
DeleteKey endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; EnumerateKey
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
EnumerateKey proc
local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
local cb:DWORD
local pbi:PKEY_BASIC_INFORMATION
local pfi:PKEY_FULL_INFORMATION
local as:ANSI_STRING
local us:UNICODE_STRING
local dwSubKeys:DWORD
local pwszKeyName:PWCHAR
invoke DbgPrint, $CTA0("/nRegistryWorks: *** Opening //Registry//User key to enumerate/n")
CCOUNTED_UNICODE_STRING "//Registry//User", g_usUserKeyName, 4
lea ecx, oa
InitializeObjectAttributes ecx, offset g_usUserKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
invoke ZwOpenKey, addr hKey, KEY_ENUMERATE_SUB_KEYS, ecx
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: Registry key openeded/n")
invoke ZwQueryKey, hKey, KeyFullInformation, NULL, 0, addr cb
.if cb != 0
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov pfi, eax
invoke ZwQueryKey, hKey, KeyFullInformation, pfi, cb, addr cb
.if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
mov eax, pfi
push (KEY_FULL_INFORMATION PTR [eax]).SubKeys
pop dwSubKeys
invoke DbgPrint, /
$CTA0("RegistryWorks: ---------- Starting enumerate subkeys ----------/n")
push ebx
xor ebx, ebx
.while ebx < dwSubKeys
invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, NULL, 0, addr cb
.if cb != 0
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov pbi, eax
invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, pbi, cb, addr cb
.if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
mov eax, pbi
mov eax, (KEY_BASIC_INFORMATION PTR [eax]).NameLength
add eax, sizeof WCHAR
mov cb, eax
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov pwszKeyName, eax
invoke memset, pwszKeyName, 0, cb
mov ecx, pbi
mov eax, (KEY_BASIC_INFORMATION PTR [ecx]).NameLength
shr eax, 1
lea ecx, (KEY_BASIC_INFORMATION PTR [ecx])._Name
invoke wcsncpy, pwszKeyName, ecx, eax
invoke RtlInitUnicodeString, addr us, pwszKeyName
invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: /=%s/=/n"), as.Buffer
invoke RtlFreeAnsiString, addr as
.endif
invoke ExFreePool, pwszKeyName
.endif
.else
invoke DbgPrint, /
$CTA0("RegistryWorks: Can't enumerate registry keys. Status: %08X/n"), eax
.endif
invoke ExFreePool, pbi
.endif
.endif
inc ebx
.endw
pop ebx
invoke DbgPrint, /
$CTA0("RegistryWorks: ------------------------------------------------/n")
.else
invoke DbgPrint, /
$CTA0("RegistryWorks: Can't query registry key information. Status: %08X/n"), eax
.endif
invoke ExFreePool, pfi
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't allocate memory. Status: %08X/n"), eax
.endif
.endif
invoke ZwClose, hKey
invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed/n")
.else
invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X/n"), eax
.endif
ret
EnumerateKey endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; DriverEntry
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
invoke DbgPrint, $CTA0("/nRegistryWorks: Entering DriverEntry/n")
;:::::::::::::::::::::::::::::::::::::::
; Создаём новый подраздел ;
;:::::::::::::::::::::::::::::::::::::::
invoke CreateKey
;:::::::::::::::::::::::::::::::::::::::
; Создаем в этом подразделе параметр ;
;:::::::::::::::::::::::::::::::::::::::
invoke SetValueKey
;:::::::::::::::::::::::::::::::::::::::
; Получаем значение параметра ;
;:::::::::::::::::::::::::::::::::::::::
invoke QueryValueKey
;:::::::::::::::::::::::::::::::::::::::
; Удаляем подраздел ;
;:::::::::::::::::::::::::::::::::::::::
invoke DeleteKey
;:::::::::::::::::::::::::::::::::::::::
; Перечисляем содержимое раздела ;
;:::::::::::::::::::::::::::::::::::::::
invoke EnumerateKey
invoke DbgPrint, $CTA0("/nRegistryWorks: Leaving DriverEntry/n")
mov eax, STATUS_DEVICE_CONFIGURATION_ERROR
ret
DriverEntry endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
end DriverEntry
:make
set drv=RegistryWorks
/masm32/bin/ml /nologo /c /coff %drv%.bat
/masm32/bin/link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native %drv%.obj
del %drv%.obj
echo.
pause
驱动程序的代码由几个独立的函数构成:CreateKey、SetValueKey、QueryValueKey、DeleteKey和EnumerateKey,每一个都是“从零开始”操作注册表的,对于学习来说,例子总是最直观的。
10.3.1 注册表键的创建与打开
在CreateKey函数中调用了函数ZwCreateKey,建立新的Registry/Machine/Software/CoolApp键。
invoke ZwCreateKey, addr hKey, KEY_WRITE, addr oa, 0, NULL, REG_OPTION_VOLATILE, addr dwDisposition
标志REG_OPTION_VOLATILE不准将建立的子键写入hive——磁盘上的一个注册表文件。这样这个子键只存在到系统下一次加载。在本例中,并不一定非要使用这个标志,我们可以自己删除所有子键。如果想在注册表中较长时间注册这个子键,就不要用这个标志了。
.if eax == STATUS_SUCCESS
.if dwDisposition == REG_CREATED_NEW_KEY
.elseif dwDisposition == REG_OPENED_EXISTING_KEY
.endif
在成功调用ZwCreateKey后,变量dwDisposition的值定义了是否已创建新的子键(REG_CREATED_NEW_KEY),要是这个子键已在注册表中存在(REG_OPENED_EXISTING_KEY),就已被打开。
invoke ZwOpenKey, addr hKey, KEY_SET_VALUE, ecx
invoke ZwOpenKey, addr hKey, KEY_QUERY_VALUE, ecx
invoke ZwOpenKey, addr hKey, KEY_ALL_ACCESS, ecx
invoke ZwOpenKey, addr hKey, KEY_ENUMERATE_SUB_KEYS, ecx
在剩下的函数中,我们调用ZwOpenKey函数来打开已经存在的键,对相应的访问类型只使用相应的标志。
10.3.2 创建注册表键值
现在,我们来在我们的子键里创建一个字符串键值“SomeData”。
. . .
CCOUNTED_UNICODE_STRING "SomeData", g_usValueName, 4
CTW0 "It's just a string", g_wszStringData, 4
. . .
invoke ZwSetValueKey, hKey, addr g_usValueName, 0, REG_SZ, /
addr g_wszStringData, sizeof g_wszStringData
常量REG_SZ定义了所创建键值的类型:以零结尾的unicode字符串。系统还有许多其它的类型——DDK中有详细描述。
10.3.3 访问注册表键值
我们来获取我们的SomeData的值。
invoke ZwQueryValueKey, hKey, addr g_usValueName, /
KeyValuePartialInformation, NULL, 0, addr cb
函数ZwQueryValueKey的第三个参数定义了请求信息的类型。在DDK中定义了三种值:KeyValueBasicInformation、KeyValueFullInformation 和KeyValuePartialInformation,每一个值都有自己相应的结构体:KEY_VALUE_BASIC_INFORMATION、KEY_VALUE_FULL_INFORMATION和KEY_VALUE_PARTIAL_INFORMATION。在本例中,我们想获得键值的内容。对此KeyValuePartialInformation完全适合。
我们事先不知道所获取信息的大小,所以我们调用ZwQueryValueKey时,第四第五个参数分别使用NULL(缓冲区指针)和0(缓冲区大小)。函数ZwQueryValueKey计并算出缓冲区所需的大小并将这个值返回到变量cb中(很多但非全部的Zw*函数都是这个样子处理的)。
.if cb != 0
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov ppi, eax
invoke ZwQueryValueKey, hKey, addr g_usValueName, /
KeyValuePartialInformation, ppi, cb, addr cb
分配必需的内存空间,再次调用ZwQueryValueKey——现在已经有缓冲区指针了。
.if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
mov eax, ppi
.if [KEY_VALUE_PARTIAL_INFORMATION PTR [eax]]._Type == REG_SZ
在任何情况下,我们都要检查键值类型。
lea eax, (KEY_VALUE_PARTIAL_INFORMATION PTR [eax]).Data
如果是REG_SZ类型的键值,则KEY_VALUE_PARTIAL_INFORMATION结构体的Data域就包含了以零结尾的unicode字符串。我们需要在调试信息中输出这个字符串,所以要将它转换为ansi字符串。
invoke RtlInitUnicodeString, addr us, eax
函数RtlInitUnicodeString初始化unicode字符串,其地址在第二个参数中,并填充UNICODE_STRING结构体,其地址在第一个参数中。
invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
函数RtlUnicodeStringToAnsiString将unicode字符串转换为ansi字符串。如果最后的参数设置为TRUE,则函数自己分配缓冲区,向那里写入转换的字符串并填充ANSI_STRING结构体(在我们这里是变量as)。ANSI_STRING的Buffer域将指向所分配的装有转换好的ansi字符串的缓冲区。如果最后一个参数为FALSE,则用于保存ansi字符串的缓冲区就需要事先分配好并将指针放在我们的ANSI_STRING结构体的Buffer域中。在本例中,我们让RtlUnicodeStringToAnsiString函数为我们分配缓冲区。
.if eax == STATUS_SUCCESS
invoke DbgPrint, /
$CTA0("RegistryWorks: Registry key value is: /=%s/=/n"), as.Buffer
invoke RtlFreeAnsiString, addr as
.endif
DDK描述了RtlUnicodeStringToAnsiString函数,而我却昏了头,说得模模糊糊的。IFS DDK更好的描述了这个函数的操作。为了处理“i上面的一点”我们来看个简单的例子。
wsz db 'a', 0, 'b', 0, 'c', 0, 0, 0
us UNICODE_STRING <>
as ANSI_STRING <>
变量us和as最初没有定义。wsz为unicode字符串,要被转换为ANSI格式。
us._Length = ?
us.MaximumLength = ?
us.Buffer = ?
as._Length = ?
as.MaximumLength = ?
as.Buffer = ?
RtlInitUnicodeString 用字符串wsz的大小填充变量us。
invoke RtlInitUnicodeString, addr us, addr wsz
us._Length = 6
us.MaximumLength = 8
us.Buffer = offset wsz
as._Length = ?
as.MaximumLength = ?
as.Buffer = ?
从RtlUnicodeStringToAnsiString的最后一个参数可以看到,它分配的缓冲区大小应该为us.MaximumLength / sizeof WCHAR。分配缓冲区并将指针放在域as.Buffer中后,函数RtlUnicodeStringToAnsiString开始真正转换字符串。如果这个操作成功完成,则变量as将包含转换好的字符串的完整的描述。
invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
us._Length = 6
us.MaximumLength = 8
us.Buffer = offset wsz
as._Length = 3
as.MaximumLength = 4
as.Buffer = -> 'a', 'b', 'c', 0 ; 指向RtlUnicodeStringToAnsiString,,,TRUE函数分配的缓冲区的指针
; 缓冲区包含有转换为ANCI格式的wsz字符串的
在使用了RtlUnicodeStringToAnsiString函数分配的缓冲区之后,还需要调用RtlFreeAnsiString释放掉它。而且充当其参数的不是指向函数自己缓冲区的指针,而是指向ANSI_STRING结构体的指针。
RtlFreeAnsiString释放掉缓冲区as.Buffer并清零变量as。
invoke RtlFreeAnsiString, addr as
us._Length = 6
us.MaximumLength = 8
us.Buffer = offset wsz
as._Length = 0
as.MaximumLength = 0
as.Buffer = NULL
现在应该全明白了吧。
10.3.4 删除注册表键
我想,这部分不用我讲您也能搞定。
10.3.5 更新注册表键
现在我们来看/Registry/User键下都有些什么。
invoke ZwQueryKey, hKey, KeyFullInformation, NULL, 0, addr cb
.if cb != 0
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov pfi, eax
invoke ZwQueryKey, hKey, KeyFullInformation, pfi, cb, addr cb
.if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
mov eax, pfi
push (KEY_FULL_INFORMATION PTR [eax]).SubKeys
pop dwSubKeys
要组织键的内容就需要知道其下子键/键值的数量。对此我们要使用KeyFullInformation信息类。分配必需的内存并将它传给ZwQueryKey,我们就在变量dwSubKeys中得到了子键/键值的数量。
push ebx
xor ebx, ebx
.while ebx < dwSubKeys
invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, NULL, 0, addr cb
.if cb != 0
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
mov pbi, eax
invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, pbi, cb, addr cb
.if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
我们在一个循环中使用KeyBasicInformation信息类来调用ZwEnumerateKey。就像前面那样,我们对其调用两次,第一次是为了得知信息的大小,第二次是为了获得这项信息本身。
mov eax, pbi
mov eax, (KEY_BASIC_INFORMATION PTR [eax]).NameLength
add eax, sizeof WCHAR
mov cb, eax
invoke ExAllocatePool, PagedPool, cb
在KEY_BASIC_INFORMATION结构体的_Name域返回的是子键/键值的名称,其形式为unicode字符串,但是这个字符串不是以零结尾的。为了在后面将其转换为ansi字符串(为了在调试信息中输出),我们应该将其完善——为其分配一个临时缓冲区。
.if eax != NULL
mov pwszKeyName, eax
invoke memset, pwszKeyName, 0, cb
mov ecx, pbi
mov eax, (KEY_BASIC_INFORMATION PTR [ecx]).NameLength
shr eax, 1
lea ecx, (KEY_BASIC_INFORMATION PTR [ecx])._Name
invoke wcsncpy, pwszKeyName, ecx, eax
将临时缓冲区清零,并向其中拷贝子键/键值的名称。
invoke RtlInitUnicodeString, addr us, pwszKeyName
invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
.if eax == STATUS_SUCCESS
invoke DbgPrint, $CTA0("RegistryWorks: /=%s/=/n"), as.Buffer
invoke RtlFreeAnsiString, addr as
.endif
我们来做必要的处理并在调试信息中输出子键/键值的名称。
invoke ExFreePool, pwszKeyName
.endif
.endif
invoke ExFreePool, pbi
.endif
.endif
inc ebx
.endw
pop ebx
.endif
invoke ExFreePool, pfi
.endif
.endif
进行必要的资源回收。
本文详细介绍Windows注册表结构及其在驱动程序中的访问方法。包括创建、读取、更新和删除注册表键的过程,以及如何枚举注册表键。通过具体实例展示如何使用Zw* API函数进行注册表操作。
3397

被折叠的 条评论
为什么被折叠?



