應用程序與驅動程序通信 DeviceIoControl


  之前寫過一篇關於通過DeviceIoControl函數來使應用程序與驅動程序通信的博客,這次再通過這個完整的代碼來簡要疏通總結一下。

   這種通信方式,就是驅動程序和應用程序自定義一種IO控制碼,然后調用DeviceIoControl函數IO管理器產生一個MajorFunction 為IRP_MJ_DEVICE_CONTROL(DeviceIoControl函數會產生此IRP),MinorFunction 為自己定義的控制碼的IRP,系統就調用相應的處理IRP_MJ_DEVICE_CONTROL的派遣函數,你在派遣函數中判斷MinorFunction ,是自定義的控制碼你就進行相應的處理

 

  一.先談一下這個定義IO控制碼 ,其實可以看作是一種通信協議。

       看看CTL_CODE原型:

  #define CTL_CODE( DeviceType, Function, Method, Access ) ( \
  ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
  )

   可以看到,這個宏四個參數,自然是一個32位分成了4部分,高16位存儲設備類型,14~15位訪問權限,2~13位操作功能,最后0,1兩位就是確定緩沖區是如何與I/O和文件系統數據緩沖區進行數據傳遞方式,最常見的就是METHOD_BUFFERED。

 

       自定義CTL_CODE:

  #define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)

  IOCTL_Device_Function:生成的IRP的MinorFunction

  DeviceType:設備對象的類型。設備類型可參考:http://blog.csdn.net/liyun123gx/article/details/38058965

  Function :自定義的IO控制碼。自己定義時取0x800到0xFFF,因為0x0到0x7FF是微軟保留的。

  Method :數據的操作模式。

              METHOD_BUFFERED:緩沖區模式

              METHOD_IN_DIRECT:直接寫模式

              METHOD_OUT_DIRECT:直接讀模式

              METHOD_NEITHER :Neither模式

Access:訪問權限,可取值有:

            FILE_ANY_ACCESS:表明用戶擁有所有的權限

            FILE_READ_DATA:表明權限為只讀

            FILE_WRITE_DATA:表明權限為可寫

            也可以 FILE_WRITE_DATA | FILE_READ_DATA:表明權限為可讀可寫,但還沒達到FILE_ANY_ACCESS的權限。

 

  

  

       繼續介紹這個緩沖區數據傳遞方式Method:

  Method表示Ring3/Ring0的通信中的內存訪問方式,有四種方式:
  #define METHOD_BUFFERED                0  
  #define METHOD_IN_DIRECT               1  
  #define METHOD_OUT_DIRECT              2  
  #define METHOD_NEITHER                  3  


       (1)如果使用METHOD_BUFFERED,表示系統將用戶的輸入輸出都經過pIrp->AssociatedIrp.SystemBuffer緩沖,因此這種方式的通信比較安全

  METHOD_BUFFERED方式相當於對Ring3的輸入輸出都進行了緩沖

       METHOD_BUFFERED方式(借圖):

       

 


  (2)如果使用METHOD_IN_DIRECTMETHOD_OUT_DIRECT方式,表示系統會將輸入緩沖在pIrp->AssociatedIrp.SystemBuffer中,並將輸出緩沖區鎖定,然后在內核模式下重新映射一段地址,這樣也是比較安全的。

  METHOD_IN_DIRECT和METHOD_OUT_DIRECT可稱為"直接方式",是指系統依然對Ring3的輸入緩沖區進行緩沖,但是對Ring3的輸出緩沖區並沒有緩沖,而是在內核中進行了鎖定。這樣Ring3輸出緩沖區在驅動程序完成I/O請求之前,都是無法訪問的,從一定程度上保障了安全性。如圖21.1.14所示。
