驅動通信:通過PIPE管道與內核層通信

来源:https://www.cnblogs.com/LyShark/archive/2022/10/01/16746250.html
-Advertisement-
Play Games

在本人前一篇博文`《驅動開發:通過ReadFile與內核層通信》`詳細介紹瞭如何使用應用層`ReadFile`系列函數實現內核通信,本篇將繼續延申這個知識點,介紹利用`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;
}

結構體一次性輸出多個,效果如下所示:

文章作者:lyshark (王瑞)
文章出處:https://www.cnblogs.com/LyShark/p/16746250.html
版權聲明:本博客文章與代碼均為學習時整理的筆記,文章 [均為原創] 作品,轉載請 [添加出處] ,您添加出處是我創作的動力!
轉載文章,請遵守《中華人民共和國著作權法》相關規定或遵守《署名CC BY-ND 4.0國際》禁止演繹規範,合理合規,攜帶原創出處轉載。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 2022-10-01 ALLOWED_HOSTS "ALLOWED_HOSTS"的含義: 它是指允許放在“ALLOWED_HOSTS”的主機進行訪問後端 “ALLOWED_HOSTS”的說明: 它一般預設是“127.0.0.1”,如果添加上其他的主機的話,其他主機也可訪問此項目的view,但是此時, ...
  • 2022-10-01 結果集的含義: 結果集就是在查詢過程中,返回的一個列表的集合。 常見的結果集在一些函數中查詢會返回,具體包括的函數有: filter、exclude、order_by 結果集的兩大特性: (1)惰性 指的是查詢時,如果不用則在mysql日誌中不會有查詢記錄。反之,如果使用(一般 ...
  • 相信在大部分的web項目中都會有導出導入Excel的需求,之前我也寫過一篇導出單個sheet工作表的文章,沒看過的小伙伴可以去看哈,鏈接也給大家放出來了:導出單個sheet 但是在我們日常的工作中,需求往往沒這麼簡單,可能需要將數據按類型分類導出或者數據量過大,需要分多張表導出等等。遇到類似的需求該 ...
  • 相信在大部分的web項目中都會有導出導入Excel的需求,今天我們就來看看如何用Java代碼去實現 用POI導出Excel表格。 一、pom引用 pom文件中,添加以下依賴 查看代碼 <!--Excel工具--> <dependency> <groupId>org.apache.poi</group ...
  • 總結: LinkedList繼承自List,具備有序性 LinkedList繼承自Deque,具備鏈表關聯性 LinkedList集合進行增刪改查操作底層實際是操作Node節點的前後鏈接關係 LinkedList進行增刪操作時,僅需要操作節點的前後鏈接關係,因此效率較ArrayList高 Linke ...
  • python中使用[]來截取字元串,語法: 字元串[起始位置:結束位置] 一、起始位置:結束位置 先看幾個例子: s = 'python' print(s) #輸出 python 直接輸出字元串 #從前面截取 print(s[1]) #輸出 y 根據下標取字元 print(s[:]) #輸出 pyt ...
  • 在開發過程中,像側邊欄這種功能的版塊,我們在很多頁面都需要使用到的時候,我們則需要在視圖函數中書寫重覆的代碼,這樣很繁瑣,我們可以將側邊欄製成inclusion_tag,後面我們需要用到側邊欄功能時,只需要導入即可! 將側邊欄製成inclusion_tag的步驟: 1.在應用下創建一個名字必須叫te ...
  • 2022-10-01 關聯查詢: 在Django項目中使用ORM模式設置表後,進行關聯查詢,即兩個表直接有聯繫的查詢。 方式: 可以通過主表查詢從表,也可以通過從表查詢主表。 方式一實例: (1)查詢編號為1的圖書中的人物 前提環境,進入pycharm,進入虛擬環境、進入shell環境。 首先,需要 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 JSON Web Token(JWT)是一個非常輕巧的規範。這個規範允許我們使用 JWT 在用戶和伺服器之間傳遞安全可靠的信息。一個 JWT 實際上就是一個字元串,它由三部分組成,頭部、載荷與簽名。前兩部分需要經過 Base64 編碼,後一部分通過前兩部分 Base64 編碼後再加密而成。針對 ...
  • 一:背景 1. 講故事 今天本來想寫一篇 非托管泄露 的生產事故分析,但想著昨天就上了一篇非托管文章,連著寫也沒什麼意思,換個口味吧,剛好前些天有位朋友也找到我,說他們的拍攝監控軟體卡死了,讓我幫忙分析下為什麼會卡死,聽到這種軟體,讓我不禁想起了前些天 在程式員桌子上安裝監控 的新聞,參考如下: 我 ...
  • 文章目錄 介紹 ABP的依賴註入系統是基於Microsoft的依賴註入擴展庫(Microsoft.Extensions.DependencyInjection nuget包)開發的。所以我們採用dotnet自帶的註入方式也是支持的。 由於ABP是一個模塊化框架,因此每個模塊都定義它自己的服務併在它自 ...
  • 前言 外觀模式,英文名稱是:Facade Pattern。我們先從名字上來理解一下“外觀模式”。我看到了“外觀”這個詞語,就想到了“外表”這個詞語,兩者有著很相近的意思。就拿談戀愛來說,“外表”很重要,如果第一眼看著很舒服、有眼緣,那就有交往下去的可能。如果長的“三寸釘、枯樹皮”,估計就夠嗆了。在這 ...
  • 模擬.NET實際應用場景,綜合應用三個主要知識點:一是使用dnSpy反編譯第三庫及調試,二是使用Lib.Harmony庫實現第三庫攔截、偽造,三是實現同一個庫支持多版本同時引用。 ...
  • 通過strimzi部署的kafka集群,如何部署prometheus+grafana去監控呢?官方文檔信息量太大,即便照著做也可能失敗,這裡有一份詳細的保姆級操作指南,助您成功部署監控服務 ...
  • 在工具類中封裝getBean,使用哪個介面來實現 實事上,在工具類中,實現BeanFactoryPostProcessor和ApplicationContextAware介面後,使用它們構造方法里的對象ConfigurableListableBeanFactory和ApplicationContex ...
  • 1章:系統基礎信息模塊詳解 通過第三方模塊獲取伺服器的基本性能、塊設備、網卡介面、網路地址庫等信息。 1.1 系統性能模塊psutil:獲取系統性能信息、記憶體信息、磁碟信息、網路信息、用戶信息等。 1.2 IP地址處理模塊IPy: 處理IP地址,網段等。 1.3 DNS處理模塊dnspython: ...
  • EasyExcel動態表頭導出(支持多級表頭) 在很多業務場景中,都會應用到動態表頭的導出,也會涉及到多級表頭的導出,如下圖所示 通過EasyExcel,我們可以快速實現這一需求,具體代碼如下 DynamicHeader import java.util.List; /** *@Author: <a ...
  • 基於java線上婚紗定製系統設計與實現,可適用於線上婚紗攝影預定系統,基於web的婚紗影樓管理系統設計,基於web的婚紗影樓管理系統設計,婚紗攝影網系統,婚紗攝影網站系統,婚紗攝影網站系統,婚紗系統,婚紗管理系統等等; ...