0%

驱动的文件操作

文件操作

相对于Windows用户模式下的文件操作,稍微复杂了一些,但也并不难。

创建文件

ZwCreateFile这个函数就在驱动层进行文件创建的一个函数,函数的原型:

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

这几个参数里面比较重要的就是第三个参数,这个参数其实是一个结构体,这个结构体里面是通过InitializeObjectAttributes这个函数生成的,这个函数比较关键的就是第二个参数,我们需要填写的是一个Unicode字符串,是我们生成文件的路径,这个路径需要注意的是,我们需要用符号连接名的方式进行存储,例如:"\\?\\C:\\1.log",这里两个\是取消转义。

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
void CreateFileTest(UNICODE_STRING * FileName)
{
OBJECT_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK iostatus;
HANDLE hfile;
UNICODE_STRING logFileUniocdeString;
//RtlInitUnicodeString(&logFileUniocdeString, L"\\??\\C:\\1.log");
//或者写成"\\Device\\HarddiskVolume1\\1.LOG"
logFileUniocdeString.Buffer = (PWSTR)ExAllocatePool(PagedPool, 1024);
logFileUniocdeString.MaximumLength = 1024;
WCHAR* wideString = L"\\??\\C:\\";
logFileUniocdeString.Length = wcslen(wideString) * 2;
RtlCopyMemory(logFileUniocdeString.Buffer, wideString, logFileUniocdeString.Length);
RtlAppendUnicodeStringToString(&logFileUniocdeString, FileName);
//初始化ObjectAttributes
InitializeObjectAttributes(&objectAttributes, &logFileUniocdeString, OBJ_CASE_INSENSITIVE, NULL, NULL);
//创建文件
NTSTATUS ntStatus = ZwCreateFile(&hfile, GENERIC_WRITE, &objectAttributes, &iostatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (NT_SUCCESS(ntStatus))
{
KdPrint(("Create File Successfully\n"));
}
else
{
KdPrint(("Create File unsuccessfully\n"));
}
/*这里进行文件的操作*/
//关闭文件句柄
ZwClose(hfile);
ExFreePool(logFileUniocdeString.Buffer);//释放之前申请的内存
}

还有一个iostatus,这个里面有个information,这个在后面是比较有用的。

修改查询文件属性

主要使用的是zwSetInformationFilezwQueryInformationFile这两个函数的参数基本相同,唯一需要注意的是我第三个参数,查询是out的,设置是in的,这个查询和设置的参数是一个PVOID,这个是取决于我们使用的结构体,有文件信息,基本信息,文件名信息,文件指针信息,这些就不记录了,网上很多。

读写文件

这个相对和用户模式下的没啥太大的区别,基本上操作类似可以说。
函数的原型:

1
2
3
4
5
6
7
8
9
10
11
NTSTATUS ZwWriteFile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_ PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
);

