在前幾篇文章中給大傢具體解釋了驅動與應用層之間正向通信的一些經典案例,本章將繼續學習驅動通信,不過這次我們學習的是通過運用`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;
}
這段代碼運行效果如下:
與同步模式不同,非同步模式
雖然同樣使用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;
}
驅動層說完了,接下來是應用層,對於應用層來說,需要使用CreateEvent
打開通知事件,或者叫做事件對象,然後當有通知時,則直接使用ReadFile
讀取對應的緩衝區,當所有讀取全部結束WaitForMultipleObjects
等待結束即輸出結果。
#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;
}
這段代碼最終運行效果如下:
文章作者:lyshark (王瑞)文章出處:https://www.cnblogs.com/LyShark/p/16750551.html
版權聲明:本博客文章與代碼均為學習時整理的筆記,文章 [均為原創] 作品,轉載請 [添加出處] ,您添加出處是我創作的動力!
轉載文章請遵守《中華人民共和國著作權法》相關規定或遵守《署名CC BY-ND 4.0國際》禁止演繹規範,合理合規攜帶原創出處轉載。