12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
查看: 373|回复: 0

IoSkipCurrentIrpStackLocation等IO栈处理API的一些探索

[复制链接]

63

主题

63

帖子

136

积分

实习版主

Rank: 7Rank: 7Rank: 7

积分
136
发表于 2020-8-10 21:07:50 | 显示全部楼层 |阅读模式
(以下内容实用于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]
(1). 递减了IRPCurrentLocation
(2). 获得下个位置的StackLocation,并设置到IRPCurrentStackLocation
(3). 设置StackLocation的DeviceObject,并调用该DeviceObject的DriverObject中该StackLocation指定的MajorFunction。

IoCallDriver做的前两件事理论上来说可以当作一件——IRP头中的CurrentLocation是StackLocation的索引,而IRP尾中的CurrentStackLocation是StackLocation的地址。
所以实际上干了两件事:1)把IO栈帧往底层移了一位;2)设置了下个IO栈的DeviceObject为传入的设备,并且调用这个设备传入的MajorFunction分发函数

2. 关于IoSkipCurrentIrpStackLocation和IoCopyCurrentIrpStackLocationToNext

2.1 IoSkipCurrentIrpStackLocation
这一段的源码wrk里写得很清晰
[C] 纯文本查看 复制代码#define IoSkipCurrentIrpStackLocation( Irp ) { \     (Irp)->CurrentLocation++; \     (Irp)->Tail.Overlay.CurrentStackLocation++; }
IoCallDriver把IO栈帧往底层移一位,而IoSkipCurrentIrpStackLocation企图预先把IO栈帧往高层移一位,这样当IoCallDriver去调用下一层的分发函数时,获得的还是当前的栈内容。
2.2 IoCopyCurrentIrpStackLocationToNext
IoCopyCurrentIrpStackLocationToNext本质上就是个RtlCopyMemory,请见wrk
[C] 纯文本查看 复制代码#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; }
从IO_STACK_LOCATION可以看到,CopyCurrentIrpStackLocation覆盖了除了这两个字段以外的全部内容
  PIO_COMPLETION_ROUTINE CompletionRoutine;
  PVOID                  Context;

我们知道,由于SkipCurrentIrpStackLocation本质上是把当前的栈帧在下次调用时重新利用,所以在必要设置回调函数时,必要一个不同于当前栈的地址。
所以当分发函数必要回调,必须要使用CopyCurrentIrpStackLocation来让下一层栈帧的CompletionRoutine设置为本层的回调函数。
有的朋友会问,既然SkipCurrent本质上重新利用了当前的栈,那我正常情况下也是用CopyCurrentIrpStackLocation有没有影响呢?
其实显然是可以的,但必要再手动设置一下Context和CompletionRoutine。一样平常倾向使用SkipCurrent完满是处于性能题目[3]

3. IO_STACK_LOCATION在内存中初始化的题目
回过头来,这篇文章最主要想解决开篇的两个题目
1)IoCopyCurrentIrpStackLocationToNext覆盖了下一层IO栈,不是等于覆盖了下一层的运行上下文?
2)为什么要IoSkipCurrentIrpStackLocation,而不让栈帧的指针直接由IoCallDriver指向下一层?
为了搞清这个情况,我特地在一个键盘过滤驱动里写了一段代码进行调试

