应用层与驱动层通信DeviceIoControl

2023-11-16

驱动层与应用层通信是通过DeviceIoControl,

符号定义

#define DEVICE_NAME  L"\\Device\\myDriver"             // Driver Name
#define SYMBOLIC_LINK_NAME  L"\\DosDevices\\myDriver"            // Symbolic Link Name
#define WIN32_LINK_NAME  "\\\\.\\myDriver"                    // Win32 Link Name

//
// Device IO Control Codes
//
#define IOCTL_BASE          0x800
#define MY_CTL_CODE(i)        \
	CTL_CODE                  \
	(                         \
	FILE_DEVICE_UNKNOWN,  \
	IOCTL_BASE + i,       \
	METHOD_BUFFERED,      \
	FILE_ANY_ACCESS       \
	)

// ===========内存数据结构, 不会用来做多端的数据传递======================
// ===========用户自己可以存些需要在内存中传递的数据======================
typedef struct _DEV_EXTENSION {
	INT32              iTest;
	WCHAR              wLastPath[512];
}DEV_EXTENSION, * PDEV_EXTENSION;

driverentry

NTSTATUS DriverEntry(
	IN PDRIVER_OBJECT DriverObject,
	IN PUNICODE_STRING RegistryPath
)
{
	NTSTATUS					status = STATUS_SUCCESS;
	UNICODE_STRING				uszDriverString;
	UNICODE_STRING				uszDeviceString;
	PDEVICE_OBJECT				pDeviceObject = NULL;
	PDEV_EXTENSION              pDevExt = NULL;
	HANDLE                      hThread = NULL;

	DbgPrint("MyDriver DriverEntry\n");

	// Point uszDriverString at the driver name
	RtlInitUnicodeString(&uszDriverString, DEVICE_NAME);

	// Create and initialize device object
	status = IoCreateDevice(
		DriverObject,
		sizeof(DEV_EXTENSION),
		&uszDriverString,
		FILE_DEVICE_UNKNOWN,
		0,
		FALSE,
		&pDeviceObject
	);
	if (!NT_SUCCESS(status))
	{
		DbgPrint("RegistryMonitor: ERROR IoCreateDevice - %08x\n", status);
		return status;
	}

	// 初始化设备扩展
	gpDeviceObject = pDeviceObject;
	pDevExt = (DEV_EXTENSION*)pDeviceObject->DeviceExtension;
	pDevExt->iTest = 0;
	wcscpy_s(pDevExt->wLastPath, sizeof(pDevExt->wLastPath)/sizeof(pDevExt->wLastPath[0]), L"");

	/* Point uszDeviceString at the device name */
	RtlInitUnicodeString(&uszDeviceString, SYMBOLIC_LINK_NAME);

	/* Create symbolic link to the user-visible name */
	status = IoCreateSymbolicLink(&uszDeviceString, &uszDriverString);
	if (!NT_SUCCESS(status))
	{
		DbgPrintEx("RegistryMonitor: ERROR IoCreateSymbolicLink - %08x\n", status);
		IoDeleteDevice(pDeviceObject);
		return status;
	}

	// Load structure to point to IRP handlers
	DriverObject->DriverUnload = UnloadDriver;
	DriverObject->MajorFunction[IRP_MJ_CREATE] = KDispatchCreateClose;
	DriverObject->MajorFunction[IRP_MJ_CLOSE] = KDispatchCreateClose;
	DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = KDispatchIoctl;

	return STATUS_SUCCESS;
}

应用层要访问的符号链接是\\.\myDriver

首先驱动层要实现:

pDriverObject->DriverUnload = UnloadDriver;
pDriverObject->MajorFunction[IRP_MJ_CREATE] = KDispatchCreateClose;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = KDispatchCreateClose;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = KDispatchIoctl;

Create和Close函数必须要实现,否则CreateFile的时候可能会报错

NTSTATUS KDispatchCreateClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
	DbgPrint("create or close happen\n");
	Irp->IoStatus.Status = STATUS_SUCCESS;
	Irp->IoStatus.Information = 0;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}

卸载例程


void UnloadDriver(IN PDRIVER_OBJECT DriverObject)
{
	UNICODE_STRING  uszDeviceString;
	NTSTATUS        ntStatus;
	RtlInitUnicodeString(&uszDeviceString, SYMBOLIC_LINK_NAME);
	IoDeleteSymbolicLink(&uszDeviceString);
	if (DriverObject->DeviceObject != NULL)
	{
		IoDeleteDevice(DriverObject->DeviceObject);
	}

	DbgPrint("Unload Success \n");
}

