zoukankan      html  css  js  c++  java
  • 《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. DeviceSerialx

    四、请求的区分

    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("分发函数接收到请求了!!
    ");
        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
    ", 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;
    }
  • 相关阅读:
    js返回上个页面再刷新
    xm-select 获取选选择的值
    接口可以跨域访问
    gitlab 版本查看
    gitlab 在linux安装环境下存储地址
    iosUILable基本使用及其所有方法
    iOS富文本字符串AttributedString具体用法
    SQL Server 开发指南
    SQL Server 数据库安全
    SQL Server 触发器
  • 原文地址:https://www.cnblogs.com/onetrainee/p/12006226.html
Copyright © 2011-2022 走看看