《Windows核心安全與驅動開發》 7.1&7.2&7.3 串列埠的過濾
阿新 • • 發佈:2019-12-08
《Windows核心安全與驅動開發》閱讀筆記 -- 索引目錄
《Windows核心安全與驅動開發》 7.1&7.2&7.3 串列埠的過濾
一、裝置繫結的核心API
- 進行過濾的最主要的方法是對一個____進行繫結。
- 我們可以首先認為:一個真實的裝置對應一個_____。通過程式設計可以生成一個__的____,並繫結到一個___的裝置上。一旦繫結,則本來作業系統傳送給____的請求,就會發送到____上。
- 一個簡單的API繫結函式是_____,必須是有____的裝置,才能使用這個核心API進行繫結。
- 如果這個裝置被其他裝置綁定了,它們在一起組成一組裝置,被稱為____。實際上,IoAttachDevice總是會繫結___上最__層的那個裝置。
二、生成過濾裝置並繫結 與 從名字獲得裝置物件
- 在繫結一個裝置之前,首先要知道如何生成一個用於____的裝置,函式_____被用於生成過濾裝置。
- 在繫結一個裝置之前,應該把這個裝置物件的多個____和要繫結的目標物件一致,包括標誌和特徵。
- 在知道一個裝置名字的情況下,可以使用函式____來獲得這個裝置物件的指標。該函式的FileObject是一個返回引數,在獲得這個裝置物件的同時會獲得一個____,就開啟串列埠而言沒什麼用,但使用這個函式之後必須把這個物件____,否則會引起____。
三、繫結所有串列埠
- 串列埠x的裝置名為____。
四、請求的區分
- 每個驅動程式只有__個驅動物件。
- 每個驅動物件可以生成___個驅裝置物件。
- 若干個裝置(它們可以屬於__的驅動)依次繫結形成一個____,總是最__端的裝置先收到請求。
五、請求的結局
- 對請求的過濾,有三種結局:____、_____、_____。
- 處理第一種最簡單,首先呼叫函式____跳過當前棧空間;然後呼叫____把這個請求傳送給___的裝置。請注意,因為____的裝置已經被過濾裝置所繫結,因此首先接收到IRP的是過濾裝置的物件。
六、寫請求的資料
- 一個寫請求儲存在那裡呢?IRP有三種,一種是___,一種是___,一種是____。不同的__類別,IRP的緩衝不同。
- ____是最有效率的解決方案。應用層的緩衝區地址直接放在___裡,在核心層去訪問。在____和_____一致的情況下,是完全正確的。但一旦核心程序進行____,這個訪問就結束了。
- 一種更簡單的解決的方案是把__層的地址空間對映到__空間,這需要在__中增加一個對映。當然這不需要程式設計者手動去修改,通過構造__就能實現這個功能。其可以翻譯為____。
答案
一、裝置繫結的核心API
- 裝置物件
- 裝置物件 虛擬 裝置物件 真實 真實裝置 虛擬裝置
- IoAttchDevice 名稱
- 裝置棧 裝置棧 頂
二、生成過濾裝置並繫結 與 從名字獲得裝置物件
- 過濾 IoCreateDevice
- 子域
- IoGetDeviceObjectPointer 檔案物件 關閉 記憶體洩漏
三、繫結所有串列埠
- \Device\Serialx
四、請求的區分
- 一個
- 若干
- 裝置棧 頂
五、請求的結局
- 請求被允許通過了 請求直接被否決了 過濾完成了這個請求
- IoSkipCurrentIrpStackLocation IoCallDriver 真實
六、寫請求的資料
- irp->MDLAddress irp->UserBuffer irp->AssociatedIrp.SystemBuffer
- UserBuffer UserBuffer 當前程序 傳送請求程序 程序切換
- 應用層 核心層 頁表 MDL 記憶體描述符鏈
三個基本概念
一、過濾的概念
過濾的概念本質就是在一個驅動中對每個串列埠生成一個過濾裝置,將每個過濾裝置附加到對應的裝置棧中。
訊息自上而下發送,在到達串列埠前必須經過過濾裝置,這樣來對此進行過濾。
二、裝置棧的概念
拿一個函式來舉個例子
NTSTATUS IoAttachDeviceToDeviceStackSafe( IN PDEVICE_OBJECT SourceDevice, IN PDEVICE_OBJECT TargetDevice, OUT PDEVICE_OBJECT *AttachedToDeviceObject );
其中之所以會輸出 AttchedToDeviceObject,是因為當將過濾裝置SourceDevice解除安裝時需要這個引數。
三、裝置、驅動與IRP請求的概念
如圖,一個驅動物件可以只繫結一個分發函式來處理所有裝置的請求。
因為分發函式的第一個引數就是對應的裝置物件,我們可以通過該成員來進行區分是來自該驅動物件的哪個裝置的。
利用過濾裝置實現監控的程式碼
該程式碼實現了一個驅動過濾的例子。
一、實驗效果
使用超級終端,埠2來建立連線(windbg往往使用埠1,再測試時無法接收資訊,後來埠2則可以收到請求)
二、原始碼
/********* 作者:OneTrianee 編寫時間:2019/12/8 編譯環境:Win10+vs2019+"Empty WDM Driver" 程式碼作用:生成過濾裝置,繫結埠監視輸入資料 參考資料:《Windows核心安全與驅動開發》 注意事項: 1. 虛擬機器中windbg往往會佔用COM1,此時通訊應該使用COM2(如果沒有,關閉去虛擬機器設定中開一個。) 2. 開機時繫結埠2成功,但是解除安裝之後再繫結就只能繫結埠1,暫時不清楚原因(接觸繫結沒發現問題..) 效果(DebugView): 分發函式接收到請求了!! comcap: Send Data: 61 分發函式接收到請求了!! comcap: Send Data: 73 ***********/ #include <ntddk.h> #include <ntstrsafe.h> // sleep 時用到的巨集 #define DELAY_ONE_MICROSECOND (-10) #define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000) #define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000) #define CCP_MAX_COM_ID 32 // 假設有32個埠 // // 函式宣告 // NTSTATUS ccpDispatch(PDEVICE_OBJECT device, PIRP irp); // 裝置分發函式 void ccpUnload(PDRIVER_OBJECT drv); // 動態解除安裝驅動函式 void ccpAttachAllComs(PDRIVER_OBJECT driver); // 繫結所有串列埠函式 PDEVICE_OBJECT ccpOpenCom(ULONG id, NTSTATUS* status); // 開啟埠裝置物件 NTSTATUS ccpAttachDevice(PDRIVER_OBJECT driver, PDEVICE_OBJECT oldobj, PDEVICE_OBJECT* fltobj, PDEVICE_OBJECT* next ); // 將裝置與驅動物件進行繫結 // // 裝置棧: // // IRP訊息 ↓ // ---------- // |過濾裝置| // ---------- // |埠裝置| // ---------- // static PDEVICE_OBJECT s_fltobj[CCP_MAX_COM_ID] = { 0 }; // 過濾裝置 static PDEVICE_OBJECT s_nextobj[CCP_MAX_COM_ID] = { 0 }; // 埠裝置(繫結後會返回) /* 函式名:ccpDispatch 函式作用:分發函式,分發裝置的irp請求 引數1 - PDEVICE_OBJECT device:傳送請求的目標裝置 引數2 - PIRP irp: IRP請求內容 */ NTSTATUS ccpDispatch(PDEVICE_OBJECT device, PIRP irp) { PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp); NTSTATUS status; ULONG i, j; // // 判斷是傳送給哪個過濾裝置 // 如果裝置存在,則打印出裝置內容 // DbgPrint("分發函式接收到請求了!!\n"); for (i = 0; i < CCP_MAX_COM_ID; i++) { if (s_fltobj[i] == device) { // 所有電源操作,全部直接放過。 if (irpsp->MajorFunction == IRP_MJ_POWER) { // 直接傳送,然後返回說已經被處理了。 PoStartNextPowerIrp(irp); IoSkipCurrentIrpStackLocation(irp); return PoCallDriver(s_nextobj[i], irp); } // 我們只考慮寫請求,如果為寫請求,則打印出來 if (irpsp->MajorFunction == IRP_MJ_WRITE) { // 獲取長度 ULONG len = irpsp->Parameters.Write.Length; // // 獲取緩衝區(三中一個) // PUCHAR buf = NULL; if (irp->MdlAddress != NULL) buf = (PUCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority); else buf = (PUCHAR)irp->UserBuffer; if (buf == NULL) buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer; // 列印內容 for (j = 0; j < len; j++) { DbgPrint("comcap: Send Data: %2x\r\n", buf[j]); } } // 這些請求直接下發即可。我們並不禁止或改變它。 IoSkipCurrentIrpStackLocation(irp); return IoCallDriver(s_nextobj[i], irp); } } // 如果根本就不在被繫結的裝置中,那是有問題的,直接返回錯誤引數。 irp->IoStatus.Information = 0; irp->IoStatus.Status = STATUS_INVALID_PARAMETER; IoCompleteRequest(irp, IO_NO_INCREMENT); return STATUS_SUCCESS; } /* 函式名:ccpAttchAllComs 函式功能:生成過濾裝置, 引數1 - dirver:生成過濾裝置需要指明其在哪個驅動中。 */ void ccpAttachAllComs(PDRIVER_OBJECT driver) { ULONG i; PDEVICE_OBJECT com_ob; NTSTATUS status; for (i = 0; i < CCP_MAX_COM_ID; i++) { // 獲取埠裝置物件 com_ob = ccpOpenCom(i, &status); if (com_ob == NULL) // 獲取失敗,繼續獲取下一個 continue; else DbgPrint("繫結埠號%u成功!", i); // 在這裡繫結,並不管繫結是否成功 ccpAttachDevice(driver, com_ob, &s_fltobj[i], &s_nextobj[i]); } } /* 函式名:ccpOpenCom 函式功能:開啟驅動埠裝置 引數1 - ULONG id:開啟的埠號ID,函式內自動給轉換為裝置名,然後開啟 引數2 - NTSTATUS* status:操作狀態 返回值 - PDEVICE_OBJECT:如果開啟成功,返回埠裝置控制代碼指標 */ PDEVICE_OBJECT ccpOpenCom(ULONG id, NTSTATUS* status) { UNICODE_STRING name_str; static WCHAR name[32] = { 0 }; PFILE_OBJECT fileobj = NULL; PDEVICE_OBJECT devobj = NULL; // // 將 ID 轉換為相應的埠裝置名UNICODE_STRING // memset(name, 0, sizeof(WCHAR) * 32); RtlStringCchPrintfW( (NTSTRSAFE_PWSTR)name, 32, (NTSTRSAFE_PWSTR)L"\\Device\\Serial%d", id); RtlInitUnicodeString(&name_str, name); // // 開啟裝置物件 // 如果開啟成功,刪除檔案物件,防止記憶體洩漏 // *status = IoGetDeviceObjectPointer(&name_str, FILE_ALL_ACCESS, &fileobj, &devobj); if (*status == STATUS_SUCCESS) ObDereferenceObject(fileobj); // 返回埠裝置控制代碼 return devobj; } /* 函式名: 函式功能:生成過濾裝置,並繫結串列埠裝置,儲存過濾裝置fltobj和原棧頂裝置next 引數1 - PDRIVER_OBJECT driver:生成過濾裝置時所在的驅動物件 引數2 - PDEVICE_OBJECT oldobj:已生成的埠裝置 引數3 - PDEVICE_OBJECT* fltobj:生成的過濾裝置(儲存在陣列中) 引數4 - PDEVICE_OBJECT* next:原裝置棧頂的裝置 */ NTSTATUS ccpAttachDevice(PDRIVER_OBJECT driver, PDEVICE_OBJECT oldobj, PDEVICE_OBJECT* fltobj, PDEVICE_OBJECT* next ) { NTSTATUS status; PDEVICE_OBJECT topdev = NULL; // // 生成過濾裝置, // 注意生成裝置的屬性與被繫結裝置一致 // status = IoCreateDevice(driver, 0, NULL, oldobj->DeviceType, 0, FALSE, fltobj); if (status != STATUS_SUCCESS) return status; // // 拷貝重要的標誌位 // if (oldobj->Flags & DO_BUFFERED_IO) (*fltobj)->Flags |= DO_BUFFERED_IO; if (oldobj->Flags & DO_DIRECT_IO) (*fltobj)->Flags |= DO_DIRECT_IO; if (oldobj->Flags & DO_BUFFERED_IO) (*fltobj)->Flags |= DO_BUFFERED_IO; if (oldobj->Characteristics & FILE_DEVICE_SECURE_OPEN) (*fltobj)->Characteristics |= FILE_DEVICE_SECURE_OPEN; (*fltobj)->Flags |= DO_POWER_PAGABLE; // // 繫結過濾裝置到埠的裝置棧中 // topdev = IoAttachDeviceToDeviceStack(*fltobj, oldobj); if (topdev == NULL) { // 如果繫結失敗,則銷燬裝置,之後重新來過 IoDeleteDevice(*fltobj); *fltobj = NULL; status = STATUS_UNSUCCESSFUL; return status; } *next = topdev; // 儲存原來棧頂的裝置 // // 設定這個裝置已京啟動 // (*fltobj)->Flags = (*fltobj)->Flags & ~DO_DEVICE_INITIALIZING; return STATUS_SUCCESS; } /* 函式名:ccpUnload 函式作用:動態解除安裝驅動物件 引數1 - PDRIVER_OBJECT drv:需要解除安裝的驅動物件指標 */ void ccpUnload(PDRIVER_OBJECT drv) { ULONG i; LARGE_INTEGER interval; // 首先解除繫結 for (i = 0; i < CCP_MAX_COM_ID; i++) { if (s_nextobj[i] != NULL) /* |--------| |過濾裝置| |--------| |next裝置| // 將該層上面的過濾裝置給解除安裝掉 |--------| |xxxx裝置| |--------| |埠裝置| |--------| */ IoDetachDevice(s_nextobj[i]); } // 睡眠5秒,等待所有irp處理結束 interval.QuadPart = (5 * 1000 * DELAY_ONE_MILLISECOND); KeDelayExecutionThread(KernelMode, FALSE, &interval); // 刪除這些裝置 for (i = 0; i < CCP_MAX_COM_ID; i++) { if (s_fltobj[i] != NULL) { IoDeleteDevice(s_fltobj[i]); } } } NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path) { size_t i; DbgPrint("過濾裝置安裝成功!"); // 所有分發函式都設定成一樣的 for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { driver->MajorFunction[i] = ccpDispatch; } // 支援動態解除安裝 driver->DriverUnload = ccpUnload; // 繫結所有串列埠 ccpAttachAllComs(driver); // 直接返回成功即可 return STATUS_SUCCESS; }
&n