• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

详解应用程序与驱动程序通信DeviceIoControl

c语言 搞代码 4年前 (2022-01-06) 26次浏览 已收录 0个评论
文章目录[隐藏]

这种通信方式,就是驱动程序和应用程序自定义一种IO控制码,然后调用DeviceIoControl函数,IO管理器会产生一个MajorFunction为IRP_MJ_DEVICE_CONTROL,MinorFunction为自己定义的控制码的IRP,系统就调用相应的处理IRP_MJ_DEVICE_CONTROL的派遣函数

一、定义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.gaodaimna/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  #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;iMajorFunction[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;             /<a style="color:transparent">来源gao($daima.com搞@代@#码(网</a>/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  #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的详细内容,更多请关注gaodaima搞代码网其它相关文章!


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:详解应用程序与驱动程序通信DeviceIoControl

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址