windows driver - DeviceIoControl 用法
原文地址:https://www.cnblogs.com/lsh123/p/7354573.html
之前写过一篇关于通过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_DIRECT或METHOD_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; }
windows driver - DeviceIoControl 用法相关推荐
- Windows Driver开发_TraceEvents调试以及加载驱动的方法
在最新版的WDK框架里,我们新建了Windows Driver KMDF模型之后默认使用的TraceEvents来打印输出的,这套输出机制叫WPP,我们可以在DriverEntry函数里看到它的初始化 ...
- Windows Driver开发_NT Driver框架:The driver is not in a state to accept this command
通常我们可以使用一些驱动加载工具来改变我们驱动的行为,如加载驱动时执行入口函数与卸载函数,这里是我用当我们点击Start时会调用DriverEvent函数,并且正常加载 但当我们点击Stop卸载时确报 ...
- Windows Driver开发_安装与配置环境
首先到这个网站里去下载Visual Studio2019,注意目前最新版的WDK仅支持Visual Studio2019,如果要下载2022版本目前推出了预览版,但需要申请下载,所以这里以2019为例 ...
- Windows SDK DDK WDK (Windows Driver Kit) 区别
转:https://www.cnblogs.com/xdot/p/9815391.html 首先,先从基础的东西说起,开发WINDOWS下的驱动程序,需要一个专门的开发包,如:开发JAVA程序,我们可 ...
- Windows rundll32的用法-批处理管理打印机
用法: rundll32 printui.dll,PrintUIEntry [options] [@commandfile]/a[file] 二进制文件名/b[name] 基本打印机名/c[name] ...
- Windows命令行用法
在介绍Windows批处命令前,我们首先来介绍Windows命令行的使用. Windows shell提供了一个黑色的框框界面,即命令行操作界面,关于命令行的作用和好处,我就不费口舌了,下面仅窥见一斑 ...
- Windows Driver Development Debug Assist Windows驱动开发助手
Windows 驱动开发助手 功能 FFU烧录操作入口 Enable/Disable BCDEdit Debug Windbg COM口调试启动 Driver Replace替换 一键弹出SD卡 GI ...
- windows上telnet用法 测试端口号
telnet 不是内部命令或外部命令,也不是可执行程序或批处理文件. 其实windows上有自带的telnet工具的,只是没有安装添加进来而已. 控制面板 -->选择程序与功能 --> 打 ...
- mysql mysqladmin 启动_mysqladmin客户端(windows环境)的用法
mysql数据库系统是基于"客户/服务器"模式的,在windows系统中执行了MySQL的安装程序之后,将同时安装了MySQL服务器和mysql,mysqladmin,mydqld ...
最新文章
- 技术11期:Atlas的概念你了解多少?
- 基于二代和三代测序技术的柚子基因组混合拼装
- 一说“并发”就想到“多线程”,那就局限了
- Ruby on rails
- Build boost 1.66.0 with c++11
- 【PAT乙级】1057 数零壹 (20 分)
- Python - WebDriver 识别登录验证码
- Apache Sentry 第一弹:Server启动、连接Hue、分组详解
- Java读取、创建Excel;验签,加密
- 【转载】【凯子哥带你学Framework】Activity界面显示全解析(下)
- vscode 插件之 htmltagwrap的修改
- SSM中拦截器和过滤器
- LR11破解License
- 安信天行全方位信息安全态势感知平台建设与运营
- 【技术】客服服务开发
- 文华财经期货K线多周期画线技术,多重短线技术共振通道线指标公式——多周期主图自动画线
- 初学Python,需要装什么软件?
- MySQL 8.0原理与实战一网打尽,甲骨文数据库专家硬刚5年之作
- C语言语句篇-------赋值语句
- 使用gcore工具产生core文件而不杀死进程