C語言驅動開發(fā)之通過ReadFile與內核層通信
驅動與應用程序的通信是非常有必要的,內核中執(zhí)行代碼后需要將其動態(tài)顯示給應用層,但驅動程序與應用層畢竟不在一個地址空間內,為了實現內核與應用層數據交互則必須有通信的方法,微軟為我們提供了三種通信方式,如下先來介紹通過ReadFile系列函數實現的通信模式。
長話短說,不說沒用的概念,首先系統(tǒng)中支持的通信模式可以總結為三種。
- 緩沖區(qū)方式讀寫(DO_BUFFERED_IO)
- 直接方式讀寫(DO_DIRECT_IO)
- 其他方式讀寫
而通過ReadFile,WriteFile
系列函數實現的通信機制則屬于緩沖區(qū)通信模式,在該模式下操作系統(tǒng)會將應用層中的數據復制到內核中,此時應用層調用ReadFile,WriteFile
函數進行讀寫時,在驅動內會自動觸發(fā) IRP_MJ_READ
與 IRP_MJ_WRITE
這兩個派遣函數,在派遣函數內則可以對收到的數據進行各類處理。
首先需要實現初始化各類派遣函數這么一個案例,如下代碼則是通用的一種初始化派遣函數的基本框架,分別處理了IRP_MJ_CREATE
創(chuàng)建派遣,以及IRP_MJ_CLOSE
關閉的派遣,此外函數DriverDefaultHandle
的作用時初始化其他派遣用的,也就是將除去CREATE/CLOSE
這兩個派遣之外,其他的全部賦值成初始值的意思,當然不增加此段代碼也是無妨,并不影響代碼的實際執(zhí)行。
#include <ntifs.h> // 卸載驅動執(zhí)行 VOID UnDriver(PDRIVER_OBJECT pDriver) { PDEVICE_OBJECT pDev; // 用來取得要刪除設備對象 UNICODE_STRING SymLinkName; // 局部變量symLinkName pDev = pDriver->DeviceObject; IoDeleteDevice(pDev); // 調用IoDeleteDevice用于刪除設備 RtlInitUnicodeString(&SymLinkName, L"\\??\\LySharkDriver"); // 初始化字符串將symLinkName定義成需要刪除的符號鏈接名稱 IoDeleteSymbolicLink(&SymLinkName); // 調用IoDeleteSymbolicLink刪除符號鏈接 DbgPrint("驅動卸載完畢..."); } // 創(chuàng)建設備連接 // LyShark.com NTSTATUS CreateDriverObject(IN PDRIVER_OBJECT pDriver) { NTSTATUS Status; PDEVICE_OBJECT pDevObj; UNICODE_STRING DriverName; UNICODE_STRING SymLinkName; // 創(chuàng)建設備名稱字符串 RtlInitUnicodeString(&DriverName, L"\\Device\\LySharkDriver"); Status = IoCreateDevice(pDriver, 0, &DriverName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj); // 指定通信方式為緩沖區(qū) pDevObj->Flags |= DO_BUFFERED_IO; // 創(chuàng)建符號鏈接 RtlInitUnicodeString(&SymLinkName, L"\\??\\LySharkDriver"); Status = IoCreateSymbolicLink(&SymLinkName, &DriverName); return STATUS_SUCCESS; } // 創(chuàng)建回調函數 NTSTATUS DispatchCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp) { pIrp->IoStatus.Status = STATUS_SUCCESS; // 返回成功 DbgPrint("派遣函數 IRP_MJ_CREATE 執(zhí)行 \n"); IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 指示完成此IRP return STATUS_SUCCESS; // 返回成功 } // 關閉回調函數 NTSTATUS DispatchClose(PDEVICE_OBJECT pDevObj, PIRP pIrp) { pIrp->IoStatus.Status = STATUS_SUCCESS; // 返回成功 DbgPrint("派遣函數 IRP_MJ_CLOSE 執(zhí)行 \n"); IoCompleteRequest(pIrp, IO_NO_INCREMENT); // 指示完成此IRP return STATUS_SUCCESS; // 返回成功 } // 默認派遣函數 NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp) { NTSTATUS status = STATUS_SUCCESS; pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return status; } // 入口函數 // By: LyShark NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegistryPath) { DbgPrint("hello lyshark \n"); // 調用創(chuàng)建設備 CreateDriverObject(pDriver); pDriver->DriverUnload = UnDriver; // 卸載函數 pDriver->MajorFunction[IRP_MJ_CREATE] = DispatchCreate; // 創(chuàng)建派遣函數 pDriver->MajorFunction[IRP_MJ_CLOSE] = DispatchClose; // 關閉派遣函數 // 初始化其他派遣 for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { DbgPrint("初始化派遣: %d \n", i); pDriver->MajorFunction[i] = DriverDefaultHandle; } DbgPrint("驅動加載完成..."); return STATUS_SUCCESS; }
代碼運行效果如下:
通用框架有了,接下來就是讓該驅動支持使用ReadWrite
的方式實現通信,首先我們需要在DriverEntry
處增加兩個派遣處理函數的初始化。
// 入口函數 // By: LyShark NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING RegistryPath) { DbgPrint("hello lyshark \n"); // 調用創(chuàng)建設備 CreateDriverObject(pDriver); // 初始化其他派遣 for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { DbgPrint("初始化派遣: %d \n", i); pDriver->MajorFunction[i] = DriverDefaultHandle; } pDriver->DriverUnload = UnDriver; // 卸載函數 pDriver->MajorFunction[IRP_MJ_CREATE] = DispatchCreate; // 創(chuàng)建派遣函數 pDriver->MajorFunction[IRP_MJ_CLOSE] = DispatchClose; // 關閉派遣函數 // 增加派遣處理 pDriver->MajorFunction[IRP_MJ_READ] = DispatchRead; // 讀取派遣函數 pDriver->MajorFunction[IRP_MJ_WRITE] = DispatchWrite; // 寫入派遣函數 DbgPrint("驅動加載完成..."); return STATUS_SUCCESS; }
接著,我們需要分別實現這兩個派遣處理函數,如下DispatchRead
負責讀取時觸發(fā),與之對應DispatchWrite
負責寫入觸發(fā)。
- 引言:
- 對于讀取請求I/O管理器分配一個與用戶模式的緩沖區(qū)大小相同的系統(tǒng)緩沖區(qū)
SystemBuffer
,當完成請求時I/O管理器將驅動程序已經提供的數據從系統(tǒng)緩沖區(qū)復制到用戶緩沖區(qū)。 - 對于寫入請求,會分配一個系統(tǒng)緩沖區(qū)并將
SystemBuffer
設置為地址,用戶緩沖區(qū)的內容會被復制到系統(tǒng)緩沖區(qū),但是不設置UserBuffer
緩沖。
通過IoGetCurrentIrpStackLocation(pIrp)
接收讀寫請求長度,偏移等基本參數,AssociatedIrp.SystemBuffer
則是讀寫緩沖區(qū),IoStatus.Information
是輸出緩沖字節(jié)數,Parameters.Read.Length
是讀取寫入的字節(jié)數。
// 讀取回調函數 NTSTATUS DispatchRead(PDEVICE_OBJECT pDevObj, PIRP pIrp) { NTSTATUS Status = STATUS_SUCCESS; PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(pIrp); ULONG ulReadLength = Stack->Parameters.Read.Length; char szBuf[128] = "hello lyshark"; pIrp->IoStatus.Status = Status; pIrp->IoStatus.Information = ulReadLength; DbgPrint("讀取長度:%d \n", ulReadLength); // 取出字符串前5個字節(jié)返回給R3層 memcpy(pIrp->AssociatedIrp.SystemBuffer, szBuf, ulReadLength); IoCompleteRequest(pIrp, IO_NO_INCREMENT); return Status; } // 接收傳入回調函數 // By: LyShark NTSTATUS DispatchWrite(struct _DEVICE_OBJECT *DeviceObject, struct _IRP *Irp) { NTSTATUS Status = STATUS_SUCCESS; PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp); ULONG ulWriteLength = Stack->Parameters.Write.Length; PVOID ulWriteData = Irp->AssociatedIrp.SystemBuffer; // 輸出傳入字符串 DbgPrint("傳入長度: %d 傳入數據: %s \n", ulWriteLength, ulWriteData); IoCompleteRequest(Irp, IO_NO_INCREMENT); return Status; }
如上部分都是在講解驅動層面的讀寫派遣,應用層還沒有介紹,在應用層我們只需要調用ReadFile
函數當調用該函數時驅動中會使用DispatchRead
派遣例程來處理這個請求,同理調用WriteFile
函數則觸發(fā)的是DispatchWrite
派遣例程。
我們首先從內核中讀出前五個字節(jié)并放入緩沖區(qū)內,輸出該緩沖區(qū)內的數據,然后在調用寫入,將hello lyshark
寫回到內核里里面,這段代碼可以這樣來寫。
#include <iostream> #include <Windows.h> #include <winioctl.h> int main(int argc, char *argv[]) { HANDLE hDevice = CreateFileA("\\\\.\\LySharkDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hDevice == INVALID_HANDLE_VALUE) { CloseHandle(hDevice); return 0; } // 從內核讀取數據到本地 char buffer[128] = { 0 }; ULONG length; // 讀入到buffer長度為5 // By:lyshark.com ReadFile(hDevice, buffer, 5, &length, 0); for (int i = 0; i < (int)length; i++) { printf("讀取字節(jié): %c", buffer[i]); } // 寫入數據到內核 char write_buffer[128] = "hello lyshark"; ULONG write_length; WriteFile(hDevice, write_buffer, strlen(write_buffer), &write_length, 0); system("pause"); CloseHandle(hDevice); return 0; }
使用驅動工具安裝我們的驅動,然后運行該應用層程序,實現通信,效果如下所示:
到此這篇關于C語言驅動開發(fā)之通過ReadFile與內核層通信的文章就介紹到這了,更多相關C語言 ReadFile內核層通信內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++順序容器(vector、deque、list)的使用詳解
本文主要介紹了C++順序容器(vector、deque、list)的使用詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-06-06c++利用stl set_difference對車輛進出區(qū)域進行判定
這篇文章主要介紹了set_difference,用于求兩個集合的差集,結果集合中包含所有屬于第一個集合但不屬于第二個集合的元素,需要的朋友可以參考下2017-03-03