8.1 Windows驱动开发:内核文件读写系列函数

在应用层下的文件操作只需要调用微软应用层下的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,此时数据就真的从硬盘里读取了出来。

8.1.1 创建文件或目录

实现创建文件或目录,创建文件或目录都可调用ZwCreateFile()这个内核函数来实现,唯一不同的区别在于当用户传入参数中包含有FILE_SYNCHRONOUS_IO_NONALERT属性时则会默认创建文件,而如果包含有FILE_DIRECTORY_FILE属性则默认为创建目录,该函数的微软定义以及备注信息如下所示;

NTSYSAPI NTSTATUS ZwCreateFile(
  [out]          PHANDLE            FileHandle,        // 指向HANDLE变量的指针,该变量接收文件的句柄。
  [in]           ACCESS_MASK        DesiredAccess,     // 指定一个ACCESS_MASK值,该值确定对对象的请求访问权限。
  [in]           POBJECT_ATTRIBUTES ObjectAttributes,  // 指向OBJECT_ATTRIBUTES结构的指针,该结构指定对象名称和其他属性。
  [out]          PIO_STATUS_BLOCK   IoStatusBlock,     // 指向IO_STATUS_BLOCK结构的指针,该结构接收最终完成状态和有关所请求操作的其他信息。 
  [in, optional] PLARGE_INTEGER     AllocationSize,    // 指向LARGE_INTEGER的指针,其中包含创建或覆盖的文件的初始分配大小(以字节为单位)。
  [in]           ULONG              FileAttributes,    // 指定一个或多个FILE_ATTRIBUTE_XXX标志,这些标志表示在创建或覆盖文件时要设置的文件属性。
  [in]           ULONG              ShareAccess,       // 共享访问的类型,指定为零或以下标志的任意组合。
  [in]           ULONG              CreateDisposition, // 指定在文件存在或不存在时要执行的操作。
  [in]           ULONG              CreateOptions,     // 指定要在驱动程序创建或打开文件时应用的选项。
  [in, optional] PVOID              EaBuffer,          // 对于设备和中间驱动程序,此参数必须是NULL指针。
  [in]           ULONG              EaLength           // 对于设备和中间驱动程序,此参数必须为零。
);

参数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(
  [in] POBJECT_ATTRIBUTES ObjectAttributes
);

接下来我们就封装三个函数MyCreateFile()用于创建文件,MyCreateFileFolder()用于创建目录,MyDeleteFileOrFileFolder()用于删除空目录。

#include <ntifs.h>
#include <ntstrsafe.h>

// 创建文件
BOOLEAN MyCreateFile(UNICODE_STRING ustrFilePath)
{
    HANDLE hFile = NULL;
    OBJECT_ATTRIBUTES objectAttributes = { 0 };
    IO_STATUS_BLOCK iosb = { 0 };
    NTSTATUS status = STATUS_SUCCESS;

    // 初始化对象属性结构体 FILE_SYNCHRONOUS_IO_NONALERT
    InitializeObjectAttributes(&objectAttributes, &ustrFilePath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    // 创建文件
    status = ZwCreateFile(&hFile, GENERIC_READ, &objectAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
    if (!NT_SUCCESS(status))
    {
        return FALSE;
    }

    // 关闭句柄
    ZwClose(hFile);

    return TRUE;
}

// 创建目录
BOOLEAN MyCreateFileFolder(UNICODE_STRING ustrFileFolderPath)
{
    HANDLE hFile = NULL;
    OBJECT_ATTRIBUTES objectAttributes = { 0 };
    IO_STATUS_BLOCK iosb = { 0 };
    NTSTATUS status = STATUS_SUCCESS;

    // 初始化对象属性结构体
    InitializeObjectAttributes(&objectAttributes, &ustrFileFolderPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    // 创建目录 FILE_DIRECTORY_FILE
    status = ZwCreateFile(&hFile, GENERIC_READ, &objectAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE, FILE_DIRECTORY_FILE, NULL, 0);
    if (!NT_SUCCESS(status))
    {
        return FALSE;
    }
    // 关闭句柄
    ZwClose(hFile);

    return TRUE;
}

// 删除文件或是空目录
BOOLEAN MyDeleteFileOrFileFolder(UNICODE_STRING ustrFileName)
{
    NTSTATUS status = STATUS_SUCCESS;
    OBJECT_ATTRIBUTES objectAttributes = { 0 };

    // 初始化属性
    InitializeObjectAttributes(&objectAttributes, &ustrFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    // 执行删除操作
    status = ZwDeleteFile(&objectAttributes);
    if (!NT_SUCCESS(status))
    {
        return FALSE;
    }

    return TRUE;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    BOOLEAN ref = FALSE;

    // 删除文件
    UNICODE_STRING ustrDeleteFile;
    RtlInitUnicodeString(&ustrDeleteFile, L"\\??\\C:\\LySharkFolder\\lyshark.txt");
    ref = MyDeleteFileOrFileFolder(ustrDeleteFile);
    if (ref != FALSE)
    {
        DbgPrint("[LyShark] 删除文件成功 \n");
    }
    else
    {
        DbgPrint("[LyShark] 删除文件失败 \n");
    }

    // 删除空目录
    UNICODE_STRING ustrDeleteFilder;
    RtlInitUnicodeString(&ustrDeleteFilder, L"\\??\\C:\\LySharkFolder");
    ref = MyDeleteFileOrFileFolder(ustrDeleteFilder);
    if (ref != FALSE)
    {
        DbgPrint("[LyShark] 删除空目录成功 \n");
    }
    else
    {
        DbgPrint("[LyShark] 删除空目录失败 \n");
    }

    DbgPrint("驱动卸载 \n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint("hello lyshark \n");

    BOOLEAN ref = FALSE;


    // 创建目录
    UNICODE_STRING ustrDirectory;
    RtlInitUnicodeString(&ustrDirectory, L"\\??\\C:\\LySharkFolder");
    ref = MyCreateFileFolder(ustrDirectory);
    if (ref != FALSE)
    {
        DbgPrint("[LyShark] 创建目录成功 \n");
    }
    else
    {
        DbgPrint("[LyShark] 创建文件失败 \n");
    }

    // 创建文件
    UNICODE_STRING ustrCreateFile;
    RtlInitUnicodeString(&ustrCreateFile, L"\\??\\C:\\LySharkFolder\\lyshark.txt");
    ref = MyCreateFile(ustrCreateFile);
    if (ref != FALSE)
    {
        DbgPrint("[LyShark] 创建文件成功 \n");
    }
    else
    {
        DbgPrint("[LyShark] 创建文件失败 \n");
    }

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

运行如上代码,分别创建LySharkFolder目录,并在其中创建lyshark.txt最终再将其删除,输出效果如下;

8.1.2 重命名文件或目录

在内核中重命名文件或目录核心功能的实现依赖于ZwSetInformationFile()这个内核函数,该函数可用于更改有关文件对象的各种信息,其微软官方定义如下;

NTSYSAPI NTSTATUS ZwSetInformationFile(
  [in]  HANDLE                 FileHandle,          // 文件句柄
  [out] PIO_STATUS_BLOCK       IoStatusBlock,       // 指向 IO_STATUS_BLOCK 结构的指针
  [in]  PVOID                  FileInformation,     // 指向缓冲区的指针,该缓冲区包含要为文件设置的信息。
  [in]  ULONG                  Length,              // 缓冲区的大小(以字节为单位)
  [in]  FILE_INFORMATION_CLASS FileInformationClass // 为文件设置的类型
);

这其中最重要的参数就是FileInformationClass根据该参数的不同则对文件的操作方式也就不同,如果需要重命名文件则此处应使用FileRenameInformation而如果需要修改文件的当前信息则应使用FilePositionInformation创建链接文件则使用FileLinkInformation即可,以重命名为例,首先我们需要定义一个FILE_RENAME_INFORMATION结构并按照要求填充,最后直接使用ZwSetInformationFile()并传入相关信息后即可完成修改,其完整代码流程如下;

#include <ntifs.h>
#include <ntstrsafe.h>

// 重命名文件或文件夹
BOOLEAN MyRename(UNICODE_STRING ustrSrcFileName, UNICODE_STRING ustrDestFileName)
{
    HANDLE hFile = NULL;
    OBJECT_ATTRIBUTES objectAttributes = { 0 };
    IO_STATUS_BLOCK iosb = { 0 };
    NTSTATUS status = STATUS_SUCCESS;

    PFILE_RENAME_INFORMATION pRenameInfo = NULL;

    ULONG ulLength = (1024 + sizeof(FILE_RENAME_INFORMATION));

    // 为PFILE_RENAME_INFORMATION结构申请内存
    pRenameInfo = (PFILE_RENAME_INFORMATION)ExAllocatePool(NonPagedPool, ulLength);
    if (NULL == pRenameInfo)
    {
        return FALSE;
    }

    // 设置重命名信息
    RtlZeroMemory(pRenameInfo, ulLength);

    // 设置文件名长度以及文件名
    pRenameInfo->FileNameLength = ustrDestFileName.Length;
    wcscpy(pRenameInfo->FileName, ustrDestFileName.Buffer);
    pRenameInfo->ReplaceIfExists = 0;
    pRenameInfo->RootDirectory = NULL;

    // 初始化结构
    InitializeObjectAttributes(&objectAttributes, &ustrSrcFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    // 打开文件
    status = ZwCreateFile(&hFile, SYNCHRONIZE | DELETE, &objectAttributes, &iosb, NULL, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NO_INTERMEDIATE_BUFFERING, NULL, 0);
    if (!NT_SUCCESS(status))
    {
        ExFreePool(pRenameInfo);
        return FALSE;
    }
    // 利用ZwSetInformationFile来设置文件信息
    status = ZwSetInformationFile(hFile, &iosb, pRenameInfo, ulLength, FileRenameInformation);
    if (!NT_SUCCESS(status))
    {
        ZwClose(hFile);
        ExFreePool(pRenameInfo);
        return FALSE;
    }

    // 释放内存,关闭句柄
    ExFreePool(pRenameInfo);
    ZwClose(hFile);

    return TRUE;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    DbgPrint("驱动卸载 \n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint("hello lyshark \n");

    // 重命名文件
    UNICODE_STRING ustrOldFile, ustrNewFile;
    RtlInitUnicodeString(&ustrOldFile, L"\\??\\C:\\MyCreateFolder\\lyshark.txt");
    RtlInitUnicodeString(&ustrNewFile, L"\\??\\C:\\MyCreateFolder\\hello_lyshark.txt");
    BOOLEAN ref = MyRename(ustrOldFile, ustrNewFile);
    if (ref == TRUE)
    {
        DbgPrint("已完成改名 \n");
    }

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

运行后将会把C:\\MyCreateFolder\\lyshark.txt目录下的文件改名为hello_lyshark.txt,前提是该目录与该文件必须存在;

那么如果你需要将文件设置为只读模式或修改文件的创建日期,那么你就需要看一下微软的定义FILE_BASIC_INFORMATION结构,依次填充此结构体并调用ZwSetInformationFile()即可实现修改,该结构的定义如下所示;

typedef struct _FILE_BASIC_INFORMATION {
    LARGE_INTEGER CreationTime;
    LARGE_INTEGER LastAccessTime;
    LARGE_INTEGER LastWriteTime;
    LARGE_INTEGER ChangeTime;
    ULONG FileAttributes;
} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION;

当然如果你要修改日期你还需要自行填充LARGE_INTEGER结构,该结构的微软定义如下所示,分为高位和低位依次填充即可;

#if defined(MIDL_PASS)
typedef struct _LARGE_INTEGER {
#else // MIDL_PASS
typedef union _LARGE_INTEGER {
    struct {
        ULONG LowPart;
        LONG HighPart;
    } DUMMYSTRUCTNAME;
    struct {
        ULONG LowPart;
        LONG HighPart;
    } u;
#endif //MIDL_PASS
    LONGLONG QuadPart;
} LARGE_INTEGER;

我们就以修改文件属性为只读模式为例,其核心代码可以被描述为如下样子,相比于改名而言其唯一的变化就是更换了PFILE_BASIC_INFORMATION结构体,其他的基本一致;

HANDLE hFile = NULL;
OBJECT_ATTRIBUTES objectAttributes = { 0 };
IO_STATUS_BLOCK iosb = { 0 };
NTSTATUS status = STATUS_SUCCESS;

PFILE_BASIC_INFORMATION pReplaceInfo = NULL;

ULONG ulLength = (1024 + sizeof(FILE_BASIC_INFORMATION));

// 为FILE_POSITION_INFORMATION结构申请内存
pReplaceInfo = (PFILE_BASIC_INFORMATION)ExAllocatePool(NonPagedPool, ulLength);
if (NULL == pReplaceInfo)
{
  return FALSE;
}

RtlZeroMemory(pReplaceInfo, ulLength);

// 设置文件基础信息,将文件设置为只读模式
pReplaceInfo->FileAttributes |= FILE_ATTRIBUTE_READONLY;

8.1.3 读取文件大小

读取特定文件的所占空间,核心原理是调用了ZwQueryInformationFile()这个内核函数,该函数可以返回有关文件对象的各种信息,参数传递上与ZwSetInformationFile()很相似,其FileInformationClass都需要传入一个文件类型结构,该函数的完整定义如下;

NTSYSAPI NTSTATUS ZwQueryInformationFile(
  [in]  HANDLE                 FileHandle,          // 文件句柄。
  [out] PIO_STATUS_BLOCK       IoStatusBlock,       // 指向接收最终完成状态和操作相关信息的 IO_STATUS_BLOCK 结构的指针。
  [out] PVOID                  FileInformation,     // 指向调用方分配的缓冲区的指针,例程将请求的有关文件对象的信息写入其中。
  [in]  ULONG                  Length,              // 长度。
  [in]  FILE_INFORMATION_CLASS FileInformationClass // 指定要在 FileInformation 指向的缓冲区中返回的有关文件的信息类型。
);

本例中我们需要读入文件的所占字节数,那么FileInformation字段就需要传入FileStandardInformation来获取文件的基本信息,获取到的信息会被存储到FILE_STANDARD_INFORMATION结构内,用户只需要解析该结构体fsi.EndOfFile.QuadPart即可得到文件长度,其完整代码如下所示;

#include <ntifs.h>
#include <ntstrsafe.h>

// 获取文件大小
ULONG64 MyGetFileSize(UNICODE_STRING ustrFileName)
{
    HANDLE hFile = NULL;
    OBJECT_ATTRIBUTES objectAttributes = { 0 };
    IO_STATUS_BLOCK iosb = { 0 };
    NTSTATUS status = STATUS_SUCCESS;
    FILE_STANDARD_INFORMATION fsi = { 0 };

    // 初始化结构
    InitializeObjectAttributes(&objectAttributes, &ustrFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    // 打开文件
    status = ZwCreateFile(&hFile, GENERIC_READ, &objectAttributes, &iosb, NULL, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
    if (!NT_SUCCESS(status))
    {
        return 0;
    }

    // 获取文件大小信息
    status = ZwQueryInformationFile(hFile, &iosb, &fsi, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation);
    if (!NT_SUCCESS(status))
    {
        ZwClose(hFile);
        return 0;
    }

    return fsi.EndOfFile.QuadPart;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    DbgPrint("驱动卸载 \n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint("hello lyshark \n");

    // 获取文件大小
    UNICODE_STRING ustrFileSize;
    RtlInitUnicodeString(&ustrFileSize, L"\\??\\C:\\lyshark.exe");
    ULONG64 ullFileSize = MyGetFileSize(ustrFileSize);
    DbgPrint("获取文件大小: %I64d Bytes \n", ullFileSize);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

编译并运行如上程序,即可读取到C盘下的lyshark.exe程序的大小字节数,如下图所示;

8.1.4 内核文件读写

内核读取文件可以使用ZwReadFile(),内核写入文件则可使用ZwWriteFile(),这两个函数的参数传递基本上一致,如下是读写两个函数的对比参数。

NTSYSAPI NTSTATUS ZwReadFile(
  [in]           HANDLE           FileHandle,      // 文件对象的句柄。
  [in, optional] HANDLE           Event,           // (可选)事件对象的句柄,在读取操作完成后设置为信号状态。
  [in, optional] PIO_APC_ROUTINE  ApcRoutine,      // 此参数为保留参数。
  [in, optional] PVOID            ApcContext,      // 此参数为保留参数。
  [out]          PIO_STATUS_BLOCK IoStatusBlock,   // 接收实际从文件读取的字节数。
  [out]          PVOID            Buffer,          // 指向调用方分配的缓冲区的指针,该缓冲区接收从文件读取的数据。
  [in]           ULONG            Length,          // 缓冲区指向的缓冲区的大小(以字节为单位)。
  [in, optional] PLARGE_INTEGER   ByteOffset,      // 指定将开始读取操作的文件中的起始字节偏移量。
  [in, optional] PULONG           Key
);

NTSYSAPI NTSTATUS ZwWriteFile(
  [in]           HANDLE           FileHandle,
  [in, optional] HANDLE           Event,
  [in, optional] PIO_APC_ROUTINE  ApcRoutine,
  [in, optional] PVOID            ApcContext,
  [out]          PIO_STATUS_BLOCK IoStatusBlock,
  [in]           PVOID            Buffer,
  [in]           ULONG            Length,
  [in, optional] PLARGE_INTEGER   ByteOffset,
  [in, optional] PULONG           Key
);

读取文件的代码如下所示,分配非分页pBuffer内存,然后调用MyReadFile()函数,将数据读入到pBuffer并输出,完整代码如下所示;

#include <ntifs.h>
#include <ntstrsafe.h>

// 读取文件数据
BOOLEAN MyReadFile(UNICODE_STRING ustrFileName, LARGE_INTEGER liOffset, PUCHAR pReadData, PULONG pulReadDataSize)
{
    HANDLE hFile = NULL;
    IO_STATUS_BLOCK iosb = { 0 };
    OBJECT_ATTRIBUTES objectAttributes = { 0 };
    NTSTATUS status = STATUS_SUCCESS;

    // 初始化结构
    InitializeObjectAttributes(&objectAttributes, &ustrFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    // 打开文件
    status = ZwCreateFile(&hFile, GENERIC_READ, &objectAttributes, &iosb, NULL,FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN,FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
    if (!NT_SUCCESS(status))
    {
        return FALSE;
    }

    // 初始化
    RtlZeroMemory(&iosb, sizeof(iosb));

    // 读入文件
    status = ZwReadFile(hFile, NULL, NULL, NULL, &iosb, pReadData, *pulReadDataSize, &liOffset, NULL);
    if (!NT_SUCCESS(status))
    {
        *pulReadDataSize = iosb.Information;
        ZwClose(hFile);
        return FALSE;
    }

    // 获取实际读取的数据
    *pulReadDataSize = iosb.Information;

    // 关闭句柄
    ZwClose(hFile);

    return TRUE;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    DbgPrint("驱动卸载 \n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint("hello lyshark \n");

    UNICODE_STRING ustrScrFile;
    ULONG ulBufferSize = 40960;
    LARGE_INTEGER liOffset = { 0 };


    // 初始化需要读取的文件名
    RtlInitUnicodeString(&ustrScrFile, L"\\??\\C:\\lyshark.exe");

    // 分配非分页内存
    PUCHAR pBuffer = ExAllocatePool(NonPagedPool, ulBufferSize);

    // 读取文件
    MyReadFile(ustrScrFile, liOffset, pBuffer, &ulBufferSize);

    // 输出文件前16个字节
    for (size_t i = 0; i < 16; i++)
    {
        DbgPrint("%02X \n", pBuffer[i]);
    }

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

编译并运行这段代码,并循环输出lyshark.exe文件的头16个字节的数据,效果图如下所示;

文件写入MyWriteFile()与读取类似,如下通过运用文件读写实现了文件拷贝功能,实现完整代码如下所示;

#include <ntifs.h>
#include <ntstrsafe.h>

// 读取文件数据
BOOLEAN MyReadFile(UNICODE_STRING ustrFileName, LARGE_INTEGER liOffset, PUCHAR pReadData, PULONG pulReadDataSize)
{
    HANDLE hFile = NULL;
    IO_STATUS_BLOCK iosb = { 0 };
    OBJECT_ATTRIBUTES objectAttributes = { 0 };
    NTSTATUS status = STATUS_SUCCESS;

    // 初始化结构
    InitializeObjectAttributes(&objectAttributes, &ustrFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    // 打开文件
    status = ZwCreateFile(&hFile, GENERIC_READ, &objectAttributes, &iosb, NULL,FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN,FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
    if (!NT_SUCCESS(status))
    {
        return FALSE;
    }

    // 初始化
    RtlZeroMemory(&iosb, sizeof(iosb));

    // 读入文件
    status = ZwReadFile(hFile, NULL, NULL, NULL, &iosb, pReadData, *pulReadDataSize, &liOffset, NULL);
    if (!NT_SUCCESS(status))
    {
        *pulReadDataSize = iosb.Information;
        ZwClose(hFile);
        return FALSE;
    }

    // 获取实际读取的数据
    *pulReadDataSize = iosb.Information;

    // 关闭句柄
    ZwClose(hFile);

    return TRUE;
}

// 向文件写入数据
BOOLEAN MyWriteFile(UNICODE_STRING ustrFileName, LARGE_INTEGER liOffset, PUCHAR pWriteData, PULONG pulWriteDataSize)
{
    HANDLE hFile = NULL;
    IO_STATUS_BLOCK iosb = { 0 };
    OBJECT_ATTRIBUTES objectAttributes = { 0 };
    NTSTATUS status = STATUS_SUCCESS;

    // 初始化结构
    InitializeObjectAttributes(&objectAttributes, &ustrFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    // 打开文件
    status = ZwCreateFile(&hFile, GENERIC_WRITE, &objectAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
    if (!NT_SUCCESS(status))
    {
        return FALSE;
    }

    // 初始化
    RtlZeroMemory(&iosb, sizeof(iosb));

    // 写出文件
    status = ZwWriteFile(hFile, NULL, NULL, NULL, &iosb, pWriteData, *pulWriteDataSize, &liOffset, NULL);
    if (!NT_SUCCESS(status))
    {
        *pulWriteDataSize = iosb.Information;
        ZwClose(hFile);
        return FALSE;
    }

    // 获取实际写入的数据
    *pulWriteDataSize = iosb.Information;
    
    // 关闭句柄
    ZwClose(hFile);

    return TRUE;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    DbgPrint("驱动卸载 \n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint("hello lyshark \n");

    // 文件读写
    UNICODE_STRING ustrScrFile, ustrDestFile;
    RtlInitUnicodeString(&ustrScrFile, L"\\??\\C:\\lyshark.exe");
    RtlInitUnicodeString(&ustrDestFile, L"\\??\\C:\\LyShark\\new_lyshark.exe");

    ULONG ulBufferSize = 40960;
    ULONG ulReadDataSize = ulBufferSize;
    LARGE_INTEGER liOffset = { 0 };

    // 分配非分页内存
    PUCHAR pBuffer = ExAllocatePool(NonPagedPool, ulBufferSize);

    do
    {
        // 读取文件
        ulReadDataSize = ulBufferSize;
        MyReadFile(ustrScrFile, liOffset, pBuffer, &ulReadDataSize);

        // 数据为空则读取结束
        if (0 >= ulReadDataSize)
        {
            break;
        }

        // 写入文件
        MyWriteFile(ustrDestFile, liOffset, pBuffer, &ulReadDataSize);

        // 更新偏移
        liOffset.QuadPart = liOffset.QuadPart + ulReadDataSize;
        DbgPrint("[+] 更新偏移: %d \n", liOffset.QuadPart);

    } while (TRUE);

    // 释放内存
    ExFreePool(pBuffer);
    DbgPrint("[*] 已将文件复制到新目录 \n");

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

编译并运行这段程序,则自动将C:\\lyshark.exe盘符下的文件拷贝到C:\\LyShark\\new_lyshark.exe目录下,实现效果图如下所示;

8.1.5 实现文件读写传递

通过如上学习相信你已经掌握了如何使用文件读写系列函数了,接下来将封装一个文件读写驱动,应用层接收,驱动层读取;

此驱动部分完整代码如下所示;

#include <ntifs.h>
#include <windef.h>

#define READ_FILE_SIZE_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
#define READ_FILE_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)

#define DEVICENAME L"\\Device\\ReadWriteDevice"
#define SYMBOLNAME L"\\??\\ReadWriteSymbolName"

typedef struct
{
    ULONG64 size;      // 读写长度
    BYTE* data;        // 读写数据集
}FileData;

// 获取文件大小
ULONG64 MyGetFileSize(UNICODE_STRING ustrFileName)
{
    HANDLE hFile = NULL;
    OBJECT_ATTRIBUTES objectAttributes = { 0 };
    IO_STATUS_BLOCK iosb = { 0 };
    NTSTATUS status = STATUS_SUCCESS;
    FILE_STANDARD_INFORMATION fsi = { 0 };

    // 初始化结构
    InitializeObjectAttributes(&objectAttributes, &ustrFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    // 打开文件
    status = ZwCreateFile(&hFile, GENERIC_READ, &objectAttributes, &iosb, NULL, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
    if (!NT_SUCCESS(status))
    {
        return 0;
    }

    // 获取文件大小信息
    status = ZwQueryInformationFile(hFile, &iosb, &fsi, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation);
    if (!NT_SUCCESS(status))
    {
        ZwClose(hFile);
        return 0;
    }

    return fsi.EndOfFile.QuadPart;
}

// 读取文件数据
BOOLEAN MyReadFile(UNICODE_STRING ustrFileName, LARGE_INTEGER liOffset, PUCHAR pReadData, PULONG pulReadDataSize)
{
    HANDLE hFile = NULL;
    IO_STATUS_BLOCK iosb = { 0 };
    OBJECT_ATTRIBUTES objectAttributes = { 0 };
    NTSTATUS status = STATUS_SUCCESS;

    // 初始化结构
    InitializeObjectAttributes(&objectAttributes, &ustrFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    // 打开文件
    status = ZwCreateFile(&hFile, GENERIC_READ, &objectAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
    if (!NT_SUCCESS(status))
    {
        return FALSE;
    }

    // 初始化
    RtlZeroMemory(&iosb, sizeof(iosb));

    // 读入文件
    status = ZwReadFile(hFile, NULL, NULL, NULL, &iosb, pReadData, *pulReadDataSize, &liOffset, NULL);
    if (!NT_SUCCESS(status))
    {
        *pulReadDataSize = iosb.Information;
        ZwClose(hFile);
        return FALSE;
    }

    // 获取实际读取的数据
    *pulReadDataSize = iosb.Information;

    // 关闭句柄
    ZwClose(hFile);

    return TRUE;
}

NTSTATUS DriverIrpCtl(PDEVICE_OBJECT device, PIRP pirp)
{
    PIO_STACK_LOCATION stack;
    stack = IoGetCurrentIrpStackLocation(pirp);
    FileData* FileDataPtr;

    switch (stack->MajorFunction)
    {

    case IRP_MJ_CREATE:
    {
        break;
    }

    case IRP_MJ_CLOSE:
    {
        break;
    }

    case IRP_MJ_DEVICE_CONTROL:
    {
        // 获取应用层传值
        FileDataPtr = pirp->AssociatedIrp.SystemBuffer;
        switch (stack->Parameters.DeviceIoControl.IoControlCode)
        {
            // 读取内存函数
        case READ_FILE_SIZE_CODE:
        {
            LARGE_INTEGER liOffset = { 0 };
            UNICODE_STRING ustrFileSize;
            RtlInitUnicodeString(&ustrFileSize, L"\\??\\C:\\Windows\\system32\\ntoskrnl.exe");

            // 获取文件长度
            ULONG64 ulBufferSize = MyGetFileSize(ustrFileSize);
            DbgPrint("获取文件大小: %I64d Bytes \n", ulBufferSize);

            // 将长度返回应用层
            FileDataPtr->size = ulBufferSize;
            break;
        }

        // 读取文件
        case READ_FILE_CODE:
        {
            FileData ptr;

            LARGE_INTEGER liOffset = { 0 };
            UNICODE_STRING ustrFileSize;
            RtlInitUnicodeString(&ustrFileSize, L"\\??\\C:\\Windows\\system32\\ntoskrnl.exe");

            // 获取文件长度
            ULONG64 ulBufferSize = MyGetFileSize(ustrFileSize);
            DbgPrint("获取文件大小: %I64d Bytes \n", ulBufferSize);

            // 读取内存到缓冲区
            BYTE* pBuffer = ExAllocatePool(NonPagedPool, ulBufferSize);
            MyReadFile(ustrFileSize, liOffset, pBuffer, &ulBufferSize);

            // 返回数据
            FileDataPtr->size = ulBufferSize;
            RtlCopyMemory(FileDataPtr->data, pBuffer, FileDataPtr->size);

            break;
        }
        }

        pirp->IoStatus.Information = sizeof(FileDataPtr);
        break;
    }

    }

    pirp->IoStatus.Status = STATUS_SUCCESS;
    IoCompleteRequest(pirp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
    if (driver->DeviceObject)
    {
        UNICODE_STRING SymbolName;
        RtlInitUnicodeString(&SymbolName, SYMBOLNAME);

        // 删除符号链接
        IoDeleteSymbolicLink(&SymbolName);
        IoDeleteDevice(driver->DeviceObject);
    }
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    NTSTATUS status = STATUS_SUCCESS;
    PDEVICE_OBJECT device = NULL;
    UNICODE_STRING DeviceName;

    DbgPrint("[LyShark] hello lyshark \n");

    // 初始化设备名
    RtlInitUnicodeString(&DeviceName, DEVICENAME);

    // 创建设备
    status = IoCreateDevice(Driver, sizeof(Driver->DriverExtension), &DeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &device);
    if (status == STATUS_SUCCESS)
    {
        UNICODE_STRING SymbolName;
        RtlInitUnicodeString(&SymbolName, SYMBOLNAME);

        // 创建符号链接
        status = IoCreateSymbolicLink(&SymbolName, &DeviceName);

        // 失败则删除设备
        if (status != STATUS_SUCCESS)
        {
            IoDeleteDevice(device);
        }
    }

    // 派遣函数初始化
    Driver->MajorFunction[IRP_MJ_CREATE] = DriverIrpCtl;
    Driver->MajorFunction[IRP_MJ_CLOSE] = DriverIrpCtl;
    Driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverIrpCtl;

    // 卸载驱动
    Driver->DriverUnload = UnDriver;

    return STATUS_SUCCESS;
}

客户端完整代码如下所示;

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>

#define READ_FILE_SIZE_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
#define READ_FILE_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)

typedef struct
{
    DWORD size;      // 读写长度
    BYTE* data;      // 读写数据集
}FileData;

int main(int argc, char* argv[])
{
    // 连接到驱动
    HANDLE handle = CreateFileA("\\??\\ReadWriteSymbolName", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    FileData data;
    DWORD dwSize = 0;

    // 首先得到文件长度
    DeviceIoControl(handle, READ_FILE_SIZE_CODE, 0, 0, &data, sizeof(data), &dwSize, NULL);
    printf("%d \n", data.size);

    // 读取机器码到BYTE字节数组
    data.data = new BYTE[data.size];

    DeviceIoControl(handle, READ_FILE_CODE, &data, sizeof(data), &data, sizeof(data), &dwSize, NULL);
    for (int i = 0; i < data.size; i++)
    {
        printf("0x%02X ", data.data[i]);
    }

    printf("\n");
    getchar();
    CloseHandle(handle);
    return 0;
}

通过驱动加载工具将WinDDK.sys拉起来,然后启动客户端进程,即可输出ntoskrnl.exe的文件数据,如下图所示;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/165408.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SQL单表复杂查询where、group by、order by、limit

1.1SQL查询代码如下&#xff1a; select job as 工作类别,count(job) as 人数 from tb_emp where entrydate <2015-01-01 group by job having count(job) > 2 order by count(job) limit 1,1where entrydate <‘2015-01-01’ 表示查询日期小于2015-01-01的记录…

Python大数据之linux学习总结——day11_ZooKeeper

ZooKeeper ZK概述 ZooKeeper概念: Zookeeper是一个分布式协调服务的开源框架。本质上是一个分布式的小文件存储系统 ZooKeeper作用: 主要用来解决分布式集群中应用系统的一致性问题。 ZooKeeper结构: 采用树形层次结构&#xff0c;ZooKeeper树中的每个节点被称为—Znode。且树…

随机过程-张灏

文章目录 导论随机过程相关 学习视频来源&#xff1a;https://www.bilibili.com/video/BV18p4y1u7NP?p1&vd_source5e8340c2f2373cdec3870278aedd8ca4 将持续更新—— 第一次更新&#xff1a;2023-11-19 导论 教材&#xff1a;《随机过程及其应用》陆大絟.张颢 参考&…

X3DAudio1_7.dll丢失原因,X3DAudio1_7.dll丢失怎样解决分享

X3DAudio1_7.dll是一款由微软公司开发的音频处理库&#xff0c;主要用于实现三维音频效果。这个库主要应用于游戏开发、多媒体应用等领域&#xff0c;它可以使得音频更加真实、自然地表现出空间感。如果在使用过程中遇到X3DAudio1_7.dll丢失的问题&#xff0c;可以尝试以下五个…

指南:关于帮助中心需要注意的一些细节

在现代商业环境中&#xff0c;帮助中心已经成为企业提供客户支持和解决问题的重要方式之一。然而&#xff0c;建立一个高效的帮助中心并不简单。除了选择合适的软件平台和工具之外&#xff0c;还需要注意一些细节&#xff0c;以确保能够真正帮助客户并提高客户满意度。 | 1.设计…

辅助解决小白遇到的电脑各种问题

写这个纯属是为了让电脑小白知道一些电脑上的简单操作&#xff0c;勿喷&#xff01;&#xff01;&#xff01; 一&#xff1a;当小白遇到电脑程序不完全退出怎么办&#xff1f; 使用软件默认的退出方式 此处拿百度网盘举例&#xff1a; 用户登录网盘后&#xff1a; 如果直接点…

spring常见面试题总结

1、spring是什么 Spring&#xff1a;是一个轻量级的IOC和AOP的java开发框架&#xff0c;为了简化企业级开发而生。核心就是控制反转和面向切面编程。 IOC&#xff1a;控制反转&#xff08;Inverse of Control&#xff09;&#xff0c;以前项目都是在哪儿用到对象 在哪儿new&a…

Vite - 静态资源处理 - json文件导入

直接就说明白了 vite 中对json 文件直接当作一个模块来解析。 可以直接导入使用&#xff01; 可以直接导入使用&#xff01; 可以直接导入使用&#xff01;json文件中的key&#xff0c;直接被作为一个属性&#xff0c;可以单独被导入。 因此&#xff0c;导入json文件有两种方式…

Rust与其他语言对比:优势在哪里?

大家好&#xff01;我是lincyang。 今天&#xff0c;我们将深入探讨Rust语言与其他编程语言比较的优势&#xff0c;并通过具体的代码示例和性能数据来加深理解。 Rust与其他语言的比较 1. 内存安全性 Rust&#xff1a;采用所有权系统&#xff0c;编译器在编译时检查内存安全…

自动驾驶学习笔记(十)——Cyber通信

#Apollo开发者# 学习课程的传送门如下&#xff0c;当您也准备学习自动驾驶时&#xff0c;可以和我一同前往&#xff1a; 《自动驾驶新人之旅》免费课程—> 传送门 《Apollo Beta宣讲和线下沙龙》免费报名—>传送门 文章目录 前言 Cyber通信 编写代码 编译程序 运行…

AI绘画使用Stable Diffusion(SDXL)绘制三星堆风格的图片

一、前言 三星堆文化是一种古老的中国文化&#xff0c;它以其精湛的青铜铸造技术闻名&#xff0c;出土文物中最著名的包括青铜面具、青铜人像、金杖、玉器等。这些文物具有独特的艺术风格&#xff0c;显示了高度的工艺水平和复杂的社会结构。 青铜面具的巨大眼睛和突出的颧骨&a…

asp.net mvc点餐系统餐厅管理系统

1. 主要功能 ① 管理员、收银员、厨师的登录 ② 管理员查看、添加、删除菜品类型 ③ 管理员查看、添加、删除菜品&#xff0c;对菜品信息进行简介和封面的修改 ④ 收银员浏览、搜索菜品&#xff0c;加入购物车后进行结算&#xff0c;生成订单 ⑤ 厨师查看待完成菜品信息…

漫谈广告机制设计 | 万剑归宗:聊聊广告机制设计与收入提升的秘密(3)

​书接上文漫谈广告机制设计 | 万剑归宗&#xff1a;聊聊广告机制设计与收入提升的秘密&#xff08;2&#xff09;&#xff0c;我们聊到囚徒困境是完全信息静态博弈&#xff0c;参与人存在占优策略&#xff0c;最终达到占优均衡&#xff0c;并且是对称占优均衡。接下来我们继续…

Jmeter——结合Allure展示测试报告

在平时用jmeter做测试时&#xff0c;生成报告的模板&#xff0c;不是特别好。大家应该也知道allure报告&#xff0c;页面美观。 先来看效果图&#xff0c;报告首页&#xff0c;如下所示&#xff1a; 报告详情信息&#xff0c;如下所示&#xff1a; 运行run.py文件&#xff0c;…

多功能神器,强劲升级,太极2.x你值得拥有!

嗨&#xff0c;大家好&#xff0c;今天给大家分享一个好用好玩的软件。那就是太极2.x软件&#xff0c;最近在1.0版本上进行了全新升级&#xff0c;升级后的功能更强更稳定&#xff0c;轻度用户使用基本功能就已经足够了&#xff0c;我们一起来看看吧&#xff01; 首页 首页左…

8.5 Windows驱动开发:内核注册表增删改查

注册表是Windows中的一个重要的数据库&#xff0c;用于存储系统和应用程序的设置信息&#xff0c;注册表是一个巨大的树形结构&#xff0c;无论在应用层还是内核层操作注册表都有独立的API函数可以使用&#xff0c;而在内核中读写注册表则需要使用内核装用API函数&#xff0c;如…

接口自动化测试很难吗?来看看这份超详细的教程!

接口自动化测试框架目的 测试工程师应用自动化测试框架的目的: 增强测试脚本的可维护性、易用性(降低公司自动化培训成本&#xff0c;让公司的测试工程师都可以开展自动化测试)。 以下框架以微信公众平台开放文档实战 地址&#xff1a;https://developers.weixin.qq.com/doc…

车载通信架构 —— 传统车内通信网络发展回顾

车载通信架构 —— 传统车内通信网络发展回顾 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何…

CD36 ; + Lectin;

CD2 LIMP-2&#xff0c; LGP85 SR-BI&#xff0c; CD36&#xff1b; 清道夫受体蛋白CD36超家族的成员是 脂质代谢 和 先天免疫 的重要调节因子。它们识别正常和修饰的脂蛋白&#xff0c;以及与病原体相关的分子模式。 该家族由三个成员组成&#xff1a; SR-BI &am…

什么是tomcat, tomcat该如何使用?(java)

tomcat是什么? tomcat翻译过来为汤姆猫, 但是他可不是猫和老鼠中的汤姆, 而是java中的tom, 虽然java中的tomcat没有猫和老鼠那么出名, 但是他仍然是java中的中流砥柱 下图为java中的tomcat, 也就是最右边这个黄色的猫: Tomcat是Apache 软件基金会&#xff08;Apache Software …
最新文章