注册表的打开与关闭

注册表是一个巨大的树形结构。操作一般都是打开某个子键。子键下有若干个值可以获得。每一个值有一个名字。值有不同的类型。一般需要查询才能获得其类型。

应用程序编程:需要提供一个根子键的句柄。

驱动:全部用路径表示。

应用编程中对应的子键 驱动编程中的路径写法:

HKEY_LOCAL_MACHINE \Registry\Machine
HKEY_USERS \Registry\User
HKEY_CLASSES_ROOT 没有对应的路径
HKEY_CURRENT_USER 没有简单的对应路径,但是可以求得

实际上应用程序和驱动程序很大的一个不同在于应用程序总是由某个“当前用户”启动的。因此可以直接读取HKEY_CLASSES_ROOT和HKEY_CURRENT_USER。而驱动程序和用户无关,所以直接去打开HKEY_CURRENT_USER也就不符合逻辑了。

打开注册表键使用函数ZwOpenKey。新建或者打开则使用ZwCreateKey。一般在驱动编程中,使用ZwOpenKey的情况比较多见。

ZwOpenKey 函数

ZwOpenKey 函数(打开注册表键),函数原型如下:

NTSTATUS ZwOpenKey(
PHANDLE KeyHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes
);

这个函数和ZwCreateFile是类似的。它并不接受直接传入一个字符串来表示一个子键。而是要求输入一个OBJECT_ATTRIBUTES的指针。

KeyHandle 参数是一个PAHDNLE类型,即句柄的指针,这个参数是一个返回参数,ZwOpenKey成功打开或创建注册表键后,KeyHandle保存注册表键的句柄。

DesiredAccess 参数表示权限,这个权限值需要根据后面具体的操作来决定,具体的参数可以查看下表。

  • KEY_QUERY_VALUE:读取键下的值。
  • KEY_SET_VALUE:设置键下的值。
  • KEY_CREATE_SUB_KEY:生成子键。
  • KEY_ENUMERATE_SUB_KEYS:枚举子键。

实际上可以用 KEY_READ 来做为通用的读权限组合。这是一个组合宏。此外对应的有KEY_WRITE。

如果需要获得全部的权限,可以使用KEY_ALL_ACCESS。

下面的例子是 读取注册表中保存的Windows 系统目录(Windows 目录)的位置,只打开子键,不读取值。

Windows目录的位置被称为SystemRoot,这一值保存在注册表中,路径是“HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion”。当然,请注意注意在驱动编程中的写法有所不同。下面的代码初始化一个OBJECT_ATTRIBUTES。

HANDLE my_key = NULL;
NTSTATUS status;
// 定义要获取的路径
UNICODE_STRING my_key_path =
RTL_CONSTANT_STRING(L” \\ Registry\\Machine\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion”);
OBJECT_ATTRIBUTE my_obj_attr = { 0 };

// 初始化OBJECT_ATTRIBUTE
InitializeObjectAttributes(
&my_obj_attr,
&my_key_path,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
// 接下来是打开Key
status = ZwOpenKey(&my_key,KEY_READ,&my_obj_attr);
if(!NT_SUCCESS(status))
{
// 失败处理
……
}

上面的代码得到了my_key。子键已经打开。然后的步骤是读取下面的SystemRoot值。这里省略。

ZwCreateKey 函数

ZwCreateKey 函数(新建或者打开注册表 ),函数原型如下:

NTSTATUS ZwCreateKey(
PHANDLE KeyHandle,
ACCESS_MASK DesireAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
ULONG TitleIndex,
PUNICODE_STRING Class,
ULONG CreateOptions,
PULONG Disposition
);

KeyHandle 参数是一个PAHDNLE类型,即句柄的指针,这个参数是一个返回参数,ZwCreateKey成功打开或创建注册表键后,KeyHandle保存注册表键的句柄。

DesiredAccess 参数表示权限,这个权限值需要根据后面具体的操作来决定。

如果需要获得全部的权限,可以使用KEY_ALL_ACCESS。

ObjectAttributes 参数在上一小节已经介绍过,表示对象的属性。ZwCreateKey函数第四个参数与第五个参数可以设置成NULL。

CreateOptions 参数 表示打开或创建注册表键的选项,常用的选项有:

  • REG_OPTION_VOLATILE 表示新建的注册表键在系统重启后不保留
  • REG_OPTION_NON_VOLATILE 表示新建的注册表键在系统重启后依然保留。

Disposition 参数是一个返回的参数,为ULONG指针,函数成功执行后,Disposition 等于 REG_CREATED_NEW_KEY或REG_OPENED_EXISTING_KEY,前者表示 ZwCreateKey 函数创建了一个新的注册表键,后者表示打开了一个已存在的注册表键。ZwCreateKey返回STATUS_SUCCESS则表示成功,否则返回一个错误码,对于内核大多数API,返回STATUS_SUCCESS则表示成功。

ZwCreateKey 函数的用法:

NTSTATUS DriverEntry(PDRIVER_OBJECT Driverobject, PUNICODE_STRING RegistryPath)
{
OBJECT_ATTRIBUTES ObjAttr = { 0 };
HANDLE hKey = NULL;
ULONG ulDisposition = 0;
NTSTATUS nStatus = STATUS_SUCCESS;
UNREFERENCED_PARAMETER ( DriverObject );
InitializeObjectAttributes(
&ObjAttr,
RegistryPath,
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
NULL,
NULL);

nStatus = ZwCreateKey(
&hKey,
KEY_WRITE,
&ObjAttr,
0,
NULL,
REG_OPTION_NON_VOLATILE,
&ulDisposition);

if (hKey != NULL)
{
ZwClose(hKey);
hKey = NULL;
}
DriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

代码中使用了InitializeObjectAttributes宏来初始化OBJECT_ATTRIBUTES结构体。

宏的每一个参数都对应着结构体中的一项成员。代码在调用InitializeObjectAttributes时,第三个参数传递的是OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,表示使用内核句柄以及对象名字不区分大小写。

注册表的读

ZwQueryValueKey 函数

ZwQueryValueKey这个函数的原型如下:

NTSTATUS ZwQueryValueKey(
HANDLE KeyHandle,
PUNICODE_STRING ValueName,
KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
PVOID KeyValueInformation,
ULONG Length,
PULONG ResultLength
);

KeyHandle:这是用ZwCreateKey或者ZwOpenKey所打开的一个注册表键句柄。

ValueName:UNICODE_STRING指针,要读取的值的名字。

KeyValueInformationClass:本次查询所需要查询的信息类型。

这有如下的三种可能:

  • KeyValueBasicInformation:获得基础信息,包含值名和类型。
  • KeyValueFullInformation:获得完整信息。包含值名、类型和值的数据。
  • KeyValuePartialInformation:获得局部信息。包含类型和值数据。

很容易看出实际上名字是已知的,获得基础信息是多此一举。同样获得完整信息也是浪费内存空间。因为调用ZwQueryValueKey的目的是为了得到类型和值数据。因此使用KeyValuePartialInformation最常见。当采用KeyValuePartialInformation的时候,一个类型为KEY_VALUE_PARTIAL_INFORMATION的结构将被返回到参数KeyValueInformation所指向的内存中。

KeyValueInformation:当KeyValueInformationClass被设置为KeyValuePartialInformation时,KEY_VALUE_PARTIAL_INFORMATION结构将被返回到这个指针所指内存中。下面是结构KEY_VALUE_PARTIAL_INFORMATION的原型。

typedef struct _KEY_VALUE_PARTIAL_INFORMATION {
ULONG TitleIndex; // 请忽略这个成员
ULONG Type; // 数据类型
ULONG DataLength; // 数据长度
UCHAR Data[1]; // 可变长度的数据
}KEY_VALUE_PARTIAL_INFORMATION,*PKEY_VALUE_PARTIAL_INFORMATIO;

数据类型Type有很多种可能,但是最常见的几种如下:

  • REG_BINARY:十六进制数据。
  • REG_DWORD:四字节整数。
  • REG_SZ:以空结束的Unicode字符串。
  • Length:用户传入的输出空间KeyValueInformation的长度。
  • ResultLength:返回实际需要的长度。
  • 返回值:如果说实际需要的长度比Length要大,那么返回STATUS_BUFFER_OVERFLOW或者是STATUS_BUFFER_TOO_SMALL。如果成功读出了全部数据,那么返回STATUS_SUCCESS。其他的情况,返回一个错误码。

注意

利用ResultLength参数以及STATUS_BUFFER_TOO_SMALL返回值,可以获取当前所查询键值信息大小,代码如下:

NTSTATUS nStatus = STATUS_SUCCESS;
UNICODE_STRING usValueName = { 0 };
ULONG ulRetSize = 0;
RtlInitUnicodeString(&usValueName, L"Start");
nStatus = ZwQueryValueKey(
hKey,
&usValueName,
KeyValuePartialInformation,
NULL,
0,
&ulRetSize);
if(nStatus == STATUS_BUFFER_TOO_SMALL && ulRetSize!= 0)
(
//ulRetSize保存的是所需要获取的信息大小
)

上述代码ZwQueryValueKey函数的KeyValueInformation传递NULL,Length传递0,ZwQueryValueKey函数会由于空间不足返回STATUS_BUFFER_TOO_SMALL,并设置ResultLength为所需大小。另外ZwQueryValueKey函数的第三个参数为KeyValuePartialInformation,表示获取键值的内容这个结构体的Data数组保存键值的内容,Data是一个长度不固定的数组,长度由DataLength决定,也就是说KEY_VALUE_PARTIAL_INFORMATION是一个变长结构体,这就是为什么首先需要通过ResultLength参数以及STATUS_BUFFER_TOO_SMALL返回值确定键值信息大小。

Length: KeyValueInformation缓冲区大小的,单位是字节。

ResultLength: 是一个返回参数,函数执行成功后,ResultLength返回的是KeyValueInformation缓冲区中实际键值信息大小,单位是字节

下面代码展示了完整的获取键值的过程:

NTSTATUS DriverEntry(PDRIVER_OBJECT Driverobject, PUNICODE_STRING RegistryPath)
{
OBJECT_ATTRIBUTES ObjAttr = { 0 };
HANDLE hKey = NULL;
ULONG ulDisposition = 0;
NTSTATUS nStatus = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(DriverObject);
InitializeObjectAttributes(
&ObjAttr,
RegistryPath,
OB3_KERNEL_HANDLE | OB3_CASE_INSENSITIVE,
NULL,
NULL);
nStatus = ZwCreateKey(
&hKey,
KEY_WRITE,
&ObjAttr,
0,
NULL,
REG_OPTION_NON_VOLATILE,
&ulDisposition);
if (hKey != NULL)
{
UNICODE_STRING usValueName = { 0 };
ULONG ulRetSize = 0;
RtlInitUnicodeString(&usValueName, L"Start");
//第一次查询获取所需长度
nStatus = ZwQueryValueKey(
hKey,
&usValueName,
KeyValuePartialInformation,
NULL,
0,
&ulRetSize);
if (nStatus == STATUS_BUFFER_TOO_SMALL && ulRetSize != 0)
{
//ulRetSize保存的是所需要获取的信息大小
ULONG ulStartValue = 0;
PKEY_VALUE_PARTIAL_INFORMATION pData = (PKEY_VALUE_PARTIAL_INFORMATION)
ExAllocatePoolWithTag(PagedPool, ulRetSize, 'DriF');
if (pData != NULL)
{
memset(pData > 0, ulRetSize);
//再次查询
nStatus = ZwQueryValueKey(
hKey,
&usValueName,
KeyValuePartialInformation,
(PVOID)pData,
ulRetSize,
&ulRetSize);
if (nStatus == STATUS_SUCCESS)
{
//获取Start的值
ulStartValue = *((ULONG*)pData->Data);
DbgPrint("Key:%wZ,ValueName:%wZ,Value:%u\n",
RegistryPath, &usValueName, ulStartValue);
}
ExFreePoolWithTag(pData, 'DriF');
pData = NULL;
}
ZwClose(hKey);
hKey = NULL;
}
Driverobject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
⬆︎TOP