首页
社区
课程
招聘
[原创]IoSkipCurrentIrpStackLocation等IO栈处理API的一些探索
发表于: 2020-7-17 18:36 8318

[原创]IoSkipCurrentIrpStackLocation等IO栈处理API的一些探索

2020-7-17 18:36
8318

(以下内容适用于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剑人编辑 ,原因:
收藏
免费 3
支持
分享
最新回复 (7)
雪    币: 6977
活跃值: (1786)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
我记得深入解析windows操作系统中有讲这个
2020-7-17 19:04
0
雪    币: 740
活跃值: (952)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
3
TopC 我记得深入解析windows操作系统中有讲这个
IRP flow基本上靠点谱的书都有,我也参考了internals,但是这书只是讲了"driver must prepare the next I/O stack location that would be looked at by the next driver in line",但是对irp flow差一点点讲到内存规划上(原来认为既然IRP package是i/o manager申请的,而且是和各个层设备“一一对应”的关系,那显然应该是已经初始化好的)。没想到只是申请了栈的内存,在IRP头构造好了指针和索引,具体的内容还需要copy或者skip才行
2020-7-17 19:13
0
雪    币: 259
活跃值: (283)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
那个图片 io_stack_location3 是属于上层设备吗 下一层的io_stack_location里面 什么major_function都没有吗
2020-7-22 10:58
0
雪    币: 740
活跃值: (952)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
5
ZwCopyAll 那个图片 io_stack_location3 是属于上层设备吗 下一层的io_stack_location里面 什么major_function都没有吗
啥也没有……我看wrk里分配的代码,栈空间和irp头的分配是连续的,但是在调试时内存里过滤驱动接受到的stack上下一个structure都是0
2020-7-22 16:49
0
雪    币: 3188
活跃值: (2859)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
6
点赞点赞
2020-10-15 16:12
0
雪    币: 154
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
谢谢分享  
2021-3-20 15:46
0
雪    币: 42
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
感谢分享
2024-3-14 16:20
0
游客
登录 | 注册 方可回帖
返回
//