驅動程序分為兩類: 一個是 Kernel(內核) 模式驅動,另一個是 Windows (用戶窗口層)模式驅動。
這兩種模式本質是相同,但細節不同。本文介紹的是內核模式驅動和驅動程序的安裝與使用。
驅動程序同普通的 .exe,.dll 一樣,都屬於 PE 文件,而且都有一個入口函數。但 .exe 中,入口函數是 main() / WinMain() 和 Unicode 的 wmain() / wWinmain(),.dll 的入口函數則可有可無,它是 DllMain()。
所以驅動程序也有入口函數,而且是必須的,它是 DriverEntry() 函數。再次提示,它是必須的! 因為 I/O 管理器會首先調用驅動程序的DriverEntry() 函數,它的作用就像 DllMain() --完成一些初始化工作。
雖然我們有時候把 DriverEntry() 比作 main(),但二者在本質上不同,DriverEntry() 的生命周期非常短,其作用僅是將內核文件鏡像加載到系統中時進行驅動初始化,調用結束后驅動程序的其他部分依舊存在,並不隨它而終止。
所以我們一般可把 DriverEntry() 稱為“入口函數”,而不可稱為“主函數”。因此作為內核驅動來說,它沒有一個明確的退出點,這應該是 atexit 無法在內核中實現的原因吧。什么是 atexit() 函數呢?它是注冊終止函數(即main執行結束后調用的函數) !
DriverEntry()一共有 2 個參數:
1)PDRIVER_OBJECT DriverObject,指向驅動程序對象的指針。我們操作驅動程序,全靠它,它是由 I/O 管理器傳遞進來的;
2)PUNICODE_STRING RegistryPath,驅動程序的服務主鍵,這個參數的使用並不多,但要注意,在DriverEntry() 返回后,它可能
會消失,所以如果需要使用,記住先要保存下來。
DriverEntry() 等內核函數的返回值一般是 NTSTATUS 類型的。使用宏NT_SUCCESS(NTSTATUS status)可檢測返回狀態是否成功。它是一個 ULONG 值(無符號長整型,另一種表示形式:Unsigned long 變量名),具體的定義,請參見 DDK 中的 NTSTATUS.H 頭文件,里邊有詳細的定義。
在DriverEntry中,一般需要做一下幾件事情: 設置驅動卸載例程、 注冊 IRP 的派遣函數、 創建設備對象等!並且驅動卸載例程與 IRP 派遣函數都是對驅動對象設置的。
既然要寫驅動程序就需要確定如何來與驅動程序通信,常用的共享內存,共享事件,IOCTL宏,或者直接 ReadFile() 或 WriteFile() 進行讀寫,在本文里我就采用一種簡單的、但又很常用的 IOCTL 宏,它依賴的 IRP 派遣例程 IRP_MJ_DEVICE_CONTROL,Win32程序使用 DeviceIoControl() 與驅動進行通信,根據不同的 IOCTL 宏,輸出不同的調試信息。
驅動程序與 I/O 管理器通信,使用的是 IRP,即 I/O 請求包。IRP 分為2部分:IRP 首部;IRP堆棧。
在驅動程序中,IRP 派遣例程起着很重要的作用,每個 IRP 派遣例程,幾乎都有對應的 Win32 API 函數。
下面介紹 IRP 派遣例程 :
名稱 | 描述 | 調用者 |
IRP_MJ_CREATE | 請求一個句柄 | CreateFile |
IRP_MJ_CLEANUP | 在關閉句柄時取消懸掛的IRP | CloseHandle |
IRP_MJ_CLOSE | 關閉句柄 | CloseHandle |
IRP_MJ_READ | 從設備得到數據 | ReadFile |
IRP_MJ_WRITE | 傳送數據到設備 | WriteFile |
IRP_MJ_DEVICE_CONTROL | 控制操作(利用IOCTL宏) | DeviceIoControl |
IRP_MJ_INTERNAL_DEVICE_CONTROL | 控制操作(只能被內核調用) | N/A |
IRP_MJ_QUERY_INFORMATION | 得到文件的長度 | GetFileSize |
IRP_MJ_SET_INFORMATION | 設置文件的長度 | SetFileSize |
IRP_MJ_FLUSH_BUFFERS | 寫輸出緩沖區或丟棄輸入緩沖區 | FlushFileBuffers\FlushConsoleInputBuffer \PurgeComm |
IRP_MJ_SHUTDOWN | 系統關閉 | InitiateSystemShutdown |
提到派遣例程,必須理解 IRP(I/O Request Package),即"輸入/輸出請求包"這個重要數據結構的概念。Ring3(用戶層)通過 DeviceIoControl 等函數向驅動發出 I/O 請求后,在內核中由操作系統將 I/O 請求包轉化為 IRP 的數據結構,並"派遣"到對應驅動的派遣函數中,如圖下圖所示。
Ring3(用戶層)應用程序調用kernel32.dll導出的DeviceIoControl函數后,會調用到ntdll.dll導出的NtDeviceIoControlFile函數,進而調用到系統內核模塊提供的服務函數NtDeviceIo ControlFile,該函數會將I/O請求轉化為IRP包,並發送到對應驅動的派遣例程函數中。對於其他I/O相關函數,如CreateFile、ReadFile、WriteFile、GetFileSize、SetFileSize、CloseHandle等也是如此。
如上圖,從 Ring3 (用戶層)的 I/O 請求到內核的 IRP 結構的請求包
一個 IRP包 該發往驅動的哪個派遣例程函數,是由 IRP 結構中的 MajorFunction (IRP主類型)屬性決定的,MajorFunction 屬性的值是一系列宏,具體可在網上查詢。
IRP 首部信息如下:
IO_STATUS_BLOCK IoStatus //包含 I/O 請求的狀態
PVOID AssociatedIrp.SystemBuffer // 如果執行緩沖區 I/O,這個指針指向系統緩沖區
PMDL MdlAddress // 如果直接 I/O,這個指針指向用戶緩沖區的存儲器描述符表
PVOID UserBuffer // I/O 緩沖區的用戶空間地址
IRP 堆棧信息如下:
UCHAR MajorFunction //(主要類型) 指示 IRP_MJ_XXX派遣例程
UCHAR MinorFunction //(IRP 的子類型) 同上,一般文件系統和 SCSI 驅動程序使用它
union Parameters { //MajorFunction的聯合類型
struct Read //IRP_MJ_READ的參數
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct Write //IRP_MJ_WRITE的參數
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct DeviceIoControl //IRP_MJ_DEVICE_CONTROL和IRP_MJ_INTERNAL_DEVICE_CONTROL的參數
ULONG OutputBufferLength
ULONG InputBufferLength
ULONG IoControlCode
PVOID Type3InputBuffer
}
PDEVICE_OBJECT DeviceObject //請求的目標設備對象的指針
PFILE_OBJECT FileObject //請求的目標文件對象的指針,如果有的話操作 IRP。
對於不同的 IRP 函數,操作也是不同的:有的只操作 IRP 首部;有的只操作 IRP 堆棧;還有操作 IRP 整體。
下面是一些常用的函數:
IRP整體:
名稱 | 描述 | 調用者 |
IoStartPacket | 發送IRP到Start I/O例程 | Dispatch (派遣) |
IoCompleteRequest | 表示所有的處理完成 | DpcForIsr (連接中斷和注冊) |
IoStartNextPacket | 發送下一個IRP到Start I/O例程 | DpcForIsr |
IoCallDriver | 發送IRP請求 | Dispatch |
IoAllocateIrp | 請求另外的IRP | Dispatch |
IoFreeIrp | 釋放驅動程序分配的IRP | I/O Completion (I/0完成) |
IRP堆棧:
名稱 | 描述 | 調用者 |
IoGetCurrentIrpStackLocation | 得到調用者堆棧的指針 | Dispatch |
IoMarkIrpPending | 為進一步的處理標記調用者I/O堆棧 | Dispatch |
IoGetNextIrpStackLocation | 得到下一個驅動程序的I/O堆棧的指針 | Dispatch |
IoSetNextIrpStackLocation | 將I/O堆棧指針壓入堆棧 | Dispatch |
/************************************************************************************************
* 驅動程序與應用程的通訊示例 *
*************************************************************************************************
* 應用程序類型:ControlAppliaction *
* 應用程序名稱:Appinformation *
* 驅 動 名 稱:AppDriver *
* 符號鏈接名稱:AppSymbolLink *
*************************************************************************************************
* 簡述:編寫一個驅動程序與應用程之間的通信訊實例,讓應用程序輸入數據,進而發送 I/O 請求,當 *
* 系統接的 I/O 管理器在接收到應用層的設備讀寫請求后,將請求封裝為一個 IRP 請求(包括 IRP 頭部 *
* 和 IRP STACK_LOCATINO 數組),然后根據 IRP 的類型發送到相應類型派遣例程,派遣例程再把接 *
* 受到的 IRP發送到對應的設備的設備棧的最頂層的那個設備驅動(如果頂層的設備對象的派遣函數結 *
* 束了IRP的請求,則這次IO請求結束,如果沒有將 IRP 請求結束,那么操作系統將IRP轉發到設備棧的 *
* 下一層設備處理。如果這個設備的派遣函數依然沒結束該IRP請求,則繼續向下層設備進行轉發)。 *
* IO請求結束后驅動會把處理后的數據返回應用程序。 *
*************************************************************************************************/
先介紹一下驅動程序和應用程序將要使用到的 Win32 API 函數:
1、DriverEntry(): 驅動程序的入口函數,它將內核文件鏡像加載到系統中時進行驅動初始化。
2、DriverUnload():負責刪除在DriverEntry中創建的設備對象並且將設備對象所關聯的符號鏈接刪除,另外,DriverUnload還負責對一些資源進行回收。
3、IoDeleteDevice():此 API 函數為刪除設備對象的函數,只有一個參數,即為妖卸載的設備名稱。
4、IoDeleteSymbolicLink() : 此 API 函數為刪除符號鏈接的函數,只有一個參數,即為符號鏈接名稱。
5、IoCreateDevice():此 API 函數創建設備對象供驅動程序使用。包含七個參數。
6、RtlInitUnicodeString():此 API 函數初始化一個Unicode字符串,主要用來實例化設備名稱或符號鏈接名稱。
7、IoCreateSymbolicLink():此 API 函數主要是對已實例化的設備名稱和符號鏈接名稱進行綁定。參數為符名和設備名
8、IoGetCurrentIrpStackLocation():此 API 函數向調用者返回一個 IRP 當前堆棧位置的指針,參數為設備對象和 IRP 指針(PIRP)。
9、IoCompleteRequest():此 API 函數表示調用方已完成所有 I/O 請求處理操作,並將給定的 IRP 傳給 I/O 管理器。無返回值。
10、CTL_CODE() 宏:此宏為系統提供的,在wdm中定義的,用於創建一個唯一的32位系統I/O控制代碼,這個控制代碼包括4部分組成。
11、CreateFile(): 此函數創建或打開(文件,文件流,目錄,磁盤,控制台緩沖區)等設備,並返回一個用來訪問這些對象的句柄 。
12、DeviceIoControl(): 此函數直接發送一個控制代碼到指定的設備驅動程序,使相應的設備來執行相應的操作。
13、CloseHandle():此函數關閉一個打開的對象句柄,該對象句柄可以是線程句柄,也可以是進程、信號量等其他內核對象的句柄 。
============================================================================================================
下面請看驅動程序代碼:
首先用 C++ 創建一個空白的“ 控制台應用程序”項目:然后新建一個源文件:Appinformation.cpp
代碼如下:
#include <iostream> #include <tchar.h> #include <Windows.h> #include "InputBuffer.h" using std::cout; using std::endl; int _tmain(int argc, _TCHAR* argv[]) { /************************************函數說明********************************************* * 函數名:CreateFile * * 類型:Win32 API函數 * * 作用:這是一多功能的函數,可打開或創建如:控制台,通信資源,目錄(只讀打開), * * 磁盤驅動器,文件,郵槽,管道等對象,並返回可訪問的句柄:。 * * 參數說明: * * LPCTSTR lpFileName : 普通文件名或者設備文件名 * * DWORD dwDesiredAccess : 訪問對象的權限,希望以哪種方式打開,建議兩者兼備。 * * DWORD dwShareMode : 共享模式,建議兩者兼備。 * * LPSECURITY_ATTRIBUTES : 用來設定一個指向是否由子進程返回的句柄可以被繼承 * * DWORD dwCreationDisposition : 表示判斷文件存在或不存在,按需求如何創建 * * DWORD dwFlagsAndAttributes : 文件標志或設備屬性 * * HANDLE hTemplateFile :模板文件,給創建的文件提供文件屬性和擴展屬性,一般為空 * * 返回值: 獲取設備的句柄 * *****************************************************************************************/ HANDLE DeviceHandle = CreateFile( L"\\\\.\\AppSymbolLink", //\\??\\設備符號鏈接名稱,注意這里要用 L 寬字符轉換 GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, //枚舉:OPEN_EXISTING 表示文件必須已經存在。 FILE_ATTRIBUTE_NORMAL, //此枚舉表示文件沒有其他屬性集。 NULL); //模板文件,一般為空 if (DeviceHandle == INVALID_HANDLE_VALUE){ printf("獲取文件或設備句柄失敗!!"); getchar(); //讀取清除緩沖區字符 return -1; } printf("獲取驅動程序句柄成功 \n"); int a=10, b=20, c=30,e=0,f=0,g=0; InputBuffer(DeviceHandle, a, b, c); for (int i = 0; i < 3; i++) { switch (i){ case 0:{ e = OtnC[i]; } case 1:{ f = OtnC[i]; } case 2:{ g = OtnC[i]; } } //End switch (i) } //End for printf("a=%d,b=%d,c=%d\n",e, f, g); CloseHandle(DeviceHandle); //過程完成后,最好關閉句柄 cout << "驅動通訊完成" << endl; system("pause"); }
好了,按設備的符號鏈接打開了設備,並得到了它的句柄,有了句柄,我們就能更好的和它進行互動了!!
如果想和他通信,就需要發送通訊口令了,我們把發送口令的函數它寫在頭文件:InputBuffer.h中。
#pragma once #include <iostream> #include <Windows.h> #include "SetCtlCode.h" int OtnC[3] = { 0 }; //整形全局數組變量 /************************************函數說明**************************************** * 函數名:InputBuffer * * 類型:自定義無返回值函數 * * 作用:這個函數用於給控制代碼函數提供參數,也為了其函數調用獲取返回值。 * * 參數說明: * * DeviceHandle : 設備句柄,用於指定通信的設備 * * a : 通訊數據。 * * b : 通訊數據。 * * c : 通訊數據 * * 返回值: 無 ,因為返回值已賦給了全局變量 * ************************************************************************************/ void InputBuffer(HANDLE DeviceHandle, int a, int b, int c){ int ItnC[3] = { a, b, c }; ULONG wtWrite; int *InBuffer = ItnC; int *OutBuffer = OtnC; /************************************函數說明******************************** * 函數名:DeviceIoControl * * 類型:Win32 API函數 * * 作用:發送控制代碼到指定設備驅動程序。 * * 參數說明: * * hDevice Long:設備句柄。用於指定通信的設備 * * dwIoControlCode Long:應用程序調用驅動程序的控制命令,就是IOCTL_XXX IOCTLs。 * * lpInBuffer Any:應用程序傳遞給驅動程序的數據緩沖區地址。 * * nInBufferSize Long:應用程序傳遞給驅動程序的數據緩沖區大小,字節數。 * * lpOutBuffer Any:驅動程序返回給應用程序的數據緩沖區地址。 * * nOutBufferSize Long:驅動程序返回給應用程序的數據緩沖區大小,字節數。 * * lpBytesReturned Long:驅動程序實際返回給應用程序的數據字節數地址。 * * lpOverlapped OVERLAPPED:針對同步操作,請用ByVal As Long傳遞零值 * * 返回值: 非0成功,0失敗 * *****************************************************************************/ DeviceIoControl(DeviceHandle, SetCtlCodeA, InBuffer, sizeof(InBuffer)*3, OutBuffer, sizeof(OutBuffer)* 3, &wtWrite, NULL); printf("DeviceIoControl 發送控制代碼完成 \n"); int e = 0, f = 0, g = 0; for (int i = 0; i < 3; i++) { OtnC[i] = OutBuffer[i]; } return ; }
看到沒! DeviceIoControl 函數中的第二個參數就需要提供口令了,這里專業的說法叫,“I/O控制碼”(也叫輸入輸出控制碼),我們把這控制代碼卸載另一個頭文件中:SetCtlCode.h
#include <winioctl.h> /************************************宏說明*********************************** * 宏名:CTL_CODE * * 類型:Win32 宏命令 * * 作用:用於創建一個唯一的32位系統I/O控制代碼。 * * 參數說明: * * DeviceType:設備類型 ,枚舉如(FILE_DEVICE_UNKNOWN)未知設備 * * Function: 設備唯一功能標識碼,有效范圍:32768-65535 * * Method: I/O訪問內存的使用方式,枚舉,一般用METHOD_BUFFERED。 * * Access: 設備訪問限制,,枚舉,一般用FILE_ANY_ACCESS * * 返回值: 無 * *****************************************************************************/ //可以只寫一個 #define SetCtlCodeA CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) #define SetCtlCodeB CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS)
好了,到此,一個發送 I/O 請求包的 控制台應用程序就創建並編寫完畢,下面來開始我們的驅動編寫:
先創建一個 WDM 工程,然后添加一個頭文件,名為:AppDriver.cpp
然后開入編寫入口函數:
#ifdef _cplusplus extern "C" { #endif #include <ntddk.h> #include "AddDevice.h" #include "SendIRP.h" #ifdef _cplusplus } #endif #define PAGEDCODE cod_seg("PAGE") //將以下函數載入到分頁內存中執行 #pragma PAGEDCODE //載入到分頁內存中執行 VOID Unload(PDRIVER_OBJECT pDriverObject); extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistrPath) { CreateMyDevice(pDriverObject); //調用自定義的 CreateMyDevice 來創建設備對象 //注冊派遣函數,與IRP 請求包的數據類型一一對應 pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Send_Irp; // 發送I/O管理器和其他系統組件、其他內核模式驅動 pDriverObject->MajorFunction[IRP_MJ_CREATE] = Send_Irp; // 創建設備,CreateFile會產生此IRP,返回一個句柄給用戶應用程序 pDriverObject->MajorFunction[IRP_MJ_READ] = Send_Irp; // 讀取設備內容,ReadFile會產生此IRP pDriverObject->MajorFunction[IRP_MJ_WRITE] = Send_Irp; // 改寫設備內容,WriteFile時會產生此IRP pDriverObject->MajorFunction[IRP_MJ_CLOSE] = Send_Irp; // 關閉保留的通往目標設備對象的句柄,CloseHandle會產生此IRP pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = Send_Irp; // 清除用戶模式應用程序的目標設備對象的句柄,CloseHandle會產生此IRP pDriverObject->DriverUnload = Unload; //調用卸載函數 KdPrint(("AppDriver 驅動加載成功!")); return STATUS_SUCCESS; } VOID Unload(PDRIVER_OBJECT pDriverObject) { pDriverObject->DeviceObject = AppDevice; //指向設備對象 IoDeleteDevice(AppDevice); //卸載設備對象 IoDeleteSymbolicLink(&AppSymbolLinkName); //卸載符號鏈接 KdPrint(("驅動卸載成功!")); }
入口函數 DriverEntry 的任務包含了 初始化系統代碼為驅動指令,還包含了對自定義 Unload 函數的調用和 IRP 請求包的派遣。
先我們利用 IoCreateDevice API函數來創建設備驅動,我把它寫在另一個頭文件中:AddDevice.h
#pragma once //僅編譯一次,用途:常出現在頭文件中,因為同一頭文件會在許多源文件中多次引用,如果沒有指定編譯一次,則編譯時出現重定義錯誤。 #ifdef _cplusplus extern "C" { #endif #include <ntddk.h> #ifdef _cplusplus } #endif static PDEVICE_OBJECT AppDevice; //設備對象全局變量 static UNICODE_STRING AppDeviceName, AppSymbolLinkName; //設備名稱和符號鏈接名稱全局變量 //創建設備 #define INITCODE cod_seg("INIT") // 初始化的時候載入內存,然后可以從內存中卸載掉 #pragma INITCODE NTSTATUS CreateMyDevice(PDRIVER_OBJECT pDriverObject){ NTSTATUS Status; //返回值局部變量 RtlInitUnicodeString(&AppDeviceName, L"\\Device\\AppDriver"); //初始化設備名稱 RtlInitUnicodeString(&AppSymbolLinkName, L"\\??\\AppSymbolLink"); //初始化符號鏈接名稱 //調用 IoCreateDevice API 函數創建設備對象 Status=IoCreateDevice(pDriverObject, 0, &AppDeviceName, FILE_DEVICE_UNKNOWN, NULL, TRUE, &AppDevice); if (!NT_SUCCESS(Status)){ //判斷返回值,看是否創建成功 switch (Status){ //如果沒有成功,返回枚舉錯誤消息 case STATUS_INSUFFICIENT_RESOURCES: KdPrint(("資源部足!!")); break; case STATUS_OBJECT_NAME_EXISTS: KdPrint(("指定對象名存在!!")); break; case STATUS_OBJECT_NAME_COLLISION: KdPrint(("對象名稱沖突!!")); break; } } //設備創建成功后,就設置它的緩沖區讀寫方式,這里為(讀寫) AppDevice->Flags |= DO_BUFFERED_IO; //調用 IoCreateSymbolicLink API 函數對設備名稱和符號鏈接名稱進行綁定 Status=IoCreateSymbolicLink(&AppSymbolLinkName, &AppDeviceName); //判斷返回值,看是否綁定成功 if (NTSTATUS(Status) != STATUS_SUCCESS){ //判斷返回值,看是否綁定成功 IoDeleteDevice(AppDevice); //如果未成功,調用IoDeleteDevice 函數刪除設備 KdPrint(("設備因符號鏈接綁定失敗而被刪除!!")); } KdPrint(("設備創建完成,設備已成功加載")); return Status; //如果成功就返回成功的宏 }
到此,我們就真正創建了一個驅動,沒錯確實創建了,但像個人樣,但卻沒有靈魂啊,沒有靈魂就沒有意識,沒有意識就是死的!!!
所以,我們就得給他構造自主的潛微的意識,讓它能對別人的呼叫會有所回應!!這里就要用到 IoGetCurrentStackLocation 這個API函數來接受被人發出的信息(IRP 請求包)派遣,從哪里獲取呢,這里所有的信息都存放在 Stack 堆棧上,所以此時就需要在堆棧上獲得: 我們再次新建一個頭文件SendIRP,在這里面編寫:
#ifdef _cplusplus extern "C" { #endif #include <ntddk.h> #include "SetCtlCode.h" #ifdef _cplusplus } #endif #define INITCODE cod_seg("INIT") // 初始化的時候載入內存,然后可以從內存中卸載掉 #pragma INITCODE NTSTATUS Send_Irp(PDEVICE_OBJECT pDeviceObject, PIRP pIrp){ NTSTATUS Status; PIO_STACK_LOCATION Stack; //定義返回值和堆棧變量 Stack=IoGetCurrentIrpStackLocation(pIrp); //獲取當前 IRP 在堆棧上的位置指針 UCHAR Irp_Type = Stack->MajorFunction; //獲取 IRP 在堆棧中的類型 KdPrint(("Irp_Type=%d \n", Irp_Type)); //對 IRP 的類型進行判斷,進行不同的處理 switch (Irp_Type){ case IRP_MJ_DEVICE_CONTROL:{ KdPrint(("In to IRP_MJ_DEVICE_CONTROL \n")); ULONG CTL_CODE = Stack->Parameters.DeviceIoControl.IoControlCode; ULONG InBufferLength = Stack->Parameters.DeviceIoControl.InputBufferLength; ULONG OutBufferLength = Stack->Parameters.DeviceIoControl.OutputBufferLength; int *OutBuffer = (int *)pIrp->AssociatedIrp.SystemBuffer; KdPrint(("Get CTL_CODE,CTL_CODE=%d \n",CTL_CODE)); KdPrint(("InBufferLength=%d \n" , InBufferLength)); KdPrint(("OutBufferLength=%d \n", OutBufferLength)); KdPrint(("*OutBuffer=%d \n", OutBuffer)); switch (CTL_CODE){ case Add_Ctl_Code:{ KdPrint(("In to Add_Ctl_Code \n")); int i, a, b, c; _asm{ //用匯編語句來(讀取)緩沖區指針中的數據 mov eax,OutBuffer mov ebx,[eax] mov a,ebx mov ebx, [eax+4] mov b,ebx mov ebx, [eax+8] mov c,ebx } KdPrint(("a=%d,b=%d,c=%d \n",a,b,c)); i = a + b + c; KdPrint(("i=a + b + c,i=%d \n", i)); a =a*i, b=b*i, c=c*i; _asm{ //用匯編語句來(填充)緩沖區指針 mov eax,a mov ebx,OutBuffer mov [ebx],eax mov eax, b mov [ebx + 4],eax mov eax,c mov [ebx + 8],eax } pIrp->IoStatus.Information = OutBufferLength; pIrp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(pIrp, IO_NO_INCREMENT); KdPrint(("Leave the Add_Ctl_Code \n", i)); break; } case Sub_Ctl_Code:{ break; } } //End (switch (CTL_CODE)) break; } case IRP_MJ_CREATE:{ break; } case IRP_MJ_READ:{ break; } case IRP_MJ_WRITE:{ break; } case IRP_MJ_CLOSE:{ break; } case IRP_MJ_CLEANUP:{ break; } } // End (switch (Irp_Type)) return STATUS_SUCCESS; } //End Send_Irp Function
此斷代碼主要對在堆棧中獲取其它應用程序發送的 I/O 請求包的 IRP 派遣,驅動程序再更具 IRP 的派遣類型就行不同的處理,然后,得到請求數據,並對它進行了重新計算,然后通過匯編語句對計算后的數據進行反饋給堆棧中,讓發送I/O 請求包 的應用程序進行接受數。
接受 或發送 I/O 請求包 都同樣需用 I/O 控制代碼,才能完成!所以我們再來編寫I/O 控制代碼,把它寫另一個頭文件中:SetCtlCode.h
#define Add_Ctl_Code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) #define Sub_Ctl_Code CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS)
由於驅動已經包含了 IOCTL 宏,所以這里不必再包含<winioctl.h>頭文件了。
到此整個應用程序和驅動整形就編寫完成! 我們可以在虛擬機制中通過 DriverMonitor .exe 和 DbgView工具進行加載驅動並進行查看,然后打開AppInformation.exe 控制台應用程序進行發送 I/O請求包,就能到接受驅動計算過的數據了!