這兩種方式,對於Ring3的輸入緩沖區和METHOD_BUFFERED方式是一致的。對於Ring3的輸出緩沖區,首先由系統鎖定,並使用pIrp->MdlAddress來描述這段內存,驅動程序需要使用MmGetSystemAddressForMdlSafe函數將這段內存映射到內核內存地址(OutputBuffer),然后可以直接寫入OutputBuffer地址,最終在驅動派遣例程返回后,由系統解除這段內存的鎖定。
 
  METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的內存訪問
  8METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的區別,僅在於打開設備的權限上,當以只讀權限打開設備時,METHOD_IN_DIRECT方式的IoControl將會成功,而METHOD_OUT_DIRECT方式將會失敗。如果以讀寫權限打開設備,兩種方式都會成功。

  METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式(借圖)

       

 


  (3)如果使用METHOD_NEITHER方式,"其他方式",雖然通信的效率提高了,但是不夠安全。驅動的派遣函數中輸入緩沖區可以通過I/O堆棧(IO_STACK_LOCATION)的stack->Parameters.DeviceIo Control.Type3InputBuffer得到。輸出緩沖區可以通過pIrp->UserBuffer得到。由於驅動中的派遣函數不能保證傳遞進來的用戶輸入和輸出地址,因此最好不要直接去讀寫這些地址的緩沖區。應該在讀寫前使用ProbeForRead和ProbeForWrite函數探測地址是否可讀和可寫。

  METHOD_ NEITHER方式是不進行緩沖的,在驅動中可以直接使用Ring3的輸入輸出內存地址

  驅動程序可以通過pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer得到Ring3的輸入緩沖區地址(其中pIrpStack是IoGetCurrentIrpStackLocation(pIrp)的返回);通過pIrp-> UserBuffer得到Ring3的輸出緩沖區地址。
  由於METHOD_NEITHER方式並不安全,因此最好對Type3InputBuffer讀取之前使用ProbeForRead函數進行探測,對UserBuffer寫入之前使用ProbeForWrite函數進行探測,當沒有發生異常時,再進行讀取和寫入操作。

  METHOD_NEITHER方式(借圖)

  


  二 .定義驅動設備名,符號鏈接名
       定義好了IO控制碼CTL_CODE,第二步驅動程序還要准備驅動設備名和符號鏈接名。     

    關於在Ring0層中要設置驅動設備名的同時還要設置符號鏈接名的原因,是因為只有符號鏈接名才可以被用戶模式下的應用程序識別

    windows下的設備是以"\Device\[設備名]”形式命名的。例如磁盤分區的c盤,d盤的設備名稱就是"\Device\HarddiskVolume1”,"\Device\HarddiskVolume2”, 當然也可以不指定設備名稱。                               如果IoCreateDevice中沒有指定設備名稱,那么I/O管理器會自動分配一個數字作為設備的名稱。例如"\Device\00000001"。\Device\[設備名],不容易記憶,通常符號鏈接可以理解為設備的別名,更重要的是設備名,只能被內核模式下的其他驅動所識別,而別名可以被用戶模式下的應用程序識別,例如c盤,就是名為"c:"的符號鏈接,其真正的設備對象是"\Device\HarddiskVolume1”,所以在寫驅動時候,一般我們創建符號鏈接,即使驅動中沒有用到,這也算是一個好的習慣吧。

    驅動中符號鏈接名是這樣寫的
    L"\\??\\HelloDDK" --->\??\HelloDDK

    或者
    L"\\DosDevices\\HelloDDK"--->\DosDevices\HelloDDK


    在應用程序中,符號鏈接名:
    L"\\\\.\\HelloDDK"-->\\.\HelloDDK

    DosDevices的符號鏈接名就是??, 所以"\\DosDevices\\XXXX"其實就是\\??\\XXXX

              

#define DEVICE_OBJECT_NAME  L"\\Device\\BufferedIODeviceObjectName"
//設備與設備之間通信
#define DEVICE_LINK_NAME    L"\\DosDevices\\BufferedIODevcieLinkName"
//設備與Ring3之間通信

  三.將符號鏈接名與設備對象名稱關聯 ,等待IO控制碼

    驅動程序要做的最后一步,先用IoCreateDevice函數創建設備對象,再用IoCreateSymbolicLink符號鏈接名與設備對象名稱關聯 ,大功告成,等待IO控制碼。

    

        //創建設備對象名稱
	RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME);
	//創建設備對象
	Status = IoCreateDevice(DriverObject,NULL,
		&DeviceObjectName,
		FILE_DEVICE_UNKNOWN,
		0, FALSE,
		&DeviceObject);
	if (!NT_SUCCESS(Status))
	{
		return Status;
	}

	//創建設備連接名稱
	RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
	//將設備連接名稱與設備名稱關聯 
	Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName);

	if (!NT_SUCCESS(Status))
	{
		IoDeleteDevice(DeviceObject);
		return Status;
	}        

  

  四.應用程序獲取設備句柄,發送IO控制碼。

    驅動程序鋪墊打理好之后,應用程序就可以由符號鏈接名通過CreateFile函數獲取到設備句柄DeviceHandle,再用本場的主角,DeviceIoControl通過這個DeviceHandle發送控制碼了。

    先看看這兩個函數:

   

