驱动开发:通过Async反向与内核通信
在前几篇文章中给大家具体解释了驱动与应用层之间正向通信的一些经典案例,本章将继续学习驱动通信,不过这次我们学习的是通过运用Async
异步模式实现的反向通信,反向通信机制在开发中时常被用到,例如一个杀毒软件如果监控到有异常进程运行或有异常注册表被改写后,该驱动需要主动的通知应用层进程让其知道,这就需要用到驱动反向通信的相关知识点,如下将循序渐进的实现一个反向通信案例。
在开始学习Async反向通信之前先来研究一个Sync正向通信案例,不论是正向反向通信其在通信模式上与《驱动开发:通过ReadFile与内核层通信》
所介绍的通信模式基本一致,都是通过ReadFile
触发驱动中的IRP_MJ_READ
读取派遣,唯一的区别是在传输数据时使用了MmGetSystemAddressForMdl
方式,它将给定MDL
描述的物理页面映射到系统空间,并调用RtlCopyMemory()
将全局字符串复制到这个空间内,这样客户端就可以循环读取内核传出的数据。
我们来看驱动端代码是如何实现的这个功能,代码并没有什么特殊的无法理解的点,只是需要注意我们在驱动入口调用IoCreateDevice()
时传入了第二个参数FILE_DEVICE_EXTENSION
,该参数的作用是,创建设备时,指定设备扩展内存的大小,传一个值进去,就会给设备分配一块非页面内存。
#include <ntddk.h>
#include <stdio.h>
// 保存一段非分页内存,用于给全局变量使用
#define FILE_DEVICE_EXTENSION 4096
// 定义全局字符串
static int global_count = 0;
static char global_char[5][128] = { 0 };
// 驱动绑定默认派遣函数
NTSTATUS _DefaultDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp)
{
_pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;
_pIrp->IoStatus.Information = 0;
IoCompleteRequest(_pIrp, IO_NO_INCREMENT);
return _pIrp->IoStatus.Status;
}
// 驱动创建后触发
NTSTATUS _SyncCreateCloseDispatch(PDEVICE_OBJECT _pDevcieObject, PIRP _pIrp)
{
_pIrp->IoStatus.Status = STATUS_SUCCESS;
_pIrp->IoStatus.Information = 0;
IoCompleteRequest(_pIrp, IO_NO_INCREMENT);
return _pIrp->IoStatus.Status;
}
// 应用层读数据后触发
NTSTATUS _SyncReadDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(_pIrp);
PVOID pBuffer = NULL;
ULONG uBufferLen = 0;
do
{
// 读写请求使用的是直接I/O方式
pBuffer = MmGetSystemAddressForMdl(_pIrp->MdlAddress);
if (pBuffer == NULL)
{
status = STATUS_UNSUCCESSFUL;
break;
}
uBufferLen = pIrpStack->Parameters.Read.Length;
DbgPrint("读字节长度: %d \n", uBufferLen);
// 最大支持20字节读请求
uBufferLen = uBufferLen >= 20 ? 20 : uBufferLen;
// 输出五次字符串
if (global_count < 5)
{
RtlCopyMemory(pBuffer, global_char[global_count], uBufferLen);
global_count = global_count + 1;
}
} while (FALSE);
// 填写返回状态及返回大小
_pIrp->IoStatus.Status = status;
_pIrp->IoStatus.Information = uBufferLen;
// 完成IRP
IoCompleteRequest(_pIrp, IO_NO_INCREMENT);
return status;
}
// 卸载驱动
VOID _UnloadDispatch(PDRIVER_OBJECT _pDriverObject)
{
// 删除创建的设备
UNICODE_STRING Win32DeviceName;
RtlInitUnicodeString(&Win32DeviceName, L"\\DosDevices\\LySharkSync");
IoDeleteDevice(_pDriverObject->DeviceObject);
}
// 驱动入口
NTSTATUS DriverEntry(PDRIVER_OBJECT _pDriverObject, PUNICODE_STRING _pRegistryPath)
{
UNICODE_STRING DeviceName, Win32DeivceName;
PDEVICE_OBJECT pDeviceObject = NULL;
NTSTATUS status;
HANDLE hThread;
OBJECT_ATTRIBUTES ObjectAttributes;
// 设置符号名
RtlInitUnicodeString(&DeviceName, L"\\Device\\LySharkSync");
RtlInitUnicodeString(&Win32DeivceName, L"\\DosDevices\\LySharkSync");
// 循环初始化IRP函数
for (ULONG i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
{
_pDriverObject->MajorFunction[i] = _DefaultDispatch;
}
// 再次覆盖派遣函数
_pDriverObject->MajorFunction[IRP_MJ_CREATE] = _SyncCreateCloseDispatch;
_pDriverObject->MajorFunction[IRP_MJ_CLOSE] = _SyncCreateCloseDispatch;
_pDriverObject->MajorFunction[IRP_MJ_READ] = _SyncReadDispatch;
_pDriverObject->DriverUnload = _UnloadDispatch;
// 分配一个自定义扩展 大小为sizeof(DEVEXT)
// By: LyShark.com
status = IoCreateDevice(_pDriverObject, sizeof(FILE_DEVICE_EXTENSION), &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject);
if (!NT_SUCCESS(status))
return status;
if (!pDeviceObject)
return STATUS_UNEXPECTED_IO_ERROR;
// 为全局变量赋值
strcpy(global_char[0], "hi,lyshark A");
strcpy(global_char[1], "hi,lyshark B");
strcpy(global_char[2], "hi,lyshark C");
strcpy(global_char[3], "hi,lyshark D");
strcpy(global_char[4], "hi,lyshark E");
// 指定读写方式为 直接I/O MDL模式
pDeviceObject->Flags |= DO_DIRECT_IO;
// 数据传输时地址校验大小
pDeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT;
status = IoCreateSymbolicLink(&Win32DeivceName, &DeviceName);
pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
return STATUS_SUCCESS;
}
对于应用层来说并没有什么特别的,同样调用ReadFile
读取内核中的参数,同样for循环读取五次,代码如下:
#include <stdio.h>
#include <Windows.h>
int main(int argc, char *argv[])
{
HANDLE hFile;
char Buffer[10] = { 0 };
DWORD dwRet = 0;
BOOL bRet;
hFile = CreateFileA("\\\\.\\LySharkSync", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return 0;
for (int x = 0; x < 5; x++)
{
bRet = ReadFile(hFile, Buffer, 20, &dwRet, NULL);
if (!bRet)
{
CloseHandle(hFile);
return 0;
}
printf("读入数据: %s -> 读取长度: %d \n", Buffer, dwRet);
}
return 0;
}
这段代码运行效果如下:
与同步模式不同,异步模式
虽然同样使用ReadFile
实现通信,但在通信中引入了Event
事件通知机制,这也是异步与同步最大的区别所在,用户层可以分别创建多个Event事件,等待内核依次做出相应并最终一并返回。
首先驱动内定义了_DeviceExtension
自定义接口,该接口用于保存此次事件所对应的Irp
以及其所对应的DPC时间等。
异步分发函数_AsyncReadDispatch
同样是被IRP_MJ_READ
派遣函数触发的,触发后其内部会首先IoGetCurrentIrpStackLocation
得到当前IRP的堆栈信息,然后设置IoMarkIrpPending()
并最终将该IRP通过InsertTailList()
插入到IRP链表内等待被处理。
- IoMarkIrpPending
- 用于标记指定的IRP,标志着某个驱动的分发例程(分发函数)因需要被其他的驱动程序进一步处理最终返回STATUS_PENDING状态。
函数_CustomDpc
则是定时器内部要执行的具体操作,在DriverEntry
驱动入口处做了如下初始化,初始化了链表,并初始化了一个定时器,最后启动这个定时器每隔1秒都会执行一次_CustomDpc
如果我们的IRP链表内IsListEmpty() 检测
存在数据,则会主动拷贝内存RtlCopyMemory
并推送到应用层。
// 初始化IRP链表
InitializeListHead(&pDevExt->IrpList);
// 初始化定时器
KeInitializeTimer(&(pDevExt->timer));
// 初始化DPC pDevExt是传给_CustomDpc函数的参数
KeInitializeDpc(&pDevExt->dpc, (PKDEFERRED_ROUTINE)_CustomDpc, pDevExt);
// 设置定时时间位1s
pDevExt->liDueTime = RtlConvertLongToLargeInteger(-10000000);
// 启动定时器
KeSetTimer(&pDevExt->timer, pDevExt->liDueTime, &pDevExt->dpc);
驱动层完成代码如下所示:
#include <ntddk.h>
// 自定义接口扩展
typedef struct _DeviceExtension
{
LIST_ENTRY IrpList;
KTIMER timer;
LARGE_INTEGER liDueTime;
KDPC dpc;
}DEV_EXT, *PDEV_EXT;
// 默认派遣函数
NTSTATUS _DefaultDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp)
{
_pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;
_pIrp->IoStatus.Information = 0;
IoCompleteRequest(_pIrp, IO_NO_INCREMENT);
return _pIrp->IoStatus.Status;
}
// 创建派遣函数
NTSTATUS _AsyncCreateCloseDispatch(PDEVICE_OBJECT _pDevcieObject, PIRP _pIrp)
{
_pIrp->IoStatus.Status = STATUS_SUCCESS;
_pIrp->IoStatus.Information = 0;
IoCompleteRequest(_pIrp, IO_NO_INCREMENT);
return _pIrp->IoStatus.Status;
}
// 读取派遣函数
NTSTATUS _AsyncReadDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp)
{
NTSTATUS status;
PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(_pIrp);
PDEV_EXT pDevExt = (PDEV_EXT)_pDeviceObject->DeviceExtension;
IoMarkIrpPending(_pIrp);
// 将IRP插入自定义链表中插入的是ListEntry
InsertTailList(&pDevExt->IrpList, &_pIrp->Tail.Overlay.ListEntry);
// 返回pending 主要返回给I/O管理器的值必须和IRP的Pending标志位一致
// By: LyShark.com
// 即调用iomarkirppending和返回值要一致
return STATUS_PENDING;
}
// DPC线程
VOID _CustomDpc(PKDPC Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2)
{
PIRP pIrp;
PDEV_EXT pDevExt = (PDEV_EXT)DeferredContext;
PVOID pBuffer = NULL;
ULONG uBufferLen = 0;
PIO_STACK_LOCATION pIrpStack = NULL;
do
{
if (!pDevExt)
{
break;
}
// 检查尾端IRP链表是否为空 为空则跳出
if (IsListEmpty(&pDevExt->IrpList))
{
break;
}
// 从IRP链表中取出一个IRP并完成该IRP 取出的是ListEntry的地址
PLIST_ENTRY pListEntry = (PLIST_ENTRY)RemoveHeadList(&pDevExt->IrpList);
if (!pListEntry)
break;
pIrp = (PIRP)CONTAINING_RECORD(pListEntry, IRP, Tail.Overlay.ListEntry);
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
DbgPrint("当前DPC Irp: 0x%x\n", pIrp);
// 驱动程序的读写方式位直接I/O
pBuffer = MmGetSystemAddressForMdl(pIrp->MdlAddress);
if (pBuffer == NULL)
{
pIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
break;
}
uBufferLen = pIrpStack->Parameters.Read.Length;
DbgPrint("读取DPC长度: %d\n", uBufferLen);
// 支持5字节以下的读请求
uBufferLen = uBufferLen > 13 ? 13 : uBufferLen;
// 复制请求内容
RtlCopyMemory(pBuffer, "hello lyshark", uBufferLen);
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = uBufferLen;
// 完成该IRP
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
} while (FALSE);
// 重新设置定时器
KeSetTimer(&pDevExt->timer, pDevExt->liDueTime, &pDevExt->dpc);
}
// 卸载驱动
VOID _UnloadDispatch(PDRIVER_OBJECT _pDriverObject)
{
UNICODE_STRING Win32DeviceName;
PDEV_EXT pDevExt = (PDEV_EXT)_pDriverObject->DeviceObject->DeviceExtension;
RtlInitUnicodeString(&Win32DeviceName, L"\\DosDevices\\LySharkAsync");
// 删除定时器
// LyShark
KeCancelTimer(&pDevExt->timer);
// 删除创建的设备
IoDeleteDevice(_pDriverObject->DeviceObject);
}
// 驱动入口
NTSTATUS DriverEntry(PDRIVER_OBJECT _pDriverObject, PUNICODE_STRING _pRegistryPath)
{
UNICODE_STRING DeviceName, Win32DeivceName;
PDEVICE_OBJECT pDeviceObject = NULL;
NTSTATUS status;
PDEV_EXT pDevExt = NULL;
HANDLE hThread;
OBJECT_ATTRIBUTES ObjectAttributes;
CLIENT_ID CID;
RtlInitUnicodeString(&DeviceName, L"\\Device\\LySharkAsync");
RtlInitUnicodeString(&Win32DeivceName, L"\\DosDevices\\LySharkAsync");
for (ULONG i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
{
_pDriverObject->MajorFunction[i] = _DefaultDispatch;
}
_pDriverObject->MajorFunction[IRP_MJ_CREATE] = _AsyncCreateCloseDispatch;
_pDriverObject->MajorFunction[IRP_MJ_CLOSE] = _AsyncCreateCloseDispatch;
_pDriverObject->MajorFunction[IRP_MJ_READ] = _AsyncReadDispatch;
_pDriverObject->DriverUnload = _UnloadDispatch;
// 分配自定义扩展
status = IoCreateDevice(_pDriverObject, sizeof(DEV_EXT), &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject);
if (!NT_SUCCESS(status))
return status;
if (!pDeviceObject)
return STATUS_UNEXPECTED_IO_ERROR;
pDeviceObject->Flags |= DO_DIRECT_IO;
pDeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT;
status = IoCreateSymbolicLink(&Win32DeivceName, &DeviceName);
pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
pDevExt = (PDEV_EXT)pDeviceObject->DeviceExtension;
// 初始化IRP链表
InitializeListHead(&pDevExt->IrpList);
// 初始化定时器
KeInitializeTimer(&(pDevExt->timer));
// 初始化DPC pDevExt是传给_CustomDpc函数的参数
KeInitializeDpc(&pDevExt->dpc, (PKDEFERRED_ROUTINE)_CustomDpc, pDevExt);
// 设置定时时间位1s
pDevExt->liDueTime = RtlConvertLongToLargeInteger(-10000000);
// 启动定时器
KeSetTimer(&pDevExt->timer, pDevExt->liDueTime, &pDevExt->dpc);
return STATUS_SUCCESS;
}
驱动层说完了,接下来是应用层,对于应用层来说,需要使用CreateEvent
打开通知事件,或者叫做事件对象,然后当有通知时,则直接使用ReadFile
读取对应的缓冲区,当所有读取全部结束WaitForMultipleObjects
等待结束即输出结果。
#include <stdio.h>
#include <Windows.h>
int main(int argc, char *argv[])
{
HANDLE hFile;
char Buffer[3][32] = { 0 };
DWORD dwRet[3] = { 0 };
OVERLAPPED ol[3] = { 0 };
HANDLE hEvent[3] = { 0 };
// By:LyShark
hFile = CreateFileA("\\\\.\\LySharkAsync", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (INVALID_HANDLE_VALUE == hFile)
return 0;
// event用来通知请求完成
hEvent[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
ol[0].hEvent = hEvent[0];
hEvent[1] = CreateEvent(NULL, TRUE, FALSE, NULL);
ol[1].hEvent = hEvent[1];
hEvent[2] = CreateEvent(NULL, TRUE, FALSE, NULL);
ol[2].hEvent = hEvent[2];
// 读取事件内容到缓存
ReadFile(hFile, Buffer[0], 13, &dwRet[0], &ol[0]);
ReadFile(hFile, Buffer[1], 13, &dwRet[1], &ol[1]);
ReadFile(hFile, Buffer[2], 13, &dwRet[2], &ol[2]);
// 等待三个事件执行完毕
WaitForMultipleObjects(3, hEvent, TRUE, INFINITE);
// 输出结果
printf("缓存LyShark A: %s \n", Buffer[0]);
printf("缓存LyShark B: %s \n", Buffer[1]);
printf("缓存LyShark C: %s \n", Buffer[2]);
CloseHandle(hFile);
return 0;
}
这段代码最终运行效果如下:
驱动开发:通过Async反向与内核通信的更多相关文章
- 驱动开发:通过ReadFile与内核层通信
驱动与应用程序的通信是非常有必要的,内核中执行代码后需要将其动态显示给应用层,但驱动程序与应用层毕竟不在一个地址空间内,为了实现内核与应用层数据交互则必须有通信的方法,微软为我们提供了三种通信方式,如 ...
- Linux 网络设备驱动开发(一) —— linux内核网络分层结构
Preface Linux内核对网络驱动程序使用统一的接口,并且对于网络设备采用面向对象的思想设计. Linux内核采用分层结构处理网络数据包.分层结构与网络协议的结构匹配,既能简化数据包处理流程,又 ...
- 转:Linux设备驱动开发(1):内核基础概念
一.linux设备驱动的作用 内核:用于管理软硬件资源,并提供运行环境.如分配4G虚拟空间等. linux设备驱动:是连接硬件和内核之间的桥梁. linux系统按个人理解可按下划分: 应用层:包括PO ...
- Windows内核安全与驱动开发
这篇是计算机中Windows Mobile/Symbian类的优质预售推荐<Windows内核安全与驱动开发>. 编辑推荐 本书适合计算机安全软件从业人员.计算机相关专业院校学生以及有一定 ...
- Linux驱动开发必看详解神秘内核(完全转载)
Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html IT168 技术文档]在开始步入L ...
- Linux内核(17) - 高效学习Linux驱动开发
这本<Linux内核修炼之道>已经开卖(网上的链接为: 卓越.当当.china-pub ),虽然是严肃文学,但为了保证流畅性,大部分文字我还都是斟词灼句,反复的念几遍才写上去的,尽量考虑到 ...
- 《Windows内核安全与驱动开发》 7.1&7.2&7.3 串口的过滤
<Windows内核安全与驱动开发>阅读笔记 -- 索引目录 <Windows内核安全与驱动开发> 7.1&7.2&7.3 串口的过滤 一.设备绑定的内核API ...
- 《Windows内核安全与驱动开发》阅读笔记 -- 索引目录
<Windows内核安全与驱动开发>阅读笔记 -- 索引目录 一.内核上机指导 二.内核编程环境及其特殊性 2.1 内核编程的环境 2.2 数据类型 2.3 重要的数据结构 2.4 函数调 ...
- 《Windows内核安全与驱动开发》 5.1&5.2 内核与应用方面的编程
<Windows内核安全与驱动开发>阅读笔记 -- 索引目录 <Windows内核安全与驱动开发> 5.1&5.2 内核与应用方面的编程 一.生成控制设备 如果一个驱 ...
随机推荐
- C++ 处理类型名(typedef,auto和decltype)
随着程序越来越复杂,程序中用到的类型也越来越复杂,这种复杂性体现在两个方面.一是一些类型难于"拼写",它们的名字既难记又容易写错,还无法明确体现其真实目的和含义.二是有时候根本搞不 ...
- java实现wordCount的map
打开IDEA,File--new --Project,新建一个项目 我们已经安装好了maven,不用白不用 这里不要选用骨架,Next.在写上Groupid,Next. 写上项目名称,finish.o ...
- 从-99打造Sentinel高可用集群限流中间件
接上篇Sentinel集群限流探索,上次简单提到了集群限流的原理,然后用官方给的 demo 简单修改了一下,可以正常运行生效. 这一次需要更进一步,基于 Sentinel 实现内嵌式集群限流的高可用方 ...
- Less混合结合:nth-child()选择器的高级玩法
1.先看效果图 上图中比较麻烦的是每块的底色处理,下面看怎么处理 2.:nth-child(n) 选择器 匹配属于其父元素的第 N 个子元素,不论元素的类型. n 可以是数字.关键词或公式. 数字:最 ...
- 5.4 NOI模拟
\(5.4\ NOI\)模拟 \(T1\) 想到分讨,但是暴力输出一下方案之后有很多特别的情况要讨论,就弃了... 假设\(a\)是原序列,\(b\)是我们得到的序列 设\(i\)是最长公共前缀,\( ...
- DolphinScheduler 功能开发:⼯作流级别任务空跑(后端),测试工作流是否正确执行...
点击上方 蓝字关注我们 ✎ 编 者 按 在今年由中国科学院软件研究所主办的开源软件所供应链点亮计划-开源之夏活动中,有不少小伙伴提交了关于 DolphinScheduler 的项目,本期是来自成都信息 ...
- java-重载、包修饰词以及堆栈管理
1.方法的重写(Override):重新写.覆盖 1)发生在父子类中,方法名称相同,参数列表相同,方法体不同 2)重写方法被调用时,看对象的类型2.重写与重载的区别: 1)重写(Override): ...
- Spring Boot部署方法
Spring Boot部署方法 网上搜到的部署方法无非是打成jar包,然后shell执行nohup java调用jar命令,或者是打成war包然后部署到tomcat或者jetty容器上面. S ...
- 基于 Sequelize.js + Express.js 开发一套 Web 后端服务器
什么是 Sequelize 我们知道 Web 应用开发中的 Web 后端开发一般都是 Java.Python.ASP.NET 等语言.十年前,Node.js 的出现使得原本仅限于运行在浏览器中的 Ja ...
- 【NOI P模拟赛】奶油蛋糕塔(状压 DP)
题面 数据范围 1 ≤ n ≤ 5 × 1 0 5 1\leq n\leq5\times10^5 1≤n≤5×105 . 题解 n ≤ 20 n\leq 20 n≤20 的状压应该都会吧,状态记录已经 ...