驅動開發:核心檔案讀寫系列函數

2023-06-09 12:01:19

在應用層下的檔案操作只需要呼叫微軟應用層下的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,此時資料就真的從硬碟裡讀取了出來。

建立檔案或目錄: 實現建立檔案或目錄,建立檔案或目錄都可呼叫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()用於刪除空目錄。

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

#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.com \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最終再將其刪除,輸出效果如下;

重新命名檔案或目錄: 在核心中重新命名檔案或目錄核心功能的實現依賴於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()並傳入相關資訊後即可完成修改,其完整程式碼流程如下;

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

#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.com \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;

讀取檔案大小: 讀取特定檔案的所佔空間,核心原理是呼叫了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即可得到檔案長度,其完整程式碼如下所示;

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

#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.com \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程式的大小位元組數,如下圖所示;

核心檔案讀寫: 核心讀取檔案可以使用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並輸出,完整程式碼如下所示;

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

#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.com \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()與讀取類似,如下通過運用檔案讀寫實現了檔案拷貝功能,實現完整程式碼如下所示;

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

#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.com \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目錄下,實現效果圖如下所示;

實現檔案讀寫傳遞: 通過如上學習相信你已經掌握瞭如何使用檔案讀寫系列函數了,接下來將封裝一個檔案讀寫驅動,應用層接收,驅動層讀取;

此驅動部分完整程式碼如下所示;

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#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.com \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;
}

使用者端完整程式碼如下所示;

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

#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的檔案資料,如下圖所示;