64位內開發第二十二講,分層過濾驅動編程詳解

64位內開發第二十二講,分層過濾驅動編程詳解

來自: iBinary - 博客園 禁止爬蟲.如果遇到此文章不是 出自 博客園 或者 騰訊雲+社區. 請舉報目標網站. 或者跳轉至 本人博客園進行查看. 因爲文章隨時更改.可能今天只是寫了一部分.或者有錯誤. 而明天就進行更改重發了. 但是爬蟲爬取的文章還是之前錯誤的文章.會爲讀者造成文章有錯誤的假象.

一丶分層驅動

1.1 分層驅動的概念

之前學過第二十一講 驅動調用驅動. 瞭解了其流程,那麼理解分層驅動也不算困難.

分層驅動可以理解爲就是一個HOOK類型的驅動. 假設有 A C兩個驅動. 正常情況下 A驅動可以發送IRP給C驅動.然後C驅動完成數據之後交由 A驅動進行處理. 而分層驅動則是自己寫一個B驅動.將B驅動掛載到C驅動上面. 這樣形成的調用鏈則是 A B C 而B驅動可以選擇是否下發IRP給C亦或者只是收集一些數據.

1.2 分層驅動的實際應用場景

分層驅動是程序員編寫的最多的一種類型的驅動. 可應用於如下

1.模塊化驅動程序. A B C 三個驅動可以獨立

2.限制讀寫請求. C驅動沒有限制讀寫大小.則添加的B過濾驅動可以限制讀寫請求

3.監視設備操作情況. 比如可以 監視用戶的按鍵.(做一個密碼盜取的驅動..) 比如可以監控網絡.比如 TDI防火牆.(雖已過時)

二丶分層驅動的概念

2.1 概念簡介

既然說過 我們可以寫一個B驅動附加到C驅動之上.那麼自然我們就要明白要如何進行附加.

原理是啥. 所以第一步就是要對設備對象進行了解. 整個主題的核心就是圍繞着設備對象如何進行掛載以及處理的. 而掛載指的就是將高一層的設備對象掛載到第一層的設備對象上,從而形成一個設備棧.

所以在這裏棧的概念也很重要. 相當於我生成了一個DriverB 然後掛載到C上面. 此時我的DriverB也有一層棧. 而DriverC 也有一層堆棧. 如何進行掛載之前的通信傳輸.其實就是如何應用每層的堆棧.

2.2 瞭解設備對象

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;          //當前的Irp指針  
    PIO_TIMER Timer;                  //計時器指針
    ULONG Flags;                      //標記參數 關乎設備的讀取方式
    ULONG Characteristics;            //設備對象的特性
    __volatile PVPB Vpb;
    PVOID DeviceExtension;            //設備擴展
    DEVICE_TYPE DeviceType;           //設備類型
    CCHAR StackSize;                  //核心--> Io堆棧大小
    union {
        LIST_ENTRY ListEntry;         //Irp的鏈表
        WAIT_CONTEXT_BLOCK Wcb;
    } Queue;
    ULONG AlignmentRequirement;       //對齊的屬性
    KDEVICE_QUEUE DeviceQueue;        //設備對象
    KDPC Dpc;                         //DPC延遲過程調用
    ULONG ActiveThreadCount;          //當前線程的額數量
    PSECURITY_DESCRIPTOR SecurityDescriptor;//安全描述符表
    KEVENT DeviceLock;                //設備鎖

    USHORT SectorSize;                        
    USHORT Spare1;

    struct _DEVOBJ_EXTENSION  *DeviceObjectExtension; //請注意這是設備對象擴展不是設備擴展
    PVOID  Reserved;

} DEVICE_OBJECT;

觀看其結構體 其實重要的成員有很多.但是前面都已經學過了. 比如 設備的讀寫標誌 設備擴展

等等.

而學習過濾驅動那麼我們就要重視裏面的三個子域的成員.

    struct _DRIVER_OBJECT *DriverObject; //核心--> 記錄着驅動對象
    struct _DEVICE_OBJECT *NextDevice;   //核心--> 記錄着下一個設備對象
    struct _DEVICE_OBJECT *AttachedDevice;//核心--> 記錄着附加的設備對象

DriverObject ---> 記錄着的就是這個設備驅動對象

NextDevice ---> 記錄着就是下一個設備對象. 比如我們Attach上了.那麼記錄的就是Attach的設備對象.

AttachedDevice --> 記錄着的附加的設備對象. 這個有點難以理解. 其實就是 原有A C兩個驅動.你有一個B驅動.然後B驅動掛載到C驅動上.調用鏈就是 A B C 而這個成員就是記錄了""自己" 是被那個設備對象所掛載的. 比如調用鏈如果爲 A ->B ->C. 那麼在C驅動裏面這個成員記錄着的就是B. 如果在B驅動裏面這個成員記錄的就是A的設備對象. 那麼就剩下了A了.A因爲沒有人掛載它(HOOK它)所以這個域記錄的就是NULL.

