詳解應用程序與驅動程序通信DeviceIoControl
一、定義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的通信中的內存訪問方式,有四種方式:
#defineMETHOD_BUFFERED0
#defineMETHOD_IN_DIRECT1
#defineMETHOD_OUT_DIRECT2
#defineMETHOD_NEITHER3
(1)如果使用METHOD_BUFFERED,表示系統將用戶的輸入輸出都經過pIrp->AssociatedIrp.SystemBuffer來緩沖,因此這種方式的通信比較安全。
METHOD_BUFFERED方式相當於對Ring3的輸入輸出都進行瞭緩沖。
METHOD_BUFFERED方式:
(2)如果使用METHOD_IN_DIRECT或METHOD_OUT_DIRECT方式,表示系統會將輸入緩沖在pIrp->AssociatedIrp.SystemBuffer中,並將輸出緩沖區鎖定,然後在內核模式下重新映射一段地址,這樣也是比較安全的。
METHOD_IN_DIRECT和METHOD_OUT_DIRECT可稱為”直接方式”,是指系統依然對Ring3的輸入緩沖區進行緩沖,但是對Ring3的輸出緩沖區並沒有緩沖,而是在內核中進行瞭鎖定。這樣Ring3輸出緩沖區在驅動程序完成I/O請求之前,都是無法訪問的,從一定程度上保障瞭安全性。
這兩種方式,對於Ring3的輸入緩沖區和METHOD_BUFFERED方式是一致的。對於Ring3的輸出緩沖區,首先由系統鎖定,並使用pIrp->MdlAddress來描述這段內存,驅動程序需要使用MmGetSystemAddressForMdlSafe函數將這段內存映射到內核內存地址(OutputBuffer),然後可以直接寫入OutputBuffer地址,最終在驅動派遣例程返回後,由系統解除這段內存的鎖定。
METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的內存訪問
METHOD_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; }
以上就是詳解應用程序與驅動程序通信DeviceIoControl的詳細內容,更多關於應用程序 驅動程序通信 DeviceIoControl的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- C語言驅動開發之通過ReadFile與內核層通信
- Vue自定義鈴聲提示音組件的實現
- vue+axios實現圖片上傳識別人臉的示例代碼
- GoLang函數棧的使用詳細講解
- java使用枚舉封裝錯誤碼及錯誤信息詳解