基于Minifilter实现目录保护软件,自定义保护目录,用户可选择是否允许文件行为

admin 2025-12-14 23:00:31 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍了一个基于Minifilter框架实现的目录保护软件,通过驱动层拦截文件操作并通知用户层决策。软件可动态配置保护目录,拦截创建、删除等文件操作,用户可选择允许或拒绝。文章详细说明了驱动层和应用层的实现原理,包括消息传递机制、请求处理流程和超时机制。该软件为Windows系统提供了灵活的目录保护方案,适用于需要控制特定目录访问的场景。 综合评分: 88 文章分类: 应用安全,安全工具,数据安全,安全建设,终端安全


cover_image

基于Minifilter实现目录保护软件,自定义保护目录,用户可选择是否允许文件行为

0346954

看雪学苑

2025年10月20日 18:00 上海

使用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函数,此时驱动会取消保护,删除目录全局链表中的信息,并且完成已经保存到请求全局链表中的请求(拒绝访问该请求)。

驱动层接收用户层的FilterSendMessage的代码如下:

NTSTATUS
DirProtectMiniMessage(
    __in PVOID ConnectionCookie,
    __in_bcount_opt(InputBufferSize) PVOID InputBuffer,
    __in ULONG InputBufferSize,
    __out_bcount_part_opt(OutputBufferSize,*ReturnOutputBufferLength) PVOID OutputBuffer,
    __in ULONG OutputBufferSize,
    __out PULONG ReturnOutputBufferLength
    )
{
    PAGED_CODE();

    UNREFERENCED_PARAMETER(InputBuffer);
    UNREFERENCED_PARAMETER(InputBufferSize);

    UNREFERENCED_PARAMETER(ReturnOutputBufferLength);
    UNREFERENCED_PARAMETER( ConnectionCookie );
    UNREFERENCED_PARAMETER( OutputBufferSize );
    UNREFERENCED_PARAMETER( OutputBuffer );

    COMMAND_HEAD* head = (COMMAND_HEAD*)InputBuffer;
    WCHAR* dir_dos = NULL;
    WCHAR* dir_nt = NULL;
    NTSTATUS status = STATUS_INVALID_PARAMETER;
    DIR_INFO* info = NULL;
    do
    {
        if (NULL == head)
        {
            break;
        }
        __try
        {
            if (ENUM_DIRINFO == head->command_type)
            {
                if (InputBufferSize == sizeof(COMMAND_MESSAGE_DIR))
                {
                    COMMAND_MESSAGE_DIR* dir = (COMMAND_MESSAGE_DIR*)InputBuffer;
                    if (NULL == dir || L'\0' == dir->protectdir_dos[0] || L'\0' == dir->protectdir_dos[0])
                    {
                        break;
                    }

                    size_t dos_len = wcslen(dir->protectdir_dos) * sizeof(WCHAR);
                    size_t nt_len = wcslen(dir->protectdir_nt) * sizeof(WCHAR);
                    if (dos_len >= sizeof(dir->protectdir_dos) || nt_len >= sizeof(dir->protectdir_nt))
                    {
                        status = STATUS_INVALID_PARAMETER;
                        break;
                    }

                    dir_dos = ExAllocatePoolWithTag(NonPagedPool, dos_len, DIR_PROTECT_POOL_TAG);
                    if (NULL != dir_dos)
                    {
                        dir_nt = ExAllocatePoolWithTag(NonPagedPool, nt_len, DIR_PROTECT_POOL_TAG);
                    }
                    if (NULL != dir_dos && NULL != dir_nt)
                    {
                        info = ExAllocatePoolWithTag(NonPagedPool, sizeof(DIR_INFO), DIR_PROTECT_POOL_TAG);
                        if (NULL != info)
                        {
                            memcpy(dir_dos, dir->protectdir_dos, dos_len);
                            info->dir_dos.Buffer = dir_dos;
                            info->dir_dos.Length = info->dir_dos.MaximumLength = (USHORT)dos_len;

                            memcpy(dir_nt, dir->protectdir_nt, nt_len);
                            info->dir_nt.Buffer = dir_nt;
                            info->dir_nt.Length = info->dir_nt.MaximumLength = (USHORT)nt_len;

                            if (TRUE == ExAcquireResourceExclusiveLite(&global.dir_lock, TRUE))
                            {
                                InsertTailList(&global.head_dir, &info->list);
                                ++global.dir_count;
                                ExReleaseResourceLite(&global.dir_lock);
                                status = STATUS_SUCCESS;
                            }
                        }
                        else
                        {
                            status = STATUS_INSUFFICIENT_RESOURCES;
                        }
                    }
                    else
                    {
                        status = STATUS_INSUFFICIENT_RESOURCES;
                    }
                }
            }
            else if (ENUM_START_PROTECT == head->command_type)
            {
                global.need_protect = TRUE;
                status = STATUS_SUCCESS;
            }
        }
        __except(EXCEPTION_EXECUTE_HANDLER)
        {
            status = STATUS_INVALID_PARAMETER;
        }

    } while (0);

    if (!NT_SUCCESS(status))
    {
        if (NULL != dir_dos)
        {
            ExFreePool(dir_dos);
            dir_dos = NULL;
        }
        if (NULL != dir_nt)
        {
            ExFreePool(dir_nt);
            dir_nt = NULL;
        }
        if (NULL != info)
        {
            ExFreePool(info);
            info = NULL;
        }
    }
    return status;
}

