注册表的相关概念
创建关闭注册表
ZwCreateKey
NTSYSAPI NTSTATUS ZwCreateKey(
PHANDLE KeyHandle, //获得的注册表句柄
ACCESS_MASK DesiredAccess, //访问权限,一般设置为KEY_ALL_ACCESS
POBJECT_ATTRIBUTES ObjectAttributes, //OBJECT_ATTRIBUTES数据结构指针
ULONG TitleIndex, //很少用到,一般设置为0
PUNICODE_STRING Class, //很少用到一般设置为NULL
ULONG CreateOptions, //创建时的选项,
PULONG Disposition //返回是创建成功,还是打开成功,返回值是REG_CREATED_NEW_KEY 就表明是创建了一个KEY Key 如果返回值是REG_OPENED_EXISTING_KEY,表明是打开一个已经存在的KEY
);
创建或者打开一个特定的注册表表项
#include <ntddk.h>
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
DbgPrint("驱动卸载成功");
}
#define MY_REG_SOFTWARE_KEY_NAME L"\\Registry\\Machine\\Software\\Zhangfan"
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegPath)
{
//在C盘创建一个文件
HANDLE hFile = NULL;
OBJECT_ATTRIBUTES objAttr = { 0 };
UNICODE_STRING RegPath;
NTSTATUS status = 0;
pDriverObject->DriverUnload = DriverUnload;
RtlInitUnicodeString(&RegPath, MY_REG_SOFTWARE_KEY_NAME);
//初始化对象属性 主要提供了一个要打开的文件名
InitializeObjectAttributes(&objAttr,
&RegPath,
OBJ_CASE_INSENSITIVE, //比较对象名时不区分大小写
0,
0);
ULONG IsCreate = 0;
HANDLE hKey;
status=ZwCreateKey(&hKey,
KEY_ALL_ACCESS,
&objAttr,
0,
0,
REG_OPTION_NON_VOLATILE, //系统重启后会保存这个KEY
&IsCreate
);
if (!NT_SUCCESS(status))
{
DbgPrint("注册表创建表项失败\n");
return status;
}
//判断是新建的注册表键 还是打开的注册表键
if (IsCreate == REG_CREATED_NEW_KEY)
{
DbgPrint("新建了一个注册表表项\n");
}
else if (IsCreate==REG_OPENED_EXISTING_KEY)
{
DbgPrint("打开了一个注册表表项\n");
}
ZwClose(hKey);
return STATUS_SUCCESS;
}
打开注册表
ZwCreateKey函数既可以创建注册表项,也可以打开注册表项。
为了简化打开的操作DKK提供了内核函数ZwOpenKey,如果ZwOpenKey指定的项不存在,不会创建这个项目,而是返回一个错误装啊提。
NTSYSAPI NTSTATUS ZwOpenKey(
PHANDLE KeyHandle, //返回被打开的句柄
ACCESS_MASK DesiredAccess, //打开的权限,一般设置为KEY_ALL_ACCESS
POBJECT_ATTRIBUTES ObjectAttributes //OBJECT_ATTRIBUTES数据结构,指示打开的状态。
);
添加,修改注册表键值
键值的分类
分类 | 描述 |
---|---|
REG_BINARY | 键值用二进制存储 |
REG_SZ | 键值用宽字符串,字符串以\0结尾 |
REG_EXPAND_SZ | 键值用宽字符串,字符串以\0结尾,该字符串是扩展的字符 |
REG_MULTI_SZ | 键值存储多个字符串,每个字符串以\0结尾 |
REG_DWORD | 键值用4字节整形存储 |
REG_QWORD | 键值用8字节存储 |
通过函数ZwSetValueKey来修改注册表的键值。
NTSYSAPI NTSTATUS ZwSetValueKey(
HANDLE KeyHandle, //注册表句柄
PUNICODE_STRING ValueName, //要新建或者修改的键名
ULONG TitleIndex, //很少用,一般设置为0
ULONG Type, //键值的类型
PVOID Data,
ULONG DataSize //记录键值数据的大小
);
使用ZwSetValueKey函数的时候,如果指定的键名不存在,则直接创建。如果指定键名已经存在,则对已有键值进行修改。当新建或者修改键值的时候,根据键值不同的类
别,Data指向不同的数据结构,并且用DataSize指明数据大小。例如,REG DWORD类
型,对应的数据大小就是4, REG_ QWORD数据类型,数据大小就是8,如果是REG_ SZ,
数据长度是字符串长度的:二倍加上两个字节。下面的代码演示了如何修改和设置键值。
#include <ntddk.h>
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
DbgPrint("驱动卸载成功");
}
#define MY_REG_SOFTWARE_KEY_NAME L"\\Registry\\Machine\\Software\\Zhangfan"
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegPath)
{
//在C盘创建一个文件
HANDLE hFile = NULL;
OBJECT_ATTRIBUTES objAttr = { 0 };
UNICODE_STRING RegPath;
NTSTATUS status = 0;
pDriverObject->DriverUnload = DriverUnload;
RtlInitUnicodeString(&RegPath, MY_REG_SOFTWARE_KEY_NAME);
//初始化对象属性 主要提供了一个要打开的文件名
InitializeObjectAttributes(&objAttr,
&RegPath,
OBJ_CASE_INSENSITIVE, //比较对象名时不区分大小写
0,
0);
ULONG IsCreate = 0;
HANDLE hKey;
status = ZwOpenKey(&hKey, KEY_ALL_ACCESS, &objAttr);
if (!NT_SUCCESS(status))
{
DbgPrint("打开注册表项失败\n");
return status;
}
else
{
DbgPrint("打开注册表项成功\n");
}
UNICODE_STRING KeyName;
RtlInitUnicodeString(&KeyName,L"UserName");
//创建或者修改键值 REG_SZ类型为宽字符串 所以参数5 data要使用宽字符,长度为字符*2+2
ZwSetValueKey(hKey, &KeyName, 0, REG_SZ, L"dale", 10);
if (!NT_SUCCESS(status))
{
DbgPrint("设置键值失败\n");
ZwClose(hKey);
return status;
}
else
{
DbgPrint("设置键值成功\n");
}
ZwClose(hKey);
return STATUS_SUCCESS;
}
查询注册表
驱动程序中有时需要对注册表的项进行查询,从而获取注册表的键值。DDK提供的
ZwQueryValueKey函数可以完成这个任务,其声明如下:
NTSYSAPI NTSTATUS ZwQueryValueKey(
HANDLE KeyHandle, //打开的注册表句柄
PUNICODE_STRING ValueName, //要查询的键名
KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, //根据KeyValueInformation的不同选择不同的查询类别
PVOID KeyValueInformation,//选择一种查询类别KeyValueBasicInformation、KeyValueFul]Information或KeyValuePartialInformation。
ULONG Length,//要查数据的长度
PULONG ResultLength//实际查询数据的长度
);
使用ZwQueryValueKey函数查询注册表时,需要用Key ValuefnformationClass选择
一种 查询方式。这可以是KeyValueBasicInformation、KeyValueFulInformation或者KeyValue
ParialInformation中的一种。 这分别代表查询基本信息,查询全部信息和查询部分信息,
每种查询类型会有对应的一-种数据结构获得查询结果。
一般情况下, 选择KeyValueParialInformation就可以查询键值的数据了,它对应的查
询数据结构是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_INFORMATION;
KEY_VALUE_PARTIAL_INFORMATION的数据结构长度不固定,所以首先要确定这
个长度。一-般使 用ZwQueryValueKey分为4个步骤。
➊用ZwQueryValueKey获取这个数据结构的长度。
❷分配如此长度的内存,用来查询。
❸再次调用ZwQueryValueKey,获取键值。
❹回收内存。
#include <ntddk.h>
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
DbgPrint("驱动卸载成功");
}
#define MY_REG_SOFTWARE_KEY_NAME L"\\Registry\\Machine\\Software\\Zhangfan"
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegPath)
{
//在C盘创建一个文件
HANDLE hFile = NULL;
OBJECT_ATTRIBUTES objAttr = { 0 };
UNICODE_STRING RegPath;
NTSTATUS status = 0;
pDriverObject->DriverUnload = DriverUnload;
RtlInitUnicodeString(&RegPath, MY_REG_SOFTWARE_KEY_NAME);
//初始化对象属性 主要提供了一个要打开的文件名
InitializeObjectAttributes(&objAttr,
&RegPath,
OBJ_CASE_INSENSITIVE, //比较对象名时不区分大小写
0,
0);
ULONG IsCreate = 0;
HANDLE hKey;
status = ZwOpenKey(&hKey, KEY_ALL_ACCESS, &objAttr);
if (!NT_SUCCESS(status))
{
DbgPrint("打开注册表项失败\n");
return status;
}
else
{
DbgPrint("打开注册表项成功\n");
}
UNICODE_STRING KeyName;
ULONG RetSize=0;
RtlInitUnicodeString(&KeyName,L"UserName");
//第一次调用ZwQueryValueKey 来获取KeyValuePartialInformation类型的大小 然后分配一个buffer
status=ZwQueryValueKey(hKey, &KeyName, KeyValuePartialInformation, NULL, 0, &RetSize);
//运行正确这个函数返回的是STATUS_BUFFER_TOO_SMALL 不能使用NT_SUCCESS来判断
if (status == STATUS_OBJECT_NAME_NOT_FOUND | status == STATUS_INVALID_PARAMETER | RetSize==0)
{
DbgPrint("查询键值类型失败");
ZwClose(hKey);
return status;
}
//分配一块内存来接收返回的信息
PKEY_VALUE_PARTIAL_INFORMATION pvpi = (PKEY_VALUE_PARTIAL_INFORMATION)ExAllocatePool(PagedPool, RetSize);
if (!pvpi)
{
DbgPrint("内存分配失败");
ZwClose(hKey);
return status;
}
//查询信息,类型为PKEY_VALUE_PARTIAL_INFORMATION
status = ZwQueryValueKey(hKey, &KeyName, KeyValuePartialInformation, pvpi, RetSize, &RetSize);
if (!NT_SUCCESS(status))
{
DbgPrint("查询键值失败\n");
return status;
}
else
{
switch (pvpi->Type)
{
case REG_SZ:
DbgPrint("查找的键的类型是:-- REG_SZ -- \n");
DbgPrint("查找的键的键值是%S\n",pvpi->Data);
break;
case REG_EXPAND_SZ:
DbgPrint("查找的键的类型是:-- REG_EXPAND_SZ -- \n");
break;
case REG_BINARY:
DbgPrint("查找的键的类型是:-- REG_BINARY -- \n");
break;
case REG_DWORD:
DbgPrint("查找的键的类型是:-- REG_DWORD -- \n");
break;
case REG_QWORD:
DbgPrint("查找的键的类型是:-- REG_QWORD -- \n");
break;
default:
DbgPrint("未解析的键值类型 -- \n");
break;
}
}
ZwClose(hKey);
return STATUS_SUCCESS;
}
枚举子项
可以通过函数ZwQueryKey 和ZwEnumerateKey函数来枚举子项。
NTSYSAPI NTSTATUS ZwQueryKey(
HANDLE KeyHandle, //注册表项的句柄
KEY_INFORMATION_CLASS KeyInformationClass, //查询的类别,一般选择KeyFullInformation
PVOID KeyInformation, //查询的数据指针,如果KeyInformationClass是KeyFullInformation,则该指针指向一个KEY_FULL_INFORMATION的数据结构.
ULONG Length,//数据长度
PULONG ResultLength//返回的数据长度
);
NTSYSAPI NTSTATUS ZwEnumerateKey(
HANDLE KeyHandle,//注册表项的句柄
ULONG Index,//很少用到,一般为0
KEY_INFORMATION_CLASS KeyInformationClass,//该子项的信息
PVOID KeyInformation,
ULONG Length,//子项信息的长度
PULONG ResultLength //返回子健信息的长度
);
ZwQueryKey的作用主要是获得某注册表项究竟有多少个子项,而ZwEnumerateKey
的作用主要是针对第几个子项获取该子项的具体信息。
在使用ZwQueryKey时,可以将参数KeyInformationClass指定为KeyFullInformation。
这样参数KeyInformation就对应-一个 KEY_FULL_INFORMATION的数据结构,该数据结
构中的SubKeys指明了项中有多少个子项。
KEY_ FULL._JINFORMATION数据结构的大小是变长的,所以要调用两次
ZwQueryKey。第一次获取KEY_ FULL_INFORMATION数据的长度,第二次真正获取
KEY_ FULL_ JINFORMATION数据。
在使用ZwEnumerateKey 时,需要将参数KeyInformationClass 设置为KeyBasic
Information,这样其参数KeyInformation就能对应KEY_ BASIC _INFORMATION的数据结构。
同理,KEY_ _BASIC _INFORMATION也是变长的数据结构,需要两次调用ZwEnumerate
Key。第一次获取KEY_ BASIC _INFORMATION的长度,第二次获取KEY_ BASIC_
INFORMATION数据。
枚举指定项的子项代码
//枚举指定键下的所有子键
NTSTATUS EnumerateKey(PWCHAR RegKeyPath)
{
PKEY_FULL_INFORMATION pfi = NULL;
OBJECT_ATTRIBUTES ObjAttr = { 0 };
UNICODE_STRING RegPath = {0};
NTSTATUS Status = STATUS_UNSUCCESSFUL;
RtlInitUnicodeString(&RegPath, RegKeyPath);
//初始化对象属性 主要提供了一个要打开的文件名
InitializeObjectAttributes(&ObjAttr,
&RegPath,
OBJ_CASE_INSENSITIVE, //比较对象名时不区分大小写
0,
0);
HANDLE KeyHandle=NULL;
do
{
Status = ZwOpenKey(&KeyHandle, KEY_ALL_ACCESS, &ObjAttr);
if (!NT_SUCCESS(Status))
{
DbgPrint("打开注册表项失败\n");
break;
}
UNICODE_STRING KeyName;
ULONG RetSize = -1;
//通过ZwQueryKey来获得KEY_FULL_INFORMATION数据的长度
Status = ZwQueryKey(KeyHandle, KeyFullInformation, NULL, 0, &RetSize);
if (RetSize == -1)
{
break;
}
pfi = (PKEY_FULL_INFORMATION)ExAllocatePoolWithTag(PagedPool, RetSize,'key');
if (!pfi)
{
break;
}
RtlZeroMemory(pfi, RetSize);
//获得KEY_FULL_INFORMATION数据
Status = ZwQueryKey(KeyHandle, KeyFullInformation, pfi, RetSize, &RetSize);
if (!NT_SUCCESS(Status))
{
break;
}
DbgPrint("子项个数是:%x\n", pfi->SubKeys);
for (ULONG i = 0; i < pfi->SubKeys; i++)
{
PKEY_BASIC_INFORMATION pbi = NULL;
//获得KEY_BASIC_INFORMATION结构的大小
RetSize = -1;
ZwEnumerateKey(KeyHandle, i, KeyBasicInformation, 0, 0, &RetSize);
if (RetSize == -1)
{
break;
}
pbi = (PKEY_BASIC_INFORMATION)ExAllocatePoolWithTag(PagedPool, RetSize,'key');
if (!pbi)
{
continue;
}
RtlZeroMemory(pbi, RetSize);
//获取KEY_BASIC_INFORMATION数据
Status = ZwEnumerateKey(KeyHandle, i, KeyBasicInformation, pbi, RetSize, &RetSize);
if (!NT_SUCCESS(Status))
{
break;
}
//因为KEY_BASIC_INFORMATION这个结构是变长的,最后一个成员Name并不是以\0结尾的,所以只能把它转化成UNICODE来输出
UNICODE_STRING SubKeyName;
SubKeyName.Length = SubKeyName.MaximumLength = (USHORT)pbi->NameLength;
SubKeyName.Buffer = pbi->Name;
DbgPrint("子项名是:%wZ\n", &SubKeyName);
WCHAR FullPath[260] = { 0 };
WCHAR KeyName[260] = { 0 };
wcscpy_s(FullPath,260,RegKeyPath);
wcscat_s(FullPath, 260, L"\\");
memcpy_s(KeyName,260, pbi->Name, pbi->NameLength);
wcscat_s(FullPath, 260, KeyName);
DbgPrint("全路径:%S\n", FullPath);
EnumerateValueKey(FullPath);
//释放掉申请的内存
ExFreePool(pbi);
}
} while (FALSE);
if (pfi)
{
ExFreePool(pfi);
pfi = NULL;
}
if (KeyHandle)
{
ZwClose(KeyHandle);
KeyHandle = NULL;
}
return Status;
}
枚举指定项下所有键值
//枚举指定键下的所有键名和键值
NTSTATUS EnumerateValueKey(PWCHAR RegKeyPath)
{
NTSTATUS Status = STATUS_SUCCESS;
PKEY_FULL_INFORMATION pkfi = NULL;
ULONG ulLength = 0;
OBJECT_ATTRIBUTES ObjAttr = { 0 };
UNICODE_STRING RegPath;
RtlInitUnicodeString(&RegPath, RegKeyPath);
//初始化对象属性 主要提供了一个要打开的文件名
InitializeObjectAttributes(&ObjAttr,
&RegPath,
OBJ_CASE_INSENSITIVE, //比较对象名时不区分大小写
0,
0);
HANDLE KeyHandle=NULL;
do
{
Status = ZwOpenKey(&KeyHandle, KEY_ALL_ACCESS, &ObjAttr);
if (!NT_SUCCESS(Status))
{
DbgPrint("yxp 打开注册表项失败,0x%x\n", Status);
break;
}
// 获取ValueKey个数
Status = ZwQueryKey(KeyHandle, KeyFullInformation, pkfi, 0, &ulLength);
// allocate key information buffer
pkfi = (PKEY_FULL_INFORMATION)ExAllocatePoolWithTag(PagedPool, ulLength, 'key');
if (pkfi == NULL)
{
DbgPrint("yxp ZwQueryKey ExAllocatePoolWithTag failed %x\n", Status);
break;
}
RtlZeroMemory(pkfi, ulLength);
Status = ZwQueryKey(KeyHandle, KeyFullInformation, pkfi, ulLength, &ulLength);
if (!NT_SUCCESS(Status))
{
DbgPrint("yxp ZwQueryKey 2 failed %x\n", Status);
break;
}
// enumerater value key
for (ULONG i = 0; i < pkfi->Values; i++)
{
PKEY_VALUE_FULL_INFORMATION pkvfi = NULL;
Status = ZwEnumerateValueKey(KeyHandle, i, KeyValueFullInformation, pkvfi, 0, &ulLength);
// allcate enumerate buffer
pkvfi = (PKEY_VALUE_FULL_INFORMATION)ExAllocatePoolWithTag(PagedPool, ulLength, 'key');
if (pkvfi == NULL)
{
DbgPrint("yxp ZwEnumerateKey ExAllocatePoolWithTag failed %x\n", Status);
continue;
}
RtlZeroMemory(pkvfi, ulLength);
Status = ZwEnumerateValueKey(KeyHandle, i, KeyValueFullInformation, pkvfi, ulLength, &ulLength);
if (!NT_SUCCESS(Status))
{
DbgPrint("yxp ZwEnumerateKey failed %x\n", Status);
ExFreePool(pkvfi);
break;
}
// show value name
UNICODE_STRING uValueKey = { 0 };
UNICODE_STRING uValueData = { 0 };
// value key
uValueKey.Length = uValueKey.MaximumLength = (USHORT)pkvfi->NameLength;
uValueKey.Buffer = pkvfi->Name;
// value data
uValueData.Length = uValueData.MaximumLength = (USHORT)pkvfi->DataLength;
uValueData.Buffer = (PWCH)((PCH)pkvfi + pkvfi->DataOffset);
if (pkvfi->Type == REG_SZ)
{
DbgPrint("yxp ValueKey : %wZ ValueData : %wZ\n", &uValueKey, &uValueData);
}
else
{
DbgPrint("yxp ValueKey : %wZ ValueData : %x\n", &uValueKey, *(PULONG)uValueData.Buffer);
}
ExFreePool(pkvfi);
}
} while (FALSE);
if (pkfi != NULL)
{
ExFreePool(pkfi);
pkfi = NULL;
}
if (KeyHandle)
{
ZwClose(KeyHandle);
KeyHandle = NULL;
}
return Status;
}
删除子项
NTSTATUS
ZwDeleteKey (
IN HANDLE KeyHandle
);
需要指出,该函数只能删除没有子项的项目。如果项中还有子项,则不能删除。这时
候需要先将该项中的所有子项全部删除后,再删除该项。下 面的例子演示了如何在驱动程
序中删除子项。
简化的注册表操作
以上的注册表操作都非常繁琐,DDK提供了一些更加简化的操作函数、
分类 | 描述 |
---|---|
RtlCreateRegistryKey | 创建注册表 |
RtlCheckRegistryKey | 查看某注册表项是否存在 |
RtlWriteRegistryKey | 写注册表 |
RtlDeleteRegistryKey | 删除注册表的子键 |
#include <ntddk.h>
#define MY_REG_SOFTWARE_KEY_NAME L"DaleReg1013"
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
DbgPrint("驱动卸载成功");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegPath)
{
DbgPrint("加载路径:%S\n", pRegPath->Buffer);
pDriverObject->DriverUnload = DriverUnload;
//判断指定注册表项存不存在 不存在就创建该项
NTSTATUS status = RtlCheckRegistryKey(RTL_REGISTRY_SERVICES, MY_REG_SOFTWARE_KEY_NAME);
if (status == STATUS_SUCCESS)
{
DbgPrint("注册表项%S已存在\n", MY_REG_SOFTWARE_KEY_NAME);
}
else
{
//创建注册表项 RTL_REGISTRY_SERVICES 就指定要创建的注册表项的路径是 \Registry\Machine\System\CurrentControlSet\Services
status = RtlCreateRegistryKey(RTL_REGISTRY_SERVICES, MY_REG_SOFTWARE_KEY_NAME);
}
if (!NT_SUCCESS(status))
{
DbgPrint("创建注册表项失败\n");
return status;
}
else
{
DbgPrint("创建注册表项成功\n");
}
//判断指定注册表项存不存在 已经创建 肯定存在
status=RtlWriteRegistryValue(RTL_REGISTRY_SERVICES, MY_REG_SOFTWARE_KEY_NAME,L"USERNAME",REG_SZ,L"dale",10);
if (!NT_SUCCESS(status))
{
DbgPrint("新建注册表键失败\n");
return status;
}
else
{
DbgPrint("新建注册表键成功\n");
}
ULONG a = 100;
//再次调用 就可以修改指定键名的键值
status = RtlWriteRegistryValue(RTL_REGISTRY_SERVICES, MY_REG_SOFTWARE_KEY_NAME, L"USERNAME", REG_DWORD,&a, 4);
if (!NT_SUCCESS(status))
{
DbgPrint("修改注册表键值失败\n");
return status;
}
else
{
DbgPrint("修改注册表键值成功\n");
}
//删除注册表的子键
RtlDeleteRegistryValue(RTL_REGISTRY_SERVICES, MY_REG_SOFTWARE_KEY_NAME, L"USERNAME");
if (!NT_SUCCESS(status))
{
DbgPrint("删除指定子键失败\n");
return status;
}
else
{
DbgPrint("删除指定子键成功\n");
}
return STATUS_SUCCESS;
}
多字符串类型
REG_MULTI_SZ类型
在注册表中多字符串成对成对的解析,第一个为源文件路径,第二个就是目标文件路径,如果目标文件路径为空 就是删除源文件例如:以下的多字符串
c:\1.txt
d:\dale\1.txt //有这个值就表明是系统重启后移动文件
c:\2.txt
//没有值说明是系统重启后删除源文件