【Windows内核驱动开发】——读取注册表

时间:2022-12-31 15:34:36

【我的】Windows驱动开发——读取注册表

作者:zcr214 时间:2016/5/5

 

注册表对于驱动来说是很重要的小伙伴,注册表可以很好的扮演用户到内核的桥梁角色,很多时候用户可以通过修改注册表的内容来达到控制驱动的目的。那么驱动要做的首先当然是读取到注册表啦,WDK提供了标准的接口函数,所以直接使用就可以了。

1.    ZwOpenKey()打开注册表

WDK提供了打开注册表的接口函数ZwOpenKey,其原型如下:

NTSTATUS

NTAPI

ZwOpenKey(

    _Out_PHANDLE KeyHandle,

    _In_ACCESS_MASK DesiredAccess,

    _In_POBJECT_ATTRIBUTES ObjectAttributes

);

这个函数将得到一个打开注册表的操作句柄指针,保存在KeyHandle,并返回状态值。

需要ACCESS_MASK来指定打开该注册表的权限,读权限对应KEY_READ,写权限对应KEY_WRITE,如果需要全部权限则是KEY_ALL_ACCESS。

从第三个参数可以看出,它不是接受一个字符串来表示一个注册表项,而是要求输入一个OBJECT_ATTRIBUTES的指针,这需要我们提前初始化一个OBJECT_ATTRIBUTES。下面举个初始化的例子:

OBJECT_ATTRIBUTESobj_attr={0};

InitializeObjectAttributes(

      &obj_attr,

      Reg_Key_Path,

      OBJ_CASE_INSENSITIVE,

      NULL,

      NULL);

其中obj_attr是初始化的OBJECT_ATTRIBUTES,Reg_Key_Path是注册表项的路径,它是一个UNICODE_STRING字符串,如UNICODE_STRINGReg_key_path=RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\ControlSet001\\services\\SwapBuffers\\");其余三个参数表示大小写敏感和安全描述符,一般情况下就按照这个例子这样填写即可。

初始化完毕后就可以打开注册表了。

   status=ZwOpenKey(&Reg_Handle,KEY_READ,&obj_attr);

2.    ZwQueryValueKey()读取键值

读取注册表子键的值,使用ZwQueryValueKey(),其原型如下:

NTSTATUS

NTAPI

ZwQueryValueKey(

    _In_HANDLE KeyHandle,

    _In_PUNICODE_STRING ValueName,

    _In_KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,

    _Out_writes_bytes_opt_(Length)PVOIDKeyValueInformation,

    _In_ULONG Length,

    _Out_PULONG ResultLength

);

KeyHandle是之前使用ZwOpenKey打开的注册表操作句柄。

ValueName是要读取的子键的名字。

KeyValueInformationClass是获取的信息类型,包括Basic,Full,Partial三种,Basic信息包含子键名和类型,Full包含子键名,类型和值,Partial包含类型和值,一般的我们读取注册表显然已经知道了名字,是为了得到数据类型和值,因此获取Partial信息最常用。即KeyValuePartialInformation。

KeyValueInformation指针所指的内存,用于保存函数返回来的注册表键值信息,是一个_KEY_VALUE_PARTIAL_INFORMATION结构,其原型如下:

typedefstruct_KEY_VALUE_PARTIAL_INFORMATION{

    ULONG   TitleIndex;

    ULONG   Type;

    ULONG   DataLength;

    _Field_size_bytes_(DataLength)UCHARData[1];//Variable size

}KEY_VALUE_PARTIAL_INFORMATION,*PKEY_VALUE_PARTIAL_INFORMATION;

Length是用户指定的输出空间KeyValueInformation的长度。

ResultLength是返回回来的实际需要的长度。

通过分析这个函数可以知道,主要问题存在于读取长度这里,如果为了方便总是定义一个足够大的空间,这样势必造成内存的浪费,所以实际应用中,应该耐心的获取合适的长度,不足时再动态分配内存,所以应如下读取键值。

   //用来试探大小的Reg_try_info

   KEY_VALUE_PARTIAL_INFORMATIONReg_try_info;

   //实际获取的Reg_info

   PKEY_VALUE_PARTIAL_INFORMATIONReg_info;

   ULONGReal_length;

UNICODE_STRINGReg_key_Name=RTL_CONSTANT_STRING(L"ChosenVolume");

   //下面开始试图读取值

   status=ZwQueryValueKey(

      Reg_Handle,

      Reg_Key_Name,

      KeyValuePartialInformation,

      &Reg_try_info,

      sizeof(KEY_VALUE_PARTIAL_INFORMATION),

      &Real_length);

   if(!NT_SUCCESS(status)

      &&status!=STATUS_BUFFER_OVERFLOW

      &&status!=STATUS_BUFFER_TOO_SMALL)

   {

      //错误处理

      DbgPrint("getRegistryValue:打开注册表键值失败");

      ZwClose(Reg_Handle);

      returnNULL;

   }

   //如果读取成功,则分配足够的空间再次读取

   Reg_info=(PKEY_VALUE_PARTIAL_INFORMATION)

      ExAllocatePoolWithTag(NonPagedPool,Real_length,NAME_TAG);

 

   if(Reg_info==NULL)

   {

      //错误处理

      DbgPrint("getRegistryValue:打开注册表键值对指针为空");

      ZwClose(Reg_Handle);

      returnNULL;

   }

   status=ZwQueryValueKey(

      Reg_Handle,

      Reg_Key_Name,

      KeyValuePartialInformation,

      Reg_info,

      Real_length,

      &Real_length);

   ZwClose(Reg_Handle);

 

所有的操作完毕,如果此时status为成功,那么注册表信息已经保存在Reg_info->Data中了,它是UCHAR类型的可变长度数组,下面只需要再转化成需要的字符串类型PCHAR,UNICODE_STRING等即可。

 

3.    注意的问题

传入函数的所有类型的参数都要严格的初始化定义,最有可能出问题的地方就是UNICODE_STRING字符串。

例如在初始化OBJECT_ATTRIBUTE或ZwQueryValueKey时,如果Reg_Key_Path或Reg_Key_Name并不是一个静态定义好的UNICODE_STRING,而是作为函数参数传递过来的,它的Buffer实际长度和Length指定的长度一定要吻合,否则将会出现错误,导致注册表打开失败。