windows驱动的简单入门(二)

简介:这是一篇继续介绍windows驱动的简单入门,包含通过驱动代码进行文件读写和注册表的操作。

前言

通过上一篇的简单入门,我们建立了一个基本的驱动编写和运行调试的环境,接下来继续了解关于驱动的其他操作,在处理IO操作上,我们想要首先了解的就是文件相关的操作。

文件操作相关

文件操作涉及的内容本就比较多,但是,这里仅仅做几个简单的操作,但是随着了解的深入,对于文件相关的操作涉及的知识点会可以逐步扩展。

ZwCreateFile

顾名思义,就是到这个函数一定是创建文件的函数。而且,如果已本身对于c或者c++等语言有了解的,文件操作的顺序一般都是有一个模式的,比如当你看到这个创建文件,你就可以大胆推断,如果能够创建或者是打开文件,那就还会有读取文件,写入文件,关闭文件等操作。至于是否如你所想,接着向下阅读,一切都会了然。

首先,依照惯例,还是把这个函数的定义放在下面。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
NTSYSAPI NTSTATUS ZwCreateFile(
  [out]          PHANDLE            FileHandle,
  [in]           ACCESS_MASK        DesiredAccess,
  [in]           POBJECT_ATTRIBUTES ObjectAttributes,
  [out]          PIO_STATUS_BLOCK   IoStatusBlock,
  [in, optional] PLARGE_INTEGER     AllocationSize,
  [in]           ULONG              FileAttributes,
  [in]           ULONG              ShareAccess,
  [in]           ULONG              CreateDisposition,
  [in]           ULONG              CreateOptions,
  [in, optional] PVOID              EaBuffer,
  [in]           ULONG              EaLength
);

虽然官网有(地址https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/wdm/nf-wdm-zwcreatefile),但是我还是按耐不住要把参数列表简单解释下: 首先要注意一点,参数列表中,in是输入参数,out是输出参数,optional是可选参数(啥是可选,可以先不管)

1
2
[out] FileHandle
// 这是个文件句柄,意思就是,创建文件之后,会给一个文件的标识,可以在读取,写入等场景的时候,用这个句柄来代替整个文件的操作。类似于给文件起个名。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[in] DesiredAccess

// 这个参数确定对文件对象的请求访问权限。毕竟windows对文件的访问是遵循权限设定的。
// 这个参数实际的值应该是一个掩码,通过不同的掩码来设定创建的文件可以赋予哪些操作权限,读取,写入,执行等等

ACCESS_MASK标志    允许调用方执行此操作
FILE_READ_DATA    从文件读取数据。
FILE_READ_ATTRIBUTES    读取文件的属性。 (有关详细信息,请参阅 FileAttributes 参数的说明。)
FILE_READ_EA    读取文件的扩展属性 (CA)  此标志与设备和中间驱动程序无关。
FILE_WRITE_DATA    将数据写入文件。
FILE_WRITE_ATTRIBUTES    写入文件的属性。 (有关详细信息,请参阅 FileAttributes 参数的说明。)
FILE_WRITE_EA    (文件的 CA) 更改扩展属性。 此标志与设备和中间驱动程序无关。
FILE_APPEND_DATA    将数据追加到文件。
FILE_EXECUTE    使用系统分页 I/O 将数据从文件读取到内存中。 此标志与设备和中间驱动程序无关。

除了以上,在vs2022中输入以上一个宏后,会出现对应的值,有一个更常用更省心的值是
#define FILE_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF)
当然,我们为了方便测试,可以选择用FILE_ALL_ACCESS
 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
[in] ObjectAttributes

// 这个参数是一个指针,指向了描述文件属性的一个结构体,这个结构体这个样子
typedef struct _OBJECT_ATTRIBUTES {
  ULONG           Length;
  HANDLE          RootDirectory;
  PUNICODE_STRING ObjectName;
  ULONG           Attributes;
  PVOID           SecurityDescriptor;
  PVOID           SecurityQualityOfService;
} OBJECT_ATTRIBUTES;

