Windows内核中注册表打开、读/写操作

1.注册表的打开

和在应用程序中编程的方式类似,注册表是一个巨大的树形结构。操作一般都是打开某个子键。子键下有若干个值可以获得。每一个值有一个名字。值有不同的类型。一般需要查询才能获得其类型。
子键一般用一个路径来表示。和应用程序编程的一点重大不同是这个路径的写法不一样。一般应用编程中需要提供一个根子键的句柄。而驱动中则全部用路径表示。相应的有一张表表示如下:

image

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

打开注册表键使用函数ZwOpenKey。新建或者打开则使用ZwCreateKey。

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

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

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”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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))
{
    // 失败处理
    ……
}

 

2.注册表的读

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

KeyHandle:这是用ZwCreateKey或者ZwOpenKey所打开的一个注册表键句柄。
ValueName:要读取的值的名字。
KeyValueInformationClass:本次查询所需要查询的信息类型。这有如下的三种可能。
①KeyValueBasicInformation:获得基础信息,包含值名和类型。
②KeyValueFullInformation:获得完整信息。包含值名、类型和值的数据。
③KeyValuePartialInformation:获得局部信息。包含类型和值数据。

当采用KeyValuePartialInformation的时候,一个类型为KEY_VALUE_PARTIAL_INFORMATION的结构将被返回到参数KeyValueInformation所指向的内存中。

typedef struct _KEY_VALUE_PARTIAL_INFORMATION {
  ULONG TitleIndex;
  ULONG Type;
  ULONG DataLength;
  UCHAR Data[1];
} KEY_VALUE_PARTIAL_INFORMATION, *PKEY_VALUE_PARTIAL_INFORMATION;

上面的数据类型Type有很多种可能:

①REG_BINARY:十六进制数据。
②REG_DWORD:四字节整数。

③REG_SZ:以空结束的Unicode字符串。

Length:用户传入的输出空间KeyValueInformation的长度。
ResultLength:返回实际需要的长度。
返回值:如果说实际需要的长度比Length要大,那么返回STATUS_BUFFER_OVERFLOW或者是STATUS_BUFFER_TOO_SMALL。如果成功读出了全部数据,那么返回STATUS_SUCCESS。其他的情况,返回一个错误码。

我们再来完成上面的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 要读取的值的名字
UNICODE_STRING my_key_name =
    RTL_CONSTANT_STRING(L”SystemRoot”);
// 用来试探大小的key_infor
KEY_VALUE_PARTIAL_INFORMATION key_infor;
// 最后实际用到的key_infor指针。内存分配在堆中
PKEY_VALUE_PARTIAL_INFORMATION ac_key_infor;
ULONG ac_length;
……
// 前面已经打开了句柄my_key,下面如此来读取值:
status = ZwQueryValueKey(
        my_key,
        &my_key_name,
        KeyValuePartialInformation,
        &key_infor,
        sizeof(KEY_VALUE_PARTIAL_INFORMATION),
        &ac_length);
if(!NT_SUCCESS(status) &&
status != STATUS_BUFFER_OVERFLOW &&
status != STATUS_BUFFER_TOO_SMALL)
{
    // 错误处理
    …
}
// 如果没失败,那么分配足够的空间,再次读取
ac_key_infor = (PKEY_VALUE_PARTIAL_INFORMATION)
ExAllocatePoolWithTag(NonpagedPool,ac_length ,MEM_TAG);
if(ac_key_infor == NULL)
{
    stauts = STATUS_INSUFFICIENT_RESOURCES;
    // 错误处理
    …
}
status = ZwQueryValueKey(
    my_key,
    &my_key_name,
    KeyValuePartialInformation,
    ac_key_infor,
    ac_length,
    &ac_length);
// 到此为止,如果status为STATUS_SUCCESS,则要读取的数据已经
// 在ac_key_infor->Data中。请利用前面学到的知识,转换为
// UNICODE_STRING
……

上面我们是先获取长度,然后不足时再动态分配内存进行读取。

3.注册表的写

NTSTATUS ZwSetValueKey(
  _In_      HANDLE KeyHandle,
  _In_      PUNICODE_STRING ValueName,
  _In_opt_  ULONG TitleIndex,
  _In_      ULONG Type,
  _In_opt_  PVOID Data,
  _In_      ULONG DataSize
);

TileIndex参数请始终填入0。KeyHandle、ValueName、Type这三个参数和ZwQueryValueKey中对应的参数相同。不同的是Data和DataSize。Data是要写入的数据的开始地址,而DataSize是要写入的数据的长度。

如果该Value已经存在,那么其值会被这次写入覆盖。如果不存在,则会新建一个。

UNICODE_STRING name = RTL_CONSTANT_STRING(L”Test”);
PWCHAR value = { L”My Test Value” };

// 写入数据。数据长度之所以要将字符串长度加上1,是为了把最后一个空结束符 写入。
status = ZwSetValueKey(my_key,
&name,0,REG_SZ,value,(wcslen(value)+1)*sizeof(WCHAR));
if(!NT_SUCCESS(status))
{
// 错误处理
……
}

本文链接:http://www.alonemonkey.com/kernel-registerrw.html