这两个星期天天都在搞这个,无数个错误被修正过后,做些笔记以备以后查阅。
为了实现机密文档的保护,文档管理系统必须对所有打开的文件进行监控,因此我们的过滤驱动需要知道当前运行时刻有几个文件被打开,它们分别被打开了几次(同一个文件可能被多次打开)。
用户态和内核态之间的关系
用户态调用CreateFile函数,子系统将请求发给IO管理器,IO管理器会用该函数的
参数生成一个IRP,将此IRP发往底层。过滤驱动截获到IRP,开始对其操作。过滤驱动
操作完了之后会继续往下发,就是调用更下层驱动程序。当下层所有操作完成之后,底
层的完成信息会向上反。这有点像函数的嵌套调用,从最内层函数开始一级一级的返回,
但这只是像。下面是过滤驱动截获返回的CreateFile时的操作。
处理IRP_MJ_CREATE的核心代码():
{
//返回的FsContext为空时,不予以处理
if(FileObject->FsContext == NULL)
{
////////////////////////////////////////DbgPrint("\n\n\n SfCreate: FileObject->FsContext 为 NULL\n\n\n");
status = Irp->IoStatus.Status;
DbgPrint("\n SfCreate() : 建立链表的第一个节点时遭遇 FsContext 为 NULL \n",status);
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return status;
}
//第一次打开链表时,链表为空,上面for语句无法运行
if(IsListEmpty(&my_list_head))
{
PFILE_CONTEXT FileCtx = (PFILE_CONTEXT)ExAllocatePoolWithTag(PagedPool,sizeof(FILE_CONTEXT),SFLT_POOL_TAG);
if(FileCtx == NULL)
return STATUS_INSUFFICIENT_RESOURCES;
FileCtx->FsContext = FileObject->FsContext;
FileCtx->RefCount = 1;
ExInterlockedInsertTailList(&my_list_head,&FileCtx->ListEntry,&my_list_lock);//多线程安全插入方式
DbgPrint("\n\n SfCreate() : 创建链表的第一个节点:\n ");
DbgPrint("SfCreate() : 第一个节点中 FileCtx->FsContext: %x , FileCtx->RefCount: %u \n\n\n",FileCtx->FsContext,FileCtx->RefCount);
status = Irp->IoStatus.Status;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return status;
}
//搜索链表,查看文件是否已经打开(链表非空时,此搜索才可以进行)
for(p=my_list_head.Flink; p!=&my_list_head; p=p->Flink)
{
PFILE_CONTEXT Elem = CONTAINING_RECORD(p,FILE_CONTEXT,ListEntry);
//文件已打开,计数器加一
if(Elem->FsContext == FileObject->FsContext)
{
KIRQL irql;
DbgPrint("\n\n\nSfCreate() : 遇到已打开过的文件 , 打印Elem节点信息如下 :\n ListEntry: %s, FsContext: %x, RefCount: %u\n",Elem->ListEntry,Elem->FsContext,Elem->RefCount);
if( FileObject->FsContext == NULL)
DbgPrint( "SfCreate() : FileObject->FsContext 为 空");
KeAcquireSpinLock(&my_list_lock,&irql);
Elem->RefCount += 1;
KeReleaseSpinLock(&my_list_lock,irql);
////////////////////////////////////////DbgPrint("\n文件第 %u 次打开\n",Elem->RefCount);
status = Irp->IoStatus.Status;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return status;
}
}
//文件未打开,填充节点并插入链表
{
//节点内存分配
PFILE_CONTEXT FileCtx = (PFILE_CONTEXT)ExAllocatePoolWithTag(PagedPool,sizeof(FILE_CONTEXT),SFLT_POOL_TAG);
if(FileCtx == NULL)
{
DbgPrint("\n\n SfCreate() : 文件第一次打开时,内存分配失败");
return STATUS_INSUFFICIENT_RESOURCES;
}
//节点内容填充
FileCtx->FsContext = FileObject->FsContext;
FileCtx->RefCount = 1;
//插入节点到链表
ExInterlockedInsertTailList(&my_list_head,&FileCtx->ListEntry,&my_list_lock);//多线程安全插入方式
}
}
大体思路说明:
获取文件对象,确认它是对一个文件的操作(不是就就忽略,直接返回)。然后获取其FsContext,这是文件的全局唯一标识,一个文件不管打开多少次,它只有一个FsContext,不同文件的FsContext也不一样。如果链表为空,直接用这个FsContext填充数据结构,创立一个节点,然后插入链表,之后返回。如果链表不为空,则遍历搜索链表,如果查找到FsContext值一样的节点则说明该文件打开过,只需将该节点数据结构中的RefCount++。如果链表中没有搜索到相应节点,那么就要新建一个节点插入链表。
既然监控就不能只监控打开的,自然也要监控关闭的。
当关闭一个文件窗口的时候,IO管理器会发送一个IRP_MJ_CLEANUP。在处理这个消息的时候,我们需要做的仅仅是从链表中找到那个文件,将其RefCount–。当RefCount值为0的时候,IO管理器就会发送一个IRP_MJ_CLOSE,截获到这个消息,我们就可以将这个文件对应的节点从链表中删除了。
OK!That’s all.
关键用法
A.链表的遍历搜索
if(IsListEmpty(&my_list_head)) { 插入第一个节点 返回 }
for(p=my_list_head.Flink; p!=&my_list_head; p=p->Flink)
从循环体内推出说明没找到,这就需要我们新建节点了
关键是要优先处理空链表的情况
B.链表的插入删除操作
ExInterlockedInsertTailList(&my_list_head,&FileCtx->ListEntry,&my_list_lock);
删除LST_ENTRY p所指向的节点,第一个参数需要用前一个节点的LIST_ENTRY
ExInterlockedRemoveHeadList(p->Blink,&my_list_lock);
C.IRQL锁与互斥体
KeAcquireSpinLock(&my_list_lock,&Irql);
Elem->RefCount -= 1;
KeReleaseSpinLock(&my_list_lock,Irql);
它通过提高IRQL来使其他线程无法执行,解决同步问题
ExAcquireFastMutex( &gSfilterAttachLock );
ExReleaseFastMutex( &gSfilterAttachLock );
这个要相对轻量级
