詳解應(yīng)用程序與驅(qū)動(dòng)程序通信DeviceIoControl
一、定義IO控制碼
其實(shí)可以看作是一種通信協(xié)議
看看CTL_CODE原型:
#define CTL_CODE( DeviceType, Function, Method, Access ) ( \ ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ )
可以看到,這個(gè)宏四個(gè)參數(shù),自然是一個(gè)32位分成了4部分,高16位存儲(chǔ)設(shè)備類型,14~15位訪問權(quán)限,2~13位操作功能,最后0,1兩位就是確定緩沖區(qū)是如何與I/O和文件系統(tǒng)數(shù)據(jù)緩沖區(qū)進(jìn)行數(shù)據(jù)傳遞方式,最常見的就是METHOD_BUFFERED。
自定義CTL_CODE:
#define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)
IOCTL_Device_Function:生成的IRP的MinorFunction
DeviceType:設(shè)備對(duì)象的類型。設(shè)備類型可參考:http://blog.csdn.net/liyun123gx/article/details/38058965
Function :自定義的IO控制碼。自己定義時(shí)取0x800到0xFFF,因?yàn)?x0到0x7FF是微軟保留的。
Method :數(shù)據(jù)的操作模式。
METHOD_BUFFERED:緩沖區(qū)模式
METHOD_IN_DIRECT:直接寫模式
METHOD_OUT_DIRECT:直接讀模式
METHOD_NEITHER :Neither模式
Access:訪問權(quán)限,可取值有:
FILE_ANY_ACCESS:表明用戶擁有所有的權(quán)限
FILE_READ_DATA:表明權(quán)限為只讀
FILE_WRITE_DATA:表明權(quán)限為可寫
也可以 FILE_WRITE_DATA | FILE_READ_DATA:表明權(quán)限為可讀可寫,但還沒達(dá)到FILE_ANY_ACCESS的權(quán)限。
繼續(xù)介紹這個(gè)緩沖區(qū)數(shù)據(jù)傳遞方式Method:
Method表示Ring3/Ring0的通信中的內(nèi)存訪問方式,有四種方式:
#defineMETHOD_BUFFERED0
#defineMETHOD_IN_DIRECT1
#defineMETHOD_OUT_DIRECT2
#defineMETHOD_NEITHER3
(1)如果使用METHOD_BUFFERED,表示系統(tǒng)將用戶的輸入輸出都經(jīng)過pIrp->AssociatedIrp.SystemBuffer來緩沖,因此這種方式的通信比較安全。
METHOD_BUFFERED方式相當(dāng)于對(duì)Ring3的輸入輸出都進(jìn)行了緩沖。
METHOD_BUFFERED方式:

(2)如果使用METHOD_IN_DIRECT或METHOD_OUT_DIRECT方式,表示系統(tǒng)會(huì)將輸入緩沖在pIrp->AssociatedIrp.SystemBuffer中,并將輸出緩沖區(qū)鎖定,然后在內(nèi)核模式下重新映射一段地址,這樣也是比較安全的。
METHOD_IN_DIRECT和METHOD_OUT_DIRECT可稱為"直接方式",是指系統(tǒng)依然對(duì)Ring3的輸入緩沖區(qū)進(jìn)行緩沖,但是對(duì)Ring3的輸出緩沖區(qū)并沒有緩沖,而是在內(nèi)核中進(jìn)行了鎖定。這樣Ring3輸出緩沖區(qū)在驅(qū)動(dòng)程序完成I/O請(qǐng)求之前,都是無法訪問的,從一定程度上保障了安全性。
這兩種方式,對(duì)于Ring3的輸入緩沖區(qū)和METHOD_BUFFERED方式是一致的。對(duì)于Ring3的輸出緩沖區(qū),首先由系統(tǒng)鎖定,并使用pIrp->MdlAddress來描述這段內(nèi)存,驅(qū)動(dòng)程序需要使用MmGetSystemAddressForMdlSafe函數(shù)將這段內(nèi)存映射到內(nèi)核內(nèi)存地址(OutputBuffer),然后可以直接寫入OutputBuffer地址,最終在驅(qū)動(dòng)派遣例程返回后,由系統(tǒng)解除這段內(nèi)存的鎖定。
METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的內(nèi)存訪問
METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的區(qū)別,僅在于打開設(shè)備的權(quán)限上,當(dāng)以只讀權(quán)限打開設(shè)備時(shí),METHOD_IN_DIRECT方式的IoControl將會(huì)成功,而METHOD_OUT_DIRECT方式將會(huì)失敗。如果以讀寫權(quán)限打開設(shè)備,兩種方式都會(huì)成功。
METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式:

