文件操作
概述
- 在内核中也是可以操作文件的,在用户层中我们操作文件是会提供一个路径,但是在内核中,我们需要在路径前加上
\\??\\
。
使用OBJECT_ATTRIBUTES
- 在内核中进行文件操作时并不直接接受一个字符串路径,使用者必须首先填写一个
OBJECT_ATTRIBUTE
结构。这个结构总是被InitalizeObjectAttribute
函数来初始化。 - 对象属性结构体大致如下:
typedef struct _OBJECT_ATTRIBUTE
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attribute;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
}OBJECT_ATTRIBUTE,*POBJECT_ATTRIBUTE;
- 这个结构并不需要自己填写,而是使用一个看似是函数实则是一个有参宏
InitalizeObjectAttribute
来初始化。
VOID InitializeObjectAttribute(
OUT POBJECT_ATTRIBUteS InitializeAttributes,//要被初始化的结构体
IN PUNICODE_STRING ObjectName, //对象名字
IN ULONG Attribute, //对象结构体的属性
IN HANDLE RootDirectory, //一般为NULL
IN PSECURITY_DESCRIPTOR SecurityDescriptor//一般为NULL
);
-
对象结构体的属性可以填写OBJ_CASE_INSENSITIVE,表明不区分大小写
也可以加上OBJ_KERNEL_HANDLE表示打开的文件句柄是一个"内核句柄“,其余可查看MSDN内核句柄比应用层句柄使用更方便,可以不受线程和进程的限制,在任何线程中都可以读/写,同时打开内核我呢见句柄不需要估计当前进程是否有权限访问该文件的问题。
打开和关闭文件
打开文件
- 使用函数如下:
NTSTATUS ZwCreateFile(
_OUT_ PHANDLE FileHandle, //输出文件句柄
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_opt_ PLAGE_INTEGER AllocationSize,
_In_ ULONG FileAttributes,
_In_ ULONG SharAccess,
_In_ ULONG CreateDisposition,
_In_ ULONG CreateOptions,
_In_ PVOID EaBuffer OPTIONAL,
_In_ ULONG EaLength);
- 下面是各个参数的详细说明:
- FileHandle:输出文件句柄,如果函数调用成功(返回STATUS_SUCCESS),那么打开的文件句柄就在这里。
- DesiredAccess:申请的各种权限如下:
属性 | 说明 |
---|---|
FILE_WRITE_DATA | 写属性 |
FILE_READ_DATA | 读属性 |
DELETE | 删除或给文件改名 |
FILE_WRITE_ATTRIBUTES | 写文件属性 |
FILE_READ_ATTRIBUTES | 读文件属性 |
GENERIC_ALL | 全部权限 |
- ObjectAttribute:对象描述,OBJECT_ATTRIBUTES结构地址。
- IoStatusBlock:也是一个结构,结构在内核开发中经常使用
typedef struct _IO_STATUS_BLOCK{
union{
NTSTATUS Status;
PVOID Pointer;
};
ULONG_PTR Information;
}IO_STATUS_BLOCK,*PIO_STATUS_BLOCK;
一般使用Status保存返回结果,成功返回STATUS_SUCCESS,进一步信息在Information中,不同的情况下返回的Information信息意义不同,有以下几种情况:
FILE_CREATED:文件被成功创建
FILE_OPENED:文件被打开了
FILE_OVERWRITTEN:文件被覆盖了
FILE_SUPERSEDED:文件被替代了
FILE_EXISTS:文件已存在
FILE_DOES_NOT_EXIST:文件不存在
另外在传输成功后,会将information设置为传输的字节数
- AlloctionSize:是一个指针,指向64位整数,定义文件初始分配的大小,参数仅关系到创建或重写文件操作。
- FileAttributes:参数控制新建立的文件属性,一般设置为0或FILE_ATTRIBUTe_NORMAL
- ShareAccess:共享访问,FILE_SHARE_READ,FILE_SHARE_WRITE,FILE_SHARE_DELETE
- CreateDisposition:参数说明了这次打开的意图,可以选择如下的宏,注意这些选择不能使用“|”组合”
值 | 文件存在采取措施 | 文件不存在时操作 |
---|---|---|
FILE_SUPERSEDE | 替换文件 | 创建文件 |
FILE_CREATE | 返回错误 | 创建文件 |
FILE_OPEN | 打开文件 | 返回错误 |
FILE_OPEN_IF | 打开文件 | 创建文件 |
FILE_OVERWRITE | 打开文件,并覆盖它 | 返回错误 |
FILE_OVERWRITE_IF | 打开文件,并覆盖它 | 创建文件 |
-
CreatePotions:属性,创建新的还是打开存在的,可以使用
FILE_NON_DIRECTORY_FILE|FILE_SYNCHRONOUS_IO_NONALERT
,此时文件被同步打开,而且打开的是文件(不是目录,创建目录使用FILE_DIRECTORY_FILE
),不过要想同步打开,前面的DesiredAccess参数必须含有SYNCHRONIZE
-
一个标准的模板:
NTStATUS status;
//返回的文件句柄
HANDLE hFile=NULL;
//初始化文件对象属性结构体
UNICODE_STRING fileName=RTL_CONSTANT_STRING(L"\\??\\C:\\1,txt");
OBJECT_ATTRIBUTES objAttributes;
//初始化
InitializeObjectAttributes(
&objAttributes,
&fileName,
OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,
NULL,
NULL);
//以FILE_OPEN_IF的方式打开文件
status=ZwCreateFile(
&hFile,
GENERIC_READ|GENERIC_WRITE,
&objAttributes,
&io_status,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN_IF,
FILE_NON_DIRECTORY_FILE|FILE_RANDOM_ACCESS|
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
- 关闭文件句柄使用
ZwCloase
函数,内核句柄的关闭不需要和打开在同一进程中。 - 微软还提供了一个函数
ZwOpenFile
用于专门打开文件,他更简单
ZwOpenFile(
_Out_ PHANDLE FileHandle;
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_ ULONG ShareAccess,
_In_ ULONG OpenOptions
);
文件读/写操作
读
ZwReadFile
NTSTATUS ZwReadFile(
_In_ HANDLE FileHandle, //句柄
_In_opt_ HANDLE Event, //为NULL即可,一个用于异步完成读时的事件
_In_opt_ PIO_APC_ROUTINE ApcRoutine,//为NULL即可,回调例程,用于异步完成读时
_In_opt_ PVOID ApcContext,//为NULL即可
_Out_ PIO_STATUS_BLOCK IoStatusBlock,//返回函数调用结果状态
_Out_ PVOID Buffer,//读取到文件内容的缓冲区
_In_ ULONG Length,//缓冲去长度
_In_ PLARGE_INTEGER ByteOffset,//尧都区的文件的偏移量,也就是读取的位置,一般不要设置为NULL,文件句柄不一定支持直接读取当前偏移量
_In_opt_ PULONG Key//为NULL即可
);
//返回值:成功返回STATUS_SUCCESS,只要能读取到任意多个字节,返回值都成功,但是如果仅读取文件长度之外的部分,返回STATUS_END_OF_FILE
写
- 和读参数完全相同
NTSTATUS ZwWriteFile(
_In_ HANDLE FileHandle, //句柄
_In_opt_ HANDLE Event, //为NULL即可,一个用于异步完成读时的事件
_In_opt_ PIO_APC_ROUTINE ApcRoutine,//为NULL即可,回调例程,用于异步完成读时
_In_opt_ PVOID ApcContext,//为NULL即可
_Out_ PIO_STATUS_BLOCK IoStatusBlock,//返回函数调用结果状态
_Out_ PVOID Buffer,//读取到文件内容的缓冲区
_In_ ULONG Length,//缓冲去长度
_In_ PLARGE_INTEGER ByteOffset,//尧都区的文件的偏移量,也就是读取的位置,一般不要设置为NULL,文件句柄不一定支持直接读取当前偏移量
_In_opt_ PULONG Key//为NULL即可
);
使用示例
- 完成打开一个文件获取其内容,然后在同文件夹下创建新的文件,然后将文件内容进行拷贝,实现一个自定义的ZwCopyFile函数
/*
完成打开一个文件获取其内容,
然后在同文件夹下创建新的文件,
然后将文件内容进行拷贝,
实现一个自定义的ZwCopyFile函数
*/
#include <ntddk.h>
VOID OnDriverUnload(PDRIVER_OBJECT pDriver)
{
pDriver;
KdPrint(("驱动被卸载\n"));
}
/*
函数实现文件拷贝功能
参数1:源文件路径
参数2:目标文件路径
//文件路径要使用\\??\\..的规范定义
*/
NTSTATUS MyZwCopyFile(PUNICODE_STRING srcFilePath,PUNICODE_STRING dstFilePath)
{
__try
{
//初始化对象属性结构
OBJECT_ATTRIBUTES srcObjAttributes;
OBJECT_ATTRIBUTES dstObjAttributes;
InitializeObjectAttributes(&srcObjAttributes, srcFilePath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
InitializeObjectAttributes(&dstObjAttributes, dstFilePath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
//创建文件句柄
HANDLE hSrc = NULL, hDst = NULL;
//用于拷贝的缓冲区
PVOID buff = NULL;
//接受文件大小结构体
LARGE_INTEGER byteOffset = { 0 };
//返回信息结构体
IO_STATUS_BLOCK ioStatusBlock = { 0 };
NTSTATUS status;
/*开始进行正式拷贝操作*/
//1.打开两个文件
status = ZwCreateFile(&hSrc, GENERIC_READ, &srcObjAttributes, &ioStatusBlock, NULL,
FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF,
FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (!NT_SUCCESS(status))
{
//不成功,释放资源,打印错误信息
KdPrint(("打开文件%wZ失败", &srcFilePath->Buffer));
return status;
}
status = ZwCreateFile(&hDst, GENERIC_WRITE, &dstObjAttributes, &ioStatusBlock, NULL,
FILE_ATTRIBUTE_NORMAL, FILE_SHARE_WRITE, FILE_SUPERSEDE,
FILE_NON_DIRECTORY_FILE | FILE_RANDOM_ACCESS | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (!NT_SUCCESS(status))
{
//不成功,打印错误信息
if (hSrc!=NULL)
{
ZwClose(hSrc);
}
KdPrint(("打开文件%wZ失败", &dstFilePath->Buffer));
return status;
}
//2.为buff开辟4BK空间
ULONG length = 4 * 1024;
buff = ExAllocatePoolWithTag(NonPagedPool, length, 'Tag1');
//3.循环进行拷贝,每次读取4KB源文件拷贝到目标文件中
while (1)
{
length = 4 * 1024; //重新设置读取大小为4kb
//读取源文件
status = ZwReadFile(hSrc, NULL, NULL, NULL, &ioStatusBlock, buff, length, &byteOffset, NULL);
if (!NT_SUCCESS(status))
{
//如果返回结果为STATUS_END_OF_FILE说明文件读取完毕了
if (status == STATUS_END_OF_FILE)
status = STATUS_SUCCESS;
break;
}
//获得实际读取到的长度
length = ioStatusBlock.Information;
//写入读取到的大小的内容到目标文件
status = ZwWriteFile(hDst, NULL, NULL, NULL, &ioStatusBlock, buff, length, &byteOffset, NULL);
if (!NT_SUCCESS(status))
{
break;
}
//移动偏移byteOffset直到读取文件到末尾
byteOffset.QuadPart += length;
}
//退出前释放所有资源
if (hSrc!=NULL)
{
ZwClose(hSrc);
}
if (hDst!=NULL)
{
ZwClose(hDst);
}
if (buff!=NULL)
{
ExFreePool(buff);
}
return status;
}
__except(EXCEPTION_EXECUTE_HANDLER) {
return STATUS_SUCCESS;
}
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath)
{
pPath;
KdPrint(("驱动已被加载\n"));
NTSTATUS status = STATUS_SUCCESS;
pDriver->DriverUnload = OnDriverUnload;
//定义两个文件路径
UNICODE_STRING srcPath = RTL_CONSTANT_STRING(L"\\??\\C:\\1.txt");
UNICODE_STRING dstPath = RTL_CONSTANT_STRING(L"\\??\\C:\\2.txt");
//调用拷贝函数
status=MyZwCopyFile(&srcPath, &dstPath);
return status;
}
遍历文件
- 内核层对文件遍历只提供了一个函数
ZwQueryDirectoryFile
,这个函数第9个参数适用于控制此函数的返回信息类型,如果为true,则函数返回“第一个文件信息”否则返回当前的“文件列表”。 - 我们可以通过“第一个文件信息”来获取第一个文件,通过“文件列表”获取剩余的以及最后一个文件信息,然后封装两个函数
MyZwFindFirstFile
和·MyZwFindNext
来完成文件的遍历 - 记得包含头文件
ntifs.h
#include <ntifs.h>
#include <ntddk.h>
VOID OnDriverUnload(PDRIVER_OBJECT pDriver)
{
pDriver;
KdPrint(("驱动被卸载\n"));
}
/*
函数用来获取第一个文件信息
参数1:文件句柄
参数2:总的文件信息列表长度
参数3:文件信息列表
参数4:第一个文件信息长度
参数5:第一个文件信息
*/
BOOLEAN MyZwFindFirstFile(
_In_ HANDLE hFile,
_In_ ULONG length,
_Out_ PFILE_BOTH_DIR_INFORMATION pDirList,
_In_ ULONG firstLength,
_Out_ PFILE_BOTH_DIR_INFORMATION pFirstDir
)
{
NTSTATUS status;
IO_STATUS_BLOCK ioStatusBlock = { 0 };
//获取第一个文件信息
status = ZwQueryDirectoryFile(
hFile, //文件句柄
NULL, NULL, NULL, //为NULL
&ioStatusBlock, //返回信息
pFirstDir, //第一个文件信息
firstLength, //文件信息长度
FileBothDirectoryInformation,//查询模式
TRUE, //是否返回一条其实信息
NULL, //文件句柄指向的文件,一般为NULL
FALSE); //是否从目录的第一项开始扫描
if (!NT_SUCCESS(status))
{
return FALSE;
}
//接着获取文件列表
status = ZwQueryDirectoryFile(hFile, NULL, NULL, NULL, &ioStatusBlock, pDirList, length, FileBothDirectoryInformation, FALSE, NULL, FALSE);
return NT_SUCCESS(status);
}
/*
函数用来遍历下一个文件
参数1:获取到的文件列表信息
参数2:传出一个文件信息
参数3:列表中的偏移
*/
BOOLEAN MyZwFindNext(
_In_ PFILE_BOTH_DIR_INFORMATION pDirList,
_Out_ PFILE_BOTH_DIR_INFORMATION pDirInfo,
_In_opt_ LONG* offset
)
{
//如果有下一项,则移动指针指向下一项
PFILE_BOTH_DIR_INFORMATION pDir = (PFILE_BOTH_DIR_INFORMATION)((PCHAR)pDirList + *offset);
LONG fileInfoLength = 0;
if (pDir->FileName[0]!='\0')
{
fileInfoLength = sizeof(FILE_BOTH_DIR_INFORMATION);
memcpy(pDirInfo, pDir, fileInfoLength + pDir->FileNameLength);
*offset += pDir->NextEntryOffset;
if (pDir->NextEntryOffset==0)
{
//没有下一项了
*offset += fileInfoLength + pDir->FileNameLength;
}
return TRUE;
}
return FALSE;
}
//打开文件
//参数1:带符号的文件路径
//参数2:文件是否为文件夹标记
NTSTATUS MyZwCreateFile(PHANDLE hFile,PUNICODE_STRING pFilePath, BOOLEAN bIsDir)
{
//定义需要的参数
NTSTATUS status;
IO_STATUS_BLOCK ioStatusBlock = { 0 };
ULONG ShareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
ULONG CreateOpt =FILE_RANDOM_ACCESS | FILE_SYNCHRONOUS_IO_NONALERT;
CreateOpt |= bIsDir ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE;
//定义对象属性结构
OBJECT_ATTRIBUTES objAttribute = { 0 };
InitializeObjectAttributes(&objAttribute, pFilePath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
//依据文件类型定义打开操作的附加标志
//打开文件
status = ZwCreateFile(hFile, GENERIC_ALL, &objAttribute, &ioStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, ShareAccess, FILE_OPEN_IF, CreateOpt, NULL, 0);
return status;
}
//遍历目录下的所有文件
//参数为路径,必须传入带符号的路径格式
// \\??\\...
NTSTATUS EnumFile(PUNICODE_STRING pDirPath)
{
//打开文件
HANDLE hFile = NULL;
NTSTATUS status;
status = MyZwCreateFile(&hFile, pDirPath, TRUE);
if (!NT_SUCCESS(status))
{
return status;
}
//为文件名预留空间
LONG fileInfoLength = sizeof(FILE_BOTH_DIR_INFORMATION) + 270 * sizeof(WCHAR);
LONG length = fileInfoLength * 0x256; //假设最多有256个文件
PFILE_BOTH_DIR_INFORMATION pFileList = NULL;
PFILE_BOTH_DIR_INFORMATION pFileTemp = NULL;
pFileList = (PFILE_BOTH_DIR_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, length, 'Tag1');
pFileTemp = (PFILE_BOTH_DIR_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, fileInfoLength, 'Tag2');
if (MyZwFindFirstFile(hFile, length, pFileList, fileInfoLength, pFileTemp))
{
LONG offset = 0;
do
{
//进行判断
WCHAR fileName[0x256] = { 0 };
RtlCopyMemory(fileName, pFileTemp->FileName, pFileTemp->FileNameLength);
//排除特殊目录
if (!wcscmp(fileName, L"..") || !wcscmp(fileName, L"."))
{
continue;
}
//输出信息
if (pFileTemp->FileAttributes&FILE_ATTRIBUTE_DIRECTORY)
{
KdPrint(("[目录]:%ls\n", fileName));
}
else
{
KdPrint(("[文件]:%ls\n", fileName));
}
memset(pFileTemp, 0, fileInfoLength);
} while (MyZwFindNext(pFileList, pFileTemp, &offset));
}
//释放资源
if (pFileList != NULL)
{
ZwClose(pFileList);
}
if (pFileTemp != NULL)
{
ZwClose(pFileTemp);
}
return status;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath)
{
pPath;
pDriver->DriverUnload = OnDriverUnload;
NTSTATUS status = STATUS_SUCCESS;
//使用C盘进行测试
UNICODE_STRING dirPath = RTL_CONSTANT_STRING(L"\\??\\C:\\");
status = EnumFile(&dirPath);
return status;
}