簡單理解: NextDevice 就是記錄了我掛載了誰.如果我是B 我掛載了C.那麼我就在C上面. 那麼此成員記錄的就是C的設備對象.

AttachedDevice 就是記錄了 我被誰掛載了. 如果我是C我被B掛載了. B在我之上.那麼我的域記錄的就是B.

2.3 設備堆棧的掛載

進行設備掛載有如下幾個API.

NTSTATUS IoAttachDevice(
  [in]  PDEVICE_OBJECT  SourceDevice,
  [in]  PUNICODE_STRING TargetDevice,
  [out] PDEVICE_OBJECT  *AttachedDevice
);
PDEVICE_OBJECT IoAttachDeviceToDeviceStack(
  [in] PDEVICE_OBJECT SourceDevice,
  [in] PDEVICE_OBJECT TargetDevice
);
NTSTATUS IoAttachDeviceToDeviceStackSafe(
  [in]  PDEVICE_OBJECT SourceDevice,
  [in]  PDEVICE_OBJECT TargetDevice,
  [out] PDEVICE_OBJECT *AttachedToDeviceObject
);
void IoDetachDevice(
  [in, out] PDEVICE_OBJECT TargetDevice
);

其實就分爲兩類 一類是掛載 另一類則是取消掛載. 而掛載是提供給我們了三個API進行使用的.在高版本上面一般都會使用 IoAttachDeviceToDeviceStackSafe 在低版本上面其它兩個也很常用. 主要功能就是提供一個 源設備對象 和一個目標設備對象名字 然後來返回一個附加的設備 三個函數中功能都類似.唯一不同就是有的通過返回值返回Attach的設備.有的通過設備名來進行綁定.而有的是通過提供一個目標設備對象來進行綁定的.

拿第二個函數舉例:

IoAttachDeviceToDeviceStack

參數1 SourceDevice: 源設備對象 可以理解爲就是自己生成的 B設備對象

參數2 TargetDevice: 這個是你想要掛載的設備對象 可以理解爲你想要將B掛載到那個設備對象上. 比如這是C. 那麼SourceDevice 就會掛載到 C上. 以後處理Irp就可以使用 SourceDevice來進行過濾了.

返回值: 返回 TargetDevice下面的第一層的設備 如果掛載的是C.那麼返回的就是C的設備對象.

2.4 尋找目標設備對象進行掛載

尋找目標對象,我們在 驅動調用驅動一節中已經講過. 可以使用API

NTSTATUS
IoGetDeviceObjectPointer(
    _In_  PUNICODE_STRING ObjectName,
    _In_  ACCESS_MASK DesiredAccess,  //FILE_ALL_ACCESS
    _Out_ PFILE_OBJECT *FileObject,
    _Out_ PDEVICE_OBJECT *DeviceObject
    );

此API 就是通過設備名稱 來找到其對應的 文件對象以及它的設備對象. 請注意不使用的話文件對象要進行引用計數-1

在驅動調用驅動一節中也說過. 如果找不到設備名字 那麼可以通過打開符號鏈接查詢符號鏈接. 查詢的時候自然就會返回設備名字給我們.

2.5 堆棧IRP的轉發處理.

之前說過,如果我們成功掛載到一個目標設備上之後.那麼我們會有一層堆棧. 如果我們不想處理那麼就要交給下一層(之前被掛載的)設備進行處理.

那麼有幾種情況

1.本層不想處理交給下一層進行處理.

使用API IoSkipCurrentIrpStackLocation(pIrp); 進行向下轉發. 然後調用API IoCallDriver(下一層的設備對象) 進行處理即可.

2.本層想直接進行處理

如果本層想直接進行處理. 那麼跟之前寫驅動的操作一樣. 直接使用 API. IoCompleteRequest函數即可. 然後不需要調用 IoCallDriver 也不需要跳過本層堆棧.

3.IoSkipCurrentIrpStackLocation的原理

在進行IRP操作的時候,每一個設備堆棧都對應着一個IO堆棧元素. 也就是 IO_STACK_LOCATION結構 IRP的內部有個指針會指向當前正在使用的 IO堆棧.

當每次調用 IoCallDriver額時候,內部的內核函數都會講IRP的當前指針往下移動,也就是指向了下一個的IO_STACK_LOCATION 指針.

但是這樣就會產生一個問題.如果當前的設備堆棧 不對IRP處理.那麼自然內核函數也不能讓其IO堆棧指針下移. 但是調用了 IoCallDriver之後就已經移動了. 那麼操作系統就提供了 IoSkipCurrentIrpStackLocation 函數.它的作用就是將已將下移的堆棧指針,移動回來.也就是向上移動.恢復到原來的位置.

