驱动开发:通过ReadFile与内核层通信
驱动与应用程序的通信是非常有必要的,内核中执行代码后需要将其动态显示给应用层,但驱动程序与应用层毕竟不在一个地址空间内,为了实现内核与应用层数据交互则必须有通信的方法,微软为我们提供了三种通信方式,如下先来介绍通过ReadFile系列函数实现的通信模式。
长话短说,不说没用的概念,首先系统中支持的通信模式可以总结为三种。
- 缓冲区方式读写(DO_BUFFERED_IO)
- 直接方式读写(DO_DIRECT_IO)
- 其他方式读写
而通过ReadFile,WriteFile系列函数实现的通信机制则属于缓冲区通信模式,在该模式下操作系统会将应用层中的数据复制到内核中,此时应用层调用ReadFile,WriteFile函数进行读写时,在驱动内会自动触发 IRP_MJ_READ 与 IRP_MJ_WRITE这两个派遣函数,在派遣函数内则可以对收到的数据进行各类处理。
首先需要实现初始化各类派遣函数这么一个案例,如下代码则是通用的一种初始化派遣函数的基本框架,分别处理了IRP_MJ_CREATE创建派遣,以及IRP_MJ_CLOSE关闭的派遣,此外函数DriverDefaultHandle的作用时初始化其他派遣用的,也就是将除去CREATE/CLOSE这两个派遣之外,其他的全部赋值成初始值的意思,当然不增加此段代码也是无妨,并不影响代码的实际执行。
#include <ntifs.h>
// 卸载驱动执行
VOID UnDriver(PDRIVER_OBJECT pDriver)
{
PDEVICE_OBJECT pDev; // 用来取得要删除设备对象
UNICODE_STRING SymLinkName; // 局部变量symLinkName
pDev = pDriver->DeviceObject;
IoDeleteDevice(pDev); // 调用IoDeleteDevice用于删除设备
RtlInitUnicodeString(&SymLinkName, L"\\??\\LySharkDriver"); // 初始化字符串将symLinkName定义成需要删除的符号链接名称
IoDeleteSymbolicLink(&SymLinkName); // 调用IoDeleteSymbolicLink删除符号链接
DbgPrint("驱动卸载完毕...");
}
// 创建设备连接
// LyShark.com
NTSTATUS CreateDriverObject(IN PDRIVER_OBJECT pDriver)
{
NTSTATUS Status;
PDEVICE_OBJECT pDevObj;
UNICODE_STRING DriverName;
UNICODE_STRING SymLinkName;
// 创建设备名称字符串
RtlInitUnicodeString(&DriverName, L"\\Device\\LySharkDriver");
Status = IoCreateDevice(pDriver, 0, &DriverName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj);
// 指定通信方式为缓冲区
pDevObj->Flags |= DO_BUFFERED_IO;
// 创建符号链接
RtlInitUnicodeString(&SymLinkName, L"\\??\\LySharkDriver");
Status = IoCreateSymbolicLink(&SymLinkName, &DriverName);
return STATUS_SUCCESS;
}
// 创建回调函数
NTSTATUS DispatchCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
pIrp->IoStatus.Status = STATUS_SUCCESS; // 返回成功
DbgPrint("派遣函数 IRP_MJ_CREATE 执行 \n");
IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 指示完成此IRP
return STATUS_SUCCESS; // 返回成功
}
// 关闭回调函数
NTSTATUS DispatchClose(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
pIrp->IoStatus.Status = STATUS_SUCCESS; // 返回成功
DbgPrint("派遣函数 IRP_MJ_CLOSE 执行 \n");
IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 指示完成此IRP
return STATUS_SUCCESS; // 返回成功
}
// 默认派遣函数
NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return status;
}
// 入口函数
// By: LyShark
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark \n");
// 调用创建设备
CreateDriverObject(pDriver);
pDriver->DriverUnload = UnDriver; // 卸载函数
pDriver->MajorFunction[IRP_MJ_CREATE] = DispatchCreate; // 创建派遣函数
pDriver->MajorFunction[IRP_MJ_CLOSE] = DispatchClose; // 关闭派遣函数
// 初始化其他派遣
for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
DbgPrint("初始化派遣: %d \n", i);
pDriver->MajorFunction[i] = DriverDefaultHandle;
}
DbgPrint("驱动加载完成...");
return STATUS_SUCCESS;
}
代码运行效果如下:

通用框架有了,接下来就是让该驱动支持使用ReadWrite的方式实现通信,首先我们需要在DriverEntry处增加两个派遣处理函数的初始化。
// 入口函数
// By: LyShark
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark \n");
// 调用创建设备
CreateDriverObject(pDriver);
// 初始化其他派遣
for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
DbgPrint("初始化派遣: %d \n", i);
pDriver->MajorFunction[i] = DriverDefaultHandle;
}
pDriver->DriverUnload = UnDriver; // 卸载函数
pDriver->MajorFunction[IRP_MJ_CREATE] = DispatchCreate; // 创建派遣函数
pDriver->MajorFunction[IRP_MJ_CLOSE] = DispatchClose; // 关闭派遣函数
// 增加派遣处理
pDriver->MajorFunction[IRP_MJ_READ] = DispatchRead; // 读取派遣函数
pDriver->MajorFunction[IRP_MJ_WRITE] = DispatchWrite; // 写入派遣函数
DbgPrint("驱动加载完成...");
return STATUS_SUCCESS;
}
接着,我们需要分别实现这两个派遣处理函数,如下DispatchRead负责读取时触发,与之对应DispatchWrite负责写入触发。
- 引言:
- 对于读取请求
I/O管理器分配一个与用户模式的缓冲区大小相同的系统缓冲区SystemBuffer,当完成请求时I/O管理器将驱动程序已经提供的数据从系统缓冲区复制到用户缓冲区。 - 对于写入请求,会分配一个系统缓冲区并将
SystemBuffer设置为地址,用户缓冲区的内容会被复制到系统缓冲区,但是不设置UserBuffer缓冲。
通过IoGetCurrentIrpStackLocation(pIrp)接收读写请求长度,偏移等基本参数,AssociatedIrp.SystemBuffer则是读写缓冲区,IoStatus.Information是输出缓冲字节数,Parameters.Read.Length是读取写入的字节数。
// 读取回调函数
NTSTATUS DispatchRead(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS Status = STATUS_SUCCESS;
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(pIrp);
ULONG ulReadLength = Stack->Parameters.Read.Length;
char szBuf[128] = "hello lyshark";
pIrp->IoStatus.Status = Status;
pIrp->IoStatus.Information = ulReadLength;
DbgPrint("读取长度:%d \n", ulReadLength);
// 取出字符串前5个字节返回给R3层
memcpy(pIrp->AssociatedIrp.SystemBuffer, szBuf, ulReadLength);
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return Status;
}
// 接收传入回调函数
// By: LyShark
NTSTATUS DispatchWrite(struct _DEVICE_OBJECT *DeviceObject, struct _IRP *Irp)
{
NTSTATUS Status = STATUS_SUCCESS;
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
ULONG ulWriteLength = Stack->Parameters.Write.Length;
PVOID ulWriteData = Irp->AssociatedIrp.SystemBuffer;
// 输出传入字符串
DbgPrint("传入长度: %d 传入数据: %s \n", ulWriteLength, ulWriteData);
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
如上部分都是在讲解驱动层面的读写派遣,应用层还没有介绍,在应用层我们只需要调用ReadFile函数当调用该函数时驱动中会使用DispatchRead派遣例程来处理这个请求,同理调用WriteFile函数则触发的是DispatchWrite派遣例程。
我们首先从内核中读出前五个字节并放入缓冲区内,输出该缓冲区内的数据,然后在调用写入,将hello lyshark写回到内核里里面,这段代码可以这样来写。
#include <iostream>
#include <Windows.h>
#include <winioctl.h>
int main(int argc, char *argv[])
{
HANDLE hDevice = CreateFileA("\\\\.\\LySharkDriver", GENERIC_READ | GENERIC_WRITE, 0,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
CloseHandle(hDevice);
return 0;
}
// 从内核读取数据到本地
char buffer[128] = { 0 };
ULONG length;
// 读入到buffer长度为5
// By:lyshark.com
ReadFile(hDevice, buffer, 5, &length, 0);
for (int i = 0; i < (int)length; i++)
{
printf("读取字节: %c", buffer[i]);
}
// 写入数据到内核
char write_buffer[128] = "hello lyshark";
ULONG write_length;
WriteFile(hDevice, write_buffer, strlen(write_buffer), &write_length, 0);
system("pause");
CloseHandle(hDevice);
return 0;
}
使用驱动工具安装我们的驱动,然后运行该应用层程序,实现通信,效果如下所示:

驱动开发:通过ReadFile与内核层通信的更多相关文章
- 驱动通信:通过PIPE管道与内核层通信
在本人前一篇博文<驱动开发:通过ReadFile与内核层通信>详细介绍了如何使用应用层ReadFile系列函数实现内核通信,本篇将继续延申这个知识点,介绍利用PIPE命名管道实现应用层与内 ...
- 字符设备驱动ioctl实现用户层内核层通信
测试代码实现 memdev.h #ifndef _MEMDEV_H_ #define _MEMDEV_H_ #include<linux/ioctl.h> #ifndef MEMDEV_M ...
- 驱动开发:通过Async反向与内核通信
在前几篇文章中给大家具体解释了驱动与应用层之间正向通信的一些经典案例,本章将继续学习驱动通信,不过这次我们学习的是通过运用Async异步模式实现的反向通信,反向通信机制在开发中时常被用到,例如一个杀毒 ...
- HarmonyOS USB DDK助你轻松实现USB驱动开发
HDF(Hardware Driver Foundation)驱动框架是HarmonyOS硬件生态开放的基础,为开发者提供了驱动加载.驱动服务管理和驱动消息机制等驱动能力,让开发者能精准且高效地开发驱 ...
- [内核驱动] miniFilter 内核层与应用程序通信
转载:http://blog.csdn.net/heyabo/article/details/8721611 转载:http://www.cnblogs.com/ljinshuan/archive/2 ...
- 驱动开发:内核层InlineHook挂钩函数
在上一章<驱动开发:内核LDE64引擎计算汇编长度>中,LyShark教大家如何通过LDE64引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的InlineHook函 ...
- windows 驱动开发 MDL 内核层 用户层共享内存
参考资料 https://blog.csdn.net/wdykanq/article/details/7752909 http://blog.51cto.com/laokaddk/404584 内核层 ...
- 《天书夜读:从汇编语言到windows内核编程》五 WDM驱动开发环境搭建
(原书)所有内核空间共享,DriverEntery是内核程序入口,在内核程序被加载时,这个函数被调用,加载入的进程为system进程,xp下它的pid是4.内核程序的编写有一定的规则: 不能调用win ...
- Windows内核安全与驱动开发
这篇是计算机中Windows Mobile/Symbian类的优质预售推荐<Windows内核安全与驱动开发>. 编辑推荐 本书适合计算机安全软件从业人员.计算机相关专业院校学生以及有一定 ...
随机推荐
- 06 app分享功能
通过某一个点击事件触发confirm弹窗 确定后正式进行分享功能处理 这是一个封装好的分享功能插件 https://ext.dcloud.net.cn/plugin?id=4860 如果自己写的话会很 ...
- maven配置的一个问题
资源导出问题 如果想和dao接口放在一个包下可以做如下配置,但是如果不放在dao接口下,那就会报错,至于为什么,那就得好好学学maven了,因为下面是yaml的,所以需要添加yaml,不然他扫描不到 ...
- Eplan创建符号详细解说
如何创建新的符号库以及在项目里面导入这个新的符号库 这个就不详细的赘述了 网上很多教程 和我一样的萌新可以参考 https://wenku.baidu.com/view/18c16641e45c3b3 ...
- SQL注入 基础学习
SQL注入学习笔记 注入原理 SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因是程序没有 ...
- Docker Compose之容器编排开发初探
1.前言 Docker Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速在集群中部署分布式应用. Compose 是一个用于定义和运行多个 Docker 应用 ...
- Apache DolphinScheduler 1.3.8 发布
小伙伴们 Apache DolphinScheduler 1.3.8 发布 2021年9月7日,正式发布 在 1.3.8 版本中,我们在 Docker&k8s 这里做了许多优化,Docker ...
- 搞定面试官 - 可以介绍一下在 MySQL 中你平时是怎么使用 COUNT() 的嘛?
大家好,我是程序员啊粥. 相信在大家的工作中,有很多的功能都需要用到 count(*) 来统计表中的数据行数.同时,对于一些大数据的表,用 count 都是瑟瑟发抖,往往会结合缓存等进行处理. 那么, ...
- java单线程100%利用率
容器内就获取个cpu利用率,怎么就占用单核100%了呢 背景:这个是在centos7 + lxcfs 和jdk11 的环境上复现的 目前这个bug已经合入到了开源社区, 链接为 https://git ...
- SiteSucker Pro for Mac 专业的网站下载工具
SiteSucker Mac版是Mac os平台上的一款帮助用户下载数据的mac下载工具,SiteSucker绝对是一扒网站的利器,不仅仅是下载网站的HTML源文件,他连网站整体架构以及下面的所有文本 ...
- [CF1536F] Omkar and Akmar(博弈论?组合数学)
题面 [CF1536F] Omkar and Akmar 甲乙轮流在一个有 N N N 个位置的环上放字母(环上每个位置不同),每次可以放一个 A 或 B ,要求不能有相同的字母相邻,轮到某个人时不能 ...