驱动通信:通过PIPE管道与内核层通信
在本人前一篇博文《驱动开发:通过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管道与内核层通信的更多相关文章
- 驱动开发:通过ReadFile与内核层通信
		驱动与应用程序的通信是非常有必要的,内核中执行代码后需要将其动态显示给应用层,但驱动程序与应用层毕竟不在一个地址空间内,为了实现内核与应用层数据交互则必须有通信的方法,微软为我们提供了三种通信方式,如 ... 
- 字符设备驱动ioctl实现用户层内核层通信
		测试代码实现 memdev.h #ifndef _MEMDEV_H_ #define _MEMDEV_H_ #include<linux/ioctl.h> #ifndef MEMDEV_M ... 
- [内核驱动] miniFilter 内核层与应用程序通信
		转载:http://blog.csdn.net/heyabo/article/details/8721611 转载:http://www.cnblogs.com/ljinshuan/archive/2 ... 
- 向Windows内核驱动传递用户层定义的事件Event,并响应内核层的通知
		完整的程序在下载:http://download.csdn.net/detail/dijkstar/7913249 用户层创建的事件Event是一个Handle句柄,和内核中的创建的内核模式下的KEV ... 
- [转载] 关于Win7 x64下过TP保护的一些思路,内核层过保护,驱动过保护
		首先特别感谢梦老大,本人一直没搞懂异常处理机制,看了他的教程之后终于明白了.在他的教程里我学到了不少东西.第一次在论坛发帖,就说说Win7 x64位下怎么过TP保护.如果有讲错的地方,还望指出.说不定 ... 
- 驱动开发:内核层InlineHook挂钩函数
		在上一章<驱动开发:内核LDE64引擎计算汇编长度>中,LyShark教大家如何通过LDE64引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的InlineHook函 ... 
- windows 驱动开发 MDL 内核层 用户层共享内存
		参考资料 https://blog.csdn.net/wdykanq/article/details/7752909 http://blog.51cto.com/laokaddk/404584 内核层 ... 
- python线程,pipe管道通信原理
		Pipe管道: * 管道实例化后会产生两个通道,分别交给两个进程* 通过send和recv来交互数据,这是一个双向的管道,child和parent可以互相收发 from multiprocessing ... 
- linux设备驱动归纳总结(一)内核的相关基础概念【转】
		本文转载自:http://blog.chinaunix.net/uid-25014876-id-59413.html linux设备驱动归纳总结(一):内核的相关基础概念 xxxxxxxxxxxxxx ... 
随机推荐
- 【Go语言】(一)环境搭建与了解VScode工具
			视频链接(p1~p8): golang入门到项目实战 [2022最新Go语言教程,没有废话,纯干货!] 参考链接: 用vscode开发go的时候,安装go包报错:connectex: A connec ... 
- 选择语句-IF和标准if-else语句以及if-else语句的扩展
			第二章 判断语句 2.1 判断语句1--if if语句的第一种格式:if if(关系表达式){ 语句体; } 执行流程 首先判断关系表达式看起结果是true还是false 如果是true就执行与具体 ... 
- python opencv图像识别(相同大小图片)
			简介 由于项目需要对比两张相同图片的相似度,因此采用opencv将图片转为灰阶数组,然后对比相应的数组来取相似度,此方法只适用于大小相同的图片,较为局限 # -*- coding: utf-8 -*- ... 
- 理解JavaScript中的window对象
			前言 每个JavaScript环境都有一个全局对象(global object).在全局范围内创建的任何变量实际上都是这个对象的属性,而任何函数都是它的方法.在浏览器环境中,全局对象是window对象 ... 
- SkiaSharp 之 WPF 自绘 拖曳小球(案例版)
			感谢各位大佬和粉丝的厚爱和关心( 催更),我会再接再厉的,其实这也是督促自己的一种方式,非常感谢. 刚写了一篇万字长文,自己也休养生息(低调发育)了一段时间,接下来来几个小案例. 拖曳小球 WPF的拖 ... 
- Docker在手,天下我有,在Win10系统下利用Docker部署Gunicorn+Flask打造独立镜像
			原文转载自「刘悦的技术博客」https://v3u.cn/a_id_164 书接上回,之前一篇:Win10环境下使用Flask配合Celery异步推送实时/定时消息(Socket.io)/2020年最 ... 
- 老板加薪!看我做的WPF Loading!!!
			老板加薪!看我做的WPF Loading!!! 控件名:RingLoading 作者:WPFDevelopersOrg 原文链接: https://github.com/WPFDevelopersOr ... 
- 使用 for 循环 打印 9X9乘法表
			C 语言自学之99乘法表 请使用for循环,倒序打印9*9乘法表 1 #include <stdio.h> 2 3 int main() 4 { 5 int i,j,result;//定义 ... 
- Go 语言图片处理简明教程
			虽然 Go 语言主要用于 Web 后端以及各类中间件和基础设施开发,也难免遇到一些图像处理的需求.Go 语言提供的 image 标准库提供了基本的图片加载.裁剪.绘制等能力,可以帮助我们实现一些绘图需 ... 
- 从HashMap的执行流程开始 揭开HashMap底层实现
			心得:如何学习源码: 从某个执行过程入手,建议先从整体入手,了解底层的数据结构是怎么一步一步优化的.最后,在了解完底层的数据结构优化过程后,从重要的核心方法入手,从它的执行流程入手,先去网上搜索了解它 ... 
