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的文件數據,如下圖所示;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章