驅動通信:通過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
  • 前言 當別人做大數據用Java、Python的時候,我使用.NET做大數據、數據挖掘,這確實是值得一說的事。 寫的並不全面,但都是實際工作中的內容。 .NET在大數據項目中,可以做什麼? 寫腳本(使用控制台程式+頂級語句) 寫工具(使用Winform) 寫介面、寫服務 使用C#寫代碼的優點是什麼? ...
  • 前言 本文寫給想學C#的朋友,目的是以儘快的速度入門 C#好學嗎? 對於這個問題,我以前的回答是:好學!但仔細想想,不是這麼回事,對於新手來說,C#沒有那麼好學。 反而學Java還要容易一些,學Java Web就行了,就是SpringBoot那一套。 但是C#方向比較多,你是學控制台程式、WebAP ...
  • 某一日晚上上線,測試同學在回歸項目黃金流程時,有一個工單項目介面報JSF序列化錯誤,馬上升級對應的client包版本,編譯部署後錯誤消失。 線上問題是解決了,但是作為程式員要瞭解問題發生的原因和本質。但這都是為什麼呢? ...
  • 本文介紹基於Python語言中TensorFlow的Keras介面,實現深度神經網路回歸的方法。 1 寫在前面 前期一篇文章Python TensorFlow深度學習回歸代碼:DNNRegressor詳細介紹了基於TensorFlow tf.estimator介面的深度學習網路;而在TensorFl ...
  • 前段時間因業務需要完成了一個工作流組件的編碼工作。藉著這個機會跟大家分享一下整個創作過程,希望大家喜歡,組件暫且命名為"easyFlowable"。 接下來的文章我將從什麼是工作流、為什麼要自研這個工作流組件、架構設計三個維度跟大家來做個整體介紹。 ...
  • 1 簡介 我們之前使用了dapr的本地托管模式,但在生產中我們一般使用Kubernetes托管,本文介紹如何在GKE(GCP Kubernetes)安裝dapr。 相關文章: dapr本地托管的服務調用體驗與Java SDK的Spring Boot整合 dapr入門與本地托管模式嘗試 2 安裝GKE ...
  • 摘要:在jvm中有很多的參數可以進行設置,這樣可以讓jvm在各種環境中都能夠高效的運行。絕大部分的參數保持預設即可。 本文分享自華為雲社區《為什麼需要對jvm進行優化,jvm運行參數之標準參數》,作者:共飲一杯無。 我們為什麼要對jvm做優化? 在本地開發環境中我們很少會遇到需要對jvm進行優化的需 ...
  • 背景 我們的業務共使用11台(阿裡雲)伺服器,使用SpringcloudAlibaba構建微服務集群,共計60個微服務,全部註冊在同一個Nacos集群 流量轉發路徑: nginx->spring-gateway->業務微服務 使用的版本如下: spring-boot.version:2.2.5.RE ...
  • 基於php+webuploader的大文件分片上傳,帶進度條,支持斷點續傳(刷新、關閉頁面、重新上傳、網路中斷等情況)。文件上傳前先檢測該文件是否已上傳,如果已上傳提示“文件已存在”,如果未上傳則直接上傳。視頻上傳時會根據設定的參數(分片大小、分片數量)進行上傳,上傳過程中會在目標文件夾中生成一個臨 ...
  • 基於php大文件分片上傳至七牛雲,使用的是七牛雲js-sdk V2版本,引入js文件,配置簡單,可以暫停,暫停後支持斷點續傳(刷新、關閉頁面、重新上傳、網路中斷等情況),可以配置分片大小和分片數量,官方文檔https://developer.qiniu.com/kodo/6889/javascrip ...