如果驅動程序要和應用程序通信,那么要生成一個設備對象.
設備對象和分發函數構成了整個內核體系的基本框架.
設備對象可以在內核中暴露出來給應用層,應用層可以像操作文件一樣操作內核驅動,所以稱之為控制設備對象.
生成設備可以使用
NTSTATUS
IoCreateDevice(
_In_ ?PDRIVER_OBJECT DriverObject,//驅動對象,可以直接從DriverEntry的參數中獲得
_In_ ?ULONG DeviceExtensionSize,//設備擴展的大小
_In_opt_ PUNICODE_STRING DeviceName,//設備名,可以為空
_In_ ?DEVICE_TYPE DeviceType,//設備類型
_In_ ?ULONG DeviceCharacteristics,//一組設備屬性
_In_ ?BOOLEAN Exclusive,//是否是獨占設備,一般都不是獨占,不過獨占有時有好處,比如只允許自己的進程打開
_Outptr_result_nullonfailure_
_At_(*DeviceObject,
__drv_allocatesMem(Mem)
_When_((((_In_function_class_(DRIVER_INITIALIZE))
||(_In_function_class_(DRIVER_DISPATCH)))),
__drv_aliasesMem))
PDEVICE_OBJECT *DeviceObject//返回結果,如果函數執行成功,這里就是設備對象的指針.
);
注意這個函數生成的設備具有默認安全屬性,結果是必須要有管理員權限的進程才能打開.如果要讓任何用戶都可以打開設備,可以用下面這個函數,但是作為商業軟件而言顯然不安全,這里只是為了使用方便.
NTSTATUS
WdmlibIoCreateDeviceSecure(
_In_ ? ? PDRIVER_OBJECT ? ? ?DriverObject,
_In_ ? ? ULONG ? ? ? ? ? ? ? DeviceExtensionSize,
_In_opt_ PUNICODE_STRING ? ? DeviceName,
_In_ ? ? DEVICE_TYPE ? ? ? ? DeviceType,
_In_ ? ? ULONG ? ? ? ? ? ? ? DeviceCharacteristics,
_In_ ? ? BOOLEAN ? ? ? ? ? ? Exclusive,
_In_ ? ? PCUNICODE_STRING ? ?DefaultSDDLString,//表示設備對象的安全設置
_In_opt_ LPCGUID ? ? ? ? ? ? DeviceClassGuid,//設備的GUID全球唯一標識
_Out_
_At_(*DeviceObject,
__drv_allocatesMem(Mem)
_When_((((_In_function_class_(DRIVER_INITIALIZE)) ||(_In_function_class_(DRIVER_DISPATCH)))),
__drv_aliasesMem)
_On_failure_(_Post_null_))
PDEVICE_OBJECT ? ? *DeviceObject
);
這個函數在 wdmsec.h ,參數和上面的一樣,只是多個兩個參數
//定義全局設備對象
PDEVICE_OBJECT gcdo = NULL;
//{06A16B65-7DA0-4A3F-9D9A-2679395D0D93}
//生成控制設備,生成符號連接 安全設置(任何用戶都可以打開的,就是完全不安全的意思) 設備名稱
UNICODE_STRING sddl = RTL_CONSTANT_STRING(L"D:P(A;;GA;;;WD)");
UNICODE_STRING cdoname = RTL_CONSTANT_STRING(L"\\Device\\misaka_201701032346");
//生成一個控制設備
status = WdmlibIoCreateDeviceSecure(driver,0,&cdoname,FILE_DEVICE_UNKNOWN,FILE_DEVICE_SECURE_OPEN,FALSE,&sddl,(LPCGUID)&SLBKGUID_CLASS_MYCDO,&gcdo);
if(!NT_SUCCESS(status)){
return status;
}
應用層無法通過設備名字來打開設備對象的,所以要建立一個暴露給應用層的符號連接
符號連接就是記錄一個字符串對應另一個字符串的簡單結構
NTSTATUS
IoCreateSymbolicLink(
_In_ PUNICODE_STRING SymbolicLinkName,//符號連接名稱
_In_ PUNICODE_STRING DeviceName//設備名稱
);
一般函數都會返回成功,如果符號名稱在系統存在(返回鏈接是windows全局存在的),則會返回失敗.
所以為了防止沖突,穩妥的方法是使用 GUID 方式來訪問設備.
//符號連接名稱常量
#define CWK_CDO_SYB_NAME L"\\??\\misaka_201701032346"
//符號名稱
UNICODE_STRING cdosyb = RTL_CONSTANT_STRING(CWK_CDO_SYB_NAME);
//刪除符號
IoDeleteSymbolicLink(&cdosyb);
//生成符號
status = IoCreateSymbolicLink(&cdosyb,&cdoname);
if(!NT_SUCCESS(status)){
//失敗,刪除設備對象
IoDeleteDevice(gcdo);
return status;
}
控制設備的刪除
在驅動卸載時應該刪除符號連接,否則符號連接會一直存在,應用程序可能會嘗試打開進行操作
操作方法是依次刪除符號連接和控制設備
ASSERT(gcdo !== NULL);//<--這個可以不用,意思是假設設備對象存在,如果不存在當然就返回(錯誤)了.
//刪除符號
IoDeleteSymbolicLink(&cdosyb);
IoDeleteDevice(gcdo);
分發函數
分發函數是一組用來處理發送給設備對象的請求的函數,由內核驅動開發者編寫.
分發函數是設置在驅動對象上的,每個驅動都有自己的分發函數
windows IO管理器收到請求時,根據請求的目標來調用這個設備對象從屬驅動對象對應的分發函數
不同分發函數處理不同的請求,也可以一個分發函數處理,自己區分
在這里先使用以下三種
打開 Create 訪問設備對象前,需要先打開成功才能發送其他請求
關閉 Close 結束后要關閉,關閉后需要再次打開才能訪問
設備控制 這個請求即可以輸入(應用程序->內核)也可以輸出(內核->應用程序)
標準分發函數如下:
NTSTATUS misakaDispatch(IN PDEVICE_OBJECT dev,IN PIRP irp)
dev:請求的目標對象 irp:請求數據結構指針
在 DriverEntry 中所有的分發函數都設置成一樣的,MajorFunction 是函數指針數組
for(i=0;i
driver->MajorFunction[i] = misakaDispatch;
}
請求的處理
先獲得棧空間,從棧空間指針中獲得主功能號(每中請求都有),主功能號說明是什么請求
打開請求的主功能號是 IRP_MJ_CREATE
關閉請求的主功能號是 IRP_MJ_CLOAS
設備控制的主功能號是 IRP_MJ_DEVICE_CONTROL
請求當前棧空間使用 IoGetCurrentIrpStackLocation 獲得
PIO_STACK_LOCATION
IoGetCurrentIrpStackLocation(
_In_ PIRP Irp
)
然后根據主功能號進行處理
NTSTATUS misakaDispatch(IN PDEVICE_OBJECT dev,IN PIRP irp){
//請求當前棧空間,獲得主功能號
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
//...
NTSTATUS status = STATUS_SUCCESS;
//緩存長度
ULONG retlen = 0;
//判斷請求是不是發給自己(因為一個驅動對象何以生成很多個設備對象?)
if(dev != gcdo){
//不處理
}else{
//判斷請求的類型
if(irpsp->MajorFunction == IRP_MJ_CREATE || irpsp->MajorFunction == IRP_MJ_CLOSE){
}
if(irpsp->MajorFunction == IRP_MJ_DEVICE_CONTROL){
//首先獲得功能號
//如果有輸入緩沖區,必須獲得輸入緩沖區的指針和長度
//如果有輸出緩沖區,必須獲得輸出緩沖區的指針和長度
//獲得緩沖區,只有設備控制設置緩存方式請求才對,這個緩沖區是輸入和輸出共享的
PVOID buffer = irp->AssociatedIrp.SystemBuffer;
//獲得輸入緩沖區的長度
ULONG inlen = irpsp->Parameters.DeviceIoContorl.InputBufferLength;
//獲得輸出緩沖區的長度
ULONG outlen = irpsp->Parameters.DeviceIoContorl.OutputBufferLength;
//處理
switch(irpsp->Parameters.DeviceIoContorl.IoControlCode){
case 800:
if(buffer != NULL and inlen > 0 and outlen == 0){
DbgPrint((char *)buffer);
}
case 801:
default:
//未知的請求,參數錯誤等等
status = STATUS_INVALID_PARAMETER;
}
}
}
}
返回,在分發函數中處理
irp->IoStatus.Information = 返回的長度,要小于或等于緩沖區
irp->IoStatus.Status = status;//完成狀態(一般情況)
IoCompleteRequest(irp,IO_NO_INCREMENT);//結束這個請求
return status;
應用程序的請求(實現隨時發送字符串給內核驅動,或從內核驅動將內核驅動緩沖的字符讀取出來)
打開設備 API CreateFile 文件的路徑是符號鏈接的路徑,在應用層中是以 \\.\ 開頭的,且要轉義
所以 #define CWK_DEV_SYM L"\\\\.\\misaka_201701032346"
HANDLE device = NULL;
//打開設備
device = CreateFile(CWK_DEV_SYM,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_SYSTEM,0);
if(device == INVALID_HANDLE_VALUE){
printf("打開驅動 failed\n");
return -1;
}else{
printf("打開驅動 successfully\n");
}
//關閉設備
CloseHandle(device);
//設備控制
#define CWK_DVC_SEND (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_WRITE_DATA)
CTL_CODE 是一個宏,SDK頭文件提供,參數有4個
1.設備類型:因為生成的控制設備和任何硬件都沒有關系,所以設置為 FILE_DEVICE_UNKNOWN(未知類型)
2.功能號核心數字,和其他參數"合成"功能號,0x0 - 0x7ff 已被微軟保留,所以要大于 0x7ff,且不能大于 0xfff,不同的功能號根據這個數字區分
3.METHOD_BUFFERED說明收用緩存方式,輸入和輸出緩存會在用戶和內核之間拷貝,比較簡單和安全的一種方式
4.需要的權限,因為要寫,設置為 FILE_WRITE_DATA
上面是發送功能號,下面定義接收功能號
#define CWK_DVC_RECV_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_READ_DATA)
//發送請求過程
char *msg = {"hello misaka driver ,my name is app, this is message from"};
if(!DeviceIoControl(device,CWK_DVC_RECV_STR,msg,strlen(msg)+1,NULL,0,&東東的,0)){
//這里寫失敗的輸出什么的
}
上面是我學習的結果,可能寫的比較亂,可以直接看代碼:
驅動的代碼:
#include
#include
//定義全局設備對象
PDEVICE_OBJECT gcdo = NULL;
//{06A16B65-7DA0-4A3F-9D9A-2679395D0D93}
static const GUID SLBKGUID_CLASS_MYCDO = { 0x06A16B65, 0x7DA0, 0x4A3F, { 0x9D, 0x9A, 0x26, 0x79, 0x39, 0x5D, 0x0D, 0x93 } };
//生成控制設備,生成符號連接 安全設置(任何用戶都可以打開的,就是完全不安全的意思) 設備名稱
UNICODE_STRING sddl = RTL_CONSTANT_STRING(L"D:P(A;;GA;;;WD)");
UNICODE_STRING cdoname = RTL_CONSTANT_STRING(L"\\Device\\misaka_201701032346");
//符號連接名稱常量
#define CWK_CDO_SYB_NAME L"\\??\\misaka_201701032346"
//符號名稱
UNICODE_STRING cdosyb = RTL_CONSTANT_STRING(CWK_CDO_SYB_NAME);
//聲明函數
NTSTATUS misakaDispatch(IN PDEVICE_OBJECT dev, IN PIRP irp);
//設備控制 發送和接收
#define CWK_DVC_SEND (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_WRITE_DATA)
#define CWK_DVC_RECV_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_READ_DATA)
VOID DriverUnload(PDRIVER_OBJECT driver){
//刪除符號和設備對象
IoDeleteSymbolicLink(&cdosyb);
IoDeleteDevice(gcdo);
DbgPrint("misaka: uninstall driver and delete symbol and device success\r\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path){
//..
NTSTATUS status;
if (gcdo == NULL){
//生成一個控制設備
status = WdmlibIoCreateDeviceSecure(driver, 0, &cdoname, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &sddl, (LPCGUID)&SLBKGUID_CLASS_MYCDO, &gcdo);
if (!NT_SUCCESS(status)){
return status;
}
//刪除符號
IoDeleteSymbolicLink(&cdosyb);
//生成符號
status = IoCreateSymbolicLink(&cdosyb, &cdoname);
if (!NT_SUCCESS(status)){
//失敗,刪除設備對象
IoDeleteDevice(gcdo);
return status;
}
}
driver->DriverUnload = DriverUnload;
//所有的分發函數都是同一個
ULONG i;
for (i = 0; i
driver->MajorFunction[i] = misakaDispatch;
}
DbgPrint("misaka: driver open success!!\r\n");
return STATUS_SUCCESS;
}
//然后根據主功能號進行處理
NTSTATUS misakaDispatch(IN PDEVICE_OBJECT dev, IN PIRP irp){
//請求當前棧空間(Irp),獲得主功能號
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
//...
NTSTATUS status = STATUS_SUCCESS;
//緩存長度
ULONG retlen = 0;
//判斷請求是不是發給自己(因為一個驅動對象何以生成很多個設備對象?)
if (dev != gcdo){
//不處理
DbgPrint("driver error 1\r\n");
}else{
//判斷請求的類型
if (irpsp->MajorFunction == IRP_MJ_CREATE){
DbgPrint("open driver\r\n");
status = STATUS_SUCCESS;
}
if (irpsp->MajorFunction == IRP_MJ_CLOSE){
DbgPrint("close driver\r\n");
status = STATUS_SUCCESS;
}
if (irpsp->MajorFunction == IRP_MJ_DEVICE_CONTROL){
DbgPrint("control number %d\r\n", irpsp->Parameters.DeviceIoControl.IoControlCode);
//首先獲得功能號
//如果有輸入緩沖區,必須獲得輸入緩沖區的指針和長度
//如果有輸出緩沖區,必須獲得輸出緩沖區的指針和長度
//獲得緩沖區,只有設備控制設置緩存方式請求才對,這個緩沖區是輸入和輸出共享的
PVOID buffer = irp->AssociatedIrp.SystemBuffer;
//獲得輸入緩沖區的長度
ULONG inlen = irpsp->Parameters.DeviceIoControl.InputBufferLength;
//獲得輸出緩沖區的長度
ULONG outlen = irpsp->Parameters.DeviceIoControl.OutputBufferLength;
//處理
switch (irpsp->Parameters.DeviceIoControl.IoControlCode){
case CWK_DVC_RECV_STR:
if (buffer != NULL && inlen > 0 && outlen == 0){
DbgPrint((char *)buffer);
status = STATUS_SUCCESS;
break;
}
case 800:
default:
//未知的請求,參數錯誤等等
status = STATUS_INVALID_PARAMETER;
}
}//endcontrol
}//endif
//返回, 在分發函數中處理
irp->IoStatus.Information = retlen;
irp->IoStatus.Status = status;//完成狀態(一般情況)
IoCompleteRequest(irp, IO_NO_INCREMENT);//結束這個請求
DbgPrint("cloase and status ? %d\r\n",status);
return status;
}
應用層的代碼:
#include
#include
//符號路徑
#define CWK_DEV_SYM L"\\\\.\\misaka_201701032346"
HANDLE device = NULL;
//設備控制 發送和接收
#define CWK_DVC_SEND (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_WRITE_DATA)
#define CWK_DVC_RECV_STR (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_READ_DATA)
int main(int argc, char* argv[]){
//打開設備
device = CreateFile(CWK_DEV_SYM, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0);
if (device == INVALID_HANDLE_VALUE){
printf("打開驅動 failed\r\n");
return -1;
}else{
printf("打開驅動 successfully\r\n");
//發送請求過程
char *msg = { "hello misaka driver ,my name is app, this is message from\r\n" };
//實際返回的字節數
DWORD dwOutput;
if (!DeviceIoControl(device, CWK_DVC_RECV_STR, msg, strlen(msg) + 1, NULL, 0, &dwOutput, 0)){
//這里寫失敗的輸出什么的
printf("發送請求失敗,%d %p %d %d\r\n", GetLastError(), device, CWK_DVC_RECV_STR, dwOutput);
}else{
printf("發送請求成功\r\n");
}
//關閉設備
CloseHandle(device);
}
return 0;
}
結果:
應用程序輸出
打開驅動 successfully
發送請求成功
驅動程序輸出
open driver
control number 2252804
hello misaka driver ,my name is app, this is message from <--重要
close driver
misaka: uninstall driver and delete symbol and device success
遇到了許多坑!最坑的是 switch 的 break !!!!!!! 我說為什么一直輸出 87 呢!
P.S. 有些輸出是測試用得,可以去掉!