在本人前一篇博文《驱动开发:通过ReadFile与内核层通信》详细介绍了如何使用应用层ReadFile系列函数实现内核通信,本篇将继续延申这个知识点,介绍利用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;
}
结构体一次性输出多个,效果如下所示:

尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此
我正在使用puppet为ruby程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这
我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是
我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search
我在理解Enumerator.new方法的工作原理时遇到了一些困难。假设文档中的示例:fib=Enumerator.newdo|y|a=b=1loopdoy[1,1,2,3,5,8,13,21,34,55]循环中断条件在哪里,它如何知道循环应该迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)? 最佳答案 Enumerator使用Fibers在内部。您的示例等效于:require'fiber'fiber=Fiber.newdoa=b=1loopdoFiber.yieldaa,b=b,a+bendend10.times.m
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题: