驱动开发:内核文件读写系列函数
在应用层下的文件操作只需要调用微软应用层下的API
函数及C库
标准函数即可,而如果在内核中读写文件则应用层的API显然是无法被使用的,内核层需要使用内核专有API,某些应用层下的API只需要增加Zw开头即可在内核中使用,例如本章要讲解的文件与目录操作相关函数,多数ARK反内核工具都具有对文件的管理功能,实现对文件或目录的基本操作功能也是非常有必要的。
首先无论在内核态还是在用户态,我们调用的文件操作函数其最终都会转换为一个IRP请求,并发送到文件系统驱动上的IRP_MJ_READ
派遣函数里面,这个读写流程大体上可分为如下四步;
- 对于FAT32分区会默认分发到
FASTFAT.SYS
,而相对于NTFS分区则会分发到NTFS.SYS
驱动上。 - 文件系统驱动经过处理后,就把IRP传给磁盘类驱动的
IRP_MJ_READ
分发函数处理,当磁盘类驱动处理完毕后,又把IRP传给磁盘小端口驱动。 - 在磁盘小端口驱动里,无论是读还是写,用的都是
IRP_MJ_SCSI
这个分发函数。 - IRP被磁盘小端口驱动处理完之后,就要依靠
HAL.DLL
进行端口IO,此时数据就真的从硬盘里读取了出来。
创建文件或目录
实现创建文件或目录,创建文件或目录都可调用ZwCreateFile()
这个内核函数来实现,唯一不同的区别在于当用户传入参数中包含有FILE_SYNCHRONOUS_IO_NONALERT
属性时则会默认创建文件,而如果包含有FILE_DIRECTORY_FILE
属性则默认为创建目录,该函数的微软定义以及备注信息如下所示;
NTSYSAPI NTSTATUS ZwCreateFile( |
参数DesiredAccess
用于指明对象访问权限的,常用的权限有FILE_READ_DATA
读取文件,FILE_WRITE_DATA
写入文件,FILE_APPEND_DATA
追加文件,FILE_READ_ATTRIBUTES
读取文件属性,以及FILE_WRITE_ATTRIBUTES
写入文件属性。
参数ObjectAttributes
指向了一个OBJECT_ATTRIBUTES
指针,通常会通过InitializeObjectAttributes()
宏对其进行初始化,当一个例程打开对象时由此结构体指定目标对象的属性。
参数ShareAccess
用于指定访问属性,通常属性有FILE_SHARE_READ
读取,FILE_SHARE_WRITE
写入,FILE_SHARE_DELETE
删除。
参数CreateDisposition
用于指定在文件存在或不存在时要执行的操作,一般而言我们会指定为FILE_OPEN_IF
打开文件,或FILE_OVERWRITE_IF
打开文件并覆盖,FILE_SUPERSEDE
替换文件。
参数CreateOptions
用于指定创建文件或目录,一般FILE_SYNCHRONOUS_IO_NONALERT
代表创建文件,参数FILE_DIRECTORY_FILE
代表创建目录。
相对于创建文件而言删除文件或目录只需要调用ZwDeleteFile()
系列函数即可,此类函数只需要传递一个OBJECT_ATTRIBUTES
参数即可,其微软定义如下所示;
NTSYSAPI NTSTATUS ZwDeleteFile( |
接下来我们就封装三个函数MyCreateFile()
用于创建文件,MyCreateFileFolder()
用于创建目录,MyDeleteFileOrFileFolder()
用于删除空目录。
|
运行如上代码,分别创建LySharkFolder
目录,并在其中创建lyshark.txt
最终再将其删除,输出效果如下;
重命名文件或目录
在内核中重命名文件或目录核心功能的实现依赖于ZwSetInformationFile()
这个内核函数,该函数可用于更改有关文件对象的各种信息,其微软官方定义如下;
NTSYSAPI NTSTATUS ZwSetInformationFile( |
这其中最重要的参数就是FileInformationClass
根据该参数的不同则对文件的操作方式也就不同,如果需要重命名文件则此处应使用FileRenameInformation
而如果需要修改文件的当前信息则应使用FilePositionInformation
创建链接文件则使用FileLinkInformation
即可,以重命名为例,首先我们需要定义一个FILE_RENAME_INFORMATION
结构并按照要求填充,最后直接使用ZwSetInformationFile()
并传入相关信息后即可完成修改,其完整代码流程如下;
|
运行后将会把C:\\MyCreateFolder\\lyshark.txt
目录下的文件改名为hello_lyshark.txt
,前提是该目录与该文件必须存在;
那么如果你需要将文件设置为只读模式或修改文件的创建日期,那么你就需要看一下微软的定义FILE_BASIC_INFORMATION
结构,依次填充此结构体并调用ZwSetInformationFile()
即可实现修改,该结构的定义如下所示;
typedef struct _FILE_BASIC_INFORMATION { |
当然如果你要修改日期你还需要自行填充LARGE_INTEGER
结构,该结构的微软定义如下所示,分为高位和低位依次填充即可;
|
我们就以修改文件属性为只读模式为例,其核心代码可以被描述为如下样子,相比于改名而言其唯一的变化就是更换了PFILE_BASIC_INFORMATION
结构体,其他的基本一致;
HANDLE hFile = NULL; |
读取文件大小
读取特定文件的所占空间,核心原理是调用了ZwQueryInformationFile()
这个内核函数,该函数可以返回有关文件对象的各种信息,参数传递上与ZwSetInformationFile()
很相似,其FileInformationClass
都需要传入一个文件类型结构,该函数的完整定义如下;
NTSYSAPI NTSTATUS ZwQueryInformationFile( |
本例中我们需要读入文件的所占字节数,那么FileInformation
字段就需要传入FileStandardInformation
来获取文件的基本信息,获取到的信息会被存储到FILE_STANDARD_INFORMATION
结构内,用户只需要解析该结构体fsi.EndOfFile.QuadPart
即可得到文件长度,其完整代码如下所示;
|
编译并运行如上程序,即可读取到C盘下的lyshark.exe
程序的大小字节数,如下图所示;
内核文件读写
内核读取文件可以使用ZwReadFile()
,内核写入文件则可使用ZwWriteFile()
,这两个函数的参数传递基本上一致,如下是读写两个函数的对比参数。
NTSYSAPI NTSTATUS ZwReadFile( |
读取文件的代码如下所示,分配非分页pBuffer
内存,然后调用MyReadFile()
函数,将数据读入到pBuffer
并输出,完整代码如下所示;
|
编译并运行这段代码,并循环输出lyshark.exe
文件的头16个字节的数据,效果图如下所示;
文件写入MyWriteFile()
与读取类似,如下通过运用文件读写实现了文件拷贝
功能,实现完整代码如下所示;
|
编译并运行这段程序,则自动将C:\\lyshark.exe
盘符下的文件拷贝到C:\\LyShark\\new_lyshark.exe
目录下,实现效果图如下所示;
文件读写传递
通过如上学习相信你已经掌握了如何使用文件读写系列函数了,接下来将封装一个文件读写驱动,应用层接收,驱动层读取;
此驱动部分完整代码如下所示;
|
客户端完整代码如下所示;
|
通过驱动加载工具将WinDDK.sys
拉起来,然后启动客户端进程,即可输出ntoskrnl.exe
的文件数据,如下图所示;