第一个参数是文件的句柄就是我们之前打开的,二三四参数一般为NULL,第五个参数就是iostatus,这个里面的information代表我们实际操作的自己数目,第六个参数是我们的缓冲区,第七个参数是我们写入数据的大小,第八个参数是我们开始的写入的偏移量,最后一个参数一般为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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void CreateFileTest(UNICODE_STRING * FileName)
{
OBJECT_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK iostatus;
HANDLE hfile;
UNICODE_STRING logFileUniocdeString;
//RtlInitUnicodeString(&logFileUniocdeString, L"\\??\\C:\\1.log");
//或者写成"\\Device\\HarddiskVolume1\\1.LOG"
logFileUniocdeString.Buffer = (PWSTR)ExAllocatePool(PagedPool, 1024);
logFileUniocdeString.MaximumLength = 1024;
WCHAR* wideString = L"\\??\\C:\\";
logFileUniocdeString.Length = wcslen(wideString) * 2;
RtlCopyMemory(logFileUniocdeString.Buffer, wideString, logFileUniocdeString.Length);
RtlAppendUnicodeStringToString(&logFileUniocdeString, FileName);
//初始化ObjectAttributes
InitializeObjectAttributes(&objectAttributes, &logFileUniocdeString, OBJ_CASE_INSENSITIVE, NULL, NULL);
//创建文件
NTSTATUS ntStatus = ZwCreateFile(&hfile, GENERIC_WRITE, &objectAttributes, &iostatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (NT_SUCCESS(ntStatus))
{
KdPrint(("Create File Successfully\n"));
}
else
{
KdPrint(("Create File unsuccessfully\n"));
}
/*这里进行文件的操作*/
#define BUFFER_SIZE 1024
PUCHAR pBuffer = (PUCHAR)ExAllocatePool(PagedPool, BUFFER_SIZE);
RtlFillMemory(pBuffer, BUFFER_SIZE, 0xAA);
KdPrint(("将写入文件%d个字节\n", BUFFER_SIZE));
ZwWriteFile(hfile, NULL, NULL, NULL, &iostatus, pBuffer, BUFFER_SIZE, NULL, NULL);
KdPrint(("已经写入文件%d个字节\n", iostatus.Information));//写文件
RtlFillMemory(pBuffer, BUFFER_SIZE, 0xBB);
KdPrint(("追加数据开始\n"));
LARGE_INTEGER number;
number.QuadPart = 1024i64;//设置文件指针,i64代表是这个类型
ZwWriteFile(hfile, NULL, NULL, NULL, &iostatus, pBuffer, BUFFER_SIZE, &number, NULL);
KdPrint(("追加数据:%d\n", iostatus.Information));
//关闭文件句柄
ZwClose(hfile);
ExFreePool(pBuffer);
ExFreePool(logFileUniocdeString.Buffer);//释放之前申请的内存
}

注意我们在CreateFile中使用的是FILE_OPEN_IF,可以覆盖也可以生成。
其他的话呢就是一个文件追加数据。
读取文件的话呢,需要注意的是我们需要知道文件的大小,这个文件大小是通过查询文件属性获得的,然后也是通过缓冲区进行查询,这里不记录了,也比较简单,也就是把写的一些参数改为了读。

注册表操作

其实相对的注册表操作十分类似于文件的操作,就连参数中的传递过去的结构体也都是一样的。
打开注册表:

1
2
3
4
5
6
7
8
9
10
NTSTATUS 
ZwCreateKey(
OUT PHANDLE KeyHandle,
IN ACCESS_MASK DesiredAccess, //访问权限,一般为KEY_ALL_ACCLESS
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN ULONG TitleIndex, //一般为NULL
IN PUNICODE_STRING Class OPTIONAL, //一般为NULL
IN ULONG CreateOptions, //一般为REG_OPTION_NON_VOLATILE
OUT PULONG Disposition OPTIONAL //返回是打开成功还是创建成功
);

ObjectAttributes和之前的文件操作是一样的,Disposition这个代表是否是已经存在了的注册表项。

打开注册表的话呢,注意的就是打开的权限,一般要ALL。

1
2
3
4
5
6
NTSTATUS 
ZwOpenKey(
OUT PHANDLE KeyHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);

设置注册表项:

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 DataSize //记录键值数据大小
);

需要注意的是,数据和数据大小是要根据键值类型进行决定的。

查询注册表项:

1
2
3
4
5
6
7
8
9
NTSTATUS 
ZwQueryValueKey(
IN HANDLE KeyHandle,
IN PUNICODE_STRING ValueName, //要查询的键名
IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, //查询的类别
OUT PVOID KeyValueInformation, //返回查询的信息的是
IN ULONG Length, //要查数据的长度
OUT PULONG ResultLength //实际查询数据的长度
);

这个函数要注意是我们一般需要用到两次,首先第一次我们不知道数据的长度,所以我们的Length参数只能传输为0,那个ResultLength就是我们的真是要查询的长度,并且我们还需要指定查询的信息类型,这个很像是查询文件的属性,这个值可以是KeyValueBasicInformation ,KeyValueFullInformation,KeyValuePartialInformation,我们就要为KeyValueInformation这个参数申请一块内存,是我们查询类型大小的内存块,然后第二次调用的时候我们就需要将查询数据的大小设置成刚才查询出来的大小了。

枚举子健和枚举子项很是相似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
NTSTATUS 
ZwQueryKey(
IN HANDLE KeyHandle,
IN KEY_INFORMATION_CLASS KeyInformationClass,
OUT PVOID KeyInformation,
IN ULONG Length,
OUT PULONG ResultLength
);



NTSTATUS
ZwEnumerateKey(
IN HANDLE KeyHandle,
IN ULONG Index,
IN KEY_INFORMATION_CLASS KeyInformationClass,
OUT PVOID KeyInformation,
IN ULONG Length,
OUT PULONG ResultLength
);

第一个函数得到的KeyInformation里有子项的个数,然后用第二个函数再获得子项的名称,这两个函数和查询注册表键值是类似的,都是要执行两次,枚举子键只是把ZwEnumerateKey换成了ZwEnumerateValueKey,这两个函数调用是类似的。

删除的话呢就只有一个参数,就是打开的句柄,但是需要注意的是,我们删除的话呢项目是不允许有子健的。

在DDK中为了简便操作,提供了Rtl开头的函数(感觉应该也是宏),操作起来比较简单,而且还有很多的功能。