专栏首页猿学猿学-内核开发知识3之串口过滤.绑定设备
原创

猿学-内核开发知识3之串口过滤.绑定设备

一丶理论知识,什么是过滤.

过滤就是在不影响上层跟下层的情况下,加入我们的新一层的设备.

当请求数据发送过来的时候.我们可以对这个数据进行操作.这个就是过滤的基本含义.

二丶过滤使用的API.以及简单的功能.

APi

功能

NTSTATUS   IoAttachDevice(    IN PDEVICE_OBJECT  SourceDevice,    IN PUNICODE_STRING  TargetDevice,    OUT PDEVICE_OBJECT  *AttachedDevice    );

绑定到一个设备上.通过设备名字绑定.参数1: 我们自己生成的设备.参数2: 我们要绑定设备的设备名称参数3: 绑定成功后返回设备对象指针的指针.

PDEVICE_OBJECT   IoAttachDeviceToDeviceStack(    IN PDEVICE_OBJECT  SourceDevice,    IN PDEVICE_OBJECT  TargetDevice    );

绑定到一个设备上.通过设备指针绑定. 参数1: 我们生成的过滤设备.参数2: 要绑定的设备的指针. 返回值: 返回值中保存了我们绑定成功后的设备对象指针.

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

同上面API功能一样.只不过其参数三当做传出参数.保存了我们绑定成功之后的设备的这指针.

NTSTATUS   IoCreateDevice(    IN PDRIVER_OBJECT  DriverObject,    IN ULONG  DeviceExtensionSize,    IN PUNICODE_STRING  DeviceName  OPTIONAL,    IN DEVICE_TYPE  DeviceType,    IN ULONG  DeviceCharacteristics,    IN BOOLEAN  Exclusive,    OUT PDEVICE_OBJECT  *DeviceObject    );

生成一个过滤设备.我们这个设备要绑定到我们要绑定的设备上面.参数1 : 驱动对象.一个内核程序只有一个驱动对象. 可以填写DriverEntry的参数.参数2:  设备扩展.暂时传入0参数3:  设备名称,如果是过滤设备,那么有一个规则就是一般不需要名称.参数4:  设备类型.保持跟绑定设备的设备类型一致即可.参数5: 设备的特征. 一般是0参数6: 此设备是否是独占设备,一般给false参数7: 传出参数.传出设备对象指针.

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

通过名字获取设备对象.参数1: 设备对象名称参数2: 访问权限.一般是 FILE_ALL_ACCESS参数3: 返回参数.即获得这个设备对象的同时,会得到一个文件对象.注意.不管这个参数在程序中有没有用.我们都要解除引用.否则内存泄漏.参数4: 得到的设备对象在参数4中存放.

VOID   IoDeleteDevice(    IN PDEVICE_OBJECT  DeviceObject    );

销毁设备参数1: 设备对象指针.

通过以上我们其实使用几个简单的API就可以做一个串口过滤.

三丶实战步骤.

  1. 生成我们自己的过滤设备. 使用IoCreateDevice
  2. 拷贝标志位.我们的生成的过滤设备跟要绑定的设备的标志要一样.
  3. 利用IoAttachDeviceToDeviceStack(也可以是安全的那个.)将我们的设备跟要绑定的设备行绑定.
  4. 设置这个设备已经启动.
  5. 封装函数.通过设备名称获取设备对象指针.内部并对文件对象进程解除引用.
  6. 封装函数.进行绑定.

如果简单封装其实就是2步骤.

  1. 通过设备名称获得设备对象指针. 并解除文件引用.
  2. 生成设备,进行绑定.

当然,上面两步骤都是我们封装的函数.

四丶串口绑定代码例子

根据上面的理论.我们可以根据API. 写简单的串口绑定了. 注意下方代码是串口绑定的代码.相当于我们在这个设备上加了一层.但是我们还没有写获取请求数据的代码.

#include <Ntddk.h> //编写内核驱动需要包含NTddk头文件.
#include <ntdef.h>
#include <Ntstrsafe.h>
VOID DriverUnload(PDRIVER_OBJECT pObj);

#define MAX_COM_ID 32  //假设我们的串口有32个.


