在本人前一篇博文《驱动开发:通过ReadFile与内核层通信》详细介绍了如何使用应用层ReadFile系列函数实现内核通信,本篇将继续延申这个知识点,介绍利用PIPE命名管道实现应用层与内核层之间的多次通信方法。

  • 什么是PIPE管道?

在Windows编程中,数据重定向需要用到管道PIPE,管道是一种用于在进程间共享数据的机制,通常由两端组成,数据从一端流入则必须从令一端流出,也就是一读一写,利用这种机制即可实现进程间直接通信。管道的本质其实是一段共享内存区域,多数情况下管道是用于应用层之间的数据交换的,其实驱动中依然可以使用命名管道实现应用层与内核层的直接通信。

那么如何在内核中创建一个管道?请看以下代码片段,以及MSDN针对函数的解析。

  • InitializeObjectAttributes

    • 初始化一个OBJECT_ATTRIBUTES结构,它设置将被打开的对象句柄的属性。然后调用方可以将一个指向该结构的指针传递给实际打开句柄的例程。
  • ZwCreateFile

    • 该函数的作用时创建或打开一个已经存在的文件,在这里其实是打开objAttr这个文件。
  • KeInitializeEvent

    • 将事件对象初始化为同步 (单个服务) 或通知类型事件,并将其设置为已发出信号或未发出信号的状态。
HANDLE g_hClient;
IO_STATUS_BLOCK g_ioStatusBlock;
KEVENT g_event; VOID NdisMSleep(IN ULONG MicrosecondsToSleep); // 初始化管道
void init()
{
UNICODE_STRING uniName;
OBJECT_ATTRIBUTES objAttr; RtlInitUnicodeString(&uniName, L"\\DosDevices\\Pipe\\LySharkPipeConn");
InitializeObjectAttributes(&objAttr, &uniName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); ZwCreateFile(&g_hClient, GENERIC_READ | GENERIC_WRITE, &objAttr, &g_ioStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (!g_hClient)
{
return;
}
KeInitializeEvent(&g_event, SynchronizationEvent, TRUE);
}

原理就是打开\\DosDevices\\Pipe\\LySharkPipeConn文件,然后将事件对象初始化为同步状态。

接下来就是如何将数据发送给应用层的问题,发送问题可以调用ZwWriteFile这个内核函数,如下我们实现的效果是将一个char类型的字符串传输给应用层。

// 将数据传到R3应用层
// LyShark
VOID ReportToR3(char* m_parameter, int lent)
{
if (!NT_SUCCESS(ZwWriteFile(g_hClient, NULL, NULL, NULL, &g_ioStatusBlock, (void*)m_parameter, lent, NULL, NULL)))
{
DbgPrint("写出错误");
}
}

内核层的核心代码就是如上这些,将这些整合在一起完整代码如下所示:

#include <ntifs.h>
#include <ndis.h>
#include <stdio.h> HANDLE g_hClient;
IO_STATUS_BLOCK g_ioStatusBlock;
KEVENT g_event; VOID NdisMSleep(IN ULONG MicrosecondsToSleep); // 初始化管道
void init()
{
UNICODE_STRING uniName;
OBJECT_ATTRIBUTES objAttr; RtlInitUnicodeString(&uniName, L"\\DosDevices\\Pipe\\LySharkPipeConn");
InitializeObjectAttributes(&objAttr, &uniName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); ZwCreateFile(&g_hClient, GENERIC_READ | GENERIC_WRITE, &objAttr, &g_ioStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (!g_hClient)
{
return;
}
KeInitializeEvent(&g_event, SynchronizationEvent, TRUE);
} // 将数据传到R3应用层
// LyShark
VOID ReportToR3(char* m_parameter, int lent)
{
if (!NT_SUCCESS(ZwWriteFile(g_hClient, NULL, NULL, NULL, &g_ioStatusBlock, (void*)m_parameter, lent, NULL, NULL)))
{
DbgPrint("写出错误");
}
} VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("驱动卸载成功 \n");
} NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
init(); // 延时3秒
NdisMSleep(3000000); DbgPrint("hello lyshark \n");
for (int x = 0; x < 10; x++)
{
// 分配空间
char *report = (char*)ExAllocatePoolWithTag(NonPagedPool, 4096, 'lysh');
if (report)
{
RtlZeroMemory(report, 4096); RtlCopyMemory(report, "hello lyshark", 13); // 发送到应用层
ReportToR3(report, 4096);
ExFreePool(report);
}
} DbgPrint("驱动加载成功 \n");
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

内核中创建了命名管道,客户端就需要创建一个相同名称的管道,并通过ReadFile函数读取管道中的数据,应用层核心代码如下所示:

#include <iostream>
#include <windows.h> int main(int argc, char *argv[])
{
HANDLE hPipe = CreateNamedPipe(TEXT("\\\\.\\Pipe\\LySharkPipeConn"), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, NMPWAIT_WAIT_FOREVER, NULL);
if (INVALID_HANDLE_VALUE == hPipe)
{
return false;
} const int size = 1024 * 10;
char buf[size];
DWORD rlen = 0;
while (true)
{
//if (ConnectNamedPipe(hPipe, NULL) != NULL)
// PowerBy: LyShark.com
if (1)
{
if (ReadFile(hPipe, buf, size, &rlen, NULL) == FALSE)
{
continue;
}
else
{
//接收信息
char* buffer_tmp = (char*)&buf; // 拷贝前半部分,不包括 buffer_data
char* buffer = (char*)malloc(size);
memcpy(buffer, buffer_tmp, size); printf("内核层数据: %s \n", buffer); free(buffer_tmp);
free(buffer);
}
}
} system("pause");
return 0;
}

至此将驱动签名后运行,并迅速打开应用层程序等待同步发送事件,即可得到如下返回结果。

此处有必要解释一下为什么会写出错误,很简单这段代码并没有控制何时触发事件,导致两边不同步,因为只是一个案例用于演示管道的应用方法,所以大家不要太较真,如果不想出错误这段代码还有很多需要改进的地方。

管道不仅可以传输字符串完全可以传输结构体数据,如下我们定义一个Networkreport结构体,并通过管道的方式多次传输给应用层,这部分传输模式适合用于驱动中一次性突出多个结构体,例如进程列表的输出,ARK工具中的驱动列表输出等功能的实现。

驱动层完整代码

#include <ntifs.h>
#include <ndis.h>
#include <stdio.h> HANDLE g_hClient;
IO_STATUS_BLOCK g_ioStatusBlock;
KEVENT g_event; typedef struct
{
int type;
unsigned long address;
unsigned long buffer_data_len;
char buffer_data[0];
}Networkreport; VOID NdisMSleep(IN ULONG MicrosecondsToSleep); // 初始化管道
void init()
{
UNICODE_STRING uniName;
OBJECT_ATTRIBUTES objAttr; RtlInitUnicodeString(&uniName, L"\\DosDevices\\Pipe\\LySharkPipeConn");
InitializeObjectAttributes(&objAttr, &uniName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); ZwCreateFile(&g_hClient, GENERIC_READ | GENERIC_WRITE, &objAttr, &g_ioStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (!g_hClient)
{
return;
}
KeInitializeEvent(&g_event, SynchronizationEvent, TRUE);
} // 将数据传到R3应用层
// PowerBy: LyShark.com
VOID ReportToR3(Networkreport* m_parameter, int lent)
{
if (!NT_SUCCESS(ZwWriteFile(g_hClient, NULL, NULL, NULL, &g_ioStatusBlock, (void*)m_parameter, lent, NULL, NULL)))
{
DbgPrint("写出错误");
}
} VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("驱动卸载成功 \n");
} NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
init(); // 延时3秒
NdisMSleep(3000000);
DbgPrint("hello lyshark \n"); for (int x = 0; x < 10; x++)
{
// 分配空间
Networkreport *report = (Networkreport*)ExAllocatePoolWithTag(NonPagedPool, 4096, 'lysh');
if (report)
{
RtlZeroMemory(report, 4096); report->type = x;
report->address = 401000 + x;
report->buffer_data_len = 13; // 定位到结构体最后一个元素上
unsigned char * tmp = (unsigned char *)report + sizeof(Networkreport);
memcpy(tmp, "hello lyshark", 13); // 发送到应用层
ReportToR3(report, 4096);
ExFreePool(report);
}
} DbgPrint("驱动加载成功 \n");
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

应用层完整代码

#include <iostream>
#include <windows.h> typedef struct
{
int type;
unsigned long address;
unsigned long buffer_data_len;
char *buffer_data;
}Networkreport; int main(int argc, char *argv[])
{
HANDLE hPipe = CreateNamedPipe(TEXT("\\\\.\\Pipe\\LySharkPipeConn"), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, NMPWAIT_WAIT_FOREVER, NULL);
if (INVALID_HANDLE_VALUE == hPipe)
{
return false;
} const int size = 1024 * 10;
char buf[size];
DWORD rlen = 0;
while (true)
{
//if (ConnectNamedPipe(hPipe, NULL) != NULL)
if (1 == 1)
{
if (ReadFile(hPipe, buf, size, &rlen, NULL) == FALSE)
{
continue;
}
else
{
//接收信息
Networkreport* buffer_tmp = (Networkreport*)&buf;
SIZE_T buffer_len = sizeof(Networkreport) + buffer_tmp->buffer_data_len; // 拷贝前半部分,不包括 buffer_data
Networkreport* buffer = (Networkreport*)malloc(buffer_len);
memcpy(buffer, buffer_tmp, buffer_len); // 对后半部 分配空间
// By: LyShark
char* data = (char*)malloc(buffer->buffer_data_len);
unsigned char* tmp = (unsigned char *)buffer + sizeof(Networkreport) - 4;
memcpy(data, tmp, buffer->buffer_data_len); printf("输出数据: %s \n", data);
printf("地址: %d \n", buffer_tmp->address);
printf("长度: %d \n", buffer_tmp->type);
printf("输出长度: %d \n", buffer_tmp->buffer_data_len); free(data);
free(buffer);
}
}
}
system("pause");
return 0;
}

结构体一次性输出多个,效果如下所示:

驱动通信:通过PIPE管道与内核层通信的更多相关文章

  1. 驱动开发:通过ReadFile与内核层通信

    驱动与应用程序的通信是非常有必要的,内核中执行代码后需要将其动态显示给应用层,但驱动程序与应用层毕竟不在一个地址空间内,为了实现内核与应用层数据交互则必须有通信的方法,微软为我们提供了三种通信方式,如 ...

  2. 字符设备驱动ioctl实现用户层内核层通信

    测试代码实现 memdev.h #ifndef _MEMDEV_H_ #define _MEMDEV_H_ #include<linux/ioctl.h> #ifndef MEMDEV_M ...

  3. [内核驱动] miniFilter 内核层与应用程序通信

    转载:http://blog.csdn.net/heyabo/article/details/8721611 转载:http://www.cnblogs.com/ljinshuan/archive/2 ...

  4. 向Windows内核驱动传递用户层定义的事件Event,并响应内核层的通知

    完整的程序在下载:http://download.csdn.net/detail/dijkstar/7913249 用户层创建的事件Event是一个Handle句柄,和内核中的创建的内核模式下的KEV ...

  5. [转载] 关于Win7 x64下过TP保护的一些思路,内核层过保护,驱动过保护

    首先特别感谢梦老大,本人一直没搞懂异常处理机制,看了他的教程之后终于明白了.在他的教程里我学到了不少东西.第一次在论坛发帖,就说说Win7 x64位下怎么过TP保护.如果有讲错的地方,还望指出.说不定 ...

  6. 驱动开发:内核层InlineHook挂钩函数

    在上一章<驱动开发:内核LDE64引擎计算汇编长度>中,LyShark教大家如何通过LDE64引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的InlineHook函 ...

  7. windows 驱动开发 MDL 内核层 用户层共享内存

    参考资料 https://blog.csdn.net/wdykanq/article/details/7752909 http://blog.51cto.com/laokaddk/404584 内核层 ...

  8. python线程,pipe管道通信原理

    Pipe管道: * 管道实例化后会产生两个通道,分别交给两个进程* 通过send和recv来交互数据,这是一个双向的管道,child和parent可以互相收发 from multiprocessing ...

  9. linux设备驱动归纳总结(一)内核的相关基础概念【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-59413.html linux设备驱动归纳总结(一):内核的相关基础概念 xxxxxxxxxxxxxx ...

随机推荐

  1. “杀死” App 上的疑难崩溃

    在移动应用性能方面,崩溃带来的影响是最为严重的,程序崩了可以打断用户正在进行的操作体验,造成关键业务中断.用户留存率下降.品牌口碑变差.生命周期价值下降等影响.很多公司将崩溃率作为优先级最高的技术指标 ...

  2. 思维导图学《On Java》基础卷

    说明 原来读过 <Java 编程思想(第 4 版)>,但是这个版本还是基于 Java 5 讲解.由于 Java 8 做出了非常大的改进(是 Java 变化最大的版本),且截止到 2022- ...

  3. .Net CLR GC 动态加载短暂堆阈值的计算及阈值超量的计算

    前言: 很多书籍或者很多文章,对于CLR或者GC这块只限于长篇大论的理论性概念,对于里面的如何运作模式,却几乎一无所知.高达近百万行的CPP文件,毕竟读懂的没有几个.以下取自CLR.Net 6 Pre ...

  4. 发布自己的npm组件库

    发布自己的npm组件库 前期准备 **git上建立自己的仓库,这样子是为了在本地初始化时,可以直接生成对应的 package.json ** 克隆仓库 创建仓库成功后,gitbush本地,执行 git ...

  5. 有趣的特性:CHECK约束

    有趣的特性:CHECK约束 功能说明 在MySQL 8.0.16以前, CREATE TABLE允许从语法层面输入下列CHECK约束,但实际没有效果: CHECK (expr) 在 MySQL 8.0 ...

  6. Java 注解及其底层原理

    目录 什么是注解? 注解的分类 Java自带的标准注解 元注解 @Retention @Documented @Target @Inherited @Repeatable 自定义注解 自定义注解的读取 ...

  7. MySQL的三值逻辑

    MySQL 采用三值逻辑 SELECT 1 = 1; SELECT 1 = 2; SELECT 1 = NULL; SELECT 1 != NULL; 上面四条语句的结果分别为: 可见MySQL采用三 ...

  8. 【Java】学习路径61-“伪”枚举类型

    public class RolyType { public static final int TEACHER = 0; public static final int STUDENT = 1; pu ...

  9. atcoder beginner contest 251(D-E)

    Tasks - Panasonic Programming Contest 2022(AtCoder Beginner Contest 251)\ D - At Most 3 (Contestant ...

  10. Java基础——01

    今日学习 2020-2-27 Java多态 多态性格式 /* 代码中体现多态性 其实就是一句话:父类指向子类对象 格式: 父类名称 对象名= new 子类名称(): 或者 接口名称 对象名 = new ...