因此使用 IoCallDriver IoSkipCurrentIrpStackLocation 就對設備堆棧的移動實現了平衡.也就是沒有改變. 此時如果在調用IoCallDriver時候其實應用的設備堆棧和上一層的設備堆棧是同一個.

4.IoCopyCurrentIrpStackLocationToNext 拷貝轉發

另一種情況過濾驅動的 IRP派遣函數中對這個IRP進行了操作.那麼我們就必須拷貝一層堆棧交給下層處理. 否則你操作的都是無效的. 比如你對IRP堆棧進行了操作. 此時你並不是拷貝並且往下轉發.而是使用的IoSkipCurrentIrpStackLocation 那麼自然下一層驅動使用的設備堆棧就和你本層驅動使用的設備堆棧是一樣的. 如果你想讓下層設備堆棧使用你修改過的那麼就必須使用IoCopyCurrentIrpStackLocationToNext 拷貝一份並且轉發.

舉個例子:

WriteFile發送IRP_MJ_WRITEDriverB. 正常情況下寫入的數據爲 Love You 而此時過濾設備會先攔截到這塊數據.在此數據裏面加入了一個 I. 那麼就是 I Love You 如果你使用拷貝下發,那麼 DriverB則會打印 I Love you 如果你使用 Skip轉發.那麼你加入的I則是無意義的.因爲 DriverB使用的堆棧並不是你修改過的. 那麼還是會打印 Love you.

下面是它的實現

VOID
IoCopyCurrentIrpStackLocationToNext(
    _Inout_ PIRP Irp
)
{
    PIO_STACK_LOCATION irpSp;
    PIO_STACK_LOCATION nextIrpSp;
    irpSp = IoGetCurrentIrpStackLocation(Irp); //獲取本層IRP
    nextIrpSp = IoGetNextIrpStackLocation(Irp);//獲取下一層IRP
    //進行內存拷貝
    RtlCopyMemory( nextIrpSp, irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine));
    nextIrpSp->Control = 0;
}

三丶代碼實戰

3.1 提供環境 Driver B代碼

首先我們要提供一個 DriverB驅動. 此驅動是被掛載的驅動. 也就是Target驅動. 也可以說成是我們將要進行HOOK的驅動.

此驅動就是和正常驅動無意義.提供並實現了 IRP_MJ_READ IRP_MJ_WRITE

其派遣函數如下:

NTSTATUS FilterWrite(IN PDEVICE_OBJECT driver, IN PIRP pIrp)
{
    KdPrint(("[DriverA] Enter FilterWrite\r\n"));
    PVOID buffer = pIrp->AssociatedIrp.SystemBuffer;
    if (buffer)
    {
        KdPrint(("[DriverA]--->Value = %ls \r\n", (wchar_t*)buffer));
    }
    pIrp->IoStatus.Information = 0;
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}
NTSTATUS FilterRead(IN PDEVICE_OBJECT driver,
    IN PIRP pIrp)
{
    KdBreakPoint();
    KdPrint(("[DriverA] Enter FilterRead\r\n"));
    NTSTATUS ntStatus = STATUS_SUCCESS;

    PVOID buffer = pIrp->AssociatedIrp.SystemBuffer;
    if (buffer != nullptr)
    {
        RtlStringCbCopyNW(
            (wchar_t*)buffer,
            0x100,
            L"DriverA Read",
            wcslen(L"DriverA Read") * 2);
        pIrp->IoStatus.Information = wcslen(L"DriverA Read") * 2;
        pIrp->IoStatus.Status = STATUS_SUCCESS;
        IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    }
    else
    {
        pIrp->IoStatus.Information = 0;
        pIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;
        IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    }
    KdPrint(("[DriverA] FilterRead End\r\n"));
    return ntStatus;
}

Write就是將數據進行輸出. Read就是給傳出一個 DriverA Read的字符串

3.2 開始實戰->尋找目標設備

尋找目標設備驅動很簡單.也就是API的使用. 函數如下:

PDEVICE_OBJECT GetAttachDeviceByName(
    IN wchar_t* attach_device_name,
    OUT PFILE_OBJECT* fileobj)
{
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    UNICODE_STRING ucd_attach_device_name = { 0 };
    PDEVICE_OBJECT target_next_device = nullptr;
    if (attach_device_name == nullptr)
        return nullptr;

    status = RtlUnicodeStringInit(&ucd_attach_device_name, attach_device_name);
    if (NT_ERROR(status))
        return nullptr;

    status = IoGetDeviceObjectPointer(&ucd_attach_device_name,
        FILE_ALL_ACCESS,
        fileobj,
        &target_next_device);
    if (NT_ERROR(status))
    {
        KdPrint(("[Filter]--->IoGetDeviceObjectPointer Error\r\n"));
        return nullptr;
    }

    return target_next_device;
}

