主要学习在内核中的文件操作,包括文件的打开,创建,读取,写入,查询文件属性等。
涉及的API和宏函数
- ZwOpenFile
- ZwCreateFile
- ZwQueryInformationFile
- ZwReadFile
- ZwWriteFile
- ZwClose
- InitializeObjectAttributes
1.文件的打开
ZwOpenFile
/************************************************************************
* 函数名称:ZwOpenFile
* 功能描述:打开文件
* 参数列表:
FileHandle:返回打开的文件句柄
DesiredAccess:打开的权限,一般设为GENERIC_ALL
ObjectAttributes:objectAttributes结构
IoStatusBlock:指向一个结构体的指针。该结构体指明打开文件的状态
ShareAccess:共享的权限。可以是FILE_SHARE_READ或者FILE_SHARE_WRITE
OpenOptions:打开选项,一般设为FILE_SYNCHRONOUS_IO_NONALERT
* 返回 值:指明文件是否被成功打开
*************************************************************************/
NTSTATUS ZwOpenFile(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG ShareAccess,
IN ULONG OpenOptions);
使用示例:
OBJECT_ATTRIBUTES objectAttributes;
IO_STATUS_BLOCK iostatus;
HANDLE hfile;
UNICODE_STRING logFileUnicodeString;
//初始化UNICODE_STRING字符串
RtlInitUnicodeString(&logFileUnicodeString, L"\\??\\C:\\1.log");
//或者写成 \\Device\\HarddiskVolume1\\1.log
//初始化objectAttributes
InitializeObjectAttributes(
&objectAttributes,
&logFileUnicodeString,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
//创建文件
NTSTATUS ntStatus = ZwOpenFile(&hfile,
GENERIC_ALL,
&objectAttributes,
&iostatus,
FILE_SHARE_READ || FILE_SHARE_WRITE,
FILE_SYNCHRONOUS_IO_NONALERT);
if( NT_SUCCESS(ntStatus) )
{
KdPrint(("Create FILE successfully!\n"));
}
else
{
KdPrint(("Create FILE unsuccessfully!\n"));
}
//文件操作
//..........
//关闭文件句柄
ZwClose(hfile);
2.文件的创建
ZwCreateFile
/************************************************************************
* 函数名称:ZwCreateFile
* 功能描述:文件的创建
* 参数列表:
FileHandle:返回打开文件的句柄
DesiredAccess:对打开文件操作的描述,读,写或是其他。一般指定为GENERIC_READ 或 GENERIC_WRITE
ObjectAttributes:是OBJECT_ATTRIBUTES结构的地址,该结构包含要打开的文件名
IoStatusBlock:指向一个IO_STATUS_BLOCK结构,该结构接收ZwCreateFile操作的结果状态
AllocationSize:是一个指针,指向一个64位整数,该数指定文件初始分配时的大小
该参数仅关系到创建或重写文件操作,如果忽略它,那么文件长度从0开始
病随着写入而增长
FileAttributes:0或FILE_ATTRIBUTE_NORMAL,指定新创建文件的属性
ShareAccess:FILE_SHARE_READ或0,指定文件的共享方式。
如果为写数据而打开文件,可能不希望其他线程访问该文件
CreateDisposition:FILE_OPEN或FILE_OVERWRITE_IF,表明当指定文件存在或不存在时应如何处理
CreateOptions:FILE_SYNCHARONOUS_IO_NONALERT,指定控制打开操作和句柄使用的附加标志位
EaBuffer:一个指针,指向可选的扩展属性区
EaLength:扩展属性区的长度
* 返回 值: NTSTATUS
*************************************************************************/
NTSTATUS ZwCreateFile(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
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);
/*
*备注:
* 1、CreateDisposition参数。
* 如果想打开文件,CreateDisposition参数设置成FILE_OPEN.
* 如果想创建文件,CreateDisposition参数设置成FILE_OVERWRITE_IF
* 此时,无论文件是否存在,都会创建新文件
* 2、文件名是通过第三个参数ObjectAttributes
* 这个参数是一个OBJECT_ATTRIBUTES结构体
* 通过InitializeObjectAttributes初始化
*/
InitializeObjectAttributes
这实际是一个宏,用来初始化属性变量的内容:
/************************************************************************
* 函数名称:InitializeObjectAttributes
* 功能描述:初始化OBJECT_ATTRIBUTES结构体
* 参数列表:
InitializedAttributes:返回的OBJECT_ATTRIBUTES结构体
ObjectName:对象名称,用UNICODE_STRING描述,这里设置的是文件名
Attributes:一般设置为OBJ_CASE_INSENSITIVE,对大小写敏感
RootDirectory:一般设置为NULL
SecurityDescriptor:一般设置为NULL
* 返回 值:相等的字节数
不一致返回零
*************************************************************************/
VOID InitializeObjectAttributes(
OUT POBJECT_ATTRIBUTES InitializedAttributes,
IN PUNICODE_STRING ObjectName,
IN ULONG Attributes,
IN HANDLE RootDirectory,
IN PSECURITY_DESCRIPTOR SecurityDescriptor);
/*
*备注:
* 1、文件名[必须]是符号链接或者是设备名
* 2、例如:盘符 "c:",就是一个符号链接
* 这里应该用 "\??\c:" 代替
* "c:\1.log" 要写成 "\??\c:\1.log"
* 3、其中 "\??\c:" 是符号链接,内核会将它转换成设备名 "\Device\HarddiskColume1"
*/
3.获取文件属性
ZwQueryInformationFile
/************************************************************************
* 函数名称:ZwQueryInformationFile
* 功能描述:获取文件属性
* 参数列表:
FileHandle:文件句柄
IoStatusBlock:返回设置的状态
FileInformation:依据FileInformationClass不同而不同。作为输出信息
Length:FileInformation数据的长度
FileInformationClass:描述修改属性的类型
* 返回 值:设置属性查询
*************************************************************************/
NTSTATUS ZwQueryInformationFile(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass);
4.文件的读取
ZwReadFile
/************************************************************************
* 函数名称:ZwReadFile
* 功能描述:文件的读操作
* 参数列表:
FileHandle:文件打开的句柄
Event:很少用到,一般设为NULL
ApcRoutine:很少用到,一般设为NULL
ApcContext:很少用到,一般设为NULL
IoStatusBlock:记录些操作的状态。其中,IoStatusBlock.Infomation记录实际写了多少字节
Buffer:从这个缓冲区开始开始从文件里读
Length:准备读多少字节
Byteoffset:从文件的多少偏移地址开始读
Key:很少用到,一般设为NULL
* 返回 值:
*************************************************************************/
NTSTATUS ZwReadFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID Buffer,
IN ULONG Length,
IN PLARGE_INTEGER Byteoffset OPTIONAL,
IN PULONG Key OPTIONAL);
5.文件的写入
ZwWriteFile
/************************************************************************
* 函数名称:ZwWriteFile
* 功能描述:文件的写操作
* 参数列表:
FileHandle:文件打开的句柄
Event:很少用到,一般设为NULL
ApcRoutine:很少用到,一般设为NULL
ApcContext:很少用到,一般设为NULL
IoStatusBlock:记录些操作的状态。其中,IoStatusBlock.Infomation记录实际写了多少字节
Buffer:从这个缓冲区开始往文件里写
Length:准备写多少字节
Byteoffset:从文件的多少便宜地址开始写
Key:很少用到,一般设为NULL
* 返回 值:
*************************************************************************/
NTSTATUS ZwWriteFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID Buffer,
IN ULONG Length,
IN PLARGE_INTEGER Byteoffset OPTIONAL,
IN PULONG Key OPTIONAL);
综合使用:实现文件拷贝
仅支持小文件,而且没有和应用层交互
#include <ntifs.h>
#include <windef.h> //使用DWORD等类型
#define DEVICE_NAME L"\\Device\\MyFirstDeviceFilerw" //设备名称
#define SYM_NAME L"\\??\\MyFirstDeviceFilerw" //符号链接
//定义自定义控制码 (做一个减法)
#define IOCTL_MUL CTL_CODE(FILE_DEVICE_UNKNOWN,0x855,METHOD_BUFFERED, FILE_ANY_ACCESS)
//分发函数
NTSTATUS MyCreate(PDEVICE_OBJECT pdevice, PIRP pirp)
{
UNREFERENCED_PARAMETER(pdevice);
NTSTATUS status = STATUS_SUCCESS;
DbgPrint("My Device has be opened!");
pirp->IoStatus.Status = status;
pirp->IoStatus.Information = 0;
IoCompleteRequest(pirp, IO_NO_INCREMENT);
return status;
}
NTSTATUS MyClearUp(PDEVICE_OBJECT pdevice, PIRP pirp)
{
UNREFERENCED_PARAMETER(pdevice);
NTSTATUS status = STATUS_SUCCESS;
DbgPrint("My Device MyClearUp!");
pirp->IoStatus.Status = status;
pirp->IoStatus.Information = 0;
IoCompleteRequest(pirp, IO_NO_INCREMENT);
return status;
}
NTSTATUS MyClose(PDEVICE_OBJECT pdevice, PIRP pirp)
{
UNREFERENCED_PARAMETER(pdevice);
NTSTATUS status = STATUS_SUCCESS;
DbgPrint("My Device MyClose!");
pirp->IoStatus.Status = status;
pirp->IoStatus.Information = 0;
IoCompleteRequest(pirp, IO_NO_INCREMENT);
return status;
}
NTSTATUS MyRead(PDEVICE_OBJECT pdevice, PIRP pirp)
{
UNREFERENCED_PARAMETER(pdevice);
NTSTATUS status = STATUS_SUCCESS;
DbgPrint("My Device MyRead!");
//获取当前IRP堆栈信息
PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(pirp);
//对应用户层的读请求
ULONG readsize = pstack->Parameters.Read.Length;
DbgPrint("用户请求读大小为%u\n", readsize);
//对应用户层所分配的缓冲区的内存位置
PCHAR readbuffer = pirp->AssociatedIrp.SystemBuffer;
// readbuffer的赋值实际上是对用户缓冲区的改变
RtlCopyMemory(readbuffer,
"This Message Come From Kernel.",
strlen("This Message Come From Kernel."));
pirp->IoStatus.Status = status;
//对于Information的赋值是返回给用户程序实际读取的长度。
pirp->IoStatus.Information = strlen("This Message Come From Kernel.");
//输出下字符串的长度
DbgPrint("Really Read Info Len is %lld\n",
strlen("This Message Come From Kernel."));
IoCompleteRequest(pirp, IO_NO_INCREMENT);
return status;
}
NTSTATUS MyWrite(PDEVICE_OBJECT pdevice, PIRP pirp)
{
UNREFERENCED_PARAMETER(pdevice);
NTSTATUS status = STATUS_SUCCESS;
DbgPrint("My Device MyWrite!");
//获取当前IRP堆栈信息
PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(pirp);
//对应用户层的写请求
ULONG writesize = pstack->Parameters.Write.Length;
DbgPrint("用户请求写大小为%u\n", writesize);
//对应用户层所分配的缓冲区的内存位置
PCHAR writebuffer = pirp->AssociatedIrp.SystemBuffer;
// 写入扩展设备之前先进行清0操作
RtlZeroMemory(pdevice->DeviceExtension, 200);
// writebuffer的数据写入到设备扩展里边
RtlCopyMemory(pdevice->DeviceExtension,writebuffer,writesize);
DbgPrint("写缓冲区内存地址:%p,设备扩展内容%s\n", writebuffer,
(PCHAR)pdevice->DeviceExtension);
pirp->IoStatus.Status = status;
pirp->IoStatus.Information = 13;
IoCompleteRequest(pirp, IO_NO_INCREMENT);
return status;
}
NTSTATUS MyControl(PDEVICE_OBJECT pdevice, PIRP pirp)
{
UNREFERENCED_PARAMETER(pdevice);
NTSTATUS status = STATUS_SUCCESS;
DbgPrint("My Device MyControl!");
PIO_STACK_LOCATION pstack = IoGetCurrentIrpStackLocation(pirp);
ULONG iocode = pstack->Parameters.DeviceIoControl.IoControlCode; // 获取控制码
ULONG inlen = pstack->Parameters.DeviceIoControl.InputBufferLength;
ULONG outlen = pstack->Parameters.DeviceIoControl.OutputBufferLength;
ULONG ioinfo = 0;
DbgPrint("InputBufferLength is %u\n", inlen);
DbgPrint("OutputBufferLength is %u\n", outlen);
switch (iocode)
{
case IOCTL_MUL:
{
//做一个减法
DWORD indata = *(PDWORD)pirp->AssociatedIrp.SystemBuffer;
DbgPrint("--Kernel Indata %d \n", indata);
indata = indata * 5;
*(PDWORD)pirp->AssociatedIrp.SystemBuffer = indata;
ioinfo = 50;
//别忘记break!
break;
}
default:
status = STATUS_UNSUCCESSFUL;
ioinfo = 0;
break;
}
pirp->IoStatus.Status = status;
pirp->IoStatus.Information = ioinfo;
IoCompleteRequest(pirp, IO_NO_INCREMENT);
return status;
}
//内核文件拷贝
NTSTATUS KernelCopyFile(PWCHAR dstfile_path, PWCHAR sourcefile_path)
{
NTSTATUS status = STATUS_SUCCESS;
HANDLE hfile1 = NULL;
UNICODE_STRING sourcefilepath = { 0 };
OBJECT_ATTRIBUTES obja1 = { 0 };
IO_STATUS_BLOCK iostack = { 0 };
//初始化
RtlInitUnicodeString(&sourcefilepath, sourcefile_path);
InitializeObjectAttributes(&obja1, &sourcefilepath,
OBJ_CASE_INSENSITIVE |OBJ_KERNEL_HANDLE, NULL, NULL);
//打开文件句柄
status = ZwOpenFile(
&hfile1,
GENERIC_ALL,
&obja1,
&iostack,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_SYNCHRONOUS_IO_NONALERT
);
if (!NT_SUCCESS(status))
{
DbgPrint("ZwOpenFile 文件打开失败 错误码 %x \n",status);
return status;
}
//查询文件大小
FILE_STANDARD_INFORMATION fsiFileInfo = { 0 };
status = ZwQueryInformationFile(
hfile1,
&iostack,
&fsiFileInfo,
sizeof(FILE_STANDARD_INFORMATION),
FileStandardInformation
);
if (!NT_SUCCESS(status))
{
DbgPrint("ZwQueryDirectoryFile 查询失败 错误码 %x \n", status);
ZwClose(hfile1);
return status;
}
//查询成功过后申请缓冲区放置查询内容
PVOID filebuffer = NULL;
filebuffer = ExAllocatePoolWithTag(NonPagedPool,
fsiFileInfo.EndOfFile.QuadPart, '1212');
if (!filebuffer)
{
DbgPrint("filebuffer 缓冲区申请失败 错误码 %x \n", status);
ZwClose(hfile1);
return status;
}
RtlZeroMemory(filebuffer, fsiFileInfo.EndOfFile.QuadPart);
//读文件
LARGE_INTEGER readoffset = { 0 };
readoffset.QuadPart = 0; //从0开始读
status = ZwReadFile(
hfile1,
NULL,
NULL,
NULL,
&iostack,
filebuffer,
(ULONG)fsiFileInfo.EndOfFile.QuadPart,
&readoffset,
NULL
);
if (!NT_SUCCESS(status))
{
DbgPrint("读文件失败 错误码 %x \n", status);
ZwClose(hfile1);
ExFreePoolWithTag(filebuffer, '1212');
return status;
}
DbgPrint("Ioinfo--- %lld", iostack.Information);
ZwClose(hfile1);
//创建新文件
HANDLE hfile2 = NULL;
UNICODE_STRING dstfilepath = { 0 };
OBJECT_ATTRIBUTES obja2 = { 0 };
IO_STATUS_BLOCK iostack2 = { 0 };
RtlInitUnicodeString(&dstfilepath, dstfile_path);
InitializeObjectAttributes(&obja2, &dstfilepath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); //文件操作描述
// 参数好多!
status = ZwCreateFile(
&hfile2,
GENERIC_ALL,
&obja2,
&iostack2,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_WRITE,
FILE_SUPERSEDE,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0
);
if (!NT_SUCCESS(status))
{
DbgPrint("创建文件失败 错误码 %x \n", status);
ExFreePoolWithTag(filebuffer, '1212');
return status;
}
// 写入文件
LARGE_INTEGER writeoffset = { 0 };
writeoffset.QuadPart = 0;
status = ZwWriteFile(
hfile2,
NULL,
NULL,
NULL,
&iostack2,
filebuffer,
(ULONG)fsiFileInfo.EndOfFile.QuadPart,
&writeoffset,
NULL
);
if (!NT_SUCCESS(status))
{
DbgPrint("写入失败 错误码 %x \n", status);
ExFreePoolWithTag(filebuffer, '1212');
ZwClose(hfile2);
return status;
}
DbgPrint("write length = %lld ", iostack2.Information);
ExFreePoolWithTag(filebuffer, '1212');
ZwClose(hfile2);
return status;
}
//卸载函数
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
DbgPrint("basedriver 卸载驱动\n");
if (DriverObject->DeviceObject)
{
IoDeleteDevice(DriverObject->DeviceObject);
}
UNICODE_STRING symLink = RTL_CONSTANT_STRING(SYM_NAME);
IoDeleteSymbolicLink(&symLink);
}
//入口函数
NTSTATUS DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
UNREFERENCED_PARAMETER(RegistryPath);
//注册卸载函数
DriverObject->DriverUnload = DriverUnload;
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_OBJECT pdevice; // 用来接收创建的设备对象
UNICODE_STRING devicename = { 0 };
RtlInitUnicodeString(&devicename, DEVICE_NAME);
//创建设备对象
status = IoCreateDevice(DriverObject,
200, // 定义设备扩展的大小,用来存放写入的数据
&devicename,
FILE_DEVICE_UNKNOWN,
0,
TRUE,
&pdevice);
if (!NT_SUCCESS(status))
{
KdPrint(("IoCreateDevice 虚拟设备打开失败 状态码 (0x%08X)\n",status));
DbgPrint("IoCreateDevice 虚拟设备打开失败 状态码 (0x%08X)\n", status);
}
if (pdevice->Flags)
{
//不要忘了设备对象的读写方式,否则会蓝屏
pdevice->Flags |= DO_BUFFERED_IO; // 缓冲区方式的读写
}
//
//创建成功,创建符号链接
//
UNICODE_STRING symname = { 0 };
RtlInitUnicodeString(&symname, SYM_NAME);
status = IoCreateSymbolicLink(&symname, &devicename); // 符号链接名 设备名
if (!NT_SUCCESS(status))
{
KdPrint(("IoCreateSymbolicLink 符号链接创建失败 状态码 (0x%08X)", status));
DbgPrint("IoCreateSymbolicLink 符号链接创建失败 状态码 (0x%08X)", status);
}
//设置分发例程
DriverObject->MajorFunction[IRP_MJ_CREATE] = MyCreate;
DriverObject->MajorFunction[IRP_MJ_CLEANUP] = MyClearUp;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyClose;
DriverObject->MajorFunction[IRP_MJ_READ] = MyRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = MyWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyControl;
KernelCopyFile(L"\\??\\C:\\888.exe", L"\\??\\C:\\567.exe");
return 0;
}
效果:
小结
这节课主要是使用内核API了做些处理,对于文件来说还是和应用层类似,注意文件句柄的创建和销毁,还有对文件操作的时的权限设置要符合需求。
另外还可以拓展,通过设定一个临时长度循环读写来拷贝大文件,还有和应用层进行应用交互文件地址等。看完系列课程有时间再做😉😉😉