使用Minifilter使用一个文件目录保护的驱动,用户层自定义保护目录,收到特定目录下的文件行为时,驱动会通知用户程序,并支持根据用户决策放行或者拒绝文件行为。
程序分为驱动层和应用层
驱动层使用Minifilter框架, 可以接收用户层传递(使用FilterSendMessage函数)的目录信息,并且设置了IRP_MJ_CREATE的前回调函数,在回调函数中判断访问的目录或者文件是否在保护目录下,如果是写文件目录或者删除文件目录的情况,根据create_disposition和create_options和mask标志来判断,并且可以获取到进程名称,那么就挂起这个请求,将请求信息插入到全局的请求链表中,并唤醒自己创建的系统线程,系统线程负责使用FltSendMessage(有超时机制,略大于60s)将请求发往用户层,用户层使用FilterGetMessage函数(使用event )来驱动发过来的获取请求,用户程序根据信息展示一个GUI界面,可以选择允许或者拒绝,存在60s的超时机制,超时则回复驱动为拒绝请求,如果允许,驱动收到请求后就完成之前的请求,驱动完成当前请求后会继续获取链表是否存在请求,如果没有请求,那么等待一个事件,precreate回调函数插入信息到链表后会触发这个event,并且在驱动Unload的函数中也会触发这个event。
用户层启动后,根据命令行参数转换DOS路径(如C:\123)为NT路径(\Device\HarddiskVolume4\123),因为在驱动中precreate的回调函数中获得的文件名是NT格式的,用户层使用FilterSendMessage将 1个或者多个路径信息传递到驱动后,会发送一个开始保护的消息,此时precreate才开始工作。
用户层退出时会触发驱动层的DisConnect函数,此时驱动会取消保护,删除目录全局链表中的信息,并且完成已经保存到请求全局链表中的请求(拒绝访问该请求)。
先判断命令类型,如果是目录信息,那么判断传递的目录是否不超过固定的缓冲区(用异常处理框架包裹处理代码),如果路径合法,那么获取独占获取共享锁global.dir_lock,接着插入到链表global.head_dir中。
如果是开始保护的命令,那么就设置global.need_protect标志为true。
如果是来自于内核的请求那么不处理,如果请求来自于发起connect请求的进程也不处理,如果此时need_protect标志为FALSE也不处理。
接着使用Data->Iopb->Parameters.Create.Options分别获取高8位和低24位作为create_disposition和create_options变量,使用Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess记录mask标志,根据这几个标志判断是否是写文件目录或者删除行为。
使用FltGetFileNameInformation获取本次要操作的文件或者目录,使用共享读锁获取global.dir_lock,判断要操作的文件对象是否在这个目录下,在这个过程要处理一些边界情况,比如保护的目录是C:\123那么需要放过C:\123.txt还有C:\123456目录还有C:\123文件,放开获取到的锁。
如果需要保护,那么就使用ZwQueryInformationProcess获取进程名称(如果获取失败那么放过请求,比如经过测试system进程获取不到进程名称),申请内存空间后使用独占锁获取global.minifilter_request_lock,在链表global.head_minirequest插入本次请求,放开获取到的锁。
设置当前函数回值为FLT_PREOP_PENDING,唤醒创建的系统线程KeSetEvent(&global.event_process_request, IO_NO_INCREMENT, FALSE);
在系统线程中先获取锁后取出一个请求信息,使用FltSendMessage发送给用户程序,调用FltSendMessage时需要传递reply的缓冲区,此时需要注意reply需要包含一个头结构FILTER_REPLY_HEADER,后追加存放数据的结构(本次使用一个枚举类型REPLY_DATA,数据大小为4字节),用户层调用FilterReplyMessage回复消息时也需要包含这个头结构内容(头结构内容来自于用户层调用到FilterGetMessage获取到的buff内容,此结构中包含一个Minifilter框架自带的MessageId信息),需要注意的是驱动FltSendMessage收到用户层的回复后,reply_len被修改为了真正的返回数据结构的长度(sizeof(REPLY_DATA)),缓冲区直接指向的就是返回数据,而不是头结构+回复信息,,即结构体USER_REPLY 中包含了头结构以及REPLY_DATA,发送时使用USER_REPLY的内存,而收到回复后此时USER_REPLY 内存中存放的REPLY_DATA,因此上面代码中使用union结构体。
收到返回信息(可能是由于用户层点击了允许或者拒绝按钮,或者用户层默认拒绝,或者是用户层主动退出或崩溃导致disconnect断开连接),只有当返回信息是允许是才放过这个请求,否则拒绝这个请求,具体实现在CompleteFirstRequest函数中。
如果找不到请求,那么返回FALSE,如果完成请求那么返回TRUE,这样设计方便后续多次调用该函数拒绝链表中的所有缓存的请求(在用户程序退出disconnect发生时)。
需要关闭客户端的port,设置need_protect标志位为FALSE,清理dir信息,调用CompleteFirstRequest拒绝缓存的请求。
用户层初始化代码完成后,就开始接收驱动拦截到的请求。
收到请求后弹框让用户选择拒绝允许,然后调用FilterReplyMessage返回给驱动,如下图:

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2025-9-20 21:07
被0346954编辑
,原因: 添加代码附件