windows read registers

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

参考链接:http://www.blogfshare.com/kernel-registerrw.html

最近在写windows驱动时需要读取注册表的值,一下内容为windows内核中注册表操作;

1.注册表打开

windows系统的注册表是一个树状结构表,每一个表中是键值对,查找时先打开相应的表(子键),然后查询表中的值;
和应用程序编程的一点重大不同是这个路径的写法不一样。一般应用编程中需要提供一个根子键的句柄。而驱动中则全部用路径表示。相应的有一张表表示如下:

应用程序中对应的子健 驱动编程中的路径写法
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。

1
2
3
4
5
NTSTATUS ZwOpenKey(
_Out_ PHANDLE KeyHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
);

DesiredAccess支持一系列的组合权限。可以是下表中所有权限的任何组合:
KEY_QUERY_VALUE:读取键下的值。
KEY_SET_VALUE:设置键下的值。
KEY_CREATE_SUB_KEY:生成子键。
KEY_ENUMERATE_SUB_KEYS:枚举子键。
不过实际上可以用KEY_READ来做为通用的读权限组合。这是一个组合宏。此外对应的有KEY_WRITE。如果需要获得全部的权限,可以使用KEY_ALL_ACCESS。

下面是一个例子。它读取注册表中保存的Machine GUID值。

Machine GUID是一台计算机的标识符,这一值保存在注册表中,路径是“\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography”,按照上面的表的转换,程序中的路径是“\Registry\Machine\SOFTWARE\Microsoft\Cryptography”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
NTSTATUS status = STATUS_SUCCESS;
HANDLE my_key_handle = NULL;
// 定义要获取的路径
UNICODE_STRING machine_guid_path =
RTL_CONSTANT_STRING(
L”\\Registry\\Machine\\SOFTWARE\\Microsoft\\Cryptography”);
OBJECT_ATTRIBUTE my_obj_attr = { 0 };
// 初始化OBJECT_ATTRIBUTE
InitializeObjectAttributes(
&my_obj_attr,
&machine_guid_path,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
// 接下来是打开Key
status = ZwOpenKey(&my_key_handle,KEY_READ,&my_obj_attr);
if(!NT_SUCCESS(status))
{
// 失败处理
……
}

2.注册表读

1
2
3
4
5
6
7
8
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所指向的内存中。

1
2
3
4
5
6
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
45
46
47
48
// 要读取的值的名字
UNICODE_STRING machine_guid_key_name = RTL_CONSTANT_STRING(L”MachineGuid”);
// 用来试探大小的key_infor
KEY_VALUE_PARTIAL_INFORMATION key_infor;
// 最后实际用到的key_infor指针。内存分配在堆中
PKEY_VALUE_PARTIAL_INFORMATION ac_key_infor;
ULONG ac_length;
// 用于最后存储为char类型的变量
char machine_guid[100] = "init";
ULONG valueSize;
……
// 前面已经打开了句柄my_key,下面如此来读取值:
status = ZwQueryValueKey(my_key_handle, &machine_guid_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 ,'tag1');
if(ac_key_infor == NULL)
{
stauts = STATUS_INSUFFICIENT_RESOURCES;
// 错误处理
}
status = ZwQueryValueKey(my_key_handle, &machine_guid_key_name,
KeyValuePartialInformation,
ac_key_infor, ac_length, &ac_length);
// 到此为止,如果status为STATUS_SUCCESS,则要读取的数据已经
// 在ac_key_infor->Data中,其中存储的是UNICODE,长度存储
// 在ac_key_infor-> DataLength中。
// 由于我需要的是char类型的,所以将其转换成char字符串
ac_key_infor->Data[ac_key_infor->DataLength / sizeof(ac_key_infor->Data[0])] = UNICODE_NULL;
status = RtlUnicodeToMultiByteN(machine_guid, sizeof(machine_guid) - 1, &valueSize,
ac_key_infor->Data, ac_key_infor->DataLength);
if (!NT_SUCCESS(status)) {
// 错误处理
...
}
machine_guid[valueSize] = '\0';

3.注册表的写

(暂时没这个需求,没测)

1
2
3
4
5
6
7
8
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已经存在,那么其值会被这次写入覆盖。如果不存在,则会新建一个。

1
2
3
4
5
6
7
8
9
10
11
UNICODE_STRING name = RTL_CONSTANT_STRING(L”Test”);
PWCHAR value = { L”My Test Value” };
// 写入数据。数据长度之所以要将字符串长度加上1,是为了把最后一个空结束符写入。
status = ZwSetValueKey(my_key_handle,
&name,0,REG_SZ,value,(wcslen(value)+1)*sizeof(WCHAR));
if(!NT_SUCCESS(status))
{
// 错误处理
……
}