首页
社区
课程
招聘
[原创]
发表于: 2024-7-30 19:06 2379

[原创]

2024-7-30 19:06
2379

文件与注册表

因为这两者都设计到对某种内核对象的操作,所以可以整理到一起。

OBJECT_ATTRIBUTES结构

内核中的重要结构,内核对象的属性结构。不论是文件还是注册表还是其他种类的内核对象,每个内核对象都有一个OBJECT_ATTRIBUTES结构来规定自身属性。

1
2
3
4
5
6
7
8
typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;                    // 结构体的大小,以字节为单位
    HANDLE RootDirectory;            // 指向根目录的句柄,如果是绝对路径则为 NULL
    PUNICODE_STRING ObjectName;      // 指向对象名称的指针
    ULONG Attributes;                // 对象的属性标志
    PVOID SecurityDescriptor;        // 安全描述符,定义对象的安全性
    PVOID SecurityQualityOfService;  // 安全质量服务,用于IPC(进程间通信)
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
1
2
3
4
5
6
7
8
//初始化使用InitializeObjectAttributes
VOID InitializeObjectAttributes(
OUT POBJECT_ATTRIBUTES InitializedAttributes, //Object_Attribute变量的指针
IN PUNICODE_STRING ObjectName,                //对象的路径
IN ULONG Attributes,                          //OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE
IN HANDLE RootDirectory,                      //如果填NULL,则第二个参数的路径为绝对路径,如果填入的是一个目录句柄,则第二个参数为相对路径
IN PSECURITY DESCRIPTOR SecurityDescriptor    //NULL
); 

初始化示例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 已初始化,可以用于创建或打开对象
}

文件操作

打开和关闭

打开和关闭文件 ZwCreateFileZwClose

1
2
3
4
5
6
7
8
9
10
11
12
13
//ZwCreateFile
NTSTATUS ZwCreateFile(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES Object_Attribute,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL
IN 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详细结构

    1
    2
    3
    4
    5
    6
    7
    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的宏

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    FILE_SUPERSEDE
     
    替换文件:如果文件存在,将其替换;如果文件不存在,则创建一个新文件。
    类似于:“如果文件存在,替换它;如果文件不存在,创建它”。
    FILE_OPEN
     
    打开文件:如果文件存在,打开它;如果文件不存在,操作将失败。
    类似于:“仅在文件存在时打开它”。
    FILE_CREATE
     
    创建文件:如果文件不存在,创建它;如果文件存在,操作将失败。
    类似于:“仅在文件不存在时创建它”。
    FILE_OPEN_IF
     
    打开或创建文件:如果文件存在,打开它;如果文件不存在,则创建一个新文件。
    类似于:“如果文件存在,打开它;如果文件不存在,创建它”。
    FILE_OVERWRITE
     
    覆盖文件:如果文件存在,覆盖它;如果文件不存在,操作将失败。
    类似于:“仅在文件存在时覆盖它”。
    FILE_OVERWRITE_IF
     
    覆盖或创建文件:如果文件存在,覆盖它;如果文件不存在,则创建一个新文件。
    类似于:“如果文件存在,覆盖它;如果文件不存在,创建它”。
  • CreateOptions: 创建或打开文件的选项(例如作为目录打开、关闭时删除)。这些选项指定了文件操作的一些附加行为。

  • EaBuffer: 指向用于传递扩展属性(EA)到文件系统的缓冲区的指针。扩展属性是文件系统特定的元数据。

  • EaLength: EaBuffer 的长度(以字节为单位)。

该函数的参数组合很复杂,下面是一个参数使用模板

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
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

1
2
//文件关闭,直接使用打开的对象句柄关闭即可
ZwClose(file_handle);

读和写

1
2
3
4
5
6
7
8
9
10
11
//读操作
NTSTATUS ZwReadFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL
IN PIO_APC_ROUTINE ApCRoutine OPTIONAL
IN PVOID ApContextOPTIONAL,
OUT PIO_STATUS_BLOCK IostatusBlock,
OUT PVOID Buffer,
IN ULONG Length,
IN PLARGE_INTEGER ByteOffset OPTIONAL,
IN PULONG Key OPTIONAL);
  • 参数解释(通用比较复杂,给出示例代码)

    参数解释

    1. FileHandle
      • 类型:HANDLE
      • 解释:文件句柄,指向要读取的文件或设备。该句柄必须是由 ZwCreateFileZwOpenFile 返回的,并且必须具有读取权限。
    2. Event
      • 类型:HANDLE
      • 解释:一个可选的事件句柄。如果提供,该事件将在读取操作完成时被设置为有信号状态。如果不需要,可以设置为 NULL
    3. ApcRoutine
      • 类型:PIO_APC_ROUTINE
      • 解释:一个可选的 APC(异步过程调用)例程指针。读取操作完成后将调用此例程。如果不需要,可以设置为 NULL
    4. ApcContext
      • 类型:PVOID
      • 解释:传递给 APC 例程的上下文信息。如果 ApcRoutineNULL,此参数将被忽略。
    5. IoStatusBlock
      • 类型:PIO_STATUS_BLOCK
      • 解释:指向一个 IO_STATUS_BLOCK 结构,该结构接收读取操作的完成状态和信息。
    6. Buffer
      • 类型:PVOID
      • 解释:指向要接收读取数据的缓冲区。
    7. Length
      • 类型:ULONG
      • 解释:要读取的字节数。
    8. ByteOffset
      • 类型:PLARGE_INTEGER
      • 解释:要读取的字节偏移量。可以设置为 NULL,以从当前文件指针位置读取。
    9. Key
      • 类型:PULONG
      • 解释:用于异步 I/O 操作的键值。对于同步操作,可以设置为 NULL
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
NTSTATUS status;
HANDLE fileHandle;
IO_STATUS_BLOCK ioStatusBlock;
CHAR buffer[100];
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是进行写入的。

1
2
3
4
5
6
7
8
9
10
11
NTSTATUS ZwWriteFile(
  HANDLE           FileHandle,
  HANDLE           Event,
  PIO_APC_ROUTINE  ApcRoutine,
  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
1
2
3
4
5
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:ZwQueryValueKeyZwSetValueKey

1
2
3
4
5
6
7
8
9
10
//读键值
NTSTATUS ZwQueryValueKey(
IN HANDLE KeyHandle,           //打开的注册表对象
IN PUNICODE_STRING ValueName,  //要修改键值的名字
//规定要获取的格式,通常填KeyValuePartialInformation,读取键类型和值
IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
OUT PVOID KeyValueInformation, //返回结果的buffer
IN ULONG Length,               //传入KeyValueInformation的大小
OUT PULONG ResultLength        //返回实际需要的长度
);

关于KeyValueInformation,如果第三个参数填入KeyValuePartialInformation,则一个KEY_VALUE_PARTIAL_INFORMATION结构体

1
2
3
4
5
6
typedef struct KEY_VALUE_PARTIAL_INFORMATION{
ULONG TitleIndex;
ULONG Type;                 //数据类型
ULONG DataLength;           //数据长度
UCHAR Data[1];              //变长buffer
}KEY_VALUE_PARTIAL_INFORMATION,*PKEY_VALUE_PARTIAL_INFORMATION;

对于ZwQueryValueKey的参数设置解析:因为我们在读取注册表时,不能确定该键值究竟是哪种类型,所以应该利用好ResultLength参数用两次调用来最终确定到底需要多大的缓冲区,并用ExAllocatePoolWithTag来动态分配内存。

1
2
3
4
5
6
7
8
9
//写键值
NTSTATUS ZwSetValueKey(
IN HANDLE KeyHandle,           //键对象
IN PUNICODE_STRING ValueName,  //待设置键值的名字
IN ULONG TitleIndex OPTIONAL,  //始终为0
IN ULONG Type,                 //待设置键值的类型
IN PVOID Data,                 //待设置键值存储数据的缓冲区
IN ULONG Datesize              //缓冲区大小
);

tips: ZwSetValueKey如果正常执行,则会覆盖已经存在的键值或者新建键值

键值代码操作实践

这段代码实现了创建注册表和读取注册表的值并打印到DbgView

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#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[print_key_value.Length / sizeof(WCHAR) - 1] = 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,入口函数,相当于main
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
    MyRegDemo();
 
    driver->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

运行前。

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


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 2
支持
分享
最新回复 (3)
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
楼主的帖子名字是不是漏掉了
2024-7-30 20:44
0
雪    币: 168
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
mb_ldbucrik 楼主的帖子名字是不是漏掉了
对,第一次发文章没弄好,后面发现看雪改帖子好像还要申请,感觉优点麻烦就没有去弄了
2024-8-2 21:50
0
雪    币: 168
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
mb_ldbucrik 楼主的帖子名字是不是漏掉了
文章介绍了下Windows 驱动编程里面注册表文件的操作,是我的学习笔记
2024-8-2 21:52
0
游客
登录 | 注册 方可回帖
返回
//