使用

target_device = GetAttachDeviceByName(L"\\Device\\DriverB", &target_fileobj);

這樣就找到了我們想要 Hook(掛載)的目標設備驅動了.

3.3 開始實戰->創建過濾設備

我們要進行過濾,那麼首先就要創建一個自己的設備對象,並且附加到目標設備對象上面. 創建設備很簡單. 學習驅動的一開始就已經學過了.

PDEVICE_OBJECT CreateFilterDevice(PDRIVER_OBJECT driver)
{
    UNICODE_STRING ucd_filter_device_name = { 0 };
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PDEVICE_OBJECT filter_device = nullptr;
    if (driver == nullptr)
        return nullptr;
    //1.初始化設備創建之前的必要信息
    status = RtlUnicodeStringInit(&ucd_filter_device_name, L"\\Device\\Filter");
    if (NT_ERROR(status))
        return nullptr;
    //2.創建設備
    status = IoCreateDevice(
        driver, 
        sizeof(DEVICE_SAVE_INFOMATION), 
        &ucd_filter_device_name,
        FILE_DEVICE_UNKNOWN, 
        0,
        FALSE,
        &filter_device);
    if (NT_ERROR(status))
        return nullptr;
    //3.設置設備通信方式
    filter_device->Flags |= DO_BUFFERED_IO;

    return filter_device;
}

注意,這裏使用了一個設備擴展.用於記錄我們綁定過後的設備對象是哪一個. 在IRP派遣函數中我們不想處理的數據可以通過記錄在設備擴展中的目標設備對象,轉發IRP進行執行.

在驅動卸載的時候也要進行解除附加.

其結構如下:

typedef struct _DEVICE_SAVE_INFOMATION
{
    PDEVICE_OBJECT src_device;    //本層的過濾設備對象
    PFILE_OBJECT   target_next_fileobj;//目標的文件指針對象
    PDEVICE_OBJECT target_next_device; //目標的設備對象
    PDEVICE_OBJECT attach_to_device;   //掛載的設備對象  <==> 目標的設備對象 他倆等價
}DEVICE_SAVE_INFOMATION,*PDEVICE_SAVE_INFOMATION;

3.4 開始實戰->掛載設備

掛載就是之前所說的API

PDEVICE_OBJECT FilterAttach(PDEVICE_OBJECT src_device,PDEVICE_OBJECT target_device)
{
    PDEVICE_OBJECT attach_to_device = nullptr;
    NTSTATUS status = STATUS_UNSUCCESSFUL;

    status = IoAttachDeviceToDeviceStackSafe(src_device, target_device, &attach_to_device);
    if (NT_ERROR(status))
        return nullptr;
    return attach_to_device;
}

掛載之後我們就要進行記錄

//5.記錄一下信息
    PDEVICE_SAVE_INFOMATION device_save_info_ptr =
        (PDEVICE_SAVE_INFOMATION)src_device->DeviceExtension;
    device_save_info_ptr->src_device = src_device;              //記錄我們的設備
    device_save_info_ptr->target_next_device = target_device;   //記錄我們要掛載的目標設備
    device_save_info_ptr->target_next_fileobj = target_fileobj; //記錄文件對象
    device_save_info_ptr->attach_to_device = attach_to_device;  //記錄下一層設備

3.5 開始實戰->處理卸載

衆所周知,驅動不卸載沒問題.最難的就是卸載了. 關於過濾驅動的卸載我們就要注意如下

1.在刪除設備之前先Detach掉附加的設備

2.注意再打開目標設備對象的時候返回的文件對象,要對其進行引用計數-1

3.注意資源泄露問題. 遍歷設備對象鏈表逐個刪除.

其實如果我們在設備擴展中記錄過信息.那麼刪除的時候注意上面說的幾點即可.

