我还是个孩子丶 发表于 2024-11-10 13:41

Minifilter之文件扫描

本帖最后由 我还是个孩子丶 于 2024-11-10 14:48 编辑

0x0前言
最近在学MInifilter,用MInifilter实现了一个文件扫描器的功能,实时监控文件并检测文件中是否含有预设特征。本代码主要在微软的官方示例MInifilter-Scanner上做出了完善。







0x1主要功能:
1.初始化时遍历所有存在符号链接的卷设备并将二者对应保存到哈希表中方便后续查找。
2.在IRP_MJ_CREATE、IRP_MJ_CLEANUP、IRP_MJ_CLOSE中取消读取文件数据(会占用大量内核内存),而是通过查表将文件的设备名路径转换为符号链接路径并发往R3进行查询
3.在IRP_MJ_WRITE中将要写入的数据发往R3进行扫描。
4.R3可以设置多个线程对到来的文件路径或是内存块进行扫描检测是否含有感兴趣的特征,并将结果返回给R0
5.R0在收到R3的检测结果以后会判断是否允许本次操作


0x2说明
1.scanner文件夹中是微软的官方示例,也可以在git上找到;test文件夹是本项目的R0驱动,其中utils是处理符号链接,转换文件路径的代码;R3Process文件夹中是R3用于扫描文件的程序。
2.驱动在初始化时会遍历所有的卷设备的符号链接,默认所有的符号链接都是 'C:' 这种形式的,对扫描到的符号链接会判断是否为卷设备‘HarddiskVolume’
3.如果为为卷设备,则会将符号链接与对应的卷设备名存入哈希表中方便后续查找可供R3使用的文件符号链接路径。
4.在IRP_MJ_CREATE的后操作回调中,如果拦截到了对于文档编辑的保存操作(如txt的保存,Winhex对二进制文件的编辑保存),在Win7Sp1中会遇到一次死锁的情况(R0等待R3的结果,R3等待其他进程释放文件),但此时FileObject->Flags并不包含同步操作的标志(FO_SYNCHRONOUS_IO、FO_SEQUENTIAL_ONLY的标志位为0),但此时明明不应该为0吧。。。希望有大佬能帮忙分析。
5.因为4的问题,我将IRP_MJ_CREATE的处理放到了前操作回调中,就没出现过这个问题了。
6.对于文本编辑的保存操作,并不能很好的拦截存在特征的数据的保存(个人感觉是缓存的问题),不过影响不大,就算保存成功了下次在想打开也会被拦截。



0x3代码:
R0IPR处理代码:FLT_PREOP_CALLBACK_STATUS
ScannerPreCreateEx(
    _Inout_ PFLT_CALLBACK_DATA Data,
    _In_ PCFLT_RELATED_OBJECTS FltObjects,
    _Flt_CompletionContext_Outptr_ PVOID *CompletionContext
)
{
    UNREFERENCED_PARAMETER(CompletionContext = NULL);
    PAGED_CODE();

    if (IoThreadToProcess(Data->Thread) == ScannerData.UserProcess) {
      return FLT_PREOP_SUCCESS_NO_CALLBACK;
    }

    PSCANNER_STREAM_HANDLE_CONTEXT scannerContext;
    FLT_POSTOP_CALLBACK_STATUS returnStatus = FLT_PREOP_SUCCESS_NO_CALLBACK;
    PFLT_FILE_NAME_INFORMATION nameInfo;
    NTSTATUS status;
    BOOLEAN safeToOpen, scanFile;
    BOOLEAN interestFile;

    safeToOpen = FALSE;

    scannerContext = ExAllocatePool(NonPagedPool, sizeof(PSCANNER_STREAM_HANDLE_CONTEXT));

    //检测文件类型
    status = FltGetFileNameInformation(Data,
      FLT_FILE_NAME_NORMALIZED |
      FLT_FILE_NAME_QUERY_DEFAULT,
      &nameInfo);

    if (!NT_SUCCESS(status)) {

      return FLT_PREOP_SUCCESS_WITH_CALLBACK;
    }

    FltParseFileNameInformation(nameInfo);

    scanFile = ScannerpCheckExtension(&nameInfo->Extension);
    //interestFile = IsMyInterestFile(&nameInfo->Name);
    interestFile = TRUE;

    if (!scanFile || !interestFile) {
      FltReleaseFileNameInformation(nameInfo);
      return FLT_PREOP_SUCCESS_NO_CALLBACK;
    }

    ANSI_STRING SymPath;
    PUNICODE_STRING temp = &nameInfo->Name;
    char Buffer;
    DeviceFilePathToSymbolFilePath(temp, &SymPath, Buffer, 512);
    FltReleaseFileNameInformation(nameInfo);
    ScanFileInUserModeByFilePath(&SymPath, &safeToOpen);

    if (!safeToOpen) {
      //关闭文件
      FltCancelFileOpen(FltObjects->Instance, FltObjects->FileObject);
      Data->IoStatus.Status = STATUS_ACCESS_DENIED;
      Data->IoStatus.Information = 0;
      //通知已经修改了数据
      SetFlag(Data->Flags, FLTFL_CALLBACK_DATA_DIRTY);
      FltSetCallbackDataDirty(Data);
      returnStatus = FLT_PREOP_COMPLETE;
    }
    else if (FltObjects->FileObject->WriteAccess) {
      //    创建者已请求写入权限,标记以重新扫描文件分配上下文
      status = FltAllocateContext(ScannerData.Filter,
            FLT_STREAMHANDLE_CONTEXT,
            sizeof(BOOLEAN),
            PagedPool,
            &scannerContext);

      if (NT_SUCCESS(status)) {

            scannerContext->RescanRequired = TRUE;

            (VOID)FltSetStreamHandleContext(FltObjects->Instance,
                FltObjects->FileObject,
                FLT_SET_CONTEXT_REPLACE_IF_EXISTS,
                scannerContext,
                NULL);
            //无论失败与否 都释放了
            FltReleaseContext(scannerContext);
      }
    }
    return returnStatus;

}

