1. 程式人生 > >【驅動開發】004 檔案操作

【驅動開發】004 檔案操作

note:本文部分程式碼參考了《Windows核心安全與驅動開發》這本書

一、OBJECT_ATTRIBUTES

         物件屬性結構。在ntdef.h檔案中有該結構的定義:

typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor;        // Points to type SECURITY_DESCRIPTOR
    PVOID SecurityQualityOfService;  // Points to type SECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES;
         核心中都是以物件為操作單位,所以我們不論開啟檔案還是關閉檔案等 都必須先填充這個結構。這個結構有個函式用來初始化資訊。
#define InitializeObjectAttributes( p, n, a, r, s ) { \
    (p)->Length = sizeof( OBJECT_ATTRIBUTES );          \
    (p)->RootDirectory = r;                             \
    (p)->Attributes = a;                                \
    (p)->ObjectName = n;                                \
    (p)->SecurityDescriptor = s;                        \
    (p)->SecurityQualityOfService = NULL;               \
    }
p :傳入OBJECT_ATTRIBUTES 指標

n :PUNICODE_STRING ObjectName 物件名字(檔案登錄檔等操作要輸入絕對路徑)

a:一般填寫 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE  名字字串不區分大小寫,開啟的控制代碼是一個核心控制代碼;核心控制代碼可以不受執行緒和程序限制,在任何程序中都可以讀寫,同時開啟的核心檔案控制代碼不需要顧及當前程序是否有許可權訪問該檔案。

r::根目錄 用於相對開啟的情況,個人建議採用絕對路徑方法訪問,這個地方填寫NULL

s :設定安全描述符號,填寫NULL

二、開啟和關閉檔案

ZwCreateFile(...) 這個函式引數比較多,這裡我們先介紹裡面的一個重要結構 IO_STATUS_BLOCK