VOID FilterUnload(IN PDRIVER_OBJECT pDriverObject)
{
    //跟以往卸載不通.過濾驅動卸載的時候 需要解除掛載.然後刪除該設備對象
    //循環卸載
    //IoEnumerateDeviceObjectList()
    KdPrint(("[Filter]-->DriverUnload \r\n"));
    PDEVICE_OBJECT next_device = nullptr;

    if (pDriverObject->DeviceObject == nullptr)
    {    
        KdPrint(("[Filter]--> Previous Driver Unload \r\n"));
        return;
    }

    next_device = pDriverObject->DeviceObject;
    while (next_device != nullptr)
    {
        PDEVICE_SAVE_INFOMATION device_save_info =
            (PDEVICE_SAVE_INFOMATION)next_device->DeviceExtension;
        if (device_save_info == nullptr)
        {
            IoDeleteDevice(next_device);
            break;
        }

        //得到記錄的下一個設備.
        if (device_save_info->attach_to_device != nullptr)
        {
            //解除附加
            IoDetachDevice(device_save_info->attach_to_device);
            device_save_info->attach_to_device = nullptr;
        }
        if (device_save_info->target_next_fileobj != nullptr)
        {
            //解除引用
            ObDereferenceObject(device_save_info->target_next_fileobj);
            device_save_info->target_next_fileobj = nullptr;
            device_save_info->target_next_device = nullptr;
        }
        //刪除設備
        IoDeleteDevice(next_device);
        device_save_info->src_device = nullptr;
        next_device = next_device->NextDevice;
    }
    KdPrint(("[Filter]--> Perfect Driver Unload \r\n"));
}

3.6.過濾驅動完成代碼

#include "CMain.h"


PDEVICE_OBJECT GetAttachDeviceByName(
    IN wchar_t* attach_device_name,
    OUT PFILE_OBJECT* fileobj)
{
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    UNICODE_STRING ucd_attach_device_name = { 0 };
    PDEVICE_OBJECT target_next_device = nullptr;
    if (attach_device_name == nullptr)
        return nullptr;

    status = RtlUnicodeStringInit(&ucd_attach_device_name, attach_device_name);
    if (NT_ERROR(status))
        return nullptr;

    status = IoGetDeviceObjectPointer(&ucd_attach_device_name,
        FILE_ALL_ACCESS,
        fileobj,
        &target_next_device);
    if (NT_ERROR(status))
    {
        KdPrint(("[Filter]--->IoGetDeviceObjectPointer Error\r\n"));
        return nullptr;
    }

    return target_next_device;
}

VOID FilterUnload(IN PDRIVER_OBJECT pDriverObject)
{
    //跟以往卸載不通.過濾驅動卸載的時候 需要解除掛載.然後刪除該設備對象
    //循環卸載
    //IoEnumerateDeviceObjectList()
    KdPrint(("[Filter]-->DriverUnload \r\n"));
    PDEVICE_OBJECT next_device = nullptr;

    if (pDriverObject->DeviceObject == nullptr)
    {    
        KdPrint(("[Filter]--> Previous Driver Unload \r\n"));
        return;
    }

    next_device = pDriverObject->DeviceObject;
    while (next_device != nullptr)
    {
        PDEVICE_SAVE_INFOMATION device_save_info =
            (PDEVICE_SAVE_INFOMATION)next_device->DeviceExtension;
        if (device_save_info == nullptr)
        {
            IoDeleteDevice(next_device);
            break;
        }

        //得到記錄的下一個設備.
        if (device_save_info->attach_to_device != nullptr)
        {
            //解除附加
            IoDetachDevice(device_save_info->attach_to_device);
            device_save_info->attach_to_device = nullptr;
        }
        if (device_save_info->target_next_fileobj != nullptr)
        {
            //解除引用
            ObDereferenceObject(device_save_info->target_next_fileobj);
            device_save_info->target_next_fileobj = nullptr;
            device_save_info->target_next_device = nullptr;
        }
        //刪除設備
        IoDeleteDevice(next_device);
        device_save_info->src_device = nullptr;
        next_device = next_device->NextDevice;
    }
    KdPrint(("[Filter]--> Perfect Driver Unload \r\n"));
}

PDEVICE_OBJECT CreateFilterDevice(PDRIVER_OBJECT driver)
{
    UNICODE_STRING ucd_filter_device_name = { 0 };
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    PDEVICE_OBJECT filter_device = nullptr;
    if (driver == nullptr)
        return nullptr;
    //1.初始化設備創建之前的必要信息
    status = RtlUnicodeStringInit(&ucd_filter_device_name, L"\\Device\\Filter");
    if (NT_ERROR(status))
        return nullptr;
    //2.創建設備
    status = IoCreateDevice(
        driver, 
        sizeof(DEVICE_SAVE_INFOMATION), 
        &ucd_filter_device_name,
        FILE_DEVICE_UNKNOWN, 
        0,
        FALSE,
        &filter_device);
    if (NT_ERROR(status))
        return nullptr;
    //3.設置設備通信方式
    filter_device->Flags |= DO_BUFFERED_IO;

    return filter_device;
}
PDEVICE_OBJECT FilterAttach(PDEVICE_OBJECT src_device,PDEVICE_OBJECT target_device)
{
    PDEVICE_OBJECT attach_to_device = nullptr;
    NTSTATUS status = STATUS_UNSUCCESSFUL;

    status = IoAttachDeviceToDeviceStackSafe(src_device, target_device, &attach_to_device);
    if (NT_ERROR(status))
        return nullptr;

    return attach_to_device;
}