/*
函数功能.格式化端口字符串.根据端口字符串打开端口.返回获取到的设备对象指针.
参数1: id. 根据id依次初始化字符串.
参数2: 返回值状态. 因为返回值是设备对象指针.所以我们通过定义传出参数来保存真实的返回值状态.
*/
PDEVICE_OBJECT RetOpenComDevicePoint(ULONG id, NTSTATUS *status); 
/*
函数功能: 根据驱动对象.传入获取的设备对象.内部创建过滤设备.进而进行绑定. 过滤设备跟 获取的设备进行绑定.
参数1: 驱动对象.
参数2: 获取的设备对象
参数3: 创建的过滤驱动.内部创建.这个参数会保存过滤设备指针. 
参数4: 临时的过滤设备指针.保存当前过滤设备最顶端的设备.
*/
NTSTATUS MyAttachDevice(__in PDRIVER_OBJECT pDriverObj,
    __in PDEVICE_OBJECT oldDeviceObj,
    __in PDEVICE_OBJECT *CreateDeviceObj,
    __in PDEVICE_OBJECT *NextDeviceObj);

static PDEVICE_OBJECT New_Obj[MAX_COM_ID] = { 0 }; //我们生成的过滤设备.
static PDEVICE_OBJECT Next_Obj[MAX_COM_ID] = { 0 }; //要绑定的设备.


NTSTATUS DriverEntry(__in struct _DRIVER_OBJECT  *DriverObject,
                      __in PUNICODE_STRING  RegistryPath)
{
    //UNICODE_STRING str = RTL_CONSTANT_STRING(L"My Frist Driver \r\n");
    
    //串口过滤
    ULONG i;
    NTSTATUS  status;
    PDEVICE_OBJECT pGetOldDevicePoint = NULL; //获取的旧的设备对象
    /*依次遍历进行串口绑定过滤*/
    for (i = 0; i < MAX_COM_ID; i++)
    {
        pGetOldDevicePoint = RetOpenComDevicePoint(i, &status);
        if (NULL == pGetOldDevicePoint)
        {
            continue;
        }
        //进行绑定.
        MyAttachDevice(DriverObject, pGetOldDevicePoint, &New_Obj[i], &Next_Obj[i]);
    }
    
    DriverObject->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

VOID DriverUnload(PDRIVER_OBJECT pObj)
{
    DbgPrint("UnLoad");
}
//通过设备对象名称.获取设备对象指针.
PDEVICE_OBJECT RetOpenComDevicePoint(ULONG id,NTSTATUS *status)
{
    //我们知道在windows中设备名字都是依次递进的.所以我们根据id格式化字符串.\Device\Serial0 C语言中要进行转义
    UNICODE_STRING  CreateDeviceStr; //设备名称字符串.
    PFILE_OBJECT pFileObj  = NULL; //文件对象指针.
    PDEVICE_OBJECT pDeviceObj = NULL;//设备对象指针.
    static WCHAR name[32] = { 0 };

    //根据ID格式化字符串转换成串口名字.
    memset(name, 0, sizeof(WCHAR) * 32);//清空内存
    RtlStringCchPrintfW(name, 32, L"\\Device\\Serial%d", id);//进行字符串格式化输出. 输入到name字符串当中.
    
    //然后将字符串初始化到Unicode字符串当中.
    RtlInitUnicodeString(&CreateDeviceStr, name); //注意,要传入地址.

    //打开设备对象.
    *status = IoGetDeviceObjectPointer(&CreateDeviceStr, FILE_ALL_ACCESS, &pFileObj,&pDeviceObj);
    //此时FileObj执向文件对象指针.  也会获得驱动对象指针.

    //判断是否成功,如果成功,对文件对象进行引用解除.否则内存泄漏.
    if (STATUS_SUCCESS == *status)
    {
        ObDereferenceObject(pFileObj);
    }
    return pDeviceObj; //返回设备对象指针.
}
/*
根据驱动对象,以及获取到设备对象.进行生成设备对象.并绑定设备对象.
*/
NTSTATUS MyAttachDevice(__in PDRIVER_OBJECT pDriverObj,
                        __in PDEVICE_OBJECT oldDeviceObj,
                        __in PDEVICE_OBJECT *CreateDeviceObj,
                        __in PDEVICE_OBJECT *NextDeviceObj)
{
    NTSTATUS status;
    PDEVICE_OBJECT TopBindingDevice = NULL; //绑定的设备.


        
    //第一步,生成我们的设备对象. 最后一个参数是一个二级指针.保存我们的设备对象指针.
    status = IoCreateDevice(pDriverObj, 0, NULL, oldDeviceObj->DeviceType, 0, FALSE, CreateDeviceObj);
    if (STATUS_SUCCESS != status)
    {
        return status;
    }
    //拷贝标志位.

    if (oldDeviceObj->Flags & DO_BUFFERED_IO) //判断标志是什么状态.
    {
        (*CreateDeviceObj)->Flags |= DO_BUFFERED_IO; //设置我们的过滤设备的标志
    }

    if (oldDeviceObj->Flags & DO_DIRECT_IO)
    {
        (*CreateDeviceObj)->Flags |= DO_DIRECT_IO;
    }
    //
    if (oldDeviceObj->Characteristics & FILE_DEVICE_SECURE_OPEN)
    {
        (*CreateDeviceObj)->Characteristics |= oldDeviceObj->Characteristics;
    }
    //标志位拷贝完毕.此时进行设备绑定. 也可以用另一个API
    status = IoAttachDeviceToDeviceStackSafe((*CreateDeviceObj), oldDeviceObj,&TopBindingDevice);
    if (STATUS_SUCCESS != status)
    {
        //绑定失败.删除我们的设备
        IoDeleteDevice((*CreateDeviceObj));
        *CreateDeviceObj = NULL;
        status = STATUS_UNSUCCESSFUL;
        return status;
    }
    
    *NextDeviceObj = TopBindingDevice;//保存最顶层的绑定设备.

    //设定设备已经启动.
    (*CreateDeviceObj)->Flags = (*CreateDeviceObj)->Flags & DO_DEVICE_INITIALIZING;
    
    return STATUS_SUCCESS;
}

五丶获取过滤数据理论

 1.过滤的理论知识.

在获取过滤之前.我们要知道.windows 会通过请求发送数据.所以我们要先明白请求的区分.

我们现在知道了 设备对象(DEVICE_OBJECT) 驱动对象(DRIVER_OBJECT) 文件对象(FILE_OBJECT)

现在我们需要知道以下的理论:

1.没一个驱动程序都已一个驱动对象.有且只有一个.

2.每个驱动程序.可以生成如若干个设备对象.这些对象都属于驱动对象.可以从驱动对象中遍历出所有设备对象.

3.若干个设备对象可以属于不同的驱动.依次绑定的时候会形成一个设备栈. 而在栈最前边的设备总会第一个接受请求的.

所以我们知道. 在内核结构中请求的传递都是用 IRP结构传递的.常见的数据结构就是IRP. 但是并不是唯一的.因为内核程序中.传递请求还有很多种方法.

不同设备也可能使用不同的请求结构来传递.

串口设备接收到的都是IRP请求.所以我们需要对IRP请求做过滤即可. 而串口过滤的时候我们只关心两种请求, 1.读请求. 2.写请求.

而过滤IRP请求则要关心他的功能号.  IRP请求的有主功能号跟次功能号. 相应的在IRP栈空间中.会有一个字节保存了这些功能号.

读请求的功能号: IRP_MJ_READ

写请求的功能号: IRP_MJ_WRITE

可以通过API 来获取IRP的堆栈空间

PIO_STACK_LOCATION  IoGetCurrentIrpStackLocation(     IN PIRP Irp     );

参数.IRP结构体 通过IRP结构.获取当前IRP堆栈空间. 然后进行标志位判断.判断是什么数据即可.

2.过滤的结局

 我们要进行过滤数据.那么会有三种结局.

1.数据通过. 那么我们过滤不会做任何事情.

2.请求被否决了.我们的过滤设备否定了这个数据往下传递.那么我们上层就会弹出错误.说数据操作失败.比如CreateFile API调用的时候.我们请求让它失败.那么就不能创建文件了.

3.数据被我们拷贝了一份.继续往下传递. 关于第三个应该是用得着的. 我们记录这个数据都做了什么. 如果是读我们可以记录读了什么数据.

 关于第一种,我们调用两个API即可进行操作.

分别是跳过当前栈空间. 然后将请求发送给真实的设备.注意,因为真实的设备已经被我们的过滤设备绑定了.所以先接受IRP请求的其实是我们的设备对象.

API:

API

作用

VOID   IoSkipCurrentIrpStackLocation(    IN PIRP  Irp    );

传入IRP结构.跳过当前的堆栈空间

NTSTATUS   IoCallDriver(    IN PDEVICE_OBJECT  DeviceObject,    IN OUT PIRP  Irp    );

传入真实设备对象指针.传入IRP结构进而将请求发送到真实的IRP结构中

 3.写请求数据发送的分析

 写请求也就是串口一次发送的请求数据. 在IRP结构中有三个缓冲区.

1.irp->MdlAddress

2.irp->UserBuffer

3.irp->AssociatedIrp.SystemBuffer;

关于IRP结构中的是哪个成员我们可以做一次解析.

SystemBuffer比较简单.一般用于比较简单且不追求效率的情况下的解决方案.也就是说吧R3(应用层数据) 拷贝到内核空间.

UserBuffer是追求效率的. UserBuffer直接放到应用层数据当中.我们在内核中访问.当前进程跟发送请求进程一致的情况下.内核访问应用层空间没错.但是不一致也就是说内核进程切换了.那么这个访问就结束了.

因为在Windows内核中内存是一样的.但是在R3中.UserBuffer则不一致.所以切换了如果在访问UserBuffer则会访问到别的进程中.

MdlAddress 这一个是将应用层的空间映射到内核空间中进行访问的.当然需要在页表(PTE)中添加一个映射.如果做开发则不需要关心这个.不用手工修改页表. 而是构造MDL就能实现,

MDL可以称为 内存描述符表 IRP中的MdlAddress是一个MDL指针.可以从这个MDL独处一个内核空间的虚拟地址. 比UserBuffer强.同时比拷贝到SystemBuffer的方法还要好.因为内存还是在实际的地方.没有进行拷贝. 只是进行了一个映射.

我们可以通过MdlAddress内存地址进行读取.也可以通过SystemBuffer,也可以通过UserBuffer

例子:

PBYTE Buffer = NULL;

//第一种方式.
if (irp->MdlAddress != NULL)
{
    Buffer = (PBYTE)MmGetSystemAddressForMdlSafe(irp->MdlAddress);
}

//第二种方式.

else
{
   Buffer = (PBYTE)Irp->UserBuffer;
}
else
{
   Buffer = (PBYTE)Irp->AssociatedIrp.SystemBuffer;
}

以上都可以.其中有一个陌生的函数.

PVOID  MmGetSystemAddressForMdlSafe(     __in PMDL Mdl,     __in MM_PAGE_PRIORITY Priority     );

这个宏返回MDL非分页虚拟内存.

六丶获取过滤数据的完整代码

 通过上面理解请求.理解过滤. 以及获取IRP堆栈. 获取IRP Buffer空间.那么我们则可以进行写代码了.

NTSTATUS FilterData(PDEVICE_OBJECT pDeviceObj,PIRP irp)
{
    PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(irp);// 获取当前的IRP堆栈
    NTSTATUS status;
    ULONG i, j;
    //进行遍历.我们要知道发送隔了那个设备. 设备一共有32个.我们自己定义的.
    //然后我们的真实设备都已经保存在了Next_obj数组中了.
    for ( i = 0; i < MAX_COM_ID; i++)
    {
        //判断真实设备是否相等.如果相等我们则进行操作.
        if (pDeviceObj == Next_Obj[i])
        {
            /*
            所有电源操作.全部直接放过
            */
            if (pIrpStack->MajorFunction == IRP_MJ_POWER) //判断功能号
            {
                /*直接发送说明我们已经处理了*/
                PoStartNextPowerIrp(irp); 
                //跳过当前IRP栈空间.
                IoSkipCurrentIrpStackLocation(irp);
                //调用真实设备
                return PoCallDriver(Next_Obj[i], irp);
            }

            /*否则过滤我们的请求.对写请求进行过滤吧*/
            if (pIrpStack->MajorFunction == IRP_MJ_WRITE)
            {
                //获取缓冲区
                PUCHAR Buffer = NULL;
                //获取长度.
                ULONG len = 0;

                len = pIrpStack->Parameters.Write.Length;
                //获取MDL缓冲区
                if (NULL != irp->MdlAddress)
                {
                    Buffer = (PUCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);
                }
                else
                {
                    //获取UserBuffer
                    Buffer = (PUCHAR)irp->UserBuffer;
                }
                if (NULL == Buffer)
                {
                    Buffer = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
                }
                
                //遍历数组打印内容. 
                for (j = 0; j < len; j++)
                {
                    DbgPrint("Send Data = %2x\r\n", Buffer[j]);
                }

            }

            /*然后跳过当前的IRP堆栈空间.发送到我们的真实设备.我们并不拦截如果拦截可以在这里拦截*/
            IoSkipCurrentIrpStackLocation(irp);
            return IoCallDriver(Next_Obj[i], irp);
        }
    }
    /*如果没有在被绑定的设备中.那么是又问题的.直接返回参数错误即可.*/
    irp->IoStatus.Information = 0;
    irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
    IoCompleteRequest(irp, IO_NO_INCREMENT);//完成IRP请求
    return STATUS_SUCCESS;
}

完成的驱动代码.可以直接编译.

#include <Ntddk.h> //编写内核驱动需要包含NTddk头文件.
#include <ntdef.h>
#include <Ntstrsafe.h>


#define MAX_COM_ID 32  //假设我们的串口有32个.

VOID DriverUnload(PDRIVER_OBJECT pObj);
/*
函数功能.格式化端口字符串.根据端口字符串打开端口.返回获取到的设备对象指针.
参数1: id. 根据id依次初始化字符串.
参数2: 返回值状态. 因为返回值是设备对象指针.所以我们通过定义传出参数来保存真实的返回值状态.
*/
PDEVICE_OBJECT RetOpenComDevicePoint(ULONG id, NTSTATUS *status); 
/*
函数功能: 根据驱动对象.传入获取的设备对象.内部创建过滤设备.进而进行绑定. 过滤设备跟 获取的设备进行绑定.
参数1: 驱动对象.
参数2: 获取的设备对象
参数3: 创建的过滤驱动.内部创建.这个参数会保存过滤设备指针. 
参数4: 临时的过滤设备指针.保存当前过滤设备最顶端的设备.
*/
NTSTATUS MyAttachDevice(__in PDRIVER_OBJECT pDriverObj,
    __in PDEVICE_OBJECT oldDeviceObj,
    __in PDEVICE_OBJECT *CreateDeviceObj,
    __in PDEVICE_OBJECT *NextDeviceObj);

//函数作用封装外层的串口绑定.
VOID AttachCom(PDRIVER_OBJECT pDirVerObj);
/*
函数功能.过滤数据
参数1: 真实设备 Next_obj数组内容.
参数2: irp请求结构.
*/
NTSTATUS FilterData(PDEVICE_OBJECT pDeviceObj, PIRP irp);

static PDEVICE_OBJECT New_Obj[MAX_COM_ID] = { 0 }; //我们生成的过滤设备.
static PDEVICE_OBJECT Next_Obj[MAX_COM_ID] = { 0 }; //要绑定的设备.


NTSTATUS DriverEntry(__in struct _DRIVER_OBJECT  *DriverObject, __in PUNICODE_STRING  RegistryPath)
{
    //UNICODE_STRING str = RTL_CONSTANT_STRING(L"My Frist Driver \r\n");
    //设置分发函数.请求发送过来的时候会拦截.

    //串口过滤
    ULONG i = 0;
    for ( i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)   //设置分发函数.当请求发送过来的时候驱动对象会过滤.
    {
        DriverObject->MajorFunction[i] = FilterData;
    }
    AttachCom(DriverObject); //绑定所有设备对象.
    DriverObject->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}

VOID DriverUnload(PDRIVER_OBJECT pObj)
{
    DbgPrint("UnLoad");
}
//函数作用封装外层的串口绑定.
VOID AttachCom(PDRIVER_OBJECT pDirVerObj)
{
    ULONG i;
    NTSTATUS  status;
    PDEVICE_OBJECT pGetOldDevicePoint = NULL; //获取的旧的设备对象
                                              /*依次遍历进行串口绑定过滤*/
    for (i = 0; i < MAX_COM_ID; i++)
    {
        pGetOldDevicePoint = RetOpenComDevicePoint(i, &status);
        if (NULL == pGetOldDevicePoint)
        {
            continue;
        }
        //进行绑定.
        MyAttachDevice(pDirVerObj, pGetOldDevicePoint, &New_Obj[i], &Next_Obj[i]);
    }

}
//通过设备对象名称.获取设备对象指针.
PDEVICE_OBJECT RetOpenComDevicePoint(ULONG id,NTSTATUS *status)
{
    //我们知道在windows中设备名字都是依次递进的.所以我们根据id格式化字符串.\Device\Serial0 C语言中要进行转义
    UNICODE_STRING  CreateDeviceStr; //设备名称字符串.
    PFILE_OBJECT pFileObj  = NULL; //文件对象指针.
    PDEVICE_OBJECT pDeviceObj = NULL;//设备对象指针.
    static WCHAR name[32] = { 0 };

    //根据ID格式化字符串转换成串口名字.
    memset(name, 0, sizeof(WCHAR) * 32);//清空内存
    RtlStringCchPrintfW(name, 32, L"\\Device\\Serial%d", id);//进行字符串格式化输出. 输入到name字符串当中.
    
    //然后将字符串初始化到Unicode字符串当中.
    RtlInitUnicodeString(&CreateDeviceStr, name); //注意,要传入地址.

    //打开设备对象.
    *status = IoGetDeviceObjectPointer(&CreateDeviceStr, FILE_ALL_ACCESS, &pFileObj,&pDeviceObj);
    //此时FileObj执向文件对象指针.  也会获得驱动对象指针.

    //判断是否成功,如果成功,对文件对象进行引用解除.否则内存泄漏.
    if (STATUS_SUCCESS == *status)
    {
        ObDereferenceObject(pFileObj);
    }
    return pDeviceObj; //返回设备对象指针.
}
/*
根据驱动对象,以及获取到设备对象.进行生成设备对象.并绑定设备对象.
*/
NTSTATUS MyAttachDevice(__in PDRIVER_OBJECT pDriverObj,__in PDEVICE_OBJECT oldDeviceObj,__in PDEVICE_OBJECT *CreateDeviceObj,__in PDEVICE_OBJECT *NextDeviceObj)
{
    NTSTATUS status;
    PDEVICE_OBJECT TopBindingDevice = NULL; //绑定的设备.


        
    //第一步,生成我们的设备对象. 最后一个参数是一个二级指针.保存我们的设备对象指针.
    status = IoCreateDevice(pDriverObj, 0, NULL, oldDeviceObj->DeviceType, 0, FALSE, CreateDeviceObj);
    if (STATUS_SUCCESS != status)
    {
        return status;
    }
    //拷贝标志位.

    if (oldDeviceObj->Flags & DO_BUFFERED_IO) //判断标志是什么状态.
    {
        (*CreateDeviceObj)->Flags |= DO_BUFFERED_IO; //设置我们的过滤设备的标志
    }

    if (oldDeviceObj->Flags & DO_DIRECT_IO)
    {
        (*CreateDeviceObj)->Flags |= DO_DIRECT_IO;
    }
    //
    if (oldDeviceObj->Characteristics & FILE_DEVICE_SECURE_OPEN)
    {
        (*CreateDeviceObj)->Characteristics |= oldDeviceObj->Characteristics;
    }
    //标志位拷贝完毕.此时进行设备绑定. 也可以用另一个API
    status = IoAttachDeviceToDeviceStackSafe((*CreateDeviceObj), oldDeviceObj,&TopBindingDevice);
    if (STATUS_SUCCESS != status)
    {
        //绑定失败.删除我们的设备
        IoDeleteDevice((*CreateDeviceObj));
        *CreateDeviceObj = NULL;
        status = STATUS_UNSUCCESSFUL;
        return status;
    }
    
    *NextDeviceObj = TopBindingDevice;//保存最顶层的绑定设备.

    //设定设备已经启动.
    (*CreateDeviceObj)->Flags = (*CreateDeviceObj)->Flags & DO_DEVICE_INITIALIZING;
    
    return STATUS_SUCCESS;
}

/*
函数功能: 过滤数据.进行对数据的操作.
参数1: 真实设备指针
参数2: Irp堆栈.
*/
NTSTATUS FilterData(PDEVICE_OBJECT pDeviceObj,PIRP irp)
{
    
    NTSTATUS status;
    ULONG i, j;
    PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(irp);// 获取当前的IRP堆栈
    ////进行遍历.我们要知道发送隔了那个设备. 设备一共有32个.我们自己定义的.
    ////然后我们的真实设备都已经保存在了Next_obj数组中了.

    for ( i = 0; i < MAX_COM_ID; i++)
    {
        //判断真实设备是否相等.如果相等我们则进行操作.
        if (pDeviceObj == Next_Obj[i])
        {
            /*
            所有电源操作.全部直接放过
            */
            if (pIrpStack->MajorFunction == IRP_MJ_POWER) //判断功能号
            {
                /*直接发送说明我们已经处理了*/
                PoStartNextPowerIrp(irp);
                //跳过当前IRP栈空间.
                IoSkipCurrentIrpStackLocation(irp);
                //调用真实设备
                return PoCallDriver(Next_Obj[i], irp);
            }
        //    /*否则过滤我们的请求.对写请求进行过滤吧*/
            if (pIrpStack->MajorFunction == IRP_MJ_WRITE)
            {
                //获取缓冲区
                PUCHAR Buffer = NULL;
                //获取长度.
                ULONG len = 0;

                len = pIrpStack->Parameters.Write.Length;
                //获取MDL缓冲区
                if (NULL != irp->MdlAddress)
                {
                    Buffer = (PUCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);
                }
                else
                {
                    //获取UserBuffer
                    Buffer = (PUCHAR)irp->UserBuffer;
                }
                if (NULL == Buffer)
                {
                    Buffer = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
                }
                
                //遍历数组打印内容. 
                for (j = 0; j < len; j++)
                {
                    DbgPrint("Send Data = %2x\r\n", Buffer[j]);
                }

            }

        //    /*然后跳过当前的IRP堆栈空间.发送到我们的真实设备.我们并不拦截如果拦截可以在这里拦截*/
            IoSkipCurrentIrpStackLocation(irp);
            return IoCallDriver(Next_Obj[i], irp);
        }
    }
    ///*如果没有在被绑定的设备中.那么是又问题的.直接返回参数错误即可.*/
    irp->IoStatus.Information = 0;
    irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
    IoCompleteRequest(irp, IO_NO_INCREMENT);//完成IRP请求
    return STATUS_SUCCESS;
}

编译的时候对应的sources 文件内容

TARGETNAME=frist
TARGETPATH=obj
TARGETTYPE=DRIVER
SOURCES=frist.c

注意,使用WDK写的在XP下面进行测试. 使用Debug版本编译.

过滤驱动的动态卸载还没有写.明天补充.

坚持两字,简单,轻便,但是真正的执行起来确实需要很长很长时间.当你把坚持两字当做你要走的路,那么你总会成功.

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 你可以从面试中学到什么?

    讲一下我对面试的一些。。。“偏见”,哈哈,熟悉我的同学们一定要批判的读接下来的内容哈。

    web前端教室
  • 复杂业务下向Mysql导入30万条数据代码优化的踩坑记录

    从毕业到现在第一次接触到超过30万条数据导入MySQL的场景(有点low),就是在顺丰公司接入我司EMM产品时需要将AD中的员工数据导入MySQL中,因此楼主负...

    haifeiWu
  • 今天我就说三句话

    腾讯NEXT学位
  • 穿越十年后看互联网+:家电行业的金矿在哪里?

    现在市场上炒得火热的智能家居未来出路在何方?做智能家居的创业者应该注意哪些机会?传统家电厂商又到底如何借助互联网进行转型?本文以智能空调为例,用故事的形式,提前...

    华章科技
  • 「我真的没有改需求」

    非著名程序员
  • 我不是算命先生,却对占卜有了疑惑——如何论证“占卜前提”的正确与否

    事出有因,我对《周易》感兴趣了很多年。只是觉得特别有趣,断断续续学习了一些皮毛。这几天又偶然接触到了《梅花易数》,觉得很是精彩,将五行八卦天干地支都串联了起来。...

    一石匠人
  • 一张图理清《梅花易数》梗概

    学《易经》的目的不一定是为了卜卦,但是了解卜卦绝对能够让你更好地了解易学。今天用一张思维导图对《梅花易数》的主要内容进行概括,希望能够给学友们提供帮助。

    一石匠人
  • 【系统设置】CentOS 修改机器名

    ken.io
  • 这是对付产品经理的一副毒药,程序员慎入

    程序员和产品经理的日常就像是一对天生的冤家,为了需求的实现,几乎天天在争吵。这不,就在昨天各大技术和产品群里一个程序员暴打产品经理的视频火了,被广泛传播。

    非著名程序员
  • SQL中GROUP BY用法示例

    GROUP BY我们可以先从字面上来理解,GROUP表示分组,BY后面写字段名,就表示根据哪个字段进行分组,如果有用Excel比较多的话,GROUP BY比较类...

    Awesome_Tang

扫码关注云+社区

领取腾讯云代金券