typedef struct _IO_STATUS_BLOCK {
    union {
        NTSTATUS Status;
        PVOID Pointer;
    } DUMMYUNIONNAME;

    ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
      IO_STATUS_BLOCK 結構中的Pointer很少用,一般返回的結果在Status中,成功則 Status = STATUS_SUCCESS,否則是一個錯誤程式碼;具體錯誤資訊在Information中。

針對ZwCreateFile檔案呼叫,Information返回值一般包括:

     FILE_CREATE: 檔案成功建立

     FILE_OPEN :檔案開啟

     FILE_OVERWRITEN:檔案被覆蓋

     FILE_SUPERSEDED:檔案被替代

     FILE_EXISTS:檔案已經存在

     FILE_DOES_NOT_EXISTS:檔案不存在(因而開啟失敗了)

      下面給出一個ZwCreateFile的例子:(注意引數的填寫)

// 要返回的檔案控制代碼
HANDLE file_handle = NULL ;
// 返回值
NTSTATUS status ;
// 首先初始化含有檔案路徑的 OBJECT_ATTRIBUTES
OBJECT_ATTRIBUTES object_attributes ;
IO_STATUS_BLOCK io_status ;

UNICODE_STRING ufile_name = RTL_CONSTANT_STRING(L"\\??\\C:\\a.txt");
InitializeObjectAttributes(
	&object_attributes,
	&ufile_name,
	OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,
	NULL,
	NULL);

// 以FILE_OPEN_IF方式開啟檔案
status = ZwCreateFile(
	&file_handle,
	GENERIC_READ | GENERIC_WRITE ,
	&object_attributes,
	&io_status,
	NULL,
	FILE_ATTRIBUTE_NORMAL,
	FILE_SHARE_READ,
	FILE_OPEN_IF,
	FILE_NON_DIRECTORY_FILE |
	FILE_RANDOM_ACCESS |
	FILE_SYNCHRONOUS_IO_NONALERT,
	NULL,
	0);
      這裡要注意:ufile_name 字串寫法:“\\??\\C:\\a.dat”  。 這是因為ZwCeateFile使用的是物件路徑,“C:”是一個符號連結物件,符號連結物件一般都在“\\??\\‘’ 路徑下。

       檔案開啟就意味著一定要關閉,開啟時候用來OBJ_KERNEL_HANDLE標誌,關閉的時候也是需要該控制代碼,核心關閉物件控制代碼不需要再同一程序中。

       ZwClose(file_handle);

三、檔案讀、寫

       在wdm.h中找到ZwReadFile的定義

ZwReadFile(
    _In_ HANDLE FileHandle,
    _In_opt_ HANDLE Event,
    _In_opt_ PIO_APC_ROUTINE ApcRoutine,
    _In_opt_ PVOID ApcContext,
    _Out_ PIO_STATUS_BLOCK IoStatusBlock,
    _Out_writes_bytes_(Length) PVOID Buffer,
    _In_ ULONG Length,
    _In_opt_ PLARGE_INTEGER ByteOffset,
    _In_opt_ PULONG Key
    );
      HANDLE:就是開啟檔案獲取的檔案控制代碼

      EVENT:事件用於非同步完成時候,設定NULL

      PIO_APC_ROUTINE :回撥例程用於非同步完成讀,設NULL

      ApcContext:NULL

      PIO_STATUS_BLOCK : 返回結果的狀態,和ZwCreateFile中的一樣功能

     BUFFER:讀取的緩衝

     Length:描述緩衝區的長度

     PLARGE_INTEGER:要讀取的檔案的偏移量,這個引數很重要

     KEY:直接填寫NULL 讀取檔案使用的附加資訊

我們找到ZwWriteFile的定義:

ZwWriteFile(
    _In_ HANDLE FileHandle,
    _In_opt_ HANDLE Event,
    _In_opt_ PIO_APC_ROUTINE ApcRoutine,
    _In_opt_ PVOID ApcContext,
    _Out_ PIO_STATUS_BLOCK IoStatusBlock,
    _In_reads_bytes_(Length) PVOID Buffer,
    _In_ ULONG Length,
    _In_opt_ PLARGE_INTEGER ByteOffset,
    _In_opt_ PULONG Key
    );
    對比後我們發現ZwWriteFile和ZwReadFile 的引數一樣,這裡就不做過多介紹。

下面給出一個使用ZwCreateFile和ZwReadFile 以及ZwWriteFile三成個函式完成檔案的複製操作。

// 檔案複製操作 借鑑 @楚狂人 的程式碼
NTSTATUS MyCopyFile( 
	PUNICODE_STRING target_path,
	PUNICODE_STRING source_path)
{
	// 源和目標的檔案控制代碼
	HANDLE target = NULL , source = NULL;

	// ObjectAttributes
	OBJECT_ATTRIBUTES object_attribute_target = {0} ;
	OBJECT_ATTRIBUTES object_attribute_source = {0} ;
	IO_STATUS_BLOCK io_status ;

	// 用來拷貝的緩衝區
	PVOID buffer = NULL ;
	ULONG length = 0 ;
	LARGE_INTEGER offset = {0} ;
#if DBG
//	_asm int 3 ;
#endif
	// 初始化OBJECT_ATTRIBUTES target
	InitializeObjectAttributes(
	&object_attribute_target,
	target_path,
	OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,
	NULL,
	NULL);

	// source
	InitializeObjectAttributes(
	&object_attribute_source,
	source_path,
	OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,
	NULL,
	NULL) ;



	do{
		// 開啟target_path檔案
		status = ZwCreateFile(
		&target,
		GENERIC_READ | GENERIC_WRITE ,
		&object_attribute_target,
		&io_status,
		NULL,
		FILE_ATTRIBUTE_NORMAL,
		FILE_SHARE_READ,
		FILE_OPEN_IF,
		FILE_NON_DIRECTORY_FILE |
		FILE_RANDOM_ACCESS |
		FILE_SYNCHRONOUS_IO_NONALERT,
		NULL,
		0);

		if( !NT_SUCCESS(status) )
		{
			DbgPrint("開啟targe檔案失敗");
			return status;
		}

		// 開啟source_path檔案
		status = ZwCreateFile(
		&source,
		GENERIC_READ | GENERIC_WRITE ,
		&object_attribute_source,
		&io_status,
		NULL,
		FILE_ATTRIBUTE_NORMAL,
		FILE_SHARE_READ,
		FILE_OPEN_IF,
		FILE_NON_DIRECTORY_FILE |
		FILE_RANDOM_ACCESS |
		FILE_SYNCHRONOUS_IO_NONALERT,
		NULL,
		0);

		if( !NT_SUCCESS(status) )
		{
			DbgPrint("開啟source檔案失敗");
			return status;
		}
	
		// 為buffer 分配一個緩衝區
		buffer = ExAllocatePoolWithTag(PagedPool,4*1024,MEM_TAG);
		if( !buffer )
		{
			// 分配記憶體失敗
			DbgPrint("分配記憶體失敗!");
			return STATUS_INSUFFICIENT_RESOURCES ;
		}

		// 然後迴圈讀取檔案,每次從檔案中讀取4KB的資料 向目標檔案中寫入4Kb
		// 直到拷貝完成為止

		while(1)
		{
			length = 4*1024 ;		// 每次讀取4KB
			// 讀取舊的檔案,注意status
			status = ZwReadFile(
				source,NULL,NULL,NULL,
				&io_status,buffer,length,&offset,NULL
				);

			if( !NT_SUCCESS(status) )
			{
				// 如果狀態為STATUS_END_OF_FILE 則說明檔案的拷貝已經完成了
				if( status == STATUS_END_OF_FILE )
				{
					status = STATUS_SUCCESS ;
				}
				break ;
			}

			// 取得實際的讀取長度
			length = io_status.Information ;

			// 現在讀取了內容,讀取長度為length,寫入的長度也應該為Length
			// 寫入必須成功 如果失敗則返回錯誤
			status = ZwWriteFile(
				target,NULL,NULL,NULL,
				&io_status,
				buffer,
				length,
				&offset,
				NULL);
			if( !NT_SUCCESS(status) )
			{
				break ;
			}

			// offset移動,然後繼續,直到出現STATUS_END_OF_FILE
			// 的時候才結束
			offset.QuadPart += length ;

		}

	}while(0) ;

	// 在退出之前釋放所有的資源,關閉所有的控制代碼
	if( target != NULL )
	{
		ZwClose(target);
	}
	if( source != NULL )
	{
		ZwClose(source);
	}
	if( buffer != NULL )
	{
		ExFreePoolWithTag(buffer,MEM_TAG);
	}

	return  STATUS_SUCCESS ;
}
在函式中呼叫測試:
	UNICODE_STRING source_path = RTL_CONSTANT_STRING(L"\\??\\C:\\a.txt");
	UNICODE_STRING targe_path = RTL_CONSTANT_STRING(L"\\??\\C:\\b.txt");
	NTSTATUS rStat ;

	rStat = MyCopyFile(&targe_path,&source_path);
	if( !NT_SUCCESS(rStat) )
	{
		DbgPrint("檔案拷貝失敗!");
	}
	else
	{
		DbgPrint("檔案拷貝成功!");
	}


總結:

1.  核心中的檔案即使物件操作,所以必須先填充OBJECT_ATTRIBUTES結構

2. ZwCreateFile 用於開啟一個現有的檔案(如果存在),或者建立一個新的檔案

3. IO_STATUS_BLOCK 比較重要,裡面的information引數記錄了詳細操作返回資訊

4. ZwReadFile 、ZwWriteFile 採用一個檔案指標記錄當前操作位置(LARGE_INTEGER)

5. 檔案操作完成後一定記得好關閉ZwClose,釋放相應的資源