NTSTATUS FilterComplete(IN PDEVICE_OBJECT driver,
    IN PIRP pIrp)
{
    KdPrint(("[Filter] Enter FilterComplete\r\n"));
    NTSTATUS ntStatus = STATUS_SUCCESS;
    //將自己完成IRP,改成由底層驅動負責
    PDEVICE_SAVE_INFOMATION pdx = (PDEVICE_SAVE_INFOMATION)driver->DeviceExtension;
    //調用底層驅動
    IoSkipCurrentIrpStackLocation(pIrp);

    ntStatus = IoCallDriver(pdx->attach_to_device, pIrp);

    KdPrint(("[Filter]  FilterComplete End\r\n"));

    return ntStatus;
}

NTSTATUS FilterRead(IN PDEVICE_OBJECT driver,
    IN PIRP pIrp)
{
    KdBreakPoint();
    KdPrint(("[Filter] Enter FilterRead\r\n"));
    NTSTATUS ntStatus = STATUS_SUCCESS;
    KdPrint(("[Filter] Enter Hook\r\n"));
    PVOID buffer = nullptr;
    ULONG bufsize = 0;
    buffer = pIrp->AssociatedIrp.SystemBuffer;
    if (buffer)
    {
        bufsize = wcslen(L"Hook\rn") * 2;
        RtlStringCbCopyNW((wchar_t*)buffer, 0x100, L"Hook", bufsize);
    }

    pIrp->IoStatus.Information = bufsize;
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    KdPrint(("[Filter]  FilterRead End\r\n"));
    return ntStatus;
}

NTSTATUS FilterWrite(IN PDEVICE_OBJECT driver,
    IN PIRP pIrp)
{
    KdBreakPoint();
    KdPrint(("[Filter] Enter FilterWrite\r\n"));
    NTSTATUS ntStatus = STATUS_SUCCESS;
    KdPrint(("[Filter] Enter Hook\r\n"));
    PVOID buffer = nullptr;
    ULONG bufsize = 0;
    buffer = pIrp->AssociatedIrp.SystemBuffer;
    wchar_t old_buf[0x100] = { 0 };
    wcscpy(old_buf, (wchar_t*)buffer);
    if (buffer)
    {
        memset(buffer, 0, 0x100);
        RtlStringCbCopyNW((wchar_t*)buffer, 0x100, L"I ", wcslen(L"I ") * 2);
        RtlStringCbCatW((WCHAR*)buffer, 0X100, old_buf);
    }


    //將自己完成IRP,改成由底層驅動負責
    PDEVICE_SAVE_INFOMATION pdx = (PDEVICE_SAVE_INFOMATION)driver->DeviceExtension;
    //調用底層驅動
    //IoSkipCurrentIrpStackLocation(pIrp);
    IoCopyCurrentIrpStackLocationToNext(pIrp);
    ntStatus = IoCallDriver(pdx->attach_to_device, pIrp);
    KdPrint(("[Filter]  FilterWrite End\r\n"));
    return ntStatus;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING regpath)
{
    //1.設置驅動的卸載以及派遣函數
    driver->DriverUnload = FilterUnload;
    for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
    {
        driver->MajorFunction[i] = FilterComplete;

    }
    driver->MajorFunction[IRP_MJ_READ] = FilterRead;
    driver->MajorFunction[IRP_MJ_WRITE] = FilterWrite;
    //2.尋找目標設備.也就是我們想要Attah的設備
    PDEVICE_OBJECT target_device = nullptr;
    PFILE_OBJECT   target_fileobj = nullptr;
    PDEVICE_OBJECT src_device = nullptr;
    PDEVICE_OBJECT attach_to_device = nullptr;
    KdBreakPoint();
    target_device = GetAttachDeviceByName(L"\\Device\\DriverB", &target_fileobj);
    if (target_device == nullptr)
    {
        KdPrint(("[Filter]--> GetAttachDeviceByName Fail \r\n"));
        return STATUS_UNSUCCESSFUL;
    }
    KdPrint(("[Filter]--> Mount Target Device = [%p] \r\n", target_device));
    //3.創建設備,以及設置設備擴展
    src_device = CreateFilterDevice(driver);
    if (src_device == nullptr)
    {

        KdPrint(("[Filter]--> CreateFilterDevice Fail \r\n"));
        if (target_fileobj != nullptr)
        {
            ObReferenceObject(target_fileobj);
            target_fileobj = nullptr;
        }
        return STATUS_UNSUCCESSFUL;
    }
    KdPrint(("[Filter]--> Filter Device = [%p] \r\n", src_device));

    //4.Attach到目標設備
    attach_to_device = FilterAttach(src_device, target_device);
    if (attach_to_device == nullptr)
    {
        KdPrint(("[Filter]--> FilterAttach Fail \r\n"));
        if (target_fileobj != nullptr)
        {
            ObReferenceObject(target_fileobj);
            target_fileobj = nullptr;
        }
        return STATUS_UNSUCCESSFUL;
    }
    KdPrint(("[Filter]--> Attach Device = [%p] \r\n", attach_to_device));
    //5.記錄一下信息
    PDEVICE_SAVE_INFOMATION device_save_info_ptr =
        (PDEVICE_SAVE_INFOMATION)src_device->DeviceExtension;
    device_save_info_ptr->src_device = src_device;              //記錄我們的設備
    device_save_info_ptr->target_next_device = target_device;   //記錄我們要掛載的目標設備
    device_save_info_ptr->target_next_fileobj = target_fileobj; //記錄文件對象
    device_save_info_ptr->attach_to_device = attach_to_device;  //記錄下一層設備
    return STATUS_SUCCESS;
}