(3)如果使用METHOD_NEITHER方式,"其他方式",雖然通信的效率提高了,但是不夠安全。驅(qū)動(dòng)的派遣函數(shù)中輸入緩沖區(qū)可以通過I/O堆棧(IO_STACK_LOCATION)的stack->Parameters.DeviceIo Control.Type3InputBuffer得到。輸出緩沖區(qū)可以通過pIrp->UserBuffer得到。由于驅(qū)動(dòng)中的派遣函數(shù)不能保證傳遞進(jìn)來的用戶輸入和輸出地址,因此最好不要直接去讀寫這些地址的緩沖區(qū)。應(yīng)該在讀寫前使用ProbeForRead和ProbeForWrite函數(shù)探測(cè)地址是否可讀和可寫。
METHOD_ NEITHER方式是不進(jìn)行緩沖的,在驅(qū)動(dòng)中可以直接使用Ring3的輸入輸出內(nèi)存地址,
驅(qū)動(dòng)程序可以通過pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer得到Ring3的輸入緩沖區(qū)地址(其中pIrpStack是IoGetCurrentIrpStackLocation(pIrp)的返回);通過pIrp-> UserBuffer得到Ring3的輸出緩沖區(qū)地址。
由于METHOD_NEITHER方式并不安全,因此最好對(duì)Type3InputBuffer讀取之前使用ProbeForRead函數(shù)進(jìn)行探測(cè),對(duì)UserBuffer寫入之前使用ProbeForWrite函數(shù)進(jìn)行探測(cè),當(dāng)沒有發(fā)生異常時(shí),再進(jìn)行讀取和寫入操作。
METHOD_NEITHER方式:

二、定義驅(qū)動(dòng)設(shè)備名,符號(hào)鏈接名
定義好了IO控制碼CTL_CODE,第二步驅(qū)動(dòng)程序還要準(zhǔn)備驅(qū)動(dòng)設(shè)備名和符號(hào)鏈接名。
關(guān)于在Ring0層中要設(shè)置驅(qū)動(dòng)設(shè)備名的同時(shí)還要設(shè)置符號(hào)鏈接名的原因,是因?yàn)橹挥蟹?hào)鏈接名才可以被用戶模式下的應(yīng)用程序識(shí)別。
windows下的設(shè)備是以"\Device\[設(shè)備名]”形式命名的。
例如磁盤分區(qū)的c盤,d盤的設(shè)備名稱就是"\Device\HarddiskVolume1”,"\Device\HarddiskVolume2”, 當(dāng)然也可以不指定設(shè)備名稱。
如果IoCreateDevice中沒有指定設(shè)備名稱,那么I/O管理器會(huì)自動(dòng)分配一個(gè)數(shù)字作為設(shè)備的名稱。
例如"\Device\00000001"。\Device\[設(shè)備名],不容易記憶,通常符號(hào)鏈接可以理解為設(shè)備的別名,更重要的是設(shè)備名,只能被內(nèi)核模式下的其他驅(qū)動(dòng)所識(shí)別,而別名可以被用戶模式下的應(yīng)用程序識(shí)別,例如c盤,就是名為"c:"的符號(hào)鏈接,其真正的設(shè)備對(duì)象是"\Device\HarddiskVolume1”,所以在寫驅(qū)動(dòng)時(shí)候,一般我們創(chuàng)建符號(hào)鏈接,即使驅(qū)動(dòng)中沒有用到,這也算是一個(gè)好的習(xí)慣吧。
驅(qū)動(dòng)中符號(hào)鏈接名是這樣寫的
L"\\??\\HelloDDK" --->\??\HelloDDK
或者
L"\\DosDevices\\HelloDDK"--->\DosDevices\HelloDDK
在應(yīng)用程序中,符號(hào)鏈接名:
L"\\\\.\\HelloDDK"-->\\.\HelloDDK
DosDevices的符號(hào)鏈接名就是??, 所以"\\DosDevices\\XXXX"其實(shí)就是\\??\\XXXX
#define DEVICE_OBJECT_NAME L"\\Device\\BufferedIODeviceObjectName" //設(shè)備與設(shè)備之間通信 #define DEVICE_LINK_NAME L"\\DosDevices\\BufferedIODevcieLinkName" //設(shè)備與Ring3之間通信
三、將符號(hào)鏈接名與設(shè)備對(duì)象名稱關(guān)聯(lián) ,等待IO控制碼
驅(qū)動(dòng)程序要做的最后一步,先用IoCreateDevice函數(shù)創(chuàng)建設(shè)備對(duì)象,再用IoCreateSymbolicLink將符號(hào)鏈接名與設(shè)備對(duì)象名稱關(guān)聯(lián),大功告成,等待IO控制碼。
//創(chuàng)建設(shè)備對(duì)象名稱
RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME);
//創(chuàng)建設(shè)備對(duì)象
Status = IoCreateDevice(DriverObject,NULL,
&DeviceObjectName,
FILE_DEVICE_UNKNOWN,
0, FALSE,
&DeviceObject);
if (!NT_SUCCESS(Status))
{
return Status;
}
//創(chuàng)建設(shè)備連接名稱
RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
//將設(shè)備連接名稱與設(shè)備名稱關(guān)聯(lián)
Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName);
if (!NT_SUCCESS(Status))
{
IoDeleteDevice(DeviceObject);
return Status;
}
四、應(yīng)用程序獲取設(shè)備句柄,發(fā)送IO控制碼
驅(qū)動(dòng)程序鋪墊打理好之后,應(yīng)用程序就可以由符號(hào)鏈接名通過CreateFile函數(shù)獲取到設(shè)備句柄DeviceHandle,再用本場(chǎng)的主角,DeviceIoControl通過這個(gè)DeviceHandle發(fā)送控制碼了。
先看看這兩個(gè)函數(shù):
BOOL WINAPI DeviceIoControl( _In_ HANDLE hDevice, //CreateFile函數(shù)打開的設(shè)備句柄 _In_ DWORD dwIoControlCode,//自定義的控制碼 _In_opt_ LPVOID lpInBuffer, //輸入緩沖區(qū) _In_ DWORD nInBufferSize, //輸入緩沖區(qū)的大小 _Out_opt_ LPVOID lpOutBuffer, //輸出緩沖區(qū) _In_ DWORD nOutBufferSize, //輸出緩沖區(qū)的大小 _Out_opt_ LPDWORD lpBytesReturned, //實(shí)際返回的字節(jié)數(shù),對(duì)應(yīng)驅(qū)動(dòng)程序中pIrp->IoStatus.Information。 _Inout_opt_ LPOVERLAPPED lpOverlapped //重疊操作結(jié)構(gòu)指針。同步設(shè)為NULL,DeviceIoControl將進(jìn)行阻塞調(diào)用;否則,應(yīng)在編程時(shí)按異步操作設(shè)計(jì) ); HANDLE CreateFile( LPCTSTR lpFileName, //打開的文件名 DWORD dwDesiredAccess, //訪問權(quán)限 DWORD dwShareMode, //共享模式 LPSECURITY_ATTRIBUTES lpSecurityAttributes, //安全屬性 DWORD dwCreationDisposition, //文件存在與不存在時(shí)的文件創(chuàng)建模式 DWORD dwFlagsAndAttributes, //文件屬性設(shè)定(隱藏、只讀、壓縮、指定為系統(tǒng)文件等) HANDLE hTemplateFile //文件副本句柄 );
五、總結(jié)DeviceIoControl的通信流程
1.驅(qū)動(dòng)程序和應(yīng)用程序自定義好IO控制碼 (CTL_CODE宏 四個(gè)參數(shù),32位,4部分,存儲(chǔ)設(shè)備類型,訪問權(quán)限,操作功能,緩沖區(qū)數(shù)據(jù)傳遞方式(四種))
2.驅(qū)動(dòng)程序定義驅(qū)動(dòng)設(shè)備名,符號(hào)鏈接名, 將符號(hào)鏈接名與設(shè)備對(duì)象名稱關(guān)聯(lián) ,等待IO控制碼(IoCreateDevice,IoCreateSymbolicLink)
3.應(yīng)用程序由符號(hào)鏈接名通過CreateFile函數(shù)獲取到設(shè)備句柄DeviceHandle,再用本場(chǎng)的主角,DeviceIoControl通過這個(gè)設(shè)備句柄發(fā)送控制碼給派遣函數(shù)。
六、源代碼
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"
//設(shè)備與設(shè)備之間通信
#define DEVICE_LINK_NAME L"\\DosDevices\\BufferedIODevcieLinkName"
//設(shè)備與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;
//創(chuàng)建設(shè)備對(duì)象名稱
RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME);
//創(chuàng)建設(shè)備對(duì)象
Status = IoCreateDevice(DriverObject,NULL,
&DeviceObjectName,
FILE_DEVICE_UNKNOWN,
0, FALSE,
&DeviceObject);
if (!NT_SUCCESS(Status))
{
return Status;
}
//創(chuàng)建設(shè)備連接名稱
RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
//將設(shè)備連接名稱與設(shè)備名稱關(guān)聯(lián)
Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName);
if (!NT_SUCCESS(Status))
{
IoDeleteDevice(DeviceObject);
return Status;
}
//設(shè)計(jì)符合我們代碼的派遣歷程
for (i=0;i<IRP_MJ_MAXIMUM_FUNCTION;i++)
{
DriverObject->MajorFunction[i] = PassThroughDispatch; //函數(shù)指針
}
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; //內(nèi)存不夠
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
// 緩沖區(qū)IO.cpp : 定義控制臺(tái)應(yīng)用程序的入口點(diǎn)。
//
#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;
}
以上就是詳解應(yīng)用程序與驅(qū)動(dòng)程序通信DeviceIoControl的詳細(xì)內(nèi)容,更多關(guān)于應(yīng)用程序 驅(qū)動(dòng)程序通信 DeviceIoControl的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 由于系統(tǒng)錯(cuò)誤 126 (SQL Server),指定驅(qū)動(dòng)程序無法加載
- 常用數(shù)據(jù)庫的驅(qū)動(dòng)程序及JDBC URL分享
- 解決Windows 2003“在系統(tǒng)啟動(dòng)時(shí)至少有一個(gè)服務(wù)或驅(qū)動(dòng)程序產(chǎn)生錯(cuò)誤”
- 驅(qū)動(dòng)程序無法通過使用安全套接字層(SSL)加密與?SQL?Server?建立安全連接,錯(cuò)誤:“The?server?selected?protocol?version?TLS10?is?not?accepted?by?client
相關(guān)文章
VSCode遠(yuǎn)程代碼開發(fā)及DNS隧道端口轉(zhuǎn)發(fā)實(shí)現(xiàn)遠(yuǎn)程辦公代碼
這篇文章主要介紹了VSCode遠(yuǎn)程代碼開發(fā)及DNS隧道端口轉(zhuǎn)發(fā)實(shí)現(xiàn)遠(yuǎn)程辦公,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
C++面試八股文之如何實(shí)現(xiàn)strncpy函數(shù)
strncpy函數(shù),主要用做字符串復(fù)制,將于字符從一個(gè)位置復(fù)制到另一個(gè)位置,那么如何實(shí)現(xiàn)一個(gè)strncpy函數(shù),下面小編就來和大家簡單講講吧2023-07-07
C++中priority_queue與仿函數(shù)實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于C++中priority_queue與仿函數(shù)實(shí)現(xiàn)的相關(guān)資料,優(yōu)先級(jí)隊(duì)列是一種容器適配器,其底層通常采用vector容器,并通過堆算法來維護(hù)元素的順序,文中通過代碼介紹的非常詳細(xì)《》需要的朋友可以參考下2024-10-10