其中MyDispatchDeviceControl用来与应用层通过DeviceIoControl通信

NTSTATUS KDispatchIoctl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
	UINT				dwDataWritten = 0;
	NTSTATUS			status = STATUS_INVALID_DEVICE_REQUEST;	 // STATUS_UNSUCCESSFUL
	PIO_STACK_LOCATION	pIrpStack = IoGetCurrentIrpStackLocation(Irp);
	ULONG				uIoControlCode = 0;
	PVOID				pIoBuffer = NULL;
	ULONG				uInSize = 0;
	ULONG				uOutSize = 0;
	int* pReturn = NULL;
	PDEV_EXTENSION   	deviceExtension = (DEV_EXTENSION*)DeviceObject->DeviceExtension;

	NTSTATUS			ntStatus;
	ANSI_STRING			imagePath;						//进程路径
	CHAR				szImageName[300];					//进程镜像名
	HANDLE				pid = PsGetCurrentProcessId();
	ULONG				information = 0;

	// Get the IoCtrl Code
	uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;

	pIoBuffer = Irp->AssociatedIrp.SystemBuffer;
	uInSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
	uOutSize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;


	if (DeviceObject == gpDeviceObject)
	{
		switch (uIoControlCode)
		{
		case MY_CTL_CODE(1):
		{
			DbgPrint("buffer:%p,%d,%d\n", pIoBuffer, uInSize, uOutSize);
			wcscpy_s(gTargetUserSid, sizeof(gTargetUserSid) / sizeof(gTargetUserSid[0]), (WCHAR*)pIoBuffer);
			DbgPrint("get str:%ws, size:%d\n", gTargetUserSid, uInSize);
			wcscpy_s((WCHAR*)pIoBuffer, uOutSize, L"ok");
			information = 6;
			status = STATUS_SUCCESS;
		}
		break;
		default:
		{
			// Invalid code sent
			DbgPrint("[HookDeviceIocontrol] Unknown IOCTL: 0x%X (%04X,%04X)\r\n",
				uIoControlCode,
				DEVICE_TYPE_FROM_CTL_CODE(uIoControlCode),
				IoGetFunctionCodeFromCtlCode(uIoControlCode));
			status = STATUS_INVALID_PARAMETER;
		}
		}


		Irp->IoStatus.Information = information;

		// ***************注意一定要在自己要处理的ctlcode中修改status和information*******
        // ***************不然deviceIoControl会报错************************************
		Irp->IoStatus.Status = status;

		IoCompleteRequest(Irp, IO_NO_INCREMENT);
		return status;

	}
	else
	{
		Irp->IoStatus.Information = 0;

		// Complete the I/O Request
		Irp->IoStatus.Status = STATUS_SUCCESS;

		IoCompleteRequest(Irp, IO_NO_INCREMENT);
		return status;
	}
}

然后应用层要打开驱动层的设备链接符号,来得到句柄,DeviceIoControl的通信依赖句柄

void TestDriver()
{
	BOOL bRet = TRUE;
	DWORD dwReturnSize = 0;
	WCHAR wzIn[MAX_PATH], wzOut[MAX_PATH];
	WCHAR symbolPath[MAX_PATH];
	HANDLE hSymbol = NULL;

    // 访问WIN32_LINK_NAME
	wcscpy_s(symbolPath, sizeof(symbolPath) / sizeof(symbolPath[0]), L"\\\\.\\myDriver");

	wcscpy_s(wzIn, sizeof(wzIn)/sizeof(wzIn[0]), L"hello world_000");

	hSymbol = CreateFileW(
		symbolPath, 
		GENERIC_WRITE | GENERIC_READ,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		0,
		NULL);
	if (INVALID_HANDLE_VALUE == hSymbol) {
		WriteLogEx(L"crete file:%s failed, error:%d\n", symbolPath, GetLastError());
		return;
	}

	DWORD dwInBufferSize = (wcslen(wzIn) + 1) * sizeof(WCHAR);
	bRet = DeviceIoControl(hSymbol, MY_CTL_CODE(1), wzIn, dwInBufferSize,
		wzOut, sizeof(wzOut), &dwReturnSize, NULL);

	if (!bRet) {
		WriteLogEx(L"DeviceIoControl:%s,size:%d failed, error:%d\n", symbolPath, dwInBufferSize, GetLastError());
        CloseHandle(hSymbol);
		return;
	}

    CloseHandle(hSymbol);
	WriteLogEx(L"done\n");
}

遇到的坑:

1.CreatFile失败

因为没有实现
DriverObject->MajorFunction[IRP_MJ_CREATE] = KDispatchCreateClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = KDispatchCreateClose;

2.deviceIocontrol返回失败,可是驱动层明明收到数据了

因为没有把返回值status调整为STATUS_SUCCESS,只是等于了一个初始化不会STATUS_SUCCESS的值

3.DeviceIoControl传到驱动层了,但是没有读到数据

一开始是读到了的,后来因为排查问题的时候以为是CtlCode的问题,于是随意调整了一下MY_CTL_CODE的第三个参数METHOD_BUFFERED,凑巧发现这个值影响到驱动层读取DeviceIoControl传递的数据,搜索一番发现微软官方是有定义的

Defining I/O Control Codes - Windows drivers | Microsoft Docsh

Method与内存传输的关系

1..METHOD_BUFFERED:缓冲区模式

inbuffer的内容被复制到IRP中的pIrp->AssociatedIrp.SystemBuffer复制的长度是DeviceIoControl中指定的nInBufferSize。

驱动返回数据时,也是向pIrp->AssociatedIrp.SystemBuffer中写入,操作系统会将数据复制到DeviceIoControl的outBuffer,复制的字节数是pIrp->IoStatus.Information, 这个数值由驱动指定。

派遣函数读取关键信息的代码如下:

	uIoControlCode = pIrpStack->Parameters.DeviceIoControl.IoControlCode;

	pIoBuffer = Irp->AssociatedIrp.SystemBuffer;
	uInSize = pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
	uOutSize = pIrpStack->Parameters.DeviceIoControl.OutputBufferLength;

设置返回数据的代码如下


		wcscpy_s((WCHAR*)pIoBuffer, uOutSize, L"ok");
		Irp->IoStatus.Information = 6;

		// Complete the I/O Request
		Irp->IoStatus.Status = STATUS_SUCCESS;

		IoCompleteRequest(Irp, IO_NO_INCREMENT);
		return status;


2 METHOD_IN_DIRECT与METHOD_OUT_DIRECT  直接内存模式
与缓冲模式一样,DeviceIoControl中的inBuffer数据被复制到IRP中的pIrp->AssociatedIrp.SystemBuffer,复制的长度是DeviceIoControl指定的nInBufferSize。

直接内存模式中,操作系统会将DeviceIoControl指定的输出缓冲区锁定,然后在内核模式地址下重新映射一段地址。

派遣函数中IRP中的pIrp->MdlAddress记录DeviceIoControl指定的输出缓冲区。派遣函数应该使用MmGetSystemAddressForMdlSafe将这段内存映射到内核模式下的内存地址。

得到输入输出缓冲区的大小以及IOCTL的方式与缓冲区模式相同。

另外需要注意CTL_CODE设置的权限问题,若以只读方式打开设备,METHOD_IN_DIRECT的IOCTL操作会失败。

派遣函数中处理直接内存模式:

//显示输入缓冲区数据
 

UCHAR* InputBuffer = (UCHAR*)pIrp->AssociatedIrp.SystemBuffer;
for (ULONG i=0;i<cbin;i++)
{
    DbgPrint("%X\n",InputBuffer[i]);
}
//pIrp->MdlAddress为DeviceIoControl输出缓冲区地址相同
DbgPrint("User Address:0X%08X\n",MmGetMdlVirtualAddress(pIrp->MdlAddress));

返回数据

UCHAR* OutputBuffer = (UCHAR*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority);
//InputBuffer被映射到内核模式下的内存地址,必定在0X80000000-0XFFFFFFFF之间memset(OutputBuffer,0xAA,cbout);

3:METHOD_NEITHER :Neither模式
因为此模式直接访问用户模式地址,这是很危险的,所以此模式很少被用到。

使用用户模式地址必须保证调用DeviceIoControl 的线程与派遣函数运行在同一个线程上下文中。

派遣函数得到输入缓冲区的方式与前两种不同,此模式是通过IO堆栈的stack->Parameters.DeviceIoControl.Type3InputBuffer;得到输入缓冲区。

驱动通过pIrp->UserBuffer得到输出缓冲区。

得到输入输出缓冲区的长度与IOCTL的方式与前两种相同。

由于驱动程序的派遣函数不能保证传递进来的用户地址是合法地址,所以要对传入的用户模式地址进行可读写判断。这就需要ProbeForRead函数和ProbeForWrite函数与_try _execpt 结合使用。