3.7 應用層代碼

#include <windows.h>
#include <stdio.h>
#include <iostream>
using namespace std;
int main()
{

    HANDLE hDevice =
        CreateFileW(L"\\\\.\\DriverB",
            GENERIC_READ | GENERIC_WRITE,
            0,        
            NULL,    
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            NULL);        

    if (hDevice == INVALID_HANDLE_VALUE)
    {
        printf("Failed to obtain file handle to device "
            "with Win32 error code: %d\n",
            GetLastError());
        system("pause");
        return 1;
    }

    wchar_t wzBuffer[0x100]{ 0 };

    DWORD dRet;
    ReadFile(
        hDevice,
        wzBuffer,
        sizeof(wzBuffer)/sizeof(wzBuffer[0]),
        &dRet, NULL);

    wcout << "ReadBuffer = " << wzBuffer << endl;
    system("pause");

    memset(wzBuffer, 0, sizeof(wzBuffer) / sizeof(wzBuffer[0]));
    wcscpy_s(wzBuffer, 0x100, L"Love You");
    WriteFile(hDevice, wzBuffer, sizeof(wzBuffer) / sizeof(wzBuffer[0]), &dRet, NULL);
    CloseHandle(hDevice);
    system("pause");
    return 0;
}

應用層代碼很簡單,就是 讀一下 寫一下.

內核層用了三種方式轉發IRP .直接完成 Skip copy方式. 便於學習.

其實現效果如下:

讀取的時候,我們進行了過濾,所以不會輸出 Driver Read 這個字符串.而是輸出了 Hook 這個字符串.

四丶完成例程

4.1 概念

在將 IRP轉發給其它驅動之前的時候.可以設置一個回調函數(參考驅動調用驅動一節 IRP同步異步處理).一旦底層驅動執行完畢.那麼完成例程設置的回調函數則會被調用. 通過設置完成例程,那麼就很方便程序員寫代碼.

IoCallDriver將Irp交給目標驅動的時候,有兩種情況,第一種同步處理.那麼代表的就是IoCallDriver返回的時候就代表Irp執行完成了.另一種是異步處理,IoCallDriver會立刻返回.但是此時並不是真正的完成了IRP. 第二種情況就是在調用IoCallDriver之前,設置一個完成例程.底層驅動完成IRP的時候則會調用這個完成例程來通知程序員. 其實完成例程就是在IRP對象中的CompletionRoutine這個域記錄着.

如果使用完成例程 那麼就不能使用內核宏(IoSkipCurrentIrpStackLocation) 而必須使用 Copy的方式將本層IRP堆棧拷貝到下一層的IRP堆棧.

4.2 完成例程的API

void IoSetCompletionRoutine(
  [in]           PIRP                   Irp,
  [in, optional] PIO_COMPLETION_ROUTINE CompletionRoutine,
  [in, optional] __drv_aliasesMem PVOID Context,
  [in]           BOOLEAN                InvokeOnSuccess,
  [in]           BOOLEAN                InvokeOnError,
  [in]           BOOLEAN                InvokeOnCancel
);

參數1: 要設置的IRP 要給那個IRP設置完成例程(也就是給IRP的子域CompletionRoutine設置)

參數2: 完成例程的回調函數. 如果爲NULL 則代表取消這個IRP設置.也就是取消完成例程的設置.簡單理解爲(Irp->completionRoutine = 參數2)

參數3: 完成例程的上下文. 也就是自定義的參數.當完成例程調用的時候可以使用傳遞的上下文.

參數4: 指定是否IRP被成功完成後進入完成例程

參數5: 指定是否IRP被錯誤完成後進入的完成例程

參數6: 是否是IRP被取消後...