// 这个结构体是需要填写内容的,但是官网的介绍过于负责了,所以就简单说下
Length 是这个结构体的长度(直接用sizeof (OBJECT_ATTRIBUTES) 填充即可)
RootDirectory 是文件所在目录(可以直接null
ObjectName 文件的绝对目录名(一般驱动层面,需要写成\\??\\盘符:\\xxx\\xx\\xx.xxx),但是这个参数是一个Unicode字符串哈

Attributes 这也是个属性,描述的是特定的操作,我一般就用OBJ_KERNEL_HANDLE  OBJ_CASE_INSENSITIVE  一个是内核模式访问,一个是不敏感大小写

SecurityDescriptor和SecurityQualityOfService不需要深究,一般复制null就够了

// 那么把上述值整理并赋值,就是以下代码
OBJECT_ATTRIBUTES objAttr = {sizeof(OBJECT_ATTRIBUTES)};
objAttr.RootDirectory = NULL;
// UNICODE_STRING可以用上次提到的宏来直接生成,额外的方法还有更多请看上篇文章。
UNICODE_STRING objName = RTL_CONSTANT_STRING(L"\\??\\c:\\ceshi.txt");
objAttr.ObjectName = &objName;
objAttr.Attributes = OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE;
objAttr.SecurityDescriptor = NULL;
objAttr.SecurityQualityOfService = NULL;
  • 初始化这个参数除了用直接赋值的方式,还可以利用一个宏函数来处理,InitializeObjectAttributes
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 定义如下:
VOID InitializeObjectAttributes(
  [out]          POBJECT_ATTRIBUTES   p,
  [in]           PUNICODE_STRING      n,
  [in]           ULONG                a,
  [in]           HANDLE               r,
  [in, optional] PSECURITY_DESCRIPTOR s
);
// p是指向OBJECT_ATTRIBUTES结构体的指针
// n就是文件的绝对路径名称
// a就是自己想要赋予的属性值
// r目录的句柄,如果n是绝对路径,r就可以是null
// s就是安全句柄,可以为NULL
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
[out] IoStatusBlock

//这个参数也是一个指针,指向一个结构体,该结构体存储函数执行结束后涉及到IO状态的描述等信息
typedef struct _IO_STATUS_BLOCK {
  union {
    NTSTATUS Status;
    PVOID    Pointer;
  };
  ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

Status //结果成功与否
Pointer //被保留,可能windows内核自己要有的(感兴趣可以研究研究它是啥玩意)
Information //传输的一些有用的信息,比如传输了多少字节,或者结果,有点像自定义,取决于使用场景来决定其中的内容,需要文档来说明,可能包括以下内容
FILE_CREATEDFILE_OPENEDFILE_OVERWRITTENFILE_SUPERSEDEDFILE_EXISTSFILE_DOES_NOT_EXIST
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[in, optional] AllocationSize

//顾名思义,就是描述,创建文件过程中分配的文件大小,但是该参数其实是指向LARGE_INTEGER这个结构体的一个指针
//另外 LARGE_INTEGER 就是为了存储64位整数的结构体
typedef union _LARGE_INTEGER {
  struct {
    DWORD LowPart;
    LONG  HighPart;
  } DUMMYSTRUCTNAME;
  struct {
    DWORD LowPart;
    LONG  HighPart;
  } u;
  LONGLONG QuadPart;
} LARGE_INTEGER;

//包含高32位和低32位
1
2
3
[in] FileAttributes

//表示在创建或覆盖文件时要设置的文件属性,通常设置为FILE_ATTRIBUTE_NORMAL(默认属性)
1
2
3
[in] ShareAccess

//共享类型,默认为0即可
1
2
3
4
[in] CreateDisposition

// 文件不存在如何做
FILE_OPEN_IF    //存在打开文件,不存在则创建文件,比较常用
1
2
3
[in] CreateOptions

//这个选项就是人家给你一些扩展选项,你可以自己设定,选项有很多,看自己心情随便选
1
2
3
4
5
6
7
8
//最后两个参数没啥可说的
[in, optional] EaBuffer

//对于设备和中间驱动程序,此参数必须是 NULL 指针。

[in, optional] EaBuffer

对于设备和中间驱动程序,此参数必须是 NULL 指针。

因此,以上函数最后统一写出一个代码,供参考如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
HANDLE hFile = 0;

OBJECT_ATTRIBUTES objAttr = {sizeof(OBJECT_ATTRIBUTES)};
objAttr.RootDirectory = NULL;
UNICODE_STRING objName = RTL_CONSTANT_STRING(L"\\??\\c:\\ceshi.txt");
objAttr.ObjectName = &objName;
objAttr.Attributes = OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE;
objAttr.SecurityDescriptor = NULL;
objAttr.SecurityQualityOfService = NULL;

IO_STATUS_BLOCK ioBlock = {0};

LARGE_INTEGER poffset = {0};

NTSTATUS status=ZwCreateFile(&hFile, FILE_ALL_ACCESS,&objAttr,&ioBlock,&poffset, FILE_ATTRIBUTE_NORMAL,0,FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE| FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);

// 另外:成功的会返回STATUS_SUCCESS

ZwReadFile

创建完成文件,接下来就可以尝试读写文件了,这个函数就是读取文件的方法,老样子,还是把函数的详细定义放在下面:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
NTSYSAPI NTSTATUS ZwReadFile(
  [in]           HANDLE           FileHandle,
  [in, optional] HANDLE           Event,
  [in, optional] PIO_APC_ROUTINE  ApcRoutine,
  [in, optional] PVOID            ApcContext,
  [out]          PIO_STATUS_BLOCK IoStatusBlock,
  [out]          PVOID            Buffer,
  [in]           ULONG            Length,
  [in, optional] PLARGE_INTEGER   ByteOffset,
  [in, optional] PULONG           Key
);

下面是参数列表的详细解释:

 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
[in] FileHandle
文件对象的句柄,ZwCreateFile创建的文件标识符

//下面三个参数都是NULL
[in, optional] Event
设备和中间驱动程序应将此参数设置为 NULL

[in, optional] ApcRoutine
此参数为保留参数。 设备和中间驱动程序应将此指针设置为 NULL

[in, optional] ApcContext
此参数为保留参数。 设备和中间驱动程序应将此指针设置为 NULL

[out] IoStatusBlock
//  这还是个IO_STATUS_BLOCK结构体的指针,不过information参数是实际从文件读取的字节数。

[out] Buffer
指向接收从文件读取数据的缓冲区。

[in] Length
Buffer缓冲区的大小(以字节为单位)

[in, optional] ByteOffset
这是一个变量的指针,这个变量指定了读取文件偏移多少字节开始读,如果位置超过文件末尾将报错,并且这个也是一个LARGE_INTEGER结构

[in, optional] Key
设备和中间驱动程序应将此指针设置为 NULL 

简单接着上面创建的文件继续写就是:

1
status=ZwReadFile(hFile, NULL, NULL, NULL, &ioreadBlock, fileBuff,0x1000,&readffset,NULL);

ZwClose

关闭文件这个就不用过多赘述了,只是需要一个文件句柄就好。

1
2
3
4
5
NTSYSAPI NTSTATUS ZwClose(
  [in] HANDLE Handle
);

ZwClose(hFile);

接下来把整个代码完整的结合起来,读取一个文件到内存。

 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
49
HANDLE hFile = 0;

OBJECT_ATTRIBUTES objAttr = {sizeof(OBJECT_ATTRIBUTES)};
objAttr.RootDirectory = NULL;
UNICODE_STRING objName = RTL_CONSTANT_STRING(L"\\??\\c:\\ceshi.txt");
objAttr.ObjectName = &objName;
objAttr.Attributes = OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE;
objAttr.SecurityDescriptor = NULL;
objAttr.SecurityQualityOfService = NULL;

IO_STATUS_BLOCK ioBlock = {0};
IO_STATUS_BLOCK ioreadBlock = {0};

LARGE_INTEGER poffset = {0};
LARGE_INTEGER readoffset = {0};

NTSTATUS status=ZwCreateFile(&hFile, FILE_ALL_ACCESS,&objAttr,&ioBlock,&poffset, FILE_ATTRIBUTE_NORMAL,0,FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE| FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);

PVOID fileBuff = NULL;
if (NT_SUCCESS(status))
{
        DbgPrint("打开文件成功!");
        //分配内存,存储文件
        fileBuff=ExAllocatePool(NonPagedPool,0x1000);
        if (fileBuff)
        {
            status=ZwReadFile(hFile, NULL, NULL, NULL, &ioreadBlock, fileBuff,0x1000,&readoffset,NULL);
            if (NT_SUCCESS(status))
            {
                DbgPrint("读取文件成功!");
            }
            else
            {
                DbgPrint("读取文件不成功!");
            }
        }

        ZwClose(hFile);
        if (fileBuff)
        {
            ////释放内存文件
            ExFreePool(fileBuff);
        }

}
else
{
    DbgPrint("打开文件不成功!");
}

ZwQueryInformationFile

这是一个很有用的函数,因为当你想对未知文件进行操作之前,需要先获取有关这个文件的一些信息,比如,文件的大小,知道了大小就知道需要有多大的空间来存储读取的内容或者是采用分配读取等。

函数的定义如下 :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
NTSYSAPI NTSTATUS ZwQueryInformationFile(
  [in]  HANDLE                 FileHandle,
  [out] PIO_STATUS_BLOCK       IoStatusBlock,
  [out] PVOID                  FileInformation,
  [in]  ULONG                  Length,
  [in]  FILE_INFORMATION_CLASS FileInformationClass
);
// FileHandle是文件句柄,
// FileInformation 获取到的文件相关的信息(下文展开说)

// IoStatusBlock 就是刚刚说过的指向 IO_STATUS_BLOCK结构体的指针,Information 的内容是FileInformation获取到的信息的大小
// Length 是指给FileInformation分配的大小
// FileInformationClass 是希望获取的信息(有很多可选的选项)

单独就FileInformation和FileInformationClass进行一些展开叙述下。首先,FileInformationClass是一个枚举值,不同的值代表需要获取不同的信息,其定义如下:具体含义可以看这个链接https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/wdm/ne-wdm-_file_information_class

 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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
typedef enum _FILE_INFORMATION_CLASS {
  FileDirectoryInformation = 1,
  FileFullDirectoryInformation = 2,
  FileBothDirectoryInformation = 3,
  FileBasicInformation = 4,
  FileStandardInformation = 5,
  FileInternalInformation = 6,
  FileEaInformation = 7,
  FileAccessInformation = 8,
  FileNameInformation = 9,
  FileRenameInformation = 10,
  FileLinkInformation = 11,
  FileNamesInformation = 12,
  FileDispositionInformation = 13,
  FilePositionInformation = 14,
  FileFullEaInformation = 15,
  FileModeInformation = 16,
  FileAlignmentInformation = 17,
  FileAllInformation = 18,
  FileAllocationInformation = 19,
  FileEndOfFileInformation = 20,
  FileAlternateNameInformation = 21,
  FileStreamInformation = 22,
  FilePipeInformation = 23,
  FilePipeLocalInformation = 24,
  FilePipeRemoteInformation = 25,
  FileMailslotQueryInformation = 26,
  FileMailslotSetInformation = 27,
  FileCompressionInformation = 28,
  FileObjectIdInformation = 29,
  FileCompletionInformation = 30,
  FileMoveClusterInformation = 31,
  FileQuotaInformation = 32,
  FileReparsePointInformation = 33,
  FileNetworkOpenInformation = 34,
  FileAttributeTagInformation = 35,
  FileTrackingInformation = 36,
  FileIdBothDirectoryInformation = 37,
  FileIdFullDirectoryInformation = 38,
  FileValidDataLengthInformation = 39,
  FileShortNameInformation = 40,
  FileIoCompletionNotificationInformation = 41,
  FileIoStatusBlockRangeInformation = 42,
  FileIoPriorityHintInformation = 43,
  FileSfioReserveInformation = 44,
  FileSfioVolumeInformation = 45,
  FileHardLinkInformation = 46,
  FileProcessIdsUsingFileInformation = 47,
  FileNormalizedNameInformation = 48,
  FileNetworkPhysicalNameInformation = 49,
  FileIdGlobalTxDirectoryInformation = 50,
  FileIsRemoteDeviceInformation = 51,
  FileUnusedInformation = 52,
  FileNumaNodeInformation = 53,
  FileStandardLinkInformation = 54,
  FileRemoteProtocolInformation = 55,
  FileRenameInformationBypassAccessCheck = 56,
  FileLinkInformationBypassAccessCheck = 57,
  FileVolumeNameInformation = 58,
  FileIdInformation = 59,
  FileIdExtdDirectoryInformation = 60,
  FileReplaceCompletionInformation = 61,
  FileHardLinkFullIdInformation = 62,
  FileIdExtdBothDirectoryInformation = 63,
  FileDispositionInformationEx = 64,
  FileRenameInformationEx = 65,
  FileRenameInformationExBypassAccessCheck = 66,
  FileDesiredStorageClassInformation = 67,
  FileStatInformation = 68,
  FileMemoryPartitionInformation = 69,
  FileStatLxInformation = 70,
  FileCaseSensitiveInformation = 71,
  FileLinkInformationEx = 72,
  FileLinkInformationExBypassAccessCheck = 73,
  FileStorageReserveIdInformation = 74,
  FileCaseSensitiveInformationForceAccessCheck = 75,
  FileKnownFolderInformation = 76,
  FileStatBasicInformation = 77,
  FileId64ExtdDirectoryInformation = 78,
  FileId64ExtdBothDirectoryInformation = 79,
  FileIdAllExtdDirectoryInformation = 80,
  FileIdAllExtdBothDirectoryInformation = 81,
  FileStreamReservationInformation,
  FileMupProviderInfo,
  FileMaximumInformation
} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;

因而,不同的信息对应的其实是不同的结构体,那么大小就会有区别,所以为了防止给FileInformation分配的大小不足够,最好确定需要的信息对应的结构体的大小。所以就可以参考上文发的链接,其中就有对应不同选择的结构体。那么使用这个函数的时候就可以这样来写:

 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
// 假设我想获取文件的大小等常规信息,我选择FileStandardInformation
status=ZwQueryInformationFile(
hFile,//文件句柄
&ioBlock,// IO_STATUS_BLOCK结构体,也就是返回信息的大小
&fileInfo,// 返回的信息
sizeof(FILE_STANDARD_INFORMATION), // 分配返回信息的大小,返回信息的结构体就是FILE_STANDARD_INFORMATION
FileStandardInformation);

// FILE_STANDARD_INFORMATION定义
typedef struct _FILE_STANDARD_INFORMATION {
  LARGE_INTEGER AllocationSize;
  LARGE_INTEGER EndOfFile;//文件的实际大小就用这个值
  ULONG         NumberOfLinks;
  BOOLEAN       DeletePending;
  BOOLEAN       Directory;
} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;

// 如果想知道时间相关的,可以用FILE_BASIC_INFORMATION
typedef struct _FILE_BASIC_INFORMATION {
  LARGE_INTEGER CreationTime;
  LARGE_INTEGER LastAccessTime;
  LARGE_INTEGER LastWriteTime;
  LARGE_INTEGER ChangeTime;
  ULONG         FileAttributes;
} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION;

ZwWriteFile

知道了读取,查询,剩下就是写入了,也是很重要的一个函数,定义如下:

 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
NTSYSAPI NTSTATUS ZwWriteFile(
  [in]           HANDLE           FileHandle,
  [in, optional] HANDLE           Event,
  [in, optional] PIO_APC_ROUTINE  ApcRoutine,
  [in, optional] PVOID            ApcContext,
  [out]          PIO_STATUS_BLOCK IoStatusBlock,
  [in]           PVOID            Buffer,
  [in]           ULONG            Length,
  [in, optional] PLARGE_INTEGER   ByteOffset,
  [in, optional] PULONG           Key
);    
[in] FileHandle
// 文件句柄,没啥好说的

[in, optional] Event
[in, optional] ApcRoutine
[in, optional] ApcContext
// 设备和中间驱动程序应将此参数设置为 NULL,也没啥好说的,好简单

[out] IoStatusBlock
// IO_STATUS_BLOCK结构体, 可以直接推断出来,Information 表示实际写入的大小字节

[in] Buffer
// 表示要写入的数据地址

[in] Length
// 上述buffer的大小

[in, optional] ByteOffset
// 写入操作的起始字节偏移量,如果超过现有文件的结束位置,会自动增加文件然后并自动更新文件的结束位置(合理哈)

复制文件

当知道如何读取文件、查询文件信息,写入文件,关闭文件,应该就可以尝试复制文件了,下面把关键部分的代码写出来:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PVOID buff = NULL;//读取过程中的缓冲区
ULONG buffLen = 10;//读取的大小
buff = ExAllocatePool(NonPagedPool, buffLen);//分配内容

...中间省略...

while (TRUE)
{
    RtlZeroMemory(buff,buffLen);//缓冲区清空
    status=ZwReadFile(hReadFile,NULL,NULL,NULL,&readBlock, buff,buffLen,&readOffset,NULL);//读取buffLen到buff中
    if (!NT_SUCCESS(status))
    {
        if (status=STATUS_END_OF_FILE)//如果读取到的是文件最后也为读取成功
        {
            status = STATUS_SUCCESS;
        }
        break;
    }
    status=ZwWriteFile(hWriteFile,NULL,NULL,NULL,&writeBlock, buff, readBlock.Information,&writeOffset,0);//将buff中的readBlock.Information大小的字节写入文件中
//同时将读取函数的偏移增加上次读到信息的大小,将写入函数的偏移增加到上次写入信息的大小
    readOffset.QuadPart += readBlock.Information;
    writeOffset.QuadPart += writeBlock.Information;
}

注册表相关

在windows的操作系统里,注册表是一个很重要的存在,虽然对很多人来说注册表比较透明,使用的也不多,但是确实无时无刻不存在的。

其次,注册表是一个很复杂的结构,操作系统在正常的运行中会反复的写入或读取注册表,只是我们不知道而已,注册表整体就像一个操作系统的配置管理数据库,每一个操作点和操作过程的配置信息全都存储在这样一个数据库中,windows注册表是一个树状结构主要分以下几个结构:

  1. key,键,类似于目录项,每一个键就是一个很长的路径
  2. value,值,每个键都有可能有多个值
  3. name,名字,每个值都有一个名字
  4. type,类型,值的类型描述 所以说,其实注册表是一个比较简单的结构,无非就是对key的增删改查而已。

ZwCreateKey

创建key的函数,定义如下:

1
2
3
4
5
6
7
8
9
NTSYSAPI NTSTATUS ZwCreateKey(
  [out]           PHANDLE            KeyHandle,
  [in]            ACCESS_MASK        DesiredAccess,
  [in]            POBJECT_ATTRIBUTES ObjectAttributes,
                  ULONG              TitleIndex,
  [in, optional]  PUNICODE_STRING    Class,
  [in]            ULONG              CreateOptions,
  [out, optional] PULONG             Disposition
);

参数列表的含义如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[out] KeyHandle
//这个参数就是key的句柄,就跟文件的fileHandle差不多一个意思

[in] DesiredAccess
//也比较好理解,就是你要针对key做什么操作,读,写,查等等,

KEY_QUERY_VALUE    读取键值。
KEY_SET_VALUE    写入键值。
KEY_CREATE_SUB_KEY    创建子项。
KEY_ENUMERATE_SUB_KEYS    读取子项。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
[in] ObjectAttributes
//还是OBJECT_ATTRIBUTES这个属性就是用来存放键的完整路径

TitleIndex
// 驱动写0

[in, optional] Class
// 这个由设别管理器配置,咱写NULL就行

[in] CreateOptions
// 创建key或者打开key的时候,设定一些附加的操作,
REG_OPTION_NON_VOLATILE,可以用这个值,重启还保留

[out, optional] Disposition
//一个变量的指针,变量内容为是最终是创建还是打开键,这个是用来接收结果的,所以一把是传入一个空结构体的指针就可以
REG_CREATED_NEW_KEY    创建了一个新密钥。
REG_OPENED_EXISTING_KEY    已打开现有密钥。

通过上述描述,可以写出来对应的代码如下:

1
2
3
4
5
6
7
8
9
HANDLE hKey = 0;
OBJECT_ATTRIBUTES objAttr = { 0 };
ULONG Disposition = 0;
UNICODE_STRING keyPath = RTL_CONSTANT_STRING(L"\\一大长串路径\\键");
//初始化OBJECT_ATTRIBUTES结构体
InitializeObjectAttributes(&objAttr,&keyPath, OBJ_CASE_INSENSITIVE| OBJ_KERNEL_HANDLE,NULL,NULL);

NTSTATUS stauts=ZwCreateKey(&hKey, KEY_ALL_ACCESS,&objAttr,0,NULL, REG_OPTION_NON_VOLATILE,&Disposition);
// 注意:这个时候还没有使用到键的名字keyname和键的值keyvalue

ZwOpenKey

打开key,这个跟create其实差不多,最主要的目的其实就是获取key的句柄。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
NTSYSAPI NTSTATUS ZwOpenKey(
  [out] PHANDLE            KeyHandle,
  [in]  ACCESS_MASK        DesiredAccess,
  [in]  POBJECT_ATTRIBUTES ObjectAttributes
);

[out] KeyHandle
//要获取的句柄

[in] DesiredAccess
//制定的访问权限,跟上传create一个样

[in] ObjectAttributes
//还是 OBJECT_ATTRIBUTES 的结构体指针,也就是包含键的全限定位置等

ZwQueryKey

查询key,查询应该算是最常用的了吧,定义如下:

1
2
3
4
5
6
7
NTSYSAPI NTSTATUS ZwQueryKey(
  [in]            HANDLE                KeyHandle,
  [in]            KEY_INFORMATION_CLASS KeyInformationClass,
  [out, optional] PVOID                 KeyInformation,
  [in]            ULONG                 Length,
  [out]           PULONG                ResultLength
);
 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
[in] KeyHandle
// 就是上面两个函数获取的key句柄

[in] KeyInformationClass
// 这个参数跟读取文件的操作很类似,就是描述需要获取哪种类型的信息,也同样有一个枚举值:KEY_INFORMATION_CLASS 根据不同的枚举值,在下面的参数指定的缓冲区中存储返回的结构体

typedef enum _KEY_INFORMATION_CLASS {
  KeyBasicInformation,
  KeyNodeInformation,
  KeyFullInformation,
  KeyNameInformation,
  KeyCachedInformation,
  KeyFlagsInformation,
  KeyVirtualizationInformation,
  KeyHandleTagsInformation,
  KeyTrustInformation,
  KeyLayerInformation,
  MaxKeyInfoClass
} KEY_INFORMATION_CLASS;

[out, optional] KeyInformation
//根据KeyInformationClass,返回指定的内容的缓冲区地址,比如返回这个结构体:

typedef struct _KEY_BASIC_INFORMATION {
  LARGE_INTEGER LastWriteTime;
  ULONG         TitleIndex;
  ULONG         NameLength;
  WCHAR         Name[1];
} KEY_BASIC_INFORMATION, *PKEY_BASIC_INFORMATION;

返回的内容具体是什么结构体可以查看这个地址:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/wdm/ne-wdm-_key_information_class

1
2
3
4
5
[in] Length
//指定缓冲区的大小,其实可以直接用sizeof(结构体)来确定

[out] ResultLength
//指向一个变量,变量的值是实际返回的信息的大小

是不是通过上述函数看,这种函数有一些通用的套路,比如,定义需要获取的信息类型,返回的信息的缓冲区,定义缓冲区大小,实际返回信息的大小等等。

ZwSetValueKey

最后一步就是设置键值了,当然之前说过keyname也没有被使用,因为一个name对应一个值,name其实就是用来描述值的,同样type也是用来表述值的,具体定义如下:

1
2
3
4
5
6
7
8
NTSYSAPI NTSTATUS ZwSetValueKey(
  [in]           HANDLE          KeyHandle,
  [in]           PUNICODE_STRING ValueName,
  [in, optional] ULONG           TitleIndex,
  [in]           ULONG           Type,
  [in, optional] PVOID           Data,
  [in]           ULONG           DataSize
);

参数列表如下(其实已经很简单了):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[in] KeyHandle
//key句柄
[in] ValueName
//keyname,也就是键的值对应的名称
[in, optional] TitleIndex
//写0即可

[in] Type
//这个是值对应的类型,例如:
REG_BINARY    任意格式的二进制数据。
REG_DWORD    一个 4 字节的数值。

具体有哪些类型可以查看这个:https://learn.microsoft.com/zh-cn/windows-hardware/drivers/ddi/wdm/nf-wdm-zwsetvaluekey

1
2
3
4
5
[in, optional] Data
// 值对应的内容,也就是键值本值,但是是指向对应内容所在的缓冲区

[in] DataSize
//缓冲区的大小(以字节为单位)

例如完整的写法可以是:

1
2
3
UNICODE_STRING keyName = RTL_CONSTANT_STRING(L"keyName");
stauts=ZwSetValueKey(hKey,&keyName,0, REG_SZ,L"hello world",(wcslen(L"hello world")+1)*sizeof(WCHAR));
//注意:缓冲区的大小应该是包含最后字符串末尾的那个\0的截止符号

好了,这篇就分享到这些,无论是文件操作还是注册表读写,其实都有很多相通的地方,无非就是windows在给与我们api的时候设定了很多复杂的扩展项,让你可以在既定的方式里去做操作而已。

updatedupdated2024-12-292024-12-29