BOOL WINAPI DeviceIoControl(
  _In_         HANDLE hDevice,       //CreateFile函數打開的設備句柄
  _In_         DWORD dwIoControlCode,//自定義的控制碼
  _In_opt_     LPVOID lpInBuffer,    //輸入緩沖區
  _In_         DWORD nInBufferSize,  //輸入緩沖區的大小
  _Out_opt_    LPVOID lpOutBuffer,   //輸出緩沖區
  _In_         DWORD nOutBufferSize, //輸出緩沖區的大小
  _Out_opt_    LPDWORD lpBytesReturned, //實際返回的字節數,對應驅動程序中pIrp->IoStatus.Information。
  _Inout_opt_  LPOVERLAPPED lpOverlapped //重疊操作結構指針。同步設為NULL,DeviceIoControl將進行阻塞調用;否則,應在編程時按異步操作設計
);







HANDLE CreateFile(
  LPCTSTR lpFileName,                         //打開的文件名
  DWORD dwDesiredAccess,                    //訪問權限
  DWORD dwShareMode,                      //共享模式
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,   //安全屬性
  DWORD dwCreationDisposition,               //文件存在與不存在時的文件創建模式
  DWORD dwFlagsAndAttributes,                //文件屬性設定(隱藏、只讀、壓縮、指定為系統文件等)
  HANDLE hTemplateFile                       //文件副本句柄
);

  

  最后總結一下DeviceIoControl的通信流程:

    1.驅動程序和應用程序自定義IO控制碼 (CTL_CODE宏 四個參數,32位,4部分,存儲設備類型,訪問權限,操作功能,緩沖區數據傳遞方式(四種))

    2.驅動程序定義驅動設備名,符號鏈接名, 將符號鏈接名與設備對象名稱關聯 ,等待IO控制碼(IoCreateDevice,IoCreateSymbolicLink)

    3.應用程序由符號鏈接名通過CreateFile函數獲取到設備句柄DeviceHandle,再用本場的主角,DeviceIoControl通過這個設備句柄發送控制碼給派遣函數

 

 

 

 

  源代碼:

  BufferedIO.h

  

#pragma once
#include <ntifs.h>


#define CTL_SYS \
	CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS)


#define DEVICE_OBJECT_NAME  L"\\Device\\BufferedIODeviceObjectName"
//設備與設備之間通信
#define DEVICE_LINK_NAME    L"\\DosDevices\\BufferedIODevcieLinkName"
//設備與Ring3之間通信
VOID DriverUnload(PDRIVER_OBJECT DriverObject);
NTSTATUS PassThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp);
NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp);

  

BufferedIO.c

#include "BufferedIO.h"


NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
	NTSTATUS Status = STATUS_SUCCESS;
	PDEVICE_OBJECT  DeviceObject = NULL;
	UNICODE_STRING  DeviceObjectName;
	UNICODE_STRING  DeviceLinkName;
	ULONG			i;
	//   棧
	//   堆
	//   全局(global Static Const)
	DriverObject->DriverUnload = DriverUnload;

	//創建設備對象名稱
	RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME);

	//創建設備對象
	Status = IoCreateDevice(DriverObject,NULL,
		&DeviceObjectName,
		FILE_DEVICE_UNKNOWN,
		0, FALSE,
		&DeviceObject);
	if (!NT_SUCCESS(Status))
	{
		return Status;
	}
	//創建設備連接名稱
	RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);

	//將設備連接名稱與設備名稱關聯 
	Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName);

	if (!NT_SUCCESS(Status))
	{
		IoDeleteDevice(DeviceObject);
		return Status;
	}
	//設計符合我們代碼的派遣歷程	
	for (i=0;i<IRP_MJ_MAXIMUM_FUNCTION;i++)
	{
		DriverObject->MajorFunction[i] = PassThroughDispatch;   //函數指針
	}
	DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ControlThroughDispatch;
	return Status;
}
//派遣歷程 
NTSTATUS PassThroughDispatch(PDEVICE_OBJECT  DeviceObject,PIRP Irp)
{
	Irp->IoStatus.Status = STATUS_SUCCESS;     //LastError()
	Irp->IoStatus.Information = 0;             //ReturnLength 
	IoCompleteRequest(Irp, IO_NO_INCREMENT);   //將Irp返回給Io管理器
	return STATUS_SUCCESS;
}
NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp)
{
	NTSTATUS Status;
	ULONG_PTR Informaiton = 0;
	PVOID InputData = NULL;
	ULONG InputDataLength = 0;
	PVOID OutputData = NULL;
	ULONG OutputDataLength = 0;
	ULONG IoControlCode = 0;
	PIO_STACK_LOCATION  IoStackLocation = IoGetCurrentIrpStackLocation(Irp);  //Irp堆棧	
	IoControlCode = IoStackLocation->Parameters.DeviceIoControl.IoControlCode;
	InputData  = Irp->AssociatedIrp.SystemBuffer;
	OutputData = Irp->AssociatedIrp.SystemBuffer;
	InputDataLength  = IoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
	OutputDataLength = IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
	switch (IoControlCode)
	{
	case CTL_SYS:
	{
		if (InputData != NULL&&InputDataLength > 0)
		{
			DbgPrint("%s\r\n", InputData);
		}
		if (OutputData != NULL&&OutputDataLength >= strlen("Ring0->Ring3") + 1)
		{
			memcpy(OutputData, "Ring0->Ring3", strlen("Ring0->Ring3") + 1);
			Status = STATUS_SUCCESS;
			Informaiton = strlen("Ring0->Ring3") + 1;
		}
		else
		{
			Status = STATUS_INSUFFICIENT_RESOURCES;   //內存不夠
			Informaiton = 0;
		}
		break;
	}
	default:
		break;
	}
	Irp->IoStatus.Status = Status;             //Ring3 GetLastError();
	Irp->IoStatus.Information = Informaiton;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);  //將Irp返回給Io管理器
	return Status;                            //Ring3 DeviceIoControl()返回值
}
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	UNICODE_STRING  DeviceLinkName;
	PDEVICE_OBJECT	v1 = NULL;
	PDEVICE_OBJECT  DeleteDeviceObject = NULL;
	
	RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
	IoDeleteSymbolicLink(&DeviceLinkName);

	DeleteDeviceObject = DriverObject->DeviceObject;
	while (DeleteDeviceObject != NULL)
	{
		v1 = DeleteDeviceObject->NextDevice;
		IoDeleteDevice(DeleteDeviceObject);
		DeleteDeviceObject = v1;
	}
}

  

IO.cpp

 

// 緩沖區IO.cpp : 定義控制台應用程序的入口點。
//

#include "stdafx.h"
#include <windows.h>
#define DEVICE_LINK_NAME    L"\\\\.\\BufferedIODevcieLinkName"


#define CTL_SYS \
	CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS)
int main()
{
	HANDLE DeviceHandle = CreateFile(DEVICE_LINK_NAME,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);
	if (DeviceHandle==INVALID_HANDLE_VALUE)
	{
		return 0;
	}
	char BufferData = NULL;
	DWORD ReturnLength = 0;
	BOOL IsOk = DeviceIoControl(DeviceHandle, CTL_SYS,
		"Ring3->Ring0",
		strlen("Ring3->Ring0")+1,
		(LPVOID)BufferData,
		0,
		&ReturnLength,
		NULL);
	if (IsOk == FALSE)
	{
		int LastError = GetLastError();

		if (LastError == ERROR_NO_SYSTEM_RESOURCES)
		{
			char BufferData[MAX_PATH] = { 0 };
			IsOk = DeviceIoControl(DeviceHandle, CTL_SYS,
				"Ring3->Ring0",
				strlen("Ring3->Ring0") + 1,
				(LPVOID)BufferData,
				MAX_PATH,
				&ReturnLength,
				NULL);

			if (IsOk == TRUE)
			{
				printf("%s\r\n", BufferData);
			}
		}
	}
	if (DeviceHandle != NULL)
	{
		CloseHandle(DeviceHandle);
		DeviceHandle = NULL;
	}
	printf("Input AnyKey To Exit\r\n");

	getchar();
    return 0;
}

  

 

       

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM