1 为什么要注册回调IoRegisterFsRegistrationChange?
注册回调是为了绑定文件系统的控制设备CDO。文件系统激活的时候会调用IoRegisterFileSystem,添加该文件系统的CDO到全局文件系统队列中。文件系统的CDO用来主要任务是修改整个驱动的内部配置,例如卷的mount,unmount。
2 为什么要绑定文件系统的CDO?
首先,过滤驱动需要过滤某个卷的文件的所有操作,比如C盘,那么过滤驱动必须先绑定C盘才可以对C盘文件的操作进行截获。而这些卷的 mount请求是通过IRP_MJ_FILE_SYSTEM_CONTROL发送给文件系统的CDO的。绑定文件系统CDO后我们的过滤驱动才可以拦截这个卷的mount请求,从而在我们的驱动中进行绑定。
由1和2可以知道,我们注册回调最终是为了拦截卷的mount信息,从而绑定卷。Sfilter中有2个绑定的函数,有人说为什么要绑2次,其实一次是绑定文件系统CDO,一次是绑定需要过滤的卷设备。
使用IoRegisterFsRegistrationChange的时候需要注意:
也许驱动的DriverEntry还没运行完,这个回调就发生了,所以要确保在调用这个函数IoRegisterFsRegistrationChange前,传入的参数已经得到了正确的赋值。
3 shadow device是什么?
姑且叫影子设备。就是在Create中避免IRP的重入。比如在Create中使用ZwCreate的时候,实质上文件系统会由此ZwCreate再次组建新的Create的IRP下来,我们再次拦截再次ZwCreate然后进入函数主体,继续ZwCreate。。。。就像小时候的故事:从前,山里有个庙,庙里有2个和尚,有一天小和尚让老和尚讲故事,于是老和尚说,从前,山里有个庙。。。。。。这样会没完没了,系统肯定没那老和尚能耗。
影子设备的过程:比如有个IRP是发给Device A的,其中用到了ZwCreate打开了文件C:\\fuct.txt,那么会有第二个IRP下来,如果还按以前的处理就会继续ZwCreate。但是这个时候如果用到影子设备,在ZwCreate的时候,我就不会传入参数(ZwCreate之前需要调用InitializeObjectAttributes初始化路径)C:\\fuct.txt,而是C-Shadow:\\fuct.txt。IO管理器会根据这个名字组建新的IRP发下来,但是这个时候是发给设备C-Shadow。C-Shadow是什么设备呢,就是一个影子设备。在Create中发现如果是发给影子设备的,那么我们可以直接下发IRP,避免了下面的操作ZwCreate。(C:是个例子,实际是\Device\Hard Volume1)
影子设备的使用:
1 创建卷的过滤设备A的时候,同时创建影子设备B
2 影子设备是不需要绑定的,只需要知道它是谁的影子
3 这个影子设备B的设备拓展中要保存过滤设备A,因为要知道下发IRP的目标
比如A的下层设备是C,那么影子设备需要知道是发往的C,所以需要保存A
4 StackSize文档中说是需要设置的,我没有设置依然没出问题。希望高手指点一下原因。
当然,XP之后是可以不用影子设备,直接调用IoCreateFileSpecifyDeviceObjectHint打开文件,这样不会有新的IRP下发,也不用担心重入。2000打上补丁以后也可以这么做。
4 获取文件的全路径
楚狂人的书中已经说了有3种办法,其中最常做的就是在IRP完成后,获取到文件的全路径。其中涉及2个函数,我想做过的人都会发生蓝屏:
ObQueryNameString:只可以在Create和Cleanup中使用,否则很容易导致死锁
IoVolumeDeviceToDosName:如果在大量的重入情况下也会导致死锁。
网上有提供IoVolumeDeviceToDosName + FileObject->FileName组合。但是看到死锁有就知道,如果在Create中调用了IoVolumeDeviceToDosName就很容易死了。但是为什么一定要在很容易重入的Create中调用呢?我的做法是在创建过滤设备的时候,调用一次该函数获取到该设备的DOS名,保存在设备拓展中,以后使用的时候,直接用保存的DOS名+ FileObject->FileName。没有大量的频繁的调用IoVolumeDeviceToDosName也就减小了死锁的可能性。
以后如果有时间,也会针对新人学习的一些疑惑进行更新,也许很肤浅,只是希望对新人有个帮助。
[课程]Linux pwn 探索篇!