下面是驱动派遣函数中Neither模式

//显示输入缓冲区数据

UCHAR* UserInputBuffer = (UCHAR*)stack->Parameters.DeviceIoControl.Type3InputBuffer;
KdPrint(("UserInputBuffer:0X%0X\n",UserInputBuffer));
 
//得到用户模式地址
PVOID UserOutputBuffer = pIrp->UserBuffer;
KdPrint(("UserOutputBuffer:0X%0X\n",UserOutputBuffer));
 
__try
{
    KdPrint(("Enter __try block\n"));
 
    //判断指针是否可读
    ProbeForRead(UserInputBuffer,cbin,4);
    //显示输入缓冲区内容
    for (ULONG i=0;i<cbin;i++)
    {
        KdPrint(("%X\n",UserInputBuffer[i]));
    }
 
    //判断指针是否可写
    ProbeForWrite(UserOutputBuffer,cbout,4);
 
    //操作输出缓冲区
    memset(UserOutputBuffer,0xAA,cbout);
 
    //如果在上面引发异常,所以以后语句不会被执行!
    pIrp->IoStatus.Information = cbout;
 
    KdPrint(("Leave __try block\n"));
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
    KdPrint(("Catch the exception\n"));
    KdPrint(("The program will keep going\n"));
    status = STATUS_UNSUCCESSFUL;
}
 
pIrp->IoStatus.Information = cbout;

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

应用层与驱动层通信DeviceIoControl 的相关文章

  • 钉钉F1 RK3399 咸鱼80元板子使用记录

    1 简单介绍 12V电源 建议2A 默认插电不开机 有大佬找到金属罩下的焊盘 短接可上电开机 在usb旁边的旁边有个端子接口 短接就可以开机 建议找个一样大的接口接个开关 到目前为止还未测试需要哪种接口 其它接口暂时不知 谁舍得拆一个钉钉F
  • 谷粒商城高级篇(36)——商品上架之上传数据到Elasticsearch

    商品上架之上传数据到Elasticsearch 功能需求分析 分析 怎么设计存储结构来保存数据 空间换时间 时间换空间 最终方案 存储结构 关于 nested 类型 商品上架功能实现 guimall common pom xml com x
  • resnet50网络结构_轻量(高效)目标检测网络结构设计

    目标检测网络可以分成如图的5个部分 input image 输入图像的大小对整个网络推断耗时有最直接的影响 小的图像 自然整个网络推断时间就会大大减少 一般来说 输入图像大小与网络深度正相关 即 大图像需要更深的网络提取更好的特征 back
  • JS下setTimeout与setInterval二者的差异

    JS下setTimeout与setInterval二者的差异 很多人都觉得这两个方法差不多 但是 实际上 他们差的很远呢 因为setTimeout 表达式 延时时间 在执行时 是在载入后延迟指定时间后 去执行一次表达式 记住 次数是一次 而

