打开文件后,最重要是的操作在是对文件的读/写。读写的方法是对称的知识参数输入与输出方向不同。读取文件内容一般用ZwReadFile,写文件一般用ZwWriteFile。
先看看 ZwReadFile 结构吧:
NTSTATUS ZwReadFile( _In_ HANDLE FileHandle, _In_opt_ HANDLE Event, _In_opt_ PIO_APC_ROUTINE ApcRoutine, _In_opt_ PVOID ApcContext, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _Out_ PVOID Buffer, _In_ ULONG Length, _In_opt_ PLARGE_INTEGER ByteOffset, _In_opt_ PULONG Key );参数解释:
FileHandle:是ZwCreateFile成功后所得到的FileHandle。若是内核句柄,ZwReadFile和ZwCreateFile并不需要在同一个进程中。句柄是个进程通用的。
Event : 一个事件,用于异步完成读时。设置为NULL 即可。
ApcRoutine :回调例程,用于异步完成读时,设置为 NULL即可。
IoStatusBlock :返回结构状态,同ZwCreateFile中的同名参数。
Buffer :缓冲区。如果读文件的内容成功,则分内容被读到这个缓冲区里。
Length :描述缓冲区的长度。这个长度也就是试图读取文件的长度。
ByteOffset : 要读取的文件的偏移量,也就是要读取的内容在文件中的位置,一般不要设置为NULL,文件句柄不一定支持直接读取当前偏移量。
Key : 读取文件时使用的一种附加信息,一般不用,设置为NULL即可。
ZwReadFile 的返回值: 调用成功,则返回STATUS_SUCCESSS。只要读取到的任意多个字节,返回值都是STATUS_SUCCESS。及时试图读取的长度超出了文件本来的大小。但是如果仅是读取文件长度之外的部分,则返回的是:STATUS_END_OF_FILE。
ZwWriteFile 参数与ZwReadFile完全相同。
说了这么多,看看代码,简单的实现了文件的拷贝。
NTSTATUS CopyFile(PUNICODE_STRING target_path, PUNICODE_STRING source_path) { //---源文件的句柄 HANDLE target = NULL, source = NULL; //----拷贝文件的缓冲区 PVOID buffer = NULL; LARGE_INTEGER offset = { 0 }; IO_STATUS_BLOCK io_status = { 0 }; do { //---这里就开始 获取 源文件 和目标路径句柄,并为 buffer分配一个页面。然后就是循环读取文件,再写入文件。 while (1) { ///--每次读取4KB length = 4 * 1024; //--- status = ZwReadFile( source, NULL, NULL, NULL, &my_io_status, buffer, length, &offset, NULL); if (!NT_SUCCESS(status)) { //---若状态为STATUS_END_OF_FILE,则文件的拷贝结束。 if (STATUS_END_OF_FILE == status) { status = STATUS_SUCCESS; } break; } //---得到文件被读取到的长度 length = IoStatus.Information; //---下面开始写文件 status = ZwWriteFile( target, NULL, NULL, NULL, &my_io_status, buffer, length, &offset, NULL); if (!NT_SUCCESS(status)) { break; } ///----继续,直到出现 STATUS_END_OF_FILE的时候结束 offset.QuadPart += length; } } while (0); //---释放资源,关闭句柄 if (NULL !=target) { ZwClose(target); } if (NULL != source) { ZwClose(source); } if (NULL != buffer) { ExFreePool(buffer); } return STATUS_SUCCESS; }