[C++] 纯文本查看 复制代码NTSTATUS c2pDispatchGeneral(PDEVICE_OBJECT DeviceObject, PIRP irp) {    DbgPrint("Other Dispatch");    PIO_STACK_LOCATION irpStack;    irpStack = IoGetCurrentIrpStackLocation(irp);    DbgBreakPoint();    IoSkipCurrentIrpStackLocation(irp);    IoCallDriver(((PC2P_DEV_EXT)DeviceObject->DeviceExtension)->LowerDeviceObject, irp);    return STATUS_SUCCESS;}
c2pDispatchGeneral是一段典型的分发函数,DbgBreakPoint()中断后,在windbg中观察rax的值和内存情况,就知道IO_STACK_LOCATION的情况了。
显然,这样的键盘过滤驱动的IO_STACK_LOCATION应该至少有两层
然而在windbg中我们得出了这样的结果:
[Asm] 纯文本查看 复制代码0: kd> dt nt!_IO_STACK_LOCATION   +0x000 MajorFunction    : UChar   +0x001 MinorFunction    : UChar   +0x002 Flags            : UChar   +0x003 Control          : UChar   +0x008 Parameters       :    +0x028 DeviceObject     : Ptr64 _DEVICE_OBJECT   +0x030 FileObject       : Ptr64 _FILE_OBJECT   +0x038 CompletionRoutine : Ptr64     long   +0x040 Context          : Ptr64 Void0: kd> dd raxffffa486`75b52548  0001000e 00000000 00000000 00000000ffffa486`75b52558  00000004 00000000 000b0008 00000000ffffa486`75b52568  00000000 00000000 780d6a50 ffffa486ffffa486`75b52578  771a76b0 ffffa486 00000000 00000000ffffa486`75b52588  00000000 00000000 00000000 00000000ffffa486`75b52598  00000000 00000000 00000000 00000000ffffa486`75b525a8  00000000 00000000 00000000 00000000ffffa486`75b525b8  00000000 00000000 00000000 000000000: kd> dd rax-0x40ffffa486`75b52508  00000000 00000000 00000000 00000000ffffa486`75b52518  00000000 00000000 00000000 00000000ffffa486`75b52528  00000000 00000000 00000000 00000000ffffa486`75b52538  00000000 00000000 00000000 00000000ffffa486`75b52548  0001000e 00000000 00000000 00000000ffffa486`75b52558  00000004 00000000 000b0008 00000000ffffa486`75b52568  00000000 00000000 780d6a50 ffffa486ffffa486`75b52578  771a76b0 ffffa486 00000000 000000000: kd> dd rax+0x40ffffa486`75b52588  00000000 00000000 00000000 00000000ffffa486`75b52598  00000000 00000000 00000000 00000000ffffa486`75b525a8  00000000 00000000 00000000 00000000ffffa486`75b525b8  00000000 00000000 00000000 00000000ffffa486`75b525c8  00000000 00000000 00000000 00000000ffffa486`75b525d8  00000000 00000000 00000000 00000000ffffa486`75b525e8  00000000 00000000 00000000 00000000ffffa486`75b525f8  00000000 00000000 00000000 00000000不管是这个分发函数的上一个IO_STACK_LOCATION,还是分发函数的下一个IO_STACK_LOCATION,竟然值全部为0!
也就是说,在IRP包初始化申请空间时,IO管理器只申请了足够长的内存,以及初始化了第一个IO_STACK_LOCATION。
这样一来,为下一层构造一个符合的IO栈是高层驱动必须要做的事变。因为IO管理器并没有为我们做这一切。
这也解释了一个题目——IO管理器申请的栈空间只是一样平常意义上(即不在申请中构造新的包/驱动层)最多使用栈空间。
第一次看到这里的,特别是每一个设备和IO栈都要“逐一对应”的时候,特别容易理解成每一层分发函数都独属一个栈空间。实际上这里的“栈”使用非常自由,你不肯定要用完IO管理器申请的所有栈空间(即全部使用Copy复制下去)。引用一个资料[2]
You'll notice that the array of IO_STACK_LOCATIONs contains an entry at the very bottom that won't be used in this scenario. In fact, if drivers underneath us play the same trick, there might be more than one location that won't be used. That's not a problem, though—it just means that something allocated more stack locations than it needed to. It's not a problem that the stack gets unwound a little bit quicker during completion processing, either. IoCompleteRequest doesn't use any absolute indices or pointers when it unwinds the stack. It just starts at whatever the current location is when it gains control and works its way upward calling completion routines. All the completion routines that got installed will get called, and the then-current stack locations will be the ones that their drivers were expecting to work with.

那么本文开头提到的两个题目的答案,自然也呼之欲出了

1)IoCopyCurrentIrpStackLocationToNext覆盖了下一层IO栈,不是等于覆盖了下一层的运行上下文?
——下一层IO栈没有任何内容,必要上一层分发函数本身构造。IoCallDriver已经帮我们填好了Device,我们只用思量有没有回调函数或者必要修改的字段,来选择Skip或者Copy就好。

2)为什么要IoSkipCurrentIrpStackLocation,而不让栈帧的指针直接由IoCallDriver指向下一层?
——因为下一层没有任何内容,完全必要本身来构造。


4. 参考内容
4.1 WRK IoCallDriver
[C] 纯文本查看 复制代码NTSTATUSFORCEINLINEIopfCallDriver(    IN PDEVICE_OBJECT DeviceObject,    IN OUT PIRP Irp    )/*++Routine Description:    This routine is invoked to pass an I/O Request Packet (IRP) to another    driver at its dispatch routine.Arguments:    DeviceObject - Pointer to device object to which the IRP should be passed.    Irp - Pointer to IRP for request.Return Value:    Return status from driver's dispatch routine.--*/{    PIO_STACK_LOCATION irpSp;    PDRIVER_OBJECT driverObject;    NTSTATUS status;    //    // Ensure that this is really an I/O Request Packet.    //    ASSERT( Irp->Type == IO_TYPE_IRP );    //    // Update the IRP stack to point to the next location.    //    Irp->CurrentLocation--;    if (Irp->CurrentLocation Tail.Overlay.CurrentStackLocation = irpSp;    //    // Save a pointer to the device object for this request so that it can    // be used later in completion.    //    irpSp->DeviceObject = DeviceObject;    //    // Invoke the driver at its dispatch routine entry point.    //    driverObject = DeviceObject->DriverObject;    //    // Prevent the driver from unloading.    //    status = driverObject->MajorFunction[irpSp->MajorFunction]( DeviceObject,                                                              Irp );    return status;}

4.2 其他参考文献
[1] AntBean [原创]驱动入门:从WRK看IRP 理论篇 , https://bbs.pediy.com/thread-130876.htm
[2] https://www-user.tu-chemnitz.de/~heha/oney_wdm/ch05e.htm
[3] https://community.osr.com/discussion/220685/ioskipcurrentirpstacklocation-and-iocopycurrentirpstacklocationtonext
[4] Windows内核原理与实现 潘爱民

风扫春残雪@52pojie/NONAME剑人@pediy

来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
楼主热帖
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|12558网页游戏私服论坛 |网站地图

GMT+8, 2024-5-22 08:35 , Processed in 0.124023 second(s), 31 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表