随机推荐

  • Linux I2C 驱动实验

    一 I2C 驱动 本章同样以 I MX6U ALPHA 开发板上的 AP3216C 这个三合一环境光传感器为例 通过 AP3216C 讲解一下如何编写 Linux 下的 I2C 设备驱动程序 Linux 的驱动分离与分层的思想 因此 Lin
  • 数据库DML数据操作语言

    文章目录 DML 数据操作语言 1 插入数据 INSERT 1 1 语法 1 2 插入默认值 注意事项 1 3 全列插入 2 修改数据 UPDATE 2 1 语法 注意 2 2 修改指定记录 添加WHERE子句 1 WHERE子句中常用的条
  • 前端面试之开发中遇到的问题【建议收藏】

    N1 精度问题 0 1 0 2 0 3 使用math js或者big js解决问题致命 重视 N2 频繁请求问题 点击按钮发送请求 但是不能疯狂发请求 等到结果返回后可再次发送请求 可以定义一个flag待请求结束打开flag 1 代码习惯
  • 年底了,清空自己,让心归零!

    眨眼一年 荏苒半生 时光总是别样匆匆 其实这一生 不就是一年一年这样过着 转眼到中年 而后到老年 时间总是悄无声息 让你我措手不及 转眼就年底了 我们都该学会沉淀自己 把过往一键清零 让未来一切重头 年底了 做个总结吧 这一年以来 让你觉得
  • 为何出现Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes ...

    当在IDEA中连接Redis时出现 Java HotSpot 64 Bit Server VM warning Sharing is only supported for boot loader classes because bootst
  • 大数据技术之Zookeeper

    第1章 Zookeeper入门 1 1 概述 Zookeeper是一个开源的分布式的 为分布式框架提供协调服务的Apache项目 Zookeeper从设计模式角度来理解 是一个基于观察者模式设计的分布式服务管理框架 它负责存储和管理大家都关
  • 前端html通栏做法实践

    在前端通栏中也有很多种 今天给大家分享我做的一种 大家可以借鉴一下 虽然可能就一般 但是一般的通栏也就够用了 大家可以根据自己的需求来自行设计即可 这只是一个参考模板而已 下面的代码复制即可用 html代码
  • web worker API开启浏览器js多进程

    面试使人进步 在大厂佬们的碾压下接触到了目前工作中没有遇到的新api和新思路以及解决方案 今天就来说说这个新的api web Worker 以下是MDN原话 指的是一种可由脚本创建的后台任务 任务执行中可以向其创建者收发信息 要创建一个 W
  • 关于微信小程序海报画布绘制

    小程序需要关于用户点击分享时弹起画布海报分享朋友或者朋友圈 1 我们需要先要创建一个画布 然后需要在js里去初始化画布并且制作出想要的画布海报样式 到目前为止咱们的画布海报图已经可以出现了 接下来咱们可以将已生成的海报图片 保存到手机或者分
  • vue 计算属性 vs 方法, 过滤器

    vue 计算属性 vs 方法 过滤器 最近换项目了 终于重新开始 使用 vue 了 继续学习中 computed 计算属性将被混入到 Vue 实例中 所有 getter 和 setter 的 this 上下文自动地绑定为 Vue 实例 变量
  • vue项目部署到nginx服务器

    相信很多刚学习vue的朋友都可以进行到将vue项目进行npm run build这部 对于将build后的结果部署到nginx服务器上却一知半解 作者刚开始的时候也是很迷惑 看到网上很多朋友在问 今天作者就将自己的部署过程记录下来 供大家参
  • Keil MDK5生成bin文件时生成了*.bin文件夹

    在Keil魔术棒中通过以下方式生成bin文件时 看提示命令执行成功 却生成了一个 bin文件夹 文件夹内部是两个bin文件 当时感觉很奇怪 第一次遇到这种情况 使用的命令 fromelf exe bin o firmware L bin o
  • Object的方法(对象的遍历,合并等)。

    1 构造函数 Object 创建一个新的 Object 对象 该对象将会包裹 wrapper 传入的参数 2 静态方法 Object assign 通过复制一个或多个对象来创建一个新的对象 Object create 使用指定的原型对象和属
  • 5个高清视频素材网站,免费下载~

    免费高清视频素材网站 这几个你一定要知道 建议收藏 1 菜鸟图库 https www sucai999 com video html v NTYxMjky 菜鸟图库网素材非常丰富 网站主要还是以设计类素材为主 高清视频素材也很多 像风景 植
  • Prometheus和Grafana

    1 首先简单介绍Prometheus和Grafana Prometheus是由SoundCloud开发的开源监控报警系统和时间序列数据库 TSDB 它是一个监控采集与数据存储框架 监控服务器端 具体采集什么数据依赖于Exporter 监控客
  • TrueCrypt简介、在VS2008下的编译过程

    转载请标明是引用于 http blog csdn net chenyujing1234 欢迎大家拍砖 编译过程中用到的工具下载地址 http download csdn net detail chenyujing1234 4448383 资
  • JSP学习之初识JSP(实现简单的计算器)

    IE浏览器 gt Tomcat gt 数据库 访问JSP的过程 如果是第一次访问 jsp文件被服务器翻译成一个对应的java文件 Servlet 然后 再被编译成一个 class文件并装载到服务器的内存中 如果以后访问JSP 那就直接调用内
  • vue + ant design vue 项目打包优化

    废话不多说 直接上代码 main js 注释掉 vue 和 antdv Vue use antd 这句话一定不能删除 antd 变量虽然没有定义 但是 是cdn资源里面的模块 一定要引入进来 如果提示 antd 未定义可能是 cdn 资源问
  • CSS基础学习--27 常使用的属性

    一 文本效果 属性 描述 CSS hanging punctuation 规定标点字符是否位于线框之外 3 punctuation trim 规定是否对标点字符进行修剪 3 text align last 设置如何对齐最后一行或紧挨着强制换
  • 应用层与驱动层通信DeviceIoControl

    驱动层与应用层通信是通过DeviceIoControl 符号定义 define DEVICE NAME L Device myDriver Driver Name define SYMBOLIC LINK NAME L DosDevices