[原创]IoSkipCurrentIrpStackLocation等IO栈处理API的一些探索
发表于:
2020-7-17 18:36
8318
[原创]IoSkipCurrentIrpStackLocation等IO栈处理API的一些探索
(以下内容适用于windows10 x64 内核) 最近新学驱动,一上来肯定是经典的过滤驱动走起。却在IoSkipCurrentIrpStackLocation和IoCopyCurrentIrpStackLocationToNext上犯了难。 在一番找各种资料&看了wrk源码以后,把一些自己走叉的不太好找的误区记录下来。可能不一定准确,还请各位大佬赐教。 我的核心问题有两个: 1)IoCopyCurrentIrpStackLocationToNext覆盖了下一层IO栈,不是等于覆盖了下一层的运行上下文? 2)为什么要IoSkipCurrentIrpStackLocation,而不让栈帧的指针直接由IoCallDriver指向下一层? 实际上最后发现是一个问题 0. 有关IRP包的生成 一切的一切,要从I/O请求包被创建的那一刻开始讲起。 IRP是I/O请求包的核心(https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_irp ),在这个struct被初始化之后, 会紧接着分配和最上层Device的StackSize相等个数的IO_STACK_LOCATION(https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_io_stack_location )[4]像这样 很多文章都说每一个StackLocation都代表了一个Device。这么说虽然没错,但其实歧义很大。因为这些StackLocation在调用的过程中并不是一一对应的。 这也是我感到困惑的核心原因——如果我调用了IoCopyCurrentIrpStackLocationToNext,岂不是把下一层的Stack覆盖了?那为什么程序还能正常运行呢?1. 有关IoCallDriver的运行机制 在讨论IoSkipCurrentIrpStackLocation和IoCopyCurrentIrpStackLocationToNext这两个函数之前,我先简单介绍一下IoCallDriver这个函数干了什么,是怎么把参数往底层“传”的 通过参考wrk1.2,IoCallDriver的流程请见WRK代码4.1 总而言之,IoCallDriver做了三件事[1]
IoCallDriver做的前两件事理论上来说可以看成一件——IRP头中的CurrentLocation是StackLocation的索引,而IRP尾中的CurrentStackLocation是StackLocation的地址。 所以实际上干了两件事:1)把IO栈帧往底层移了一位;2)设置了下个IO栈的DeviceObject为传入的设备,并且调用这个设备传入的MajorFunction分发函数2. 关于IoSkipCurrentIrpStackLocation和IoCopyCurrentIrpStackLocationToNext 2.1 IoSkipCurrentIrpStackLocation 这一段的源码wrk里写得很清晰
IoCallDriver把IO栈帧往底层移一位,而IoSkipCurrentIrpStackLocation企图预先把IO栈帧往高层移一位,这样当IoCallDriver去调用下一层的分发函数时,获得的还是当前的栈内容。2.2 IoCopyCurrentIrpStackLocationToNext IoCopyCurrentIrpStackLocationToNext本质上就是个RtlCopyMemory,请见wrk
从IO_STACK_LOCATION可以看到,CopyCurrentIrpStackLocation覆盖了除了这两个字段以外的全部内容
我们知道,由于SkipCurrentIrpStackLocation本质上是把当前的栈帧在下次调用时重新利用,所以在需要设置回调函数时,需要一个不同于当前栈的地址。 所以当分发函数需要回调,必须要使用CopyCurrentIrpStackLocation来让下一层栈帧的CompletionRoutine设置为本层的回调函数。 有的朋友会问,既然SkipCurrent本质上重新利用了当前的栈,那我正常情况下也是用CopyCurrentIrpStackLocation有没有影响呢? 其实显然是可以的,但需要再手动设置一下Context和CompletionRoutine。一般倾向使用SkipCurrent完全是处于性能问题[3]3. IO_STACK_LOCATION在内存中初始化的问题 回过头来,这篇文章最主要想解决开篇的两个问题 1)IoCopyCurrentIrpStackLocationToNext覆盖了下一层IO栈,不是等于覆盖了下一层的运行上下文? 2)为什么要IoSkipCurrentIrpStackLocation,而不让栈帧的指针直接由IoCallDriver指向下一层? 为了搞清这个情况,我在一个键盘过滤驱动里写了一段代码进行调试
c2pDispatchGeneral是一段典型的分发函数,DbgBreakPoint()中断后,在windbg中观察rax的值和内存情况,就知道IO_STACK_LOCATION的情况了。 显然,这样的键盘过滤驱动的IO_STACK_LOCATION应该至少有两层 然而在windbg中我们得出了这样的结果:
不管是这个分发函数的上一个IO_STACK_LOCATION,还是分发函数的下一个IO_STACK_LOCATION,竟然值全部为0! 也就是说,在IRP包初始化申请空间时,IO管理器只申请了足够长的内存,以及初始化了第一个IO_STACK_LOCATION。 这样一来,为下一层构造一个合适的IO栈是高层驱动必须要做的事情。因为IO管理器并没有为我们做这一切。 这也解释了一个问题——IO管理器申请的栈空间只是一般意义上(即不在申请中构造新的包/驱动层)最多使用栈空间。 第一次看到这里的,特别是每一个设备和IO栈都要“一一对应”的时候,特别容易理解成每一层分发函数都独属一个栈空间。实际上这里的“栈”使用非常自由,你不一定要用完IO管理器申请的所有栈空间(即全部使用Copy复制下去)。引用一个资料[2]
那么本文开头提到的两个问题的答案,自然也呼之欲出了 1)IoCopyCurrentIrpStackLocationToNext覆盖了下一层IO栈,不是等于覆盖了下一层的运行上下文? ——下一层IO栈没有任何内容,需要上一层分发函数自己构造。IoCallDriver已经帮我们填好了Device,我们只用考虑有没有回调函数或者需要修改的字段,来选择Skip或者Copy就好。 2)为什么要IoSkipCurrentIrpStackLocation,而不让栈帧的指针直接由IoCallDriver指向下一层? ——因为下一层没有任何内容,完全需要自己来构造。4. 参考内容 4.1 WRK IoCallDriver
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-7-17 18:36
被NONAME剑人编辑
,原因: