SMGER 发表于 2024-7-30 18:39

Windows驱动开发中的文件和注册表

文件与注册表
因为这两者都设计到对某种内核对象的操作,所以可以整理到一起。OBJECT_ATTRIBUTES结构
内核中的重要结构,内核对象的属性结构。不论是文件还是注册表还是其他种类的内核对象,每个内核对象都有一个OBJECT_ATTRIBUTES结构来规定自身属性。typedef struct _OBJECT_ATTRIBUTES {    ULONG Length;                  // 结构体的大小,以字节为单位    HANDLE RootDirectory;            // 指向根目录的句柄,如果是绝对路径则为 NULL    PUNICODE_STRING ObjectName;      // 指向对象名称的指针    ULONG Attributes;                // 对象的属性标志    PVOID SecurityDescriptor;      // 安全描述符,定义对象的安全性    PVOID SecurityQualityOfService;// 安全质量服务,用于IPC(进程间通信)} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;//初始化使用InitializeObjectAttributesVOID InitializeObjectAttributes(OUT POBJECT_ATTRIBUTES InitializedAttributes, //Object_Attribute变量的指针IN PUNICODE_STRING ObjectName,                //对象的路径IN ULONG Attributes,                        //OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLEIN HANDLE RootDirectory,                      //如果填NULL,则第二个参数的路径为绝对路径,如果填入的是一个目录句柄,则第二个参数为相对路径IN PSECURITY DESCRIPTOR SecurityDescriptor    //NULL);
初始化示例如下VOID Example() {    UNICODE_STRING objectName;    RtlInitUnicodeString(&objectName, L"\\\\Device\\\\ExampleDevice");    OBJECT_ATTRIBUTES objectAttributes;    InitializeObjectAttributes(      &objectAttributes,// 初始化后的属性结构指针      &objectName,      // 对象名称      OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, // 属性标志,这里表示名称不区分大小写且打开内核对象      NULL,               // 根目录句柄,这里为 NULL 表示不使用根目录      NULL                // 安全描述符,这里为 NULL 表示默认安全    );    // 现在 objectAttributes 已初始化,可以用于创建或打开对象}文件操作打开和关闭
打开和关闭文件 ZwCreateFile 和 ZwClose//ZwCreateFileNTSTATUS ZwCreateFile(OUT PHANDLE FileHandle,IN ACCESS_MASK DesiredAccess,IN POBJECT_ATTRIBUTES Object_Attribute,OUT PIO_STATUS_BLOCK IoStatusBlock,IN PLARGE_INTEGER AllocationSize OPTIONALIN ULONG FileAttributes,IN ULONG ShareAccess,IN ULONG CreateDisposition,IN ULONG CreateOptions,IN PVOID EaBuffer OPTIONAL,IN ULONG EaLength);
[*]
FileHandle: 指向接收文件对象句柄的变量的指针。当文件被成功创建或打开时,这个句柄用于标识文件。
[*]
DesiredAccess: 指定对对象的访问权限(例如读取、写入)。这是一个访问掩码可用|进行组合,定义了调用者希望对文件执行的操作类型(GENERIC_READ, GENERIC_WRITE, GENERIC_ALL分别代表常用的读权限,写权限和所有权限)。
[*]
ObjectAttributes: 指向 OBJECT_ATTRIBUTES 结构的指针,该结构指定对象的名称和属性(如对象名称、根目录等)。
[*]
IoStatusBlock: 指向 IO_STATUS_BLOCK 结构的指针,该结构接收操作的最终完成状态和有关操作的信息。
[*]
IoStatusBlock详细结构typedef struct _IO_STATUS_BLOCK {union {        NTSTATUS Status; //存放返回结果        PVOID Pointer;};ULONG_PTR Information; //存放错误的详细信息}IO_STATUS_BLOCK,*PIO_STATUS_BLOCK;
[*]
AllocationSize: 指向一个变量的指针,该变量指定文件的初始分配大小(以字节为单位)。仅在创建新文件时使用,如果打开现有文件则忽略此参数。
[*]
FileAttributes: 文件属性(例如只读、隐藏)。一般设为0。
[*]
ShareAccess: 共享访问类型(例如共享读取、共享写入)。定义其他进程是否可以同时访问该文件以及以何种方式访问。
[*]
CreateDisposition: 指定在文件已存在或不存在时采取的操作(例如创建新文件、打开现有文件)。定义了文件创建或打开的行为。
[*]
关于CreateDisposition的宏FILE_SUPERSEDE替换文件:如果文件存在,将其替换;如果文件不存在,则创建一个新文件。类似于:“如果文件存在,替换它;如果文件不存在,创建它”。FILE_OPEN打开文件:如果文件存在,打开它;如果文件不存在,操作将失败。类似于:“仅在文件存在时打开它”。FILE_CREATE创建文件:如果文件不存在,创建它;如果文件存在,操作将失败。类似于:“仅在文件不存在时创建它”。FILE_OPEN_IF打开或创建文件:如果文件存在,打开它;如果文件不存在,则创建一个新文件。类似于:“如果文件存在,打开它;如果文件不存在,创建它”。FILE_OVERWRITE覆盖文件:如果文件存在,覆盖它;如果文件不存在,操作将失败。类似于:“仅在文件存在时覆盖它”。FILE_OVERWRITE_IF覆盖或创建文件:如果文件存在,覆盖它;如果文件不存在,则创建一个新文件。类似于:“如果文件存在,覆盖它;如果文件不存在,创建它”。
[*]
CreateOptions: 创建或打开文件的选项(例如作为目录打开、关闭时删除)。这些选项指定了文件操作的一些附加行为。
[*]
EaBuffer: 指向用于传递扩展属性(EA)到文件系统的缓冲区的指针。扩展属性是文件系统特定的元数据。
[*]
EaLength: EaBuffer 的长度(以字节为单位)。

该函数的参数组合很复杂,下面是一个参数使用模板NTSTATUS CreateFileExample(){    HANDLE fileHandle;    OBJECT_ATTRIBUTES objectAttributes;    IO_STATUS_BLOCK ioStatusBlock;    UNICODE_STRING fileName;    NTSTATUS status;      RtlInitUnicodeString(&fileName, L"\\\\??\\\\C:\\\\example.txt");    InitializeObjectAttributes(&objectAttributes, &fileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);      status = ZwCreateFile(      &fileHandle,      GENERIC_WRITE,      &objectAttributes,      &ioStatusBlock,      NULL,      FILE_ATTRIBUTE_NORMAL,      0,      FILE_OVERWRITE_IF,      FILE_SYNCHRONOUS_IO_NONALERT,      NULL,      0    );    if (NT_SUCCESS(status)) {      // 文件创建成功,进行进一步操作      ZwClose(fileHandle);    }    return status;}
tips:打开路径为对象路径C:算是一个符号链接对象,符号链接对象一般在\\??\\下
[*]
符号链接
符号链接(英语:Symbolic link),又称软链接是一类特殊的文件, 其包含有一条以绝对路径或者相对路径的形式指向其它文件或者目录的引用。
详细请参照看wiki
//文件关闭,直接使用打开的对象句柄关闭即可ZwClose(file_handle);读和写//读操作NTSTATUS ZwReadFile(IN HANDLE FileHandle,IN HANDLE Event OPTIONALIN PIO_APC_ROUTINE ApCRoutine OPTIONALIN PVOID ApContextOPTIONAL,OUT PIO_STATUS_BLOCK IostatusBlock,OUT PVOID Buffer,IN ULONG Length,IN PLARGE_INTEGER ByteOffset OPTIONAL,IN PULONG Key OPTIONAL);
[*]
参数解释(通用比较复杂,给出示例代码)参数解释

[*]FileHandle:

[*]类型:HANDLE
[*]解释:文件句柄,指向要读取的文件或设备。该句柄必须是由 ZwCreateFile 或 ZwOpenFile 返回的,并且必须具有读取权限。
[*]Event:

[*]类型:HANDLE
[*]解释:一个可选的事件句柄。如果提供,该事件将在读取操作完成时被设置为有信号状态。如果不需要,可以设置为 NULL。
[*]ApcRoutine:

[*]类型:PIO_APC_ROUTINE
[*]解释:一个可选的 APC(异步过程调用)例程指针。读取操作完成后将调用此例程。如果不需要,可以设置为 NULL。
[*]ApcContext:

[*]类型:PVOID
[*]解释:传递给 APC 例程的上下文信息。如果 ApcRoutine 为 NULL,此参数将被忽略。
[*]IoStatusBlock:

[*]类型:PIO_STATUS_BLOCK
[*]解释:指向一个 IO_STATUS_BLOCK 结构,该结构接收读取操作的完成状态和信息。
[*]Buffer:

[*]类型:PVOID
[*]解释:指向要接收读取数据的缓冲区。
[*]Length:

[*]类型:ULONG
[*]解释:要读取的字节数。
[*]ByteOffset:

[*]类型:PLARGE_INTEGER
[*]解释:要读取的字节偏移量。可以设置为 NULL,以从当前文件指针位置读取。
[*]Key:

[*]类型:PULONG
[*]解释:用于异步 I/O 操作的键值。对于同步操作,可以设置为 NULL。
NTSTATUS status;HANDLE fileHandle;IO_STATUS_BLOCK ioStatusBlock;CHAR buffer;LARGE_INTEGER byteOffset;byteOffset.QuadPart = 0; // 从文件开始读取status = ZwReadFile(fileHandle,//刚刚打开的文件对象NULL,NULL,NULL,&ioStatusBlock, //反馈buffer,         //接受文件数据的缓冲区sizeof(buffer), //缓冲区大小&byteOffset,    //读取的起始位置(类比文件指针)NULL);if (NT_SUCCESS(status)) {// 读取成功,处理数据} else {// 处理错误}
tips: 写操作与读操作是对称相反的过程,即其余参数作用均相同,只是buffer是进行写入的。NTSTATUS ZwWriteFile(HANDLE         FileHandle,HANDLE         Event,PIO_APC_ROUTINEApcRoutine,PVOID            ApcContext,PIO_STATUS_BLOCK IoStatusBlock,PVOID            Buffer,ULONG            Length,PLARGE_INTEGER   ByteOffset,PULONG         Key);注册表操作打开或新建注册表键
注意:在内核编程中读取注册表不能像应用编程一样用HKEY_LOCAL_MACHINE来表示根键,用全路径表示。在用户态编程时,已经确定了操作用户的具体信息,而内核态没有。所以内核态没有根键HKEY_CLASSES_ROOT和HKEY_CURRENT_USER的具体代替路径。
[*]HKEY_LOCAL_MACHINE → \Registry\Machine
[*]HKEY_USER → \Registry\User
NTSTATUS ZwOpenKey(OUT PHANDLE KeyHandle,               //返回的注册表对象句柄IN ACCESS_MASK DesiredAccess,          IN POBJECT_ATTRIBUTES ObjectAttributes //对象的OBJECT_ATTRIBUTE结构体);
其中,第二个参数为组合,指定对对象的访问权限。这是一个访问掩码可用|进行组合,与ZwCreateFile处的DesiredAccess参数用法一致,以下是一些组合值。
KEY_QUERY_VALUE :读取键下的值。KEY_SET_VALUE :设置键下的值。KEY_CREATE_SUB_KEY :生成子键。KEY_ENUMERATE_SUB_KEYS :枚举子键。KEY_READ :通用的读权限组合。KEY_WRITE :通用写权限组合。KEY_ALL_ACCESS :全部权限。读写键值
API:ZwQueryValueKey 和 ZwSetValueKey//读键值NTSTATUS ZwQueryValueKey(IN HANDLE KeyHandle,         //打开的注册表对象IN PUNICODE_STRING ValueName,//要修改键值的名字//规定要获取的格式,通常填KeyValuePartialInformation,读取键类型和值IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, OUT PVOID KeyValueInformation, //返回结果的bufferIN ULONG Length,               //传入KeyValueInformation的大小OUT PULONG ResultLength      //返回实际需要的长度);
关于KeyValueInformation,如果第三个参数填入KeyValuePartialInformation,则一个KEY_VALUE_PARTIAL_INFORMATION结构体typedef struct KEY_VALUE_PARTIAL_INFORMATION{ULONG TitleIndex;ULONG Type;               //数据类型ULONG DataLength;         //数据长度UCHAR Data;            //变长buffer}KEY_VALUE_PARTIAL_INFORMATION,*PKEY_VALUE_PARTIAL_INFORMATION;
对于ZwQueryValueKey的参数设置解析:因为我们在读取注册表时,不能确定该键值究竟是哪种类型,所以应该利用好ResultLength参数用两次调用来最终确定到底需要多大的缓冲区,并用ExAllocatePoolWithTag来动态分配内存。//写键值NTSTATUS ZwSetValueKey(IN HANDLE KeyHandle,         //键对象IN PUNICODE_STRING ValueName,//待设置键值的名字IN ULONG TitleIndex OPTIONAL,//始终为0IN ULONG Type,               //待设置键值的类型IN PVOID Data,               //待设置键值存储数据的缓冲区IN ULONG Datesize            //缓冲区大小);
tips: ZwSetValueKey如果正常执行,则会覆盖已经存在的键值或者新建键值键值代码操作实践
这段代码实现了创建注册表和读取注册表的值并打印到DbgView#include <ntddk.h>// 定义内存分配标识, 注意只能定义四位字符#pragma warning( disable : 4100)#define MEM_TAG'MyTg'VOID MyRegDemo(){    // 定义好各种变量    OBJECT_ATTRIBUTES my_reg_attribute = {0};    UNICODE_STRING objectName;    RtlInitUnicodeString(&objectName, L"\\\\Registry\\\\Machine\\\\SOFTWARE\\\\Microsoft\\\\Windows NT\\\\CurrentVersion");    HANDLE my_reg;    UNICODE_STRING my_key_name;    RtlInitUnicodeString(&my_key_name, L"my_key");    KEY_VALUE_PARTIAL_INFORMATION key_infor;    PKEY_VALUE_PARTIAL_INFORMATION ac_key_infor = NULL;    ULONG ac_length;    NTSTATUS status;    // 定义键值    UNICODE_STRING my_key_value = RTL_CONSTANT_STRING(L"A_String");    // 定义UNICODE_STRING的其他部分    my_key_value.Length = my_key_value.MaximumLength = 0x12;    // 初始化attribute    InitializeObjectAttributes(      &my_reg_attribute,      &objectName,      OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,      NULL,      NULL    );    // 打开注册表对象    status = ZwOpenKey(&my_reg, KEY_ALL_ACCESS, &my_reg_attribute);    if (!NT_SUCCESS(status)) {      DbgPrint("ZwOpenKey failed: %08X\\n", status);      return;    }    // 写入一个键值    status = ZwSetValueKey(      my_reg,      &my_key_name,      0,      REG_SZ,      my_key_value.Buffer,      my_key_value.Length    );    if (!NT_SUCCESS(status)) {      DbgPrint("ZwSetValueKey failed: %08X\\n", status);      ZwClose(my_reg);      return;    }    // 读取刚刚写入后的键值,查询键值长度    status = ZwQueryValueKey(      my_reg,      &my_key_name,      KeyValuePartialInformation,      &key_infor,      sizeof(KEY_VALUE_PARTIAL_INFORMATION),      &ac_length    );    if (status != STATUS_BUFFER_OVERFLOW && status != STATUS_BUFFER_TOO_SMALL) {      DbgPrint("ZwQueryValueKey failed: %08X\\n", status);      ZwClose(my_reg);      return;    }    // 为查询结果分配内存    ac_key_infor = (PKEY_VALUE_PARTIAL_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, ac_length, MEM_TAG);    if (ac_key_infor == NULL) {      DbgPrint("ExAllocatePoolWithTag failed\\n");      ZwClose(my_reg);      return;    }    // 查询键值    status = ZwQueryValueKey(      my_reg,      &my_key_name,      KeyValuePartialInformation,      ac_key_infor,      ac_length,      &ac_length    );    if (!NT_SUCCESS(status)) {      DbgPrint("ZwQueryValueKey failed: %08X\\n", status);      ExFreePoolWithTag(ac_key_infor, MEM_TAG);      ZwClose(my_reg);      return;    }    // 将buffer转移到Unicode_String中    UNICODE_STRING print_key_value;    print_key_value.Length = print_key_value.MaximumLength = (USHORT)ac_key_infor->DataLength;    print_key_value.Buffer = (PWSTR)ExAllocatePoolWithTag(NonPagedPool, print_key_value.Length, MEM_TAG);    if (print_key_value.Buffer == NULL) {      DbgPrint("ExAllocatePoolWithTag for print_key_value failed\\n");      ExFreePoolWithTag(ac_key_infor, MEM_TAG);      ZwClose(my_reg);      return;    }    RtlCopyMemory(print_key_value.Buffer, ac_key_infor->Data, ac_key_infor->DataLength);    print_key_value.Buffer = L'\\0';    DbgPrint("%wZ\\n", &print_key_value);    // 释放分配的内存    ExFreePoolWithTag(print_key_value.Buffer, MEM_TAG);    ExFreePoolWithTag(ac_key_infor, MEM_TAG);    ZwClose(my_reg);}// 提供一个 Unload 函数只是为了让这个程序能动态卸载,方便调试VOID DriverUnload(PDRIVER_OBJECT driver){    DbgPrint("Driver is unloading...\\n");}// DriverEntry,入口函数,相当于mainNTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path){    MyRegDemo();    driver->DriverUnload = DriverUnload;   return STATUS_SUCCESS;}
运行前。

运行后,创建新键并打印。

LXGZJ237 发表于 2024-7-30 21:53

这排版........{:301_979:}

mmffddyy 发表于 2024-7-30 22:22

建议楼主优化下排版{:1_937:}{:1_937:}

romobin 发表于 2024-7-31 00:21

omg... 这代码看着太吃力了 可以用md重新排一下

wujun3000 发表于 2024-7-31 00:58

楼主编码辛苦了,收下后慢慢看

289 发表于 2024-7-31 03:37

很多自动换行的在线排版的 推荐LZ用下 我看了眼花缭乱!

coolpjh 发表于 2024-7-31 09:31

辛苦了 ,就是费眼睛

SMGER 发表于 2024-8-7 17:14

抱歉了,第一次发文章没弄好

SMGER 发表于 2024-8-7 17:15

LXGZJ237 发表于 2024-7-30 21:53
这排版........

不好意思啊,第一次发帖子没弄好
页: [1]
查看完整版本: Windows驱动开发中的文件和注册表