3.9 注册表
注册表是Windows系统中重要的数据配置存储结构,存储着系统绝大部分的核心配置信息。注册表实际上也是一种文件,这些文件大多数存储在系统盘system32\config目录下,如笔者系统安装在C盘,那这个目录就是C:\Windows\System32\config。在该文件夹下可以看到SOFTWARE、SYSTEM、SAM等文件,这些文件被以内存映射的方式映射到内核空间,然后以一种被称为“HIVE”的方式组织起来,注册表API实际上操作的是这份HIVE内存数据,对HIVE数据的改动,最终会被回写到config目录下对应的文件中。读者如果只是想单纯使用注册表做数据读取或存储,可以没有必要深入去研究内核对注册表的实现方式。
由于注册表操作较多且参数含义基本雷同,所以本节选取了一些常用以及典型的操作进行介绍。
3.9.1 注册表的打开与关闭
操作注册表前,一般都需要打开或创建一个注册表键,可使用ZwCreateKey函数,函数原型如下:
参数KeyHandle是一个PAHDNLE类型,即句柄的指针,这个参数是一个返回参数,ZwCreateKey成功打开或创建注册表键后,KeyHandle保存注册表键的句柄。
第二个参数DesiredAccess表示权限,这个权限值需要根据后面具体的操作来决定,如开发者打开注册表后需要修改键值,则需要传递KEY_SET_VALUE,如果为了方便,可以传递KEY_ALL_ACCESS以表示所有权限,但这并非是一个好习惯。
第三个参数ObjectAttributes在上一小节已经介绍过,表示对象的属性。
ZwCreateKey函数第四个参数与第五个参数可以设置成NULL。
第六个参数CreateOptions表示打开或创建注册表键的选项,常用的选项有:
●REG_OPTION_VOLATILE 表示新建的注册表键在系统重启后不保留。
●REG_OPTION_NON_VOLATILE 表示新建的注册表键在系统重启后依然保留。
ZwCreateKey函数的最后一个参数Disposition是一个返回的参数,为ULONG指针,函数成功执行后,Disposition等于REG_CREATED_NEW_KEY或REG_OPENED_EXISTING_KEY,前者表示ZwCreateKey函数创建了一个新的注册表键,后者表示打开了一个已存在的注册表键。
ZwCreateKey返回STATUS_SUCCESS则表示成功,否则返回一个错误码,读者应该可以发现,对于内核大多数API,返回STATUS_SUCCESS则表示成功。
下面通过一段代码为读者展示ZwCreateKey的用法:
值得注意的是,代码中使用了InitializeObjectAttributes宏来初始化OBJECT_ATTRIBUTES结构体,InitializeObjectAttributes宏的定义如下:
从InitializeObjectAttributes宏的实现可知,宏的每一个参数都对应着结构体中的一项成员。代码在调用InitializeObjectAttributes时,第三个参数传递的是OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,表示使用内核句柄以及对象名字不区分大小写。
最后需要提醒读者注意ZwCreateKey函数的IRQL,该函数只能在PASSIVE_LEVEL下运行。这里有一个小技巧,一般来说,以Zw开头的内核函数,IRQL都要求运行在PASSIVE_LEVEL下。
ZwCreateKey函数成功执行后,开发者可以获取到一个注册表键的句柄,开发者可以通过这个句柄去操作(如删除、增加一个新键值)这个注册表键。当这个句柄使用完毕后,需要对该句柄进行关闭,在内核中,关闭句柄都是使用同一个函数ZwClose,该函数原型很简单:
其中Handle参数表示需要关闭的句柄,句柄关闭成功,ZwClose函数返回STATUS_SUCCESS。
使用先前提到的小技巧,以Zw开头的内核函数,IRQL要求是PASSIVE_LEVEL,事实上,WDK对ZwClose函数IRQL的描述确实如此。
3.9.2 注册表的修改
本节为读者介绍注册表的修改,修改注册表的键值(VALUE)需要通过ZwSetValueKey函数,ZwSetValueKey函数原型如下:
KeyHandle表示需要修改的注册表键的句柄。
ValueName为一个UNICODE_STRING指针,表示需要修改的注册表键值的名字。
TitleIndex参数为保留参数,设置为0即可。
Type参数表示键值的类型,常见的类型有REG_BINARY(二进制类型)、REG_DWORD(双字类型)、REG_MULTI_SZ(多字符串类型)、REG_SZ(字符串类型)等。
Data参数为一个PVOID指针,表示需要把该Data指向的数据写入到键值中。
最后一个参数DataSize为Data指向缓冲区的大小,单位是字节。
下面在上一小节示例代码的基础上,增加一个修改注册表键值的功能:
上面代码的作用是驱动程序加载后修改自身的启动方式为“自动启动”,请读者自行阅读。
3.9.3 注册表的读取
使用ZwQueryValueKey函数读取注册表键值的内容,ZwQueryValueKey函数原型如下:
相对于注册表键值的修改,注册表键值的读取要复杂一些,主要表现在ZwQueryValueKey函数的使用上,ZwQueryValueKey函数除了可以查询指定键值的内容,还可以查询指定键值的类型、名字等。
在使用ZwQueryValueKey函数获取相关信息前,必须确定所获取信息的内存大小,下面详细介绍ZwQueryValueKey各参数的含义。
●KeyHandle为需要被查询的注册表键句柄。
●ValueName是UNICODE_STRING指针,表示需要被查询的键值名字。
●KeyValueInformationClass是一个枚举值,表示查询的类型,如果想查询键值的内容,可以传递KeyValuePartialInformation;如果想查询键值所有信息,可以传递KeyValueFullInformation。
●KeyValueInformation表示用于接收键值信息的缓冲区。KeyValueInformation缓冲区的结构由KeyValueInformationClass的类型决定,如KeyValuePartialInformation类型要求KeyValueInformation指向KEY_VALUE_PARTIAL_INFORMATION结构体。
●Length表示KeyValueInformation缓冲区大小的,单位是字节。
●ResultLength是一个返回参数,函数执行成功后,ResultLength返回的是KeyValueInformation缓冲区中实际键值信息大小,单位是字节。
与ZwSetValueKey函数不同,ZwQueryValueKey函数的返回值除了返回STATUS_SUCCESS表示成功,还有两个重要的返回值需要关注:
STATUS_BUFFER_OVERFLOW:表示KeyValueInformation缓冲区太小,只有一部分键值的信息数据被复制到KeyValueInformation缓冲区中。
STATUS_BUFFER_TOO_SMALL:表示KeyValueInformation缓冲区太小,没有任何信息数据被复制到KeyValueInformation缓冲区中。
利用ResultLength参数以及STATUS_BUFFER_TOO_SMALL返回值,可以获取当前所查询键值信息大小,代码如下:
上述代码ZwQueryValueKey函数的KeyValueInformation传递NULL,Length传递0,ZwQueryValueKey函数会由于空间不足返回STATUS_BUFFER_TOO_SMALL,并设置ResultLength为所需大小。另外ZwQueryValueKey函数的第三个参数为KeyValuePartialInformation,表示获取键值的内容,KeyValueInformation所对应的结构体是KEY_VALUE_PARTIAL_INFORMATION,定义如下:
这个结构体的Data数组保存键值的内容,Data是一个长度不固定的数组,长度由DataLength决定,也就是说KEY_VALUE_PARTIAL_INFORMATION是一个变长结构体,这就是为什么首先需要通过ResultLength参数以及STATUS_BUFFER_TOO_SMALL返回值确定键值信息大小。
下面代码展示了完整的获取键值的过程:
运行后,日志如下: