天天看點

驅動開發:通過Async反向與核心通信

在前幾篇文章中給大家具體解釋了驅動與應用層之間正向通信的一些經典案例,本章将繼續學習驅動通信,不過這次我們學習的是通過運用

Async

異步模式實作的反向通信,反向通信機制在開發中時常被用到,例如一個防毒軟體如果監控到有異常程序運作或有異常系統資料庫被改寫後,該驅動需要主動的通知應用層程序讓其知道,這就需要用到驅動反向通信的相關知識點,如下将循序漸進的實作一個反向通信案例。

在開始學習Async反向通信之前先來研究一個Sync正向通信案例,不論是正向反向通信其在通信模式上與

《驅動開發:通過ReadFile與核心層通信》

所介紹的通信模式基本一緻,都是通過

ReadFile

觸發驅動中的

IRP_MJ_READ

讀取派遣,唯一的差別是在傳輸資料時使用了

MmGetSystemAddressForMdl

方式,它将給定

MDL

描述的實體頁面映射到系統空間,并調用

RtlCopyMemory()

将全局字元串複制到這個空間内,這樣用戶端就可以循環讀取核心傳出的資料。

我們來看驅動端代碼是如何實作的這個功能,代碼并沒有什麼特殊的無法了解的點,隻是需要注意我們在驅動入口調用

IoCreateDevice()

時傳入了第二個參數

FILE_DEVICE_EXTENSION

,該參數的作用是,建立裝置時,指定裝置擴充記憶體的大小,傳一個值進去,就會給裝置配置設定一塊非頁面記憶體。

#include <ntddk.h>
#include <stdio.h>

// 儲存一段非分頁記憶體,用于給全局變量使用
#define FILE_DEVICE_EXTENSION 4096

// 定義全局字元串
static int global_count = 0;
static char global_char[5][128] = { 0 };

// 驅動綁定預設派遣函數
NTSTATUS _DefaultDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp)
{
	_pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;
	_pIrp->IoStatus.Information = 0;
	IoCompleteRequest(_pIrp, IO_NO_INCREMENT);
	return _pIrp->IoStatus.Status;
}

// 驅動建立後觸發
NTSTATUS _SyncCreateCloseDispatch(PDEVICE_OBJECT _pDevcieObject, PIRP _pIrp)
{
	_pIrp->IoStatus.Status = STATUS_SUCCESS;
	_pIrp->IoStatus.Information = 0;
	IoCompleteRequest(_pIrp, IO_NO_INCREMENT);
	return _pIrp->IoStatus.Status;
}

// 應用層讀資料後觸發
NTSTATUS _SyncReadDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp)
{
	NTSTATUS status = STATUS_SUCCESS;
	PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(_pIrp);
	PVOID pBuffer = NULL;
	ULONG uBufferLen = 0;

	do
	{
		// 讀寫請求使用的是直接I/O方式
		pBuffer = MmGetSystemAddressForMdl(_pIrp->MdlAddress);
		if (pBuffer == NULL)
		{
			status = STATUS_UNSUCCESSFUL;
			break;
		}
		uBufferLen = pIrpStack->Parameters.Read.Length;
		DbgPrint("讀位元組長度: %d \n", uBufferLen);

		// 最大支援20位元組讀請求
		uBufferLen = uBufferLen >= 20 ? 20 : uBufferLen;

		// 輸出五次字元串
		if (global_count < 5)
		{
			RtlCopyMemory(pBuffer, global_char[global_count], uBufferLen);
			global_count = global_count + 1;
		}

	} while (FALSE);

	// 填寫傳回狀态及傳回大小
	_pIrp->IoStatus.Status = status;
	_pIrp->IoStatus.Information = uBufferLen;

	// 完成IRP
	IoCompleteRequest(_pIrp, IO_NO_INCREMENT);
	return status;
}

// 解除安裝驅動
VOID _UnloadDispatch(PDRIVER_OBJECT _pDriverObject)
{
	// 删除建立的裝置
	UNICODE_STRING  Win32DeviceName;
	RtlInitUnicodeString(&Win32DeviceName, L"\\DosDevices\\LySharkSync");
	IoDeleteDevice(_pDriverObject->DeviceObject);
}

// 驅動入口
NTSTATUS DriverEntry(PDRIVER_OBJECT _pDriverObject, PUNICODE_STRING _pRegistryPath)
{
	UNICODE_STRING DeviceName, Win32DeivceName;
	PDEVICE_OBJECT pDeviceObject = NULL;
	NTSTATUS status;
	HANDLE hThread;
	OBJECT_ATTRIBUTES ObjectAttributes;

	// 設定符号名
	RtlInitUnicodeString(&DeviceName, L"\\Device\\LySharkSync");
	RtlInitUnicodeString(&Win32DeivceName, L"\\DosDevices\\LySharkSync");

	// 循環初始化IRP函數
	for (ULONG i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
	{
		_pDriverObject->MajorFunction[i] = _DefaultDispatch;
	}

	// 再次覆寫派遣函數
	_pDriverObject->MajorFunction[IRP_MJ_CREATE] = _SyncCreateCloseDispatch;
	_pDriverObject->MajorFunction[IRP_MJ_CLOSE] = _SyncCreateCloseDispatch;
	_pDriverObject->MajorFunction[IRP_MJ_READ] = _SyncReadDispatch;
	_pDriverObject->DriverUnload = _UnloadDispatch;

	// 配置設定一個自定義擴充 大小為sizeof(DEVEXT)
	// By: LyShark.com
	status = IoCreateDevice(_pDriverObject, sizeof(FILE_DEVICE_EXTENSION), &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject);
	if (!NT_SUCCESS(status))
		return status;
	
	if (!pDeviceObject)
		return STATUS_UNEXPECTED_IO_ERROR;

	// 為全局變量指派
	strcpy(global_char[0], "hi,lyshark A");
	strcpy(global_char[1], "hi,lyshark B");
	strcpy(global_char[2], "hi,lyshark C");
	strcpy(global_char[3], "hi,lyshark D");
	strcpy(global_char[4], "hi,lyshark E");

	// 指定讀寫方式為 直接I/O MDL模式
	pDeviceObject->Flags |= DO_DIRECT_IO;

	// 資料傳輸時位址校驗大小
	pDeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT;
	status = IoCreateSymbolicLink(&Win32DeivceName, &DeviceName);

	pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
	return STATUS_SUCCESS;
}
           

對于應用層來說并沒有什麼特别的,同樣調用

ReadFile

讀取核心中的參數,同樣for循環讀取五次,代碼如下:

#include <stdio.h>
#include <Windows.h>

int main(int argc, char *argv[])
{
	HANDLE hFile;
	char Buffer[10] = { 0 };
	DWORD dwRet = 0;
	BOOL bRet;

	hFile = CreateFileA("\\\\.\\LySharkSync", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return 0;

	for (int x = 0; x < 5; x++)
	{
		bRet = ReadFile(hFile, Buffer, 20, &dwRet, NULL);
		if (!bRet)
		{
			CloseHandle(hFile);
			return 0;
		}
		printf("讀入資料: %s -> 讀取長度: %d \n", Buffer, dwRet);
	}
	return 0;
}
           

這段代碼運作效果如下:

驅動開發:通過Async反向與核心通信

與同步模式不同,

異步模式

雖然同樣使用

ReadFile

實作通信,但在通信中引入了

Event

事件通知機制,這也是異步與同步最大的差別所在,使用者層可以分别建立多個Event事件,等待核心依次做出相應并最終一并傳回。

首先驅動内定義了

_DeviceExtension

自定義接口,該接口用于儲存此次事件所對應的

Irp

以及其所對應的DPC時間等。

異步分發函數

_AsyncReadDispatch

同樣是被

IRP_MJ_READ

派遣函數觸發的,觸發後其内部會首先

IoGetCurrentIrpStackLocation

得到目前IRP的堆棧資訊,然後設定

IoMarkIrpPending()

并最終将該IRP通過

InsertTailList()

插入到IRP連結清單内等待被處理。

  • IoMarkIrpPending
    • 用于标記指定的IRP,标志着某個驅動的分發例程(分發函數)因需要被其他的驅動程式進一步處理最終傳回STATUS_PENDING狀态。

函數

_CustomDpc

則是定時器内部要執行的具體操作,在

DriverEntry

驅動入口處做了如下初始化,初始化了連結清單,并初始化了一個定時器,最後啟動這個定時器每隔1秒都會執行一次

_CustomDpc

如果我們的IRP連結清單内

IsListEmpty() 檢測

存在資料,則會主動拷貝記憶體

RtlCopyMemory

并推送到應用層。

// 初始化IRP連結清單
InitializeListHead(&pDevExt->IrpList);
// 初始化定時器
KeInitializeTimer(&(pDevExt->timer));
// 初始化DPC pDevExt是傳給_CustomDpc函數的參數
KeInitializeDpc(&pDevExt->dpc, (PKDEFERRED_ROUTINE)_CustomDpc, pDevExt);

// 設定定時時間位1s
pDevExt->liDueTime = RtlConvertLongToLargeInteger(-10000000);
// 啟動定時器
KeSetTimer(&pDevExt->timer, pDevExt->liDueTime, &pDevExt->dpc);
           

驅動層完成代碼如下所示:

#include <ntddk.h>

// 自定義接口擴充
typedef struct _DeviceExtension
{
	LIST_ENTRY IrpList;
	KTIMER timer;
	LARGE_INTEGER liDueTime;
	KDPC dpc;
}DEV_EXT, *PDEV_EXT;

// 預設派遣函數
NTSTATUS _DefaultDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp)
{
	_pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;
	_pIrp->IoStatus.Information = 0;
	IoCompleteRequest(_pIrp, IO_NO_INCREMENT);
	return _pIrp->IoStatus.Status;
}

// 建立派遣函數
NTSTATUS _AsyncCreateCloseDispatch(PDEVICE_OBJECT _pDevcieObject, PIRP _pIrp)
{
	_pIrp->IoStatus.Status = STATUS_SUCCESS;
	_pIrp->IoStatus.Information = 0;
	IoCompleteRequest(_pIrp, IO_NO_INCREMENT);
	return  _pIrp->IoStatus.Status;
}

// 讀取派遣函數
NTSTATUS _AsyncReadDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp)
{
	NTSTATUS status;
	PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(_pIrp);
	PDEV_EXT pDevExt = (PDEV_EXT)_pDeviceObject->DeviceExtension;

	IoMarkIrpPending(_pIrp);

	// 将IRP插入自定義連結清單中插入的是ListEntry
	InsertTailList(&pDevExt->IrpList, &_pIrp->Tail.Overlay.ListEntry);

	// 傳回pending 主要傳回給I/O管理器的值必須和IRP的Pending标志位一緻
	// By: LyShark.com
	// 即調用iomarkirppending和傳回值要一緻 
	return STATUS_PENDING;
}

// DPC線程
VOID _CustomDpc(PKDPC Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2)
{
	PIRP pIrp;
	PDEV_EXT pDevExt = (PDEV_EXT)DeferredContext;
	PVOID pBuffer = NULL;
	ULONG uBufferLen = 0;
	PIO_STACK_LOCATION pIrpStack = NULL;

	do
	{
		if (!pDevExt)
		{
			break;
		}

		// 檢查尾端IRP連結清單是否為空 為空則跳出
		if (IsListEmpty(&pDevExt->IrpList))
		{
			break;
		}

		// 從IRP連結清單中取出一個IRP并完成該IRP 取出的是ListEntry的位址
		PLIST_ENTRY pListEntry = (PLIST_ENTRY)RemoveHeadList(&pDevExt->IrpList);
		if (!pListEntry)
			break;

		pIrp = (PIRP)CONTAINING_RECORD(pListEntry, IRP, Tail.Overlay.ListEntry);
		pIrpStack = IoGetCurrentIrpStackLocation(pIrp);

		DbgPrint("目前DPC Irp: 0x%x\n", pIrp);

		// 驅動程式的讀寫方式位直接I/O
		pBuffer = MmGetSystemAddressForMdl(pIrp->MdlAddress);
		if (pBuffer == NULL)
		{
			pIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;
			pIrp->IoStatus.Information = 0;
			IoCompleteRequest(pIrp, IO_NO_INCREMENT);

			break;
		}
		uBufferLen = pIrpStack->Parameters.Read.Length;
		DbgPrint("讀取DPC長度: %d\n", uBufferLen);

		// 支援5位元組以下的讀請求
		uBufferLen = uBufferLen > 13 ? 13 : uBufferLen;

		// 複制請求内容
		RtlCopyMemory(pBuffer, "hello lyshark", uBufferLen);

		pIrp->IoStatus.Status = STATUS_SUCCESS;
		pIrp->IoStatus.Information = uBufferLen;

		// 完成該IRP
		IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	} while (FALSE);

	// 重新設定定時器
	KeSetTimer(&pDevExt->timer, pDevExt->liDueTime, &pDevExt->dpc);
}

// 解除安裝驅動
VOID _UnloadDispatch(PDRIVER_OBJECT _pDriverObject)
{
	UNICODE_STRING Win32DeviceName;
	PDEV_EXT pDevExt = (PDEV_EXT)_pDriverObject->DeviceObject->DeviceExtension;

	RtlInitUnicodeString(&Win32DeviceName, L"\\DosDevices\\LySharkAsync");

	// 删除定時器
	// LyShark
	KeCancelTimer(&pDevExt->timer);
	// 删除建立的裝置
	IoDeleteDevice(_pDriverObject->DeviceObject);
}

// 驅動入口
NTSTATUS DriverEntry(PDRIVER_OBJECT _pDriverObject, PUNICODE_STRING _pRegistryPath)
{
	UNICODE_STRING DeviceName, Win32DeivceName;
	PDEVICE_OBJECT pDeviceObject = NULL;
	NTSTATUS status;
	PDEV_EXT pDevExt = NULL;
	HANDLE hThread;
	OBJECT_ATTRIBUTES ObjectAttributes;
	CLIENT_ID CID;

	RtlInitUnicodeString(&DeviceName, L"\\Device\\LySharkAsync");
	RtlInitUnicodeString(&Win32DeivceName, L"\\DosDevices\\LySharkAsync");

	for (ULONG i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
	{
		_pDriverObject->MajorFunction[i] = _DefaultDispatch;
	}

	_pDriverObject->MajorFunction[IRP_MJ_CREATE] = _AsyncCreateCloseDispatch;
	_pDriverObject->MajorFunction[IRP_MJ_CLOSE] = _AsyncCreateCloseDispatch;
	_pDriverObject->MajorFunction[IRP_MJ_READ] = _AsyncReadDispatch;
	_pDriverObject->DriverUnload = _UnloadDispatch;

	// 配置設定自定義擴充
	status = IoCreateDevice(_pDriverObject, sizeof(DEV_EXT), &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject);
	if (!NT_SUCCESS(status))
		return status;
	if (!pDeviceObject)
		return STATUS_UNEXPECTED_IO_ERROR;

	pDeviceObject->Flags |= DO_DIRECT_IO;
	pDeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT;
	status = IoCreateSymbolicLink(&Win32DeivceName, &DeviceName);

	pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
	pDevExt = (PDEV_EXT)pDeviceObject->DeviceExtension;

	// 初始化IRP連結清單
	InitializeListHead(&pDevExt->IrpList);
	// 初始化定時器
	KeInitializeTimer(&(pDevExt->timer));
	// 初始化DPC pDevExt是傳給_CustomDpc函數的參數
	KeInitializeDpc(&pDevExt->dpc, (PKDEFERRED_ROUTINE)_CustomDpc, pDevExt);

	// 設定定時時間位1s
	pDevExt->liDueTime = RtlConvertLongToLargeInteger(-10000000);
	// 啟動定時器
	KeSetTimer(&pDevExt->timer, pDevExt->liDueTime, &pDevExt->dpc);

	return STATUS_SUCCESS;
}
           
#include <stdio.h>
#include <Windows.h>

int main(int argc, char *argv[])
{
	HANDLE hFile;
	char Buffer[3][32] = { 0 };
	DWORD dwRet[3] = { 0 };
	OVERLAPPED ol[3] = { 0 };
	HANDLE hEvent[3] = { 0 };

	// By:LyShark
	hFile = CreateFileA("\\\\.\\LySharkAsync", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
		NULL);
	if (INVALID_HANDLE_VALUE == hFile)
		return  0;

	// event用來通知請求完成
	hEvent[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
	ol[0].hEvent = hEvent[0];

	hEvent[1] = CreateEvent(NULL, TRUE, FALSE, NULL);
	ol[1].hEvent = hEvent[1];

	hEvent[2] = CreateEvent(NULL, TRUE, FALSE, NULL);
	ol[2].hEvent = hEvent[2];

	// 讀取事件内容到緩存
	ReadFile(hFile, Buffer[0], 13, &dwRet[0], &ol[0]);
	ReadFile(hFile, Buffer[1], 13, &dwRet[1], &ol[1]);
	ReadFile(hFile, Buffer[2], 13, &dwRet[2], &ol[2]);

	// 等待三個事件執行完畢
	WaitForMultipleObjects(3, hEvent, TRUE, INFINITE);

	// 輸出結果
	printf("緩存LyShark A: %s \n", Buffer[0]);
	printf("緩存LyShark B: %s \n", Buffer[1]);
	printf("緩存LyShark C: %s \n", Buffer[2]);

	CloseHandle(hFile);
	return  0;
}
           

繼續閱讀