R0向R3发送消息的代码:
NTSTATUS
ScanFileInUserModeByFilePath(
      _In_ PANSI_STRING FilePath,
      _Out_ PBOOLEAN SafeToOpen
)
{
      NTSTATUS status = STATUS_SUCCESS;
      ULONG bytesRead;
      PCommData notification = NULL;
      ULONG replyLength, length;

      *SafeToOpen = TRUE;


      if (ScannerData.ClientPort == NULL) {

                return STATUS_SUCCESS;
                KdBreakPoint();
      }
      try {
                //在PostCreate中执行时可能处于DPC_LEVEL
                notification = (PCommData)ExAllocatePoolWithTag(PagedPool,
                        sizeof(CommData),
                        'nacS');

                if (NULL == notification) {

                        status = STATUS_INSUFFICIENT_RESOURCES;
                        KdBreakPoint();
                        leave;
                }
                //KdBreakPoint();
                RtlZeroMemory(notification, sizeof(CommData));
                notification->ScanFile.scantype = ScanWithFilePath;
                notification->ScanFile.length = FilePath->Length;
                RtlCopyMemory(¬ification->ScanFile.FilePath,
                        FilePath->Buffer,
                        FilePath->Length);

                replyLength = sizeof(CommData);

                status = FltSendMessage(ScannerData.Filter,
                        &ScannerData.ClientPort,
                        notification,
                        sizeof(CommData),
                        notification,
                        &replyLength,
                        NULL);

                if (STATUS_SUCCESS == status) {
                        *SafeToOpen = ((PSCANNER_REPLY)notification)->SafeToOpen;
                }
                else {

                        DbgPrint("!!! scanner.sys --- couldn't send message to user-mode to scan file, status 0x%X\n", status);
                }
      }
      finally{
         if (NULL != notification) {

               ExFreePoolWithTag(notification, 'nacS');
         }
      }

      return status;
}
初始化符号链接哈希表:void InitializeDeviceNameHash()
{
      for (int i = 0; i < MAX_VOLUME_NUM; ++i)
      {
                PCHAR buffer = ExAllocatePoolWithTag(NonPagedPool, 32, 'NaHs');
                RtlZeroMemory(buffer, 32);
                RtlInitEmptyAnsiString(&hashDeviceName.Symbol, buffer, 32);
                hashDeviceName.isExist = FALSE;
      }

      WCHAR i = 'A';
      UNICODE_STRING DeviceName;
      WCHAR nameBuffer;
      RtlInitEmptyUnicodeString(&DeviceName, nameBuffer, sizeof(nameBuffer));
      ULONG retlen;
      NTSTATUS status;
      while (i <= 'Z')
      {
                retlen = 0;
                RtlZeroMemory(nameBuffer, sizeof(nameBuffer));
                status = GetDeviceNameBySymbol(i, &DeviceName, &retlen);
                if (status == STATUS_BUFFER_TOO_SMALL)
                {
                        //尽量让缓冲区不出先这种状况
                        KdBreakPoint();
                        KdPrintEx((77, 0, " :BUFFER_TOO_SMALL\r\n"));
                }
                if (NT_SUCCESS(status) && IsMyInterestDevice(&DeviceName))
                {
                        int id = DeviceName.Buffer - L'0';
                        if (!hashDeviceName.isExist)
                        {
                              RtlStringCchPrintfA(hashDeviceName.Symbol.Buffer, hashDeviceName.Symbol.MaximumLength, "%C:", i);
                              KdPrintEx((77, 0, ":%Z\r\n", hashDeviceName.Symbol));
                              hashDeviceName.isExist = TRUE;
                        }
                }
                i++;
      }
}
将设备名文件路径转换为符号链接文件路径:NTSTATUS DeviceFilePathToSymbolFilePath(
      _In_ PUNICODE_STRING DeviceFilePath,
      _Out_ PANSI_STRING SymbolFilePath,
      _In_ PCHAR SymPathBuffer,
      _In_ ULONG BufferLenght)
{
      if (DeviceFilePath == NULL || SymbolFilePath == NULL)
      {
                return STATUS_INVALID_PARAMETER;
      }
      //KdBreakPoint();
      ANSI_STRING TempFilePath;
      ANSI_STRING SymName;
      char symBuffer;
      char tempBuffer;
      UNICODE_STRING DeviceName;
      WCHAR deviceNameBuffer;
      NTSTATUS status;
      RtlInitEmptyAnsiString(&TempFilePath, tempBuffer, sizeof(tempBuffer));
      RtlInitEmptyAnsiString(&SymName, symBuffer, sizeof(symBuffer));
      RtlInitEmptyUnicodeString(&DeviceName, deviceNameBuffer, sizeof(deviceNameBuffer));
      RtlZeroMemory(SymPathBuffer, BufferLenght);
      status = SplitFilePath(DeviceFilePath, &DeviceName, &TempFilePath);
      if (!NT_SUCCESS(status))
      {
                return status;
      }

      status = GetSymLinkByDeviceName(&DeviceName, &SymName);
      if (!NT_SUCCESS(status))
      {
                return status;
      }
      RtlStringCchPrintfA(SymPathBuffer,
                BufferLenght,
                "%s%s", SymName.Buffer, TempFilePath.Buffer);
      RtlInitAnsiString(SymbolFilePath, SymPathBuffer);
      SymbolFilePath->MaximumLength = BufferLenght;
      //这里要检测一一下 SymbolFilePath的长度是否正确
      //KdBreakPoint();
      //KdPrintEx((77, 0, " SymbolFilePath: %Z\r\n", SymbolFilePath));
      return status;
}R3线程函数(接收消息并处理):DWORD
ScannerWorker(
      _In_ PSCANNER_THREAD_CONTEXT Context
)
{
      PCommData notification;
      SCANNER_REPLY_MESSAGE replyMessage;
      PSCANNER_MESSAGE message;
      LPOVERLAPPED pOvlp;
      BOOL result;
      DWORD outSize;
      HRESULT hr;
      ULONG_PTR key;


      while (TRUE) {

                //      从过滤器组件中轮询消息以进行扫描。

                result = GetQueuedCompletionStatus(Context->Completion, &outSize, &key, &pOvlp, INFINITE);
                printf("正在获取消息...\r\n");
                //      通过pOvlp逆向获取消息
                message = CONTAINING_RECORD(pOvlp, SCANNER_MESSAGE, Ovlp);

                if (!result) {
                        hr = HRESULT_FROM_WIN32(GetLastError());
                        break;
                }

                notification = &message->Notification;

                //扫描工作
                if (notification->ScanFile.scantype == ScanWithFilePath)
                {
                        printf("检测到扫描文件请求, file path : %s\n", message->Notification.ScanFile.FilePath);
                        printf(": %d\n", message->MessageHeader.ReplyLength);
                        printf(": %lld\n", message->MessageHeader.MessageId);
                        printf(": %x\n", message->Notification.ScanFile.scantype);
                        printf(": %s\n", message->Notification.ScanFile.FilePath);
                        printf(": %d\n", message->Notification.ScanFile.length);
                        result = ScanFile(notification->ScanFile.FilePath);
                }
                else if (notification->ScanBuffer.scantype == ScanWithBuffer)
                {
                        printf("检测到扫描缓存的请求!%llx\n", pOvlp->InternalHigh);
                        result = ScanBuffer(notification->ScanBuffer.Buffer, notification->ScanBuffer.length);
                }

                replyMessage.ReplyHeader.Status = 0;
                replyMessage.ReplyHeader.MessageId = message->MessageHeader.MessageId;

                //
                //Need to invert the boolean -- result is true if found
                //foul language, in which case SafeToOpen should be set to false.
                //

                //replyMessage.Reply.SafeToOpen = !result;
                replyMessage.Reply.SafeToOpen = !result;

                printf("Replying message, SafeToOpen: %d\n", replyMessage.Reply.SafeToOpen);

                hr = FilterReplyMessage(Context->Port,
                        (PFILTER_REPLY_HEADER)&replyMessage,
                        sizeof(replyMessage));

                if (SUCCEEDED(hr)) {

                        printf("Replied message\n");

                }
                else {

                        printf("Scanner: Error replying message. Error = 0x%X\n", hr);
                        break;
                }

                memset(&message->Ovlp, 0, sizeof(OVERLAPPED));

                hr = FilterGetMessage(Context->Port,
                        &message->MessageHeader,
                        FIELD_OFFSET(SCANNER_MESSAGE, Ovlp),
                        &message->Ovlp);

                if (hr != HRESULT_FROM_WIN32(ERROR_IO_PENDING)) {

                        printf("hr = ***%ld***\r\n", hr);
                        break;
                }
      }

      if (!SUCCEEDED(hr)) {

                if (hr == HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE)) {


                        printf("Scanner: Port is disconnected, probably due to scanner filter unloading.\n");

                }
                else {

                        printf("Scanner: Unknown error occured. Error = 0x%X\n", hr);
                }
      }

      return hr;
}
其余代码详见项目压缩包:https://wwi.lanzoub.com/iNvGn2epk5he 解压密码:52pj
新手写帖,欢迎各位大佬帮忙指正。

你好,再见 发表于 2024-11-10 18:47

支持一下

nidiexixi 发表于 2024-11-10 19:14

感谢分享

我还是个孩子丶 发表于 2024-11-10 21:21

你好,再见 发表于 2024-11-10 18:47
支持一下感谢大佬:lol:lol:lol:lol
页: [1]
查看完整版本: Minifilter之文件扫描