1.先判断命令类型,如果是目录信息,那么判断传递的目录是否不超过固定的缓冲区(用异常处理框架包裹处理代码),如果路径合法,那么获取独占获取共享锁global.dir_lock,接着插入到链表global.head_dir中。

2.如果是开始保护的命令,那么就设置global.need_protect标志为true。

precreate的回调函数

FLT_PREOP_CALLBACK_STATUS
DirProtectPreCreate(
    __inout PFLT_CALLBACK_DATA Data,
    __in PCFLT_RELATED_OBJECTS FltObjects,
    __deref_out_opt PVOID* CompletionContext
)
{
    NTSTATUS status;
    PFLT_FILE_NAME_INFORMATION name_info = NULL;

    FLT_PREOP_CALLBACK_STATUS call_status = FLT_PREOP_SUCCESS_NO_CALLBACK;

    UNREFERENCED_PARAMETER(FltObjects);
    UNREFERENCED_PARAMETER(CompletionContext);

    UNREFERENCED_PARAMETER(Data);

    PAGED_CODE();

    if (KernelMode == Data->RequestorMode)
    {
        return call_status;
    }

    if (FALSE == global.need_protect)
    {
        return call_status;
    }

    if (FALSE == FLT_IS_IRP_OPERATION(Data))
    {
        return call_status;
    }

    ULONG pid = FltGetRequestorProcessId(Data);
    if (pid == global.client_pid)
    {
        return call_status;
    }

    void* nbuf = NULL;
    ULONG nlen = 0;
    WCHAR* nptr = NULL;

    MINI_REQUEST* mini_request = NULL;
    SYS_2_USER* sys_2_user = NULL;

    ULONG create_disposition = (Data->Iopb->Parameters.Create.Options >> 24) & 0xFF; // high 8 bit
    ULONG create_options = Data->Iopb->Parameters.Create.Options & 0x00FFFFFF;       // low 24 bit
    BOOLEAN is_create_operation = (create_disposition == FILE_CREATE) || (create_disposition == FILE_OPEN_IF) || (create_disposition == FILE_OVERWRITE_IF);
    ACCESS_MASK mask = Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess;

    ASK_REASON reason = REASON_NO_REASON;
    if (is_create_operation)
    {
        if (create_options & FILE_DIRECTORY_FILE)
        {
            if (mask & DELETE)
                reason = REASON_DELETE_DIR;
            else
                reason = REASON_CREATE_DIR;
        }
        else
        {
            if (mask & DELETE)
                reason = REASON_DELETE_FILE;
            else
                reason = REASON_CREATE_FILE;
        }
    }

    if (REASON_NO_REASON == reason)
    {
        if (create_options & FILE_DELETE_ON_CLOSE)
        {
            if (create_options & FILE_NON_DIRECTORY_FILE)
                reason = REASON_DELETE_FILE;
            else
                reason = REASON_DELETE_DIR;
        }
    }
    if (REASON_NO_REASON == reason)
    {
        if (mask & DELETE)
        {
            if (create_options & FILE_DIRECTORY_FILE)
                reason = REASON_DELETE_DIR;
            else
                reason = REASON_DELETE_FILE;
        }
    }

    status = FltGetFileNameInformation(Data,
        FLT_FILE_NAME_NORMALIZED |
        FLT_FILE_NAME_QUERY_DEFAULT,
        &name_info);
    if (REASON_NO_REASON != reason && NT_SUCCESS(status))
    {
        if (TRUE == ExAcquireResourceSharedLite(&global.dir_lock, TRUE))
        {
            BOOLEAN found = FALSE;
            __try
            {
                PLIST_ENTRY entry = global.head_dir.Flink;

                while (entry != &global.head_dir)
                {
                    DIR_INFO* dir_info = CONTAINING_RECORD(entry, DIR_INFO, list);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(dir_info->dir_nt.Length <= name_info->Name.Length && TRUE ==&nbsp;RtlPrefixUnicodeString(&dir_info->dir_nt, &name_info->Name, TRUE))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; BOOLEAN needprotect = FALSE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(name_info->Name.Length == dir_info->dir_nt.Length)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; BOOLEAN isdirectory = create_options & FILE_DIRECTORY_FILE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(TRUE == isdirectory)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; needprotect = TRUE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(name_info->Name.Length > dir_info->dir_nt.Length && L'\\'&nbsp;== name_info->Name.Buffer[dir_info->dir_nt.Length /&nbsp;sizeof(WCHAR)])
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; needprotect = TRUE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(FALSE == needprotect)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; entry = entry->Flink;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Process_GetProcessName((ULONG_PTR)pid, &nbuf, &nlen, &nptr);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(NULL == nbuf)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mini_request =&nbsp;ExAllocatePoolWithTag(NonPagedPool,&nbsp;sizeof(MINI_REQUEST), DIR_PROTECT_POOL_TAG);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(NULL == mini_request)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Data->IoStatus.Status = STATUS_ACCESS_DENIED;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Data->IoStatus.Information =&nbsp;0;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; call_status = FLT_PREOP_COMPLETE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sys_2_user = &mini_request->sys_2_user;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;RtlZeroMemory(mini_request,&nbsp;sizeof(MINI_REQUEST));

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sys_2_user->file_action = ACTION_CREATE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sys_2_user->access_mask = mask;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sys_2_user->ask_reason = reason;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;((ULONG64)name_info->Name.Length + dir_info->dir_dos.Length - dir_info->dir_nt.Length <=&nbsp;sizeof(sys_2_user->filename) -&nbsp;sizeof(WCHAR))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;memcpy(sys_2_user->filename, dir_info->dir_dos.Buffer, dir_info->dir_dos.Length);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(name_info->Name.Length > dir_info->dir_nt.Length)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;memcpy((PCHAR)sys_2_user->filename + dir_info->dir_dos.Length, (PCHAR)name_info->Name.Buffer + dir_info->dir_nt.Length, name_info->Name.Length - dir_info->dir_nt.Length);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; found = TRUE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; entry = entry->Flink;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; __finally
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ExReleaseResourceLite(&global.dir_lock);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(TRUE == found && NULL != sys_2_user)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sys_2_user->pid = pid;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;wcsncpy_s(sys_2_user->processname,&nbsp;ARRAYSIZE(sys_2_user->processname) -&nbsp;1, nptr,&nbsp;wcslen(nptr));

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mini_request->Data = Data;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(TRUE ==&nbsp;ExAcquireResourceExclusiveLite(&global.minifilter_request_lock, TRUE))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;//insert data
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;InsertTailList(&global.head_minirequest, &mini_request->list);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ++global.minirequest_count;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ExReleaseResourceLite(&global.minifilter_request_lock);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;//wake system thread
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;KeSetEvent(&global.event_process_request, IO_NO_INCREMENT, FALSE);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; call_status = FLT_PREOP_PENDING;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;if&nbsp;(NULL != name_info)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;FltReleaseFileNameInformation(name_info);
&nbsp; &nbsp; &nbsp; &nbsp; name_info = NULL;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;if&nbsp;(FLT_PREOP_PENDING != call_status)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(NULL != mini_request)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ExFreePool(mini_request);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mini_request = NULL;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;if&nbsp;(NULL != nbuf)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ExFreePool(nbuf);
&nbsp; &nbsp; &nbsp; &nbsp; nbuf = NULL;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;return&nbsp;call_status;
}

1.如果是来自于内核的请求那么不处理,如果请求来自于发起connect请求的进程也不处理,如果此时need_protect标志为FALSE也不处理。

2.接着使用Data->Iopb->Parameters.Create.Options分别获取高8位和低24位作为create_disposition和create_options变量,使用Data->Iopb->Parameters.Create.SecurityContext->DesiredAccess记录mask标志,根据这几个标志判断是否是写文件目录或者删除行为。

3.使用FltGetFileNameInformation获取本次要操作的文件或者目录,使用共享读锁获取global.dir_lock,判断要操作的文件对象是否在这个目录下,在这个过程要处理一些边界情况,比如保护的目录是C:\123那么需要放过C:\123.txt还有C:\123456目录还有C:\123文件,放开获取到的锁。

4.如果需要保护,那么就使用ZwQueryInformationProcess获取进程名称(如果获取失败那么放过请求,比如经过测试system进程获取不到进程名称),申请内存空间后使用独占锁获取global.minifilter_request_lock,在链表global.head_minirequest插入本次请求,放开获取到的锁。

5.设置当前函数回值为FLT_PREOP_PENDING,唤醒创建的系统线程KeSetEvent(&global.event_process_request, IO_NO_INCREMENT, FALSE);

系统线程执行的代码如下:

VOID ThreadProc(PVOID StartContext)
{
&nbsp; &nbsp;&nbsp;UNREFERENCED_PARAMETER(StartContext);

&nbsp; &nbsp;&nbsp;do
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; SYS_2_USER sys_2_user = {&nbsp;0&nbsp;};
&nbsp; &nbsp; &nbsp; &nbsp; BOOLEAN have_data =&nbsp;FALSE;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(TRUE&nbsp;==&nbsp;global.minifilter_request_lock_initialized &&&nbsp;TRUE&nbsp;==&nbsp;ExAcquireResourceSharedLite(&global.minifilter_request_lock,&nbsp;TRUE))
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; __try
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PLIST_ENTRY entry =&nbsp;global.head_minirequest.Flink;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;(entry != &global.head_minirequest)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; MINI_REQUEST* request =&nbsp;CONTAINING_RECORD(entry, MINI_REQUEST,&nbsp;list);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sys_2_user = request->sys_2_user;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; have_data =&nbsp;TRUE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; __finally
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ExReleaseResourceLite(&global.minifilter_request_lock);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(TRUE&nbsp;== have_data)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; union
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; USER_REPLY recv;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; REPLY_DATA data;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }reply;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ULONG reply_len =&nbsp;sizeof(FILTER_REPLY_HEADER) +&nbsp;sizeof(REPLY_DATA);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LARGE_INTEGER timeout;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; timeout.QuadPart = -70&nbsp;*&nbsp;1000&nbsp;*&nbsp;1000&nbsp;*&nbsp;10;&nbsp;// 70s
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; NTSTATUS status =&nbsp;FltSendMessage(gFilterHandle, &gClientPort, &sys_2_user,&nbsp;sizeof(SYS_2_USER), &reply.recv, &reply_len, &timeout);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;CompleteFirstRequest(NT_SUCCESS(status) && REPLY_ALLOW == reply.data);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;KeWaitForSingleObject(&global.event_process_request, Executive, KernelMode,&nbsp;FALSE,&nbsp;NULL);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }&nbsp;while&nbsp;(FALSE&nbsp;==&nbsp;global.stop);
&nbsp; &nbsp;&nbsp;PsTerminateSystemThread(STATUS_SUCCESS);
}

1.在系统线程中先获取锁后取出一个请求信息,使用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结构体。

2.收到返回信息(可能是由于用户层点击了允许或者拒绝按钮,或者用户层默认拒绝,或者是用户层主动退出或崩溃导致disconnect断开连接),只有当返回信息是允许是才放过这个请求,否则拒绝这个请求,具体实现在CompleteFirstRequest函数中。

CompleteFirstRequest函数代码如下:

BOOLEAN CompleteFirstRequest(BOOLEAN allow)
{
&nbsp; &nbsp; MINI_REQUEST* request =&nbsp;NULL;
&nbsp; &nbsp;&nbsp;if&nbsp;(TRUE&nbsp;==&nbsp;ExAcquireResourceExclusiveLite(&global.minifilter_request_lock,&nbsp;TRUE))
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; __try
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(0&nbsp;!=&nbsp;global.minirequest_count)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PLIST_ENTRY entry =&nbsp;RemoveHeadList(&global.head_minirequest);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; --global.minirequest_count;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; request =&nbsp;CONTAINING_RECORD(entry, MINI_REQUEST,&nbsp;list);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; __finally
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ExReleaseResourceLite(&global.minifilter_request_lock);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;if&nbsp;(NULL&nbsp;!= request)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(TRUE&nbsp;== allow)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;FltCompletePendedPreOperation(request->Data, FLT_PREOP_SUCCESS_NO_CALLBACK,&nbsp;NULL);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; request->Data->IoStatus.Status = STATUS_ACCESS_DENIED;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; request->Data->IoStatus.Information =&nbsp;0;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;FltCompletePendedPreOperation(request->Data, FLT_PREOP_COMPLETE,&nbsp;NULL);
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ExFreePoolWithTag(request, DIR_PROTECT_POOL_TAG);
&nbsp; &nbsp; &nbsp; &nbsp; request =&nbsp;NULL;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;TRUE;
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;return&nbsp;FALSE;
}

如果找不到请求,那么返回FALSE,如果完成请求那么返回TRUE,这样设计方便后续多次调用该函数拒绝链表中的所有缓存的请求(在用户程序退出disconnect发生时)。

disconnect代码如下:

VOID DirProtectMiniDisconnect(__in_opt PVOID ConnectionCookie)
{
&nbsp; &nbsp;&nbsp;PAGED_CODE();
&nbsp; &nbsp;&nbsp;UNREFERENCED_PARAMETER( ConnectionCookie );
DbgPrint("[mini-filter] DirProtectMiniDisconnect");

&nbsp; &nbsp;&nbsp;// &nbsp;Close our handle
&nbsp; &nbsp;&nbsp;FltCloseClientPort( gFilterHandle, &gClientPort );
&nbsp; &nbsp; gClientPort =&nbsp;NULL;

&nbsp; &nbsp;&nbsp;global.client_pid =&nbsp;0;

&nbsp; &nbsp;&nbsp;global.need_protect =&nbsp;FALSE;

&nbsp; &nbsp;&nbsp;//clear dir
&nbsp; &nbsp;&nbsp;if&nbsp;(TRUE&nbsp;==&nbsp;ExAcquireResourceExclusiveLite(&global.dir_lock,&nbsp;TRUE))
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;while&nbsp;(global.dir_count >&nbsp;0)
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; PLIST_ENTRY entry =&nbsp;RemoveHeadList(&global.head_dir);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; --global.dir_count;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DIR_INFO* dir_info =&nbsp;CONTAINING_RECORD(entry, DIR_INFO,&nbsp;list);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(NULL&nbsp;!= dir_info)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(NULL&nbsp;!= dir_info->dir_dos.Buffer)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ExFreePool(dir_info->dir_dos.Buffer);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dir_info->dir_dos.Buffer =&nbsp;NULL;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(NULL&nbsp;!= dir_info->dir_nt.Buffer)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ExFreePool(dir_info->dir_nt.Buffer);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dir_info->dir_nt.Buffer =&nbsp;NULL;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ExFreePool(dir_info);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dir_info =&nbsp;NULL;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ExReleaseResourceLite(&global.dir_lock);
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;//deny cached all request
&nbsp; &nbsp;&nbsp;while&nbsp;(CompleteFirstRequest(FALSE))
&nbsp; &nbsp; {

&nbsp; &nbsp; }

}

需要关闭客户端的port,设置need_protect标志位为FALSE,清理dir信息,调用CompleteFirstRequest拒绝缓存的请求。

用户层初始化代码完成后,就开始接收驱动拦截到的请求。

用户层代码

void&nbsp;Run(){
size_t&nbsp;len =&nbsp;sizeof(FILTER_MESSAGE_HEADER) +&nbsp;sizeof(SYS_2_USER);
    FILTER_MESSAGE_HEADER* header = (FILTER_MESSAGE_HEADER*)malloc(sizeof(FILTER_MESSAGE_HEADER) +&nbsp;sizeof(SYS_2_USER));
if&nbsp;(NULL&nbsp;== header)
    {
return;
    }
    USER_REPLY* reply = (USER_REPLY*)malloc(sizeof(FILTER_REPLY_HEADER) +&nbsp;sizeof(REPLY_DATA));
if&nbsp;(NULL&nbsp;== reply)
    {
free(header);
return;
    }

    OVERLAPPED over = {&nbsp;0&nbsp;};
    over.hEvent =&nbsp;CreateEvent(NULL, FALSE, FALSE,&nbsp;NULL);
if&nbsp;(NULL&nbsp;== over.hEvent)
    {
free(header);
free(reply);
return;
    }
    SYS_2_USER* sys_2_user = (SYS_2_USER*)((PCHAR)header +&nbsp;sizeof(FILTER_MESSAGE_HEADER));

while&nbsp;(1)
    {
memset(header,&nbsp;0, len);
        HRESULT result =&nbsp;FilterGetMessage(g_hPort, header, (DWORD)len, &over);
if&nbsp;(result !=&nbsp;HRESULT_FROM_WIN32(ERROR_IO_PENDING))
        {
DebugBreak();
break;
        }
WaitForSingleObject(over.hEvent, INFINITE);
        ASK_REASON reason = sys_2_user->ask_reason;
wprintf(L"pid:%d processname:%s access_mask:0x%x &nbsp;%s filename:%s\n", sys_2_user->pid, sys_2_user->processname, sys_2_user->access_mask,&nbsp;ReasonToString(reason), sys_2_user->filename);

        INT_PTR dlg_ret =&nbsp;DialogBoxParamW(NULL,&nbsp;MAKEINTRESOURCE(IDD_DIALOG1),&nbsp;NULL, DlgProc, (LPARAM)sys_2_user);
if&nbsp;(1&nbsp;== dlg_ret)
        {
            reply->reply_data = REPLY_ALLOW;
        }
else
        {
            reply->reply_data = REPLY_DENY;
        }

        reply->reply_header.Status =&nbsp;0;
        reply->reply_header.MessageId = header->MessageId;

if&nbsp;(S_OK !=&nbsp;FilterReplyMessage(g_hPort, (PFILTER_REPLY_HEADER)reply,&nbsp;sizeof(FILTER_REPLY_HEADER) +&nbsp;sizeof(REPLY_DATA)))
        {
break;
        }
    }
free(header);
free(reply);
CloseHandle(over.hEvent);
}

收到请求后弹框让用户选择拒绝允许,然后调用FilterReplyMessage返回给驱动,如下图:

点击拒绝后,右侧的cmd会显示拒绝访问

总结:

完成了一个可以动态配置保护目录,动态启用保护,可以根据用户层决策来允许或者拒绝文件请求的Minifilter框架程序,可以拦截到创建文件、创建目录,删除文件、删除目录。

还需要修改的地方:在precreate根据标志猜测行为,可以拦截到但是显示在界面上的行为不准确,还需要进行调试修改;对于拖动文件到保护文件夹下的情况选择拒绝后(拒绝访问保护目录)还是可以拖动成功,这个还需要调整。

在虚拟机中设置测试模式,安装驱动时需要有sys文件、inf文件、cat文件,右键inf安装驱动,再在cmd下运行sc start dirprotect 或者fltmc load dirprotect启动驱动服务,使用sc stop dirprotect 或者fltmc unload dirprotect停止驱动服务。

驱动启动后,如果要监控C:\123目录,可以运行程序ProtectControl.exe C:\123  传入参数C:\123,也可以监控多个目录,启动后不要关闭程序,后续可以接收驱动发过来的请求。

代码参考原帖附件。

#

#

看雪ID:0346954

https://bbs.kanxue.com/user-home-762319.htm

*本文为看雪论坛优秀文章,由 0346954 原创,转载请注明来自看雪社区

报名中!看雪·第九届安全开发者峰会(SDC 2025)

往期推荐

无”痕”加载驱动模块之傀儡驱动 (上)

为 CobaltStrike 增加 SMTP Beacon

隐蔽通讯常见种类介绍

buuctf-re之CTF分析

物理读写/无附加读写实验

球分享

球点赞

球在看

点击阅读原文查看更多


评论:0   参与:  5