內核知識第五講.驅動框架編寫,以及3環和0環通信.
一丶了解內核驅動加載方式
內核加載方式有兩種方式.
1.動態加載方式.
2.靜態加載方式
動態加載方式:
動態態加載方式則是調用3環API 進行代碼加載.
詳情請點擊 : 內核驅動加載工具的編寫.
靜態加載方式
靜態的加載方式則是利用 后綴為.inf 的文件進行加載.
有關.inf的語法,可以百度或者通過學習WDK中提供的源碼例子進行學習.
動態加載一般很常用.
二丶驅動框架的介紹.
在講解內核驅動框架的是否,我們要先了解設備是什么. 設備和驅動之間的數據關系是什么.
1.什么是驅動.什么是設備
驅動: 驅動則是用來操作設備的.
設備: 設備則是我們常說的外設. 比如鍵盤. 顯示器. 鼠標等等..
其中.任何一個驅動操作設備. 都應該提供公共的方法.
打開. 關閉. 讀. 寫. 控制....
如圖:
用戶操作設備的是否. 這個時候會通過內核驅動.提供的 回調方法.(也就是公共的接口)進而來操作設備.
2.驅動和設備之間的關系.
驅動和設備之間的關系是一對多的關系.
驅動可以操作很多設備.
比如我們的鍵盤驅動有一個. 但是可以操作的鍵盤則有很多個. 你鍵盤壞了.換了很多.但是驅動則沒換過.
所以如果是數據關系的時候. 驅動作為外鍵放到設備表中.
例如以下:
設備 |
驅動 |
A鍵盤 |
標准驅動 |
B鍵盤 |
標准驅動 |
有了數據關系.那么就可以講解驅動框架了.
3.驅動框架的介紹.
驅動對象.設備對象.
在驅動中有這兩個概念.
驅動對象: 簡單來說就是一個結構體,存儲了驅動的各種信息.
設備對象: 簡單來說也是一個結構體,存儲的是設備的各種信息.
但依據上面的數據關系來說. 設備對象中肯定會存儲驅動對象結構體的指針. 驅動對象做外鍵存儲到設備對象中.
設備對象結構體:
typedef struct _DRIVER_OBJECT { CSHORT Type; //類型 CSHORT Size; //當前結構體大小.內核中任何一個結構體都是這兩個成員開頭. // // The following links all of the devices created by a single driver // together on a list, and the Flags word provides an extensible flag // location for driver objects. // PDEVICE_OBJECT DeviceObject;//設備對象指針,存疑? 不是說數據關系是 設備表中有驅動對象嗎. 怎么驅動對象表中有設備對象指針.??????? ULONG Flags; //通訊協議以及方式. // // The following section describes where the driver is loaded. The count // field is used to count the number of times the driver has had its // registered reinitialization routine invoked. // PVOID DriverStart; ULONG DriverSize; PVOID DriverSection; PDRIVER_EXTENSION DriverExtension; // // The driver name field is used by the error log thread // determine the name of the driver that an I/O request is/was bound. // UNICODE_STRING DriverName; // // The following section is for registry support. Thise is a pointer // to the path to the hardware information in the registry // PUNICODE_STRING HardwareDatabase; // // The following section contains the optional pointer to an array of // alternate entry points to a driver for "fast I/O" support. Fast I/O // is performed by invoking the driver routine directly with separate // parameters, rather than using the standard IRP call mechanism. Note // that these functions may only be used for synchronous I/O, and when // the file is cached. // PFAST_IO_DISPATCH FastIoDispatch; // // The following section describes the entry points to this particular // driver. Note that the major function dispatch table must be the last // field in the object so that it remains extensible. // PDRIVER_INITIALIZE DriverInit; PDRIVER_STARTIO DriverStartIo; PDRIVER_UNLOAD DriverUnload; PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; //提供的公共方法的接口. 創建.打開,關閉.讀寫控制... 里面存放的是函數指針.單用戶操作設備的是否.則會調用這些回調函數指針. } DRIVER_OBJECT; typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;
存疑部分:
上面標紅的部分. 不是說表關系應該是上面那種嗎.?如下圖所示
設備 |
驅動 |
A鍵盤 |
標准驅動 |
B鍵盤 |
標准驅動 |
可為何設計為這樣.
原因:
我們的內核驅動可以操作設備. 但是我們要知道有多少設備怎么辦. 所以這里給了一個設備對象的指針. 而不是我們說的數據關系.
而在設備對象中.存儲的則是我們的驅動對象指針.
而這里的指針.則是一個鏈表形式的. 為了方便遍歷.
例如:
typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT { CSHORT Type; USHORT Size; LONG ReferenceCount; struct _DRIVER_OBJECT *DriverObject; //驅動對象做外鍵存儲 struct _DEVICE_OBJECT *NextDevice; //鏈表. struct _DEVICE_OBJECT *AttachedDevice; struct _IRP *CurrentIrp; PIO_TIMER Timer; ULONG Flags; // See above: DO_... ULONG Characteristics; // See ntioapi: FILE_... __volatile PVPB Vpb; PVOID DeviceExtension; DEVICE_TYPE DeviceType; CCHAR StackSize; union { LIST_ENTRY ListEntry; WAIT_CONTEXT_BLOCK Wcb; } Queue; ULONG AlignmentRequirement; KDEVICE_QUEUE DeviceQueue; KDPC Dpc; // // The following field is for exclusive use by the filesystem to keep // track of the number of Fsp threads currently using the device // ULONG ActiveThreadCount; PSECURITY_DESCRIPTOR SecurityDescriptor; KEVENT DeviceLock; USHORT SectorSize; USHORT Spare1; struct _DEVOBJ_EXTENSION *DeviceObjectExtension; PVOID Reserved; } DEVICE_OBJECT; typedef struct _DEVICE_OBJECT *PDEVICE_OBJECT;
三丶編寫驅動框架.
上面我們已經簡單的了解了驅動對象.設備對象是什么了.那么現在開始編寫驅動框架
步驟
1.首先注冊設備回調函數.當用戶對設備進行操作的是否.驅動會調用這個回調函數進行操作.
2.創建設備.創建虛擬的設備給用戶使用.
3.指定通訊方式. 什么意思?比如ring3中操作設備進行讀寫的時候 如果用ReadFile讀取.那么你們的通訊方式是否是字符串
4.創建符號連接.
符號連接: 我們知道.在操作系統下有C盤.D盤一說.但是在驅動下面.則沒有怎么一說.只有卷一說.所以我們要綁定一下.
PS: 鑒於篇幅原因.只寫重點.如果想要完整的驅動框架. 請下載資料進行查看.
1.注冊回調函數.
pDriverObject->MajorFunction[IRP_MJ_CREATE] = 創建的回調函數指針; pDriverObject->MajorFunction[IRP_MJ_CLOSE] = 關閉的回調函數指針; pDriverObject->MajorFunction[IRP_MJ_READ] = 讀取的回調函數指針; pDriverObject->MajorFunction[IRP_MJ_WRITE] = 寫入的回調函數指針; pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 控制的回調函數指針;
回調函數的寫法.
NTSTATUS 自定義的函數名(__in struct _DEVICE_OBJECT *DeviceObject, __inout struct _IRP *Irp) { ......... return STATUS_SUCCESS; }
2.創建虛擬設備.
創建設備等等.都屬於IO操作.
IO操作創建設備的API
NTSTATUS IoCreateDevice( IN PDRIVER_OBJECT DriverObject, //調用者驅動對象的指針.一般是驅動入口的參數 IN ULONG DeviceExtensionSize, //設備擴展大小 IN PUNICODE_STRING DeviceName OPTIONAL, //設備對象名稱,注意不是我們ring3下的路徑. IN DEVICE_TYPE DeviceType, //我們的設備類型 IN ULONG DeviceCharacteristics, //驅動的附加信息. IN BOOLEAN Exclusive, //創建的設備是否其余的可以使用,是否獨占 OUT PDEVICE_OBJECT *DeviceObject //傳出參數,設備的信息. );
注意紅點標注:
在內核中並沒有路徑一說.所以這個路徑是特殊的.
UNICODE_STRING 內核中新的字符串格式.其實是一個結構體.系統提供了操作這種結構體的API
我們拼接一個路徑
UNICODE_STRING uStrDeviceName;
RtlInitUnicodeString(&uStrDeviceName,L"\\Device\\xxx名字即可");
注意,創建設備的時候.我們前邊需要加上 \Device. 這里因為是轉義字符.所以加了好多\\
拼接好則可以給了.
status = IoCreateDevice(pDriverObject, 0, &ustrDeviceName, //設備路徑 FILE_DEVICE_UNKNOWN,//設備類型設置為不知道 FILE_DEVICE_SECURE_OPEN, FALSE, //是否獨占 &pDevObj); if (status != STATUS_SUCCESS) { return status; }
3.設置通訊方式.
pDevObj->Flags |= DO_BUFFERED_IO; //指定通訊方式,為緩沖區
4.創建符號連接
我們創建的符號連接.可以通過 Win0bj工具進行查看. 這個工具可以查看所有設備.但是只有0環才可以操作.
status = IoCreateSymbolicLink(&g_ustrSymbolName, &ustrDeviceName); if (status != STATUS_SUCCESS) { //刪除設備 IoDeleteDevice(pDevObj); return status; }
注意符號連接名字.我們也是初始化得出的.
RtlInitUnicodeString(&g_ustrSymbolName, L"\\DosDevices\\xxxx名字");
完整的框架已經寫完.剩下的就是 三環和0環驅動通訊. 依賴我們寫的框架.
四丶三環和0環的通訊.
三環操作設備的API就是 CreateFile ReadFile.....等等.不做介紹了.
利用三環程序.操作我們的設備.從中讀取內容.
HANDLE hFile = CreateFile("\\\\?\\符號連接名稱", GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
打開我們的設備.注意文件名並不是路徑.而是我們綁定的符號連接. 這里我們可以寫成\\?
讀取內容.
char szBuf[10]; ReadFile(hFile, szBuff, sizeof(szBuff), &dwBytes, NULL)
請注意,當讀取設備的是否.我們一開始注冊的回調函數就會來. 這時候我們給它什么都可以了.
但是此時需要講解一下通訊協議.
當讀取的是否,回調函數會來. 然后操作系統會填寫 struct _IRP 結構體.用來和我們的零環通信.
typedef struct _IRP { . //省略了兩個成員,這兩個成員一個是類型.一個是大小. . PMDL MdlAddress; ULONG Flags; union { struct _IRP *MasterIrp; . . PVOID SystemBuffer;//ring3下的緩沖區.操作系統會填寫.我們給里面填寫什么內容.那么ring3就讀取到什么. } AssociatedIrp; . . IO_STATUS_BLOCK IoStatus; KPROCESSOR_MODE RequestorMode; BOOLEAN PendingReturned; . . BOOLEAN Cancel; KIRQL CancelIrql; . . PDRIVER_CANCEL CancelRoutine; PVOID UserBuffer; union { struct { . . union { KDEVICE_QUEUE_ENTRY DeviceQueueEntry; struct { PVOID DriverContext[4]; }; }; . . PETHREAD Thread; . . LIST_ENTRY ListEntry; . . } Overlay; . . } Tail; } IRP, *PIRP;
我們有了緩沖區,但是不知道緩沖區的大小.這個是否需要通過IO函數.從當前棧中獲取參數.
IoGetCurrentIrpStackLocation(pIrp)
返回當前IRP的棧.我們從返回值中獲取參數即可.
操作完成之后,我們完成IRP請求即可.這個IRP請求主要是為了多線程使用.有的時候可能在讀取的是否.線程就切換了.
ring0下的讀取回調完整編寫.
//獲取當前irp堆棧 PIO_STACK_LOCATION pIrpStack = NULL; PVOID lpBuff = NULL; ULONG Length = 0; //PsGetCurrentThreadId(); KdBreakPoint(); __try { pIrpStack = IoGetCurrentIrpStackLocation(pIrp); //check lpBuff = pIrp->AssociatedIrp.SystemBuffer; Length = pIrpStack->Parameters.Read.Length; KdPrint(("[FirstWDK] DispatchRead\n")); RtlStringCbCopyA(lpBuff, Length, "Hello World!"); //check pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 6; //完成Irp請求 IoCompleteRequest(pIrp, IO_NO_INCREMENT); //check } __except(1) { } return STATUS_SUCCESS;
課堂3環和0環的完整代碼資料:
鏈接:https://pan.baidu.com/s/1edffGy 密碼:vpo0
原創不易,轉載請注明出處.