1. 程式人生 > >《Windows核心安全與驅動開發》 7.1&7.2&7.3 串列埠的過濾

《Windows核心安全與驅動開發》 7.1&7.2&7.3 串列埠的過濾

《Windows核心安全與驅動開發》閱讀筆記 -- 索引目錄

《Windows核心安全與驅動開發》 7.1&7.2&7.3 串列埠的過濾

一、裝置繫結的核心API

  1. 進行過濾的最主要的方法是對一個____進行繫結。
  2. 我們可以首先認為:一個真實的裝置對應一個_____。通過程式設計可以生成一個__的____,並繫結到一個___的裝置上。一旦繫結,則本來作業系統傳送給____的請求,就會發送到____上。
  3. 一個簡單的API繫結函式是_____,必須是有____的裝置,才能使用這個核心API進行繫結。
  4. 如果這個裝置被其他裝置綁定了,它們在一起組成一組裝置,被稱為____。實際上,IoAttachDevice總是會繫結___上最__層的那個裝置。

二、生成過濾裝置並繫結 與 從名字獲得裝置物件

  1. 在繫結一個裝置之前,首先要知道如何生成一個用於____的裝置,函式_____被用於生成過濾裝置。
  2. 在繫結一個裝置之前,應該把這個裝置物件的多個____和要繫結的目標物件一致,包括標誌和特徵。
  3. 在知道一個裝置名字的情況下,可以使用函式____來獲得這個裝置物件的指標。該函式的FileObject是一個返回引數,在獲得這個裝置物件的同時會獲得一個____,就開啟串列埠而言沒什麼用,但使用這個函式之後必須把這個物件____,否則會引起____。

三、繫結所有串列埠

  1. 串列埠x的裝置名為____。

四、請求的區分

  1. 每個驅動程式只有__個驅動物件。
  2. 每個驅動物件可以生成___個驅裝置物件。
  3. 若干個裝置(它們可以屬於__的驅動)依次繫結形成一個____,總是最__端的裝置先收到請求。

五、請求的結局

  1. 對請求的過濾,有三種結局:____、_____、_____。
  2. 處理第一種最簡單,首先呼叫函式____跳過當前棧空間;然後呼叫____把這個請求傳送給___的裝置。請注意,因為____的裝置已經被過濾裝置所繫結,因此首先接收到IRP的是過濾裝置的物件。

六、寫請求的資料

  1. 一個寫請求儲存在那裡呢?IRP有三種,一種是___,一種是___,一種是____。不同的__類別,IRP的緩衝不同。
  2. ____是最有效率的解決方案。應用層的緩衝區地址直接放在___裡,在核心層去訪問。在____和_____一致的情況下,是完全正確的。但一旦核心程序進行____,這個訪問就結束了。
  3. 一種更簡單的解決的方案是把__層的地址空間對映到__空間,這需要在__中增加一個對映。當然這不需要程式設計者手動去修改,通過構造__就能實現這個功能。其可以翻譯為____。

 答案

一、裝置繫結的核心API

  1. 裝置物件
  2. 裝置物件  虛擬 裝置物件  真實 真實裝置 虛擬裝置
  3. IoAttchDevice 名稱
  4. 裝置棧 裝置棧 頂

二、生成過濾裝置並繫結 與 從名字獲得裝置物件

  1. 過濾 IoCreateDevice
  2. 子域
  3. IoGetDeviceObjectPointer 檔案物件 關閉 記憶體洩漏

三、繫結所有串列埠

  1. \Device\Serialx

四、請求的區分

  1. 一個
  2. 若干
  3. 裝置棧 頂

五、請求的結局

  1. 請求被允許通過了 請求直接被否決了 過濾完成了這個請求
  2. IoSkipCurrentIrpStackLocation IoCallDriver 真實

六、寫請求的資料

  1. irp->MDLAddress irp->UserBuffer irp->AssociatedIrp.SystemBuffer
  2. UserBuffer UserBuffer 當前程序  傳送請求程序   程序切換
  3. 應用層 核心層 頁表 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