5.應用程序和內核通信(有點長)

如果驅動程序要和應用程序通信,那么要生成一個設備對象.

設備對象和分發函數構成了整個內核體系的基本框架.

設備對象可以在內核中暴露出來給應用層,應用層可以像操作文件一樣操作內核驅動,所以稱之為控制設備對象.

生成設備可以使用

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. 有些輸出是測試用得,可以去掉!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容

  • 技術原理 何為符號鏈接?符號鏈接是一個別名,可以指向任意一個有名字的對象. ZwCreateFile 不但可以打開...
    f675b1a02698閱讀 621評論 0 0
  • 設備綁定的內核API之一 驅動 --> 生成多個 --> 設備對象 --> 對應 --> 真實的一個設備 wind...
    f675b1a02698閱讀 710評論 0 0
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,721評論 18 399
  • chgrp命令用來改變文件或目錄所屬的用戶組。 3) -h --no-dereference 只對符號連接的文...
    金星show閱讀 454評論 0 0