windows内核开发学习笔记十七:IRP 和 IO_STACK_LOCATION 的交互

windows内核开发学习笔记十七:IRP 和 IO_STACK_LOCATION 的交互

     前面两篇学习笔记分别介绍了IRP和IO_STACK_LOCATION,整个设备栈来处理这个IRP,但是每个设备都应该有自己的参数信息,这个参数信息就是通过IO_STACK_LOCATION 来保管的,那么IRP是怎么保管IO_STACK_LOCATION的呢?本文我们来分析一下IRP和IO_STACK_LOCATION交互作用的整个流程。

    

一、IoAllocateIrp

这个函数用来分配一个IRP,我们看一下IRP的分配过程:

PIRP    IopAllocateIrpPrivate(
    IN CCHAR StackSize,
    IN BOOLEAN ChargeQuota
    )
{
    //...
    packetSize = IoSizeOfIrp(StackSize);
    allocateSize = packetSize;
    //...
    irp = ExAllocatePoolWithTag(NonPagedPool, allocateSize, ' prI');
    //...
    IopInitializeIrp(irp, packetSize, StackSize);
    //...
}

这个函数中,我们通过packetSize = IoSizeOfIrp(StackSize);来计算整个IRP的大小,这个声明如下:

#define IoSizeOfIrp( StackSize ) \
    ((USHORT) (sizeof( IRP ) + ((StackSize) * (sizeof( IO_STACK_LOCATION )))))

也就是说是IRP的大小 + 设备栈个数个IO_STACK_LOCATION.

IopInitializeIrp : 用来初始化IRP,这个是个宏定义如下:

#define IopInitializeIrp( Irp, PacketSize, StackSize ) {          \
    RtlZeroMemory( (Irp), (PacketSize) );                         \
    (Irp)->Type = (CSHORT) IO_TYPE_IRP;                           \
    (Irp)->Size = (USHORT) ((PacketSize));                        \
    (Irp)->StackCount = (CCHAR) ((StackSize));                    \
    (Irp)->CurrentLocation = (CCHAR) ((StackSize) + 1);           \
    (Irp)->ApcEnvironment = KeGetCurrentApcEnvironment();         \
    InitializeListHead (&(Irp)->ThreadListEntry);                 \
    (Irp)->Tail.Overlay.CurrentStackLocation =                    \
        ((PIO_STACK_LOCATION) ((UCHAR *) (Irp) +                  \
            sizeof( IRP ) +                                       \
            ( (StackSize) * sizeof( IO_STACK_LOCATION )))); }

这个IRP和IO_STACK_LOCATION交互得有三个成员:

(Irp)->StackCount = (CCHAR) ((StackSize)) 设备栈的大小:
(Irp)->CurrentLocation = (CCHAR) ((StackSize) + 1) : 当前设备栈,放到了最顶端的下一个。
(Irp)->Tail.Overlay.CurrentStackLocation : 指向最顶层的IO_STACK_LOCATION, 从这里发现IO_STACK_LOCATION是从后往前来使用的。

二、 IO_STACK_LOCATION的操作

在使用IO_STACK_LOCATION的时候,我们先要获取这个这个程序,看下Windows是怎么获取的:

NTSTATUS
FASTCALL
IopfCallDriver(
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PIRP Irp
    )

{
    PIO_STACK_LOCATION irpSp;
    PDRIVER_OBJECT driverObject;
    NTSTATUS status;

    ASSERT( Irp->Type == IO_TYPE_IRP );

    Irp->CurrentLocation--;

    if (Irp->CurrentLocation <= 0) {
        KeBugCheckEx( NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR) Irp, 0, 0, 0 );
    }

    irpSp = IoGetNextIrpStackLocation( Irp );
    Irp->Tail.Overlay.CurrentStackLocation = irpSp;

    irpSp->DeviceObject = DeviceObject;

    driverObject = DeviceObject->DriverObject;

    PERFINFO_DRIVER_MAJORFUNCTION_CALL(Irp, irpSp, driverObject);

    status = driverObject->MajorFunction[irpSp->MajorFunction]( DeviceObject,
                                                              Irp );

    PERFINFO_DRIVER_MAJORFUNCTION_RETURN(Irp, irpSp, driverObject);

    return status;
}

#define IoGetNextIrpStackLocation( Irp ) (\
    (Irp)->Tail.Overlay.CurrentStackLocation - 1 )

在调用设备的分发函数之前,先使用IoGetNextIrpStackLocation是的IRP中的(Irp)->Tail.Overlay.CurrentStackLocation指向正确位置,因为初始化的时候是指向最后一个的末尾的,因此这里要前移一个。

在分发函数中,我们可以使用IoGetCurrentIrpStackLocation获取当前的IO_STACK_LOCATION,这个宏定义如下:

#define IoGetCurrentIrpStackLocation( Irp ) ( (Irp)->Tail.Overlay.CurrentStackLocation )

直接返回了IRP中的成员。

当设备栈中的设备完成处理之后,需要向下传递IRP,因此需要调用IoCallDriver,在上面我们看到过,IoCallDriver会调用IoGetNextIrpStackLocation下移设备栈的指针,因此我们需要对设备栈做如下之一的操作:

#define IoCopyCurrentIrpStackLocationToNext( Irp ) { \
    PIO_STACK_LOCATION irpSp; \
    PIO_STACK_LOCATION nextIrpSp; \
    irpSp = IoGetCurrentIrpStackLocation( (Irp) ); \
    nextIrpSp = IoGetNextIrpStackLocation( (Irp) ); \
    RtlCopyMemory( nextIrpSp, irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine)); \
    nextIrpSp->Control = 0; }

#define IoSkipCurrentIrpStackLocation( Irp ) \
    (Irp)->CurrentLocation++; \
    (Irp)->Tail.Overlay.CurrentStackLocation++;

IoCopyCurrentIrpStackLocationToNext 拷贝IO_STACK_LOCATION 成员到下一层。
IoSkipCurrentIrpStackLocation 上移一层,是的下次使用的时候仍旧使用当前的IO_STACK_LOCATION。

三、流程性说明

不管是I/O 管理器构建的IRP还是自己手动构建的IRP,对IRP的操作都有以下4种情况:

1、直接完成IRP,返回。
2、放入IRP队列中,调用StartIO完成IRP串行化。
3、传递IRP到下一层,由下一层(或者再下一层完成)并且不需要获得IRP完成的情况
4、传递IRP到下一层,同时需要得到获得下一层处理完IRP的信息(键盘过滤器等很多FilterDriver就这么干)

其实可以把第1点和第2点归为一起,都是本层处理,把第4点归结到第3点上,都是下一层处理,但是这两者之间的IO设备栈的处理有点差异。

接下来说明一个驱动程序的创建一般过程:
DriverEntry和AddDevice两个函数由即插即用管理器调用,DriverEntry里面完成分发函数的注册,AddDevice用来创建一个设备对象并且挂载到下一层设备对象上。一般的WDM驱动程序都有一个总线设备驱动程序,负责枚举和分配设备资源。为了说明问题,假设存在设备A和设备B,设备B挂载到设备A上,现在你要创建设备C也要挂载到A上,那么使用IoAttatchDeviceToDeviceStack函数挂载到A时,返回的是B的设备对象。也就是从上往下看IRP的传递是C->B->A的流向。到这里就可以讨论IO设备栈了!!!
回到一开始说的当收到一个IRP时,本层驱动需要做什么处理,4种情况处理IRP不同,如下:

  • 直接完成IRP:IoCompleteRequest()
  • 放入IRP队列:IoMarkIrpPending()和IoStartPacket()
  • 传递IRP到下一层:IoSkipCurrentIrpStackLocation()和IoCallDriver()
  • 需要获取完成信息:IoCopyCurrentIrpStackLocationToNext()和IoCallDriver()

     到这里也许很多驱动的资料都会这么讲,但为什么需要这么做??每一层设备对象都需要一个设备栈,用来保存上一层创建或者传递IRP的信息,使用IoGetCurrentIrpStackLocation()可以获得当前设备栈,也即获得里面的IRP信息。当前设备首先获得IRP处理的权利,可以完成,可以放入队列,也可以传递到下一层。而是用IoCallDriver完成IRP的传递,可以使用反汇编查看IoCallDriver()或者看DDK文档就会知道,IoCallDriver里面首先会将CurrentLocation减1(汇编指令为dec),然后再去和0进行判断比较跳转,当当前值比0大,那么会去将当前设备栈指针移动到前一个设备栈(结合stack结构的概念,先入后出)。
       那么如果当前设备不关心IRP的完成,那么也就不需要当前设备栈,那么为了让底层驱动处理这个IRP,就必须应该把当前设备栈信息告诉底层设备,可以采取两种方式,使用IoSkipCurrentIrpStackLocation()把指针拨回去(和IoCallDriver()方向相反),也就是下一层的设备栈其实为本层的设备栈(速度快,另外一种是拷贝的方式,见下面)。一般不是很关心的IRP都这么做,例如PnP的,电源管理的(记住,电源管理的传递IRP使用的是 PoCallDriver())。但是作为过滤器驱动,都不会放过Read和Write的,Rootkit不会,HIPS也不会(现在的攻防问题,也许不再是这么简单的Hook了,但是思想都是一样,看谁最先获得IRP的主控权)。那么IoCopyCurrentIrpStackLocationToNext()的好处就在于,把本层的设备栈拷贝到下一层,那么拷贝完之后呢??如何第一时间得到下一层完成了IRP,也就是数据已经有效了??

        可以使用同步或者异步方式,同步方式是等待IoCallDriver()返回,异步方式采用的是完成例程和事件通知的方式。一般都是采用完成例程+事件通知方式(具体实现可以参考一些DDK例子),若是在IRP总设置了完成例程,那么下一层驱动程序完成IRP后会直接去调用完成例程(回调函数概念),Hook就达到,你就可以在这个例程中为所欲为了(例如我直接从IRP中获得读缓冲区地址和长度,然后加密数据,想搞破坏的人可能会原封不动的把数据上传,但是他会备份一份,前面一直提到了IRP的创建,创建IRP的方式有以下几种:

  • IoAllocateIrp 常用,创建人一类型的IRP。(其实下面的方式都封装了这个函数),但一定记住了,需要IoFreeIrp(一般在完成函数里调用),否则内存泄露。
  • IoBuildAsynchronousFsdRequest,IoBuildSynchronousFsdRequest。创建 IRP_MJ_PNP, IRP_MJ_READ, IRP_MJ_WRITE,IRP_MJ_FLUSH_BUFFERS, IRP_MJ_SHUTDOWN这些类型的IRP,分同步和异步,异步IRP可以工作在任意设备上下文中。
  • IoBuildDeviceIoControlRequest 编写USB设备驱动时用过。创建 IRP_MJ_INTERNAL_DEVICE_CONTROL和IRP_MJ_DEVICE_CONTROL

你可能感兴趣的:(系统内核,操作系统,windows内核,系统内核,操作系统,windows内核,C/C++,驱动程序)