此函數的內核實現:

VOID
IoSetCompletionRoutine(
    _In_ PIRP Irp,
    _In_opt_ PIO_COMPLETION_ROUTINE CompletionRoutine,
    _In_opt_ __drv_aliasesMem PVOID Context,
    _In_ BOOLEAN InvokeOnSuccess,
    _In_ BOOLEAN InvokeOnError,
    _In_ BOOLEAN InvokeOnCancel
    )
{
    PIO_STACK_LOCATION irpSp;
    NT_ASSERT( (InvokeOnSuccess || InvokeOnError || InvokeOnCancel) ? (CompletionRoutine != NULL) : TRUE );
    irpSp = IoGetNextIrpStackLocation(Irp); //獲取下一層IRP堆棧
    irpSp->CompletionRoutine = CompletionRoutine;//設置完成例程
    irpSp->Context = Context;//設置上下文
    irpSp->Control = 0;

    if (InvokeOnSuccess) {
        irpSp->Control = SL_INVOKE_ON_SUCCESS;
    }

    if (InvokeOnError) {
        irpSp->Control |= SL_INVOKE_ON_ERROR;
    }

    if (InvokeOnCancel) {
        irpSp->Control |= SL_INVOKE_ON_CANCEL;
    }
}

IRP處於某個設備棧的時候,IRPIoCompleteRequest完成的時候,則會一層一層的出棧.出棧的時候如果遇到了該棧的完成例程.那麼就會調用. 因此完成例程可以作爲通知IRP完成的標誌. 並且可以很清楚的知道IRP的完成情況.

4.3 完成例程的巧用

完成例程可以知道IRP的完成情況.

當 IoCallDriver被調用之後,當前的驅動(派遣函數)內就會失去對IRP的控制.如果此時本層修改IRP.那麼就會BSOD(藍屏). 而完成回調有兩種返回可能.一種是 STATUS_SUCCESS. 返回此標記代表驅動不會在得到IRP的控制. 另一種是返回 STATUS_MORE_PROCESSING__REQUIRED

如果是這個標記.那麼本層設備堆棧就會重新獲取對IRP的控制 並且設備棧不會向上彈出. 也就可我們可以選擇在此向底層設備發送此IRP

4.4 pending位的傳播

當低級別的 驅動完成IRP之後,會將堆棧向上回捲 此時底層驅動中的 IO堆棧中的 Control域 如果是 SL_PENDING_RETURNED 那麼就必須傳送給上一層. 如果本層沒有 設置完成例程.那麼這種傳播是自動的 也就是不需要程序員參與. 如果設置了完成例程,那麼就要程序員自己實現.

在沒有設置完成例程的時候,編寫的代碼類似於下面這種情況.

//向下複製堆棧
IoCopyCurrentIrpStackLocationToNext(...)
//向下轉發IRP
status =  IoCallDriver(...);
return status;

觀看上面的代碼,我們並沒有調用 IoMarkIrpPending 來掛起IRP. 是因爲我們沒有設置完成例程. 這樣底層驅動會自動將堆棧的control域複製到本層堆棧中.

而如果設置了 完成例程 那麼則需要我們自己去在完成例程裏面去掛起IRP.

NTSTATUS CopletionRoutine(...)
{
    if (Irp->PendingReturned)
{
     //如果底層驅動有pending位.那麼當前堆棧也要設置pending位.使用API設置
    IoMarkIrpPending(irp);
}
return STATUS_CONTINUE_COMPLETION; //等價於STATUS_SUCCESS
}

前面說過.當調用 IoCallDriver的時候 本層就不再對IRP有掌控權了.也就是不能修改了.

那麼pending的傳播.如果我們設置了完成例程.而又寫了如下代碼.那麼就會出問題.

//向下複製堆棧
IoCopyCurrentIrpStackLocationToNext(...)
//向下轉發IRP
status =  IoCallDriver(...);

if (status == STATUS_PENDING)

{
    //錯誤, 如果設置了完成例程,則必須在完成例程裏面設置pending位. 本層已經喪失了IRP的操作

    //如果沒有設置完成例程,則pending位,操作系統已經幫我們設置了.我們不需要設置.
    IoMarkIrpPending(irp);

}
return status;

4.5 完成例程返回 STATUS_MORE_PROCESSING__REQUIRED

如果返回 STATUS_MORE_PROCESSING__REQUIRED 那麼調用 IoCallDriver之後還可以繼續使用Irp. 也可以直接使用 IoCompleteRequest來完成這個Irp 因爲此標記代表回捲停止.我們還可以繼續操作IRP.

如果返回 STATUS_SUCCESS 那麼則不能操作了.因爲已經回捲過了. 此時這個標記只是作爲一個通知我們的手段而已.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章