《寒江独钓 Windows内核安全》——串口的过滤

过滤是在不影响上层和下层的接口,在windows系统内核中加入新的层,从而不需要修改上层的软件或者下层的真实驱动程序,就加入了新的功能。

1、用到的Windows API

IoAttachDevice——绑定到真实设备上(有名称的设备)

/*
IoAttachDevice例程将调用方的设备对象附加到命名的目标设备对象,以便将绑定到目标设备的I / O请求首先路由到调用方。
*/
NTSTATUS IoAttachDevice(
  PDEVICE_OBJECT  SourceDevice,
  PUNICODE_STRING TargetDevice,
  PDEVICE_OBJECT  *AttachedDevice
);

SourceDevice

[in]指向调用者创建的设备对象的指针。

TargetDevice

[in]指向缓冲区的指针,该缓冲区包含要附加指定SourceDevice的设备对象的名称。

AttachedDevice

[out]指向指向呼叫者的存储的指针。 返回时,如果附件成功,则包含指向目标设备对象的指针。

注意:必须是有名字的设备才能用这个API

IoAttachDeviceToDeviceStack——绑定到真实的设备上(设备对象)

PDEVICE_OBJECT IoAttachDeviceToDeviceStack(
  PDEVICE_OBJECT SourceDevice,
  PDEVICE_OBJECT TargetDevice
);

IoAttachDeviceToDeviceStackSafe——绑定到真实的设备上(更安全)

NTSTATUS IoAttachDeviceToDeviceStackSafe(
  PDEVICE_OBJECT SourceDevice,
  PDEVICE_OBJECT TargetDevice,
  PDEVICE_OBJECT *AttachedToDeviceObject
);

IoCreateDevice——创建设备

//创建一个驱动程序使用的设备对象
NTSTATUS IoCreateDevice(
  PDRIVER_OBJECT  DriverObject,
  ULONG           DeviceExtensionSize,
  PUNICODE_STRING DeviceName,
  DEVICE_TYPE     DeviceType,
  ULONG           DeviceCharacteristics,
  BOOLEAN         Exclusive,
  PDEVICE_OBJECT  *DeviceObject
);

IoGetDeviceObjectPointer——从名称获取设备对象

NTSTATUS IoGetDeviceObjectPointer(
  PUNICODE_STRING ObjectName,
  ACCESS_MASK     DesiredAccess,
  PFILE_OBJECT    *FileObject,
  PDEVICE_OBJECT  *DeviceObject
);

IoDetachDevice——将绑定的设备解除绑定

void IoDetachDevice(
  PDEVICE_OBJECT TargetDevice
);

IoDeleteDevice——删除掉生成的设备,回收内存

void IoDeleteDevice(
  PDEVICE_OBJECT DeviceObject
);

2、实现代码

#include <ntddk.h>
#include <ntstrsafe.h>

#define CCP_MAX_COM_ID          32                              ///> 假设计算机上最大的串口数
#define DELAY_ONE_MICROSECOND   (-10)                           ///> 微妙
#define DELAY_ONE_MILLISECOND   (DELAY_ONE_MICROSECOND * 1000)  ///> 毫秒
#define DELAY_ONE_SECOND        (DELAY_ONE_MILLISECOND * 1000)  ///> 秒
static PDEVICE_OBJECT gs_fltObj[CCP_MAX_COM_ID] = { 0 };        ///> 保存所有过滤设备指针
static PDEVICE_OBJECT gs_nextObj[CCP_MAX_COM_ID] = { 0 };       ///> 保存所有真实设备指针

/*
 生成设备,然后绑定
*/
NTSTATUS ccpAttachDevice(
    PDRIVER_OBJECT driver,
    PDEVICE_OBJECT oldobj,
    PDEVICE_OBJECT *fltobj,
    PDEVICE_OBJECT *next
)
{
    NTSTATUS status = STATUS_SUCCESS;
    PDEVICE_OBJECT tobdev = NULL;

    ///> 生成设备,然后绑定
    status = IoCreateDevice(driver,
        0,
        NULL,
        oldobj->DeviceType,
        0,
        FALSE,
        fltobj);
    if (STATUS_SUCCESS != status)
    {
        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->Characteristics & FILE_DEVICE_SECURE_OPEN)
    {
        (*fltobj)->Characteristics |= FILE_DEVICE_SECURE_OPEN;
    }

    (*fltobj)->Flags |= DO_POWER_PAGABLE;

    ///> 将一个设备绑定到另一个设备上
    status = IoAttachDeviceToDeviceStackSafe(*fltobj,
        oldobj,
        &tobdev);

    if (STATUS_SUCCESS != status)
    {
        ///> 绑定失败了,则删除设备
        IoDeleteDevice(*fltobj);
        *fltobj = NULL;
        return status;
    }

    *next = tobdev;
    ///> 设置设备已经启动
    (*fltobj)->Flags = (*fltobj)->Flags & (~DO_DEVICE_INITIALIZING);
    return STATUS_SUCCESS;
}

/*
 打开一个端口设备
*/
PDEVICE_OBJECT ccpOpenCom(
    ULONG id,
    NTSTATUS *status
)
{
    ///> 输入的为串口id,需要转成字符串形式
    UNICODE_STRING name_str = { 0 };
    static WCHAR name[32] = { 0 };
    PFILE_OBJECT fileObj = NULL;
    PDEVICE_OBJECT devObj = NULL;

    ///> 根据id转成字符串
    memset(name, 0, sizeof(WCHAR) * 32);
    RtlStringCchPrintfW(
        name,
        32,
        L"\\Device\\Serial%d",
        id
    );

    RtlInitUnicodeString(&name_str, name);

    ///> 根据名称获取设备对象
    *status = IoGetDeviceObjectPointer(
        &name_str,
        FILE_ALL_ACCESS,
        &fileObj,
        &devObj
    );

    ///> 成功了,需要对文件对象进行解引用,否则会内存泄漏
    if (STATUS_SUCCESS == *status)
    {
        ObDereferenceObject(fileObj);
    }

    ///> 返回设备对象
    return devObj;
}

/*
 绑定所有的串口
*/
void ccpAttachAllCom(PDRIVER_OBJECT driver)
{
    ULONG i = 0;
    PDEVICE_OBJECT com_obj;
    NTSTATUS status = STATUS_SUCCESS;
    for (i = 0; i < CCP_MAX_COM_ID; i++)
    {
        ///> 获取object引用
        com_obj = ccpOpenCom(i, &status);
        if (NULL == com_obj)
        {
            continue;
        }
        ///> 绑定
        ccpAttachDevice(driver, com_obj, &gs_fltObj[i], &gs_nextObj[i]);
    }
}

/*
 完整的分发函数
 处理所有串口的写请求,所有从串口输出的数据打印
*/
NTSTATUS ccpDispatch(PDEVICE_OBJECT device, PIRP irp)
{
    PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
    NTSTATUS status = STATUS_SUCCESS;
    ///> 首先要知道发送给了哪个设备。设备最多一共有CCP_MAX_COM_ID个,是前面的代码保存好的,都在gs_fltObj
    for (ULONG i = 0; i < CCP_MAX_COM_ID; ++i)
    {
        if (gs_fltObj[i] == device)
        {
            switch (irpsp->MajorFunction)
            {
                case IRP_MJ_POWER:
                {
                    ///> 所有电源操作,全部直接放过
                    ///> 直接发送,然后返回说已经被处理了
                    PoStartNextPowerIrp(irp);
                    IoSkipCurrentIrpStackLocation(irp);
                    return PoCallDriver(gs_nextObj[i], irp);
                    break;
                }
                case IRP_MJ_WRITE:
                {
                    ///> 此外我们只过滤写请求。写请求,获得缓冲区及其长度,然后打印。
                    ///> 如果是写,先获得长度
                    ULONG len = irpsp->Parameters.Write.Length;
                    ///> 获取缓冲区
                    PUCHAR buf = NULL;
                    if (irp->MdlAddress)
                    {
                        buf =
                            (PUCHAR)MmGetSystemAddressForMdlSafe(
                                irp->MdlAddress, NormalPagePriority);
                    }
                    else
                    {
                        buf = (PUCHAR)irp->UserBuffer;
                    }

                    if (NULL == buf)
                    {
                        buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
                    }

                    for (ULONG j = 0; j < len; j++)
                    {
                        DbgPrint("comcap: Send Data: %2x\r\n", buf[j]);
                    }
                }
                default:
                {
                    ///> 这些请求直接下发执行即可,我们并不禁止或者改变他
                    IoSkipCurrentIrpStackLocation(irp);
                    return IoCallDriver(gs_nextObj[i], irp);
                }
            }
        }
    }

    ///> 如果根本就不在被绑定的设备中,那是有问题的,直接返回参数错误
    irp->IoStatus.Information = 0;
    irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
    IoCompleteRequest(irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

void ccpUnload(PDRIVER_OBJECT drviver)
{
    LARGE_INTEGER interval;

    ///> 首先解除绑定
    for (ULONG i = 0; i < CCP_MAX_COM_ID; i++)
    {
        if (gs_nextObj[i])
        {
            ///> 将绑定的设备解除绑定
            IoDetachDevice(gs_nextObj[i]);
        }
    }

    ///> 睡眠5秒。等待所有IRP处理结束,单位是100纳秒
    interval.QuadPart = (5 * DELAY_ONE_SECOND);
    KeDelayExecutionThread(KernelMode, FALSE, &interval);

    ///> 删除这些设备
    for (ULONG i = 0; i < CCP_MAX_COM_ID; i++)
    {
        if (gs_fltObj[i])
        {
            ///> 删除掉生成的设备,回收内存
            IoDeleteDevice(gs_fltObj[i]);
            gs_fltObj[i] = NULL;
        }
    }
}

void DriverUnload(PDRIVER_OBJECT pdriver)
{
    DbgPrint("Unload\n");
    ccpUnload(pdriver);
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pdriver, PUNICODE_STRING reg_path)
{
    NTSTATUS status = STATUS_SUCCESS;

    ///> 所有的分发函数都设置成一样的
    for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
    {
        pdriver->MajorFunction[i] = ccpDispatch;
    }

    ///> 支持动态卸载
    pdriver->DriverUnload = DriverUnload;

    ///> 绑定所有的串口
    ccpAttachAllCom(pdriver);

    return status;
}

3、笔记

IRP请求中的三个缓冲区

irp->AssociatedIrp.SystemBuffer:一般用于比较简单且不追求效率的情况下的解决方案,把应用层中内存空间中的缓冲数据拷贝到内核空间。

irp->UserBuffer:是最追求效率的解决方案。应用层的缓冲区地址直接放入UserBuffer中,在内核空间中访问,需要保证当前进程和发送请求进程一致。

irp->MdlAddress:是一个MDL(内存描述符链)指针,可以读出一个内核空间的虚拟地址,这样可以实现将应用层的地址空间映射到内核空间

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值