有没有办法将整个文件从文件系统微型过滤器驱动程序(内核模式)传递到用户模式应用程序?

Is there a way to pass an entire file from file system mini filter driver (kernel mode) up to a user mode application?

我有一个使用 WDK 8.1 示例的扫描仪文件系统微型过滤器驱动程序。

我想知道我是否可以将整个文件发送到用户端应用程序,这样我就可以进行更复杂的计算,例如 MD5 哈希或其他任何东西,这样我就不必在其中编写更复杂的操作迷你过滤器驱动程序,而是在我不介意引入 windows.h 并且我可以在堆上分配内存而不是使用 ExAllocatePoolWithTag 之类的东西的用户空间应用程序中。

我可以在一次通知中将整个文件传递到用户登陆模式吗?

如果不是,我将如何进行分块和同步。

这是 8.1 扫描仪文件系统微型过滤器驱动程序示例的接口,它指示微型过滤器驱动程序与用户端应用程序之间的通信:

/*++

Copyright (c) 1999-2002  Microsoft Corporation

Module Name:

    scanuk.h

Abstract:

    Header file which contains the structures, type definitions,
    constants, global variables and function prototypes that are
    shared between kernel and user mode.

Environment:

    Kernel & user mode

--*/

#ifndef __SCANUK_H__
#define __SCANUK_H__

//
//  Name of port used to communicate
//

const PWSTR ScannerPortName = L"\ScannerPort";


#define SCANNER_READ_BUFFER_SIZE   1024

typedef struct _SCANNER_NOTIFICATION {

    ULONG BytesToScan;
    ULONG Reserved;             // for quad-word alignement of the Contents structure
    UCHAR Contents[SCANNER_READ_BUFFER_SIZE];

} SCANNER_NOTIFICATION, *PSCANNER_NOTIFICATION;

typedef struct _SCANNER_REPLY {

    BOOLEAN SafeToOpen;

} SCANNER_REPLY, *PSCANNER_REPLY;

#endif //  __SCANUK_H__

注意传递的缓冲区大小有 1024 的限制。这意味着我不能在用户端应用程序中对整个文件进行 MD5 散列,然后将被迫在微型过滤器驱动程序中进行,我希望尽可能避免这种情况。

下面是迷你过滤器驱动程序中的函数,它使用上面的接口将消息发送到用户端应用程序:

//////////////////////////////////////////////////////////////////////////
//  Local support routines.
//
/////////////////////////////////////////////////////////////////////////

NTSTATUS
ScannerpScanFileInUserMode (
    _In_ PFLT_INSTANCE Instance,
    _In_ PFILE_OBJECT FileObject,
    _Out_ PBOOLEAN SafeToOpen
    )
/*++

Routine Description:

    This routine is called to send a request up to user mode to scan a given
    file and tell our caller whether it's safe to open this file.

    Note that if the scan fails, we set SafeToOpen to TRUE.  The scan may fail
    because the service hasn't started, or perhaps because this create/cleanup
    is for a directory, and there's no data to read & scan.

    If we failed creates when the service isn't running, there'd be a
    bootstrapping problem -- how would we ever load the .exe for the service?

Arguments:

    Instance - Handle to the filter instance for the scanner on this volume.

    FileObject - File to be scanned.

    SafeToOpen - Set to FALSE if the file is scanned successfully and it contains
                 foul language.

Return Value:

    The status of the operation, hopefully STATUS_SUCCESS.  The common failure
    status will probably be STATUS_INSUFFICIENT_RESOURCES.

--*/

{
    NTSTATUS status = STATUS_SUCCESS;
    PVOID buffer = NULL;
    ULONG bytesRead;
    PSCANNER_NOTIFICATION notification = NULL;
    FLT_VOLUME_PROPERTIES volumeProps;
    LARGE_INTEGER offset;
    ULONG replyLength, length;
    PFLT_VOLUME volume = NULL;

    *SafeToOpen = TRUE;

    //
    //  If not client port just return.
    //

    if (ScannerData.ClientPort == NULL) {

        return STATUS_SUCCESS;
    }

    try {

        //
        //  Obtain the volume object .
        //

        status = FltGetVolumeFromInstance( Instance, &volume );

        if (!NT_SUCCESS( status )) {

            leave;
        }

        //
        //  Determine sector size. Noncached I/O can only be done at sector size offsets, and in lengths which are
        //  multiples of sector size. A more efficient way is to make this call once and remember the sector size in the
        //  instance setup routine and setup an instance context where we can cache it.
        //

        status = FltGetVolumeProperties( volume,
                                         &volumeProps,
                                         sizeof( volumeProps ),
                                         &length );
        //
        //  STATUS_BUFFER_OVERFLOW can be returned - however we only need the properties, not the names
        //  hence we only check for error status.
        //

        if (NT_ERROR( status )) {

            leave;
        }

        length = max( SCANNER_READ_BUFFER_SIZE, volumeProps.SectorSize );

        //
        //  Use non-buffered i/o, so allocate aligned pool
        //

        buffer = FltAllocatePoolAlignedWithTag( Instance,
                                                NonPagedPool,
                                                length,
                                                'nacS' );

        if (NULL == buffer) {

            status = STATUS_INSUFFICIENT_RESOURCES;
            leave;
        }

        notification = ExAllocatePoolWithTag( NonPagedPool,
                                              sizeof( SCANNER_NOTIFICATION ),
                                              'nacS' );

        if(NULL == notification) {

            status = STATUS_INSUFFICIENT_RESOURCES;
            leave;
        }

        //
        //  Read the beginning of the file and pass the contents to user mode.
        //

        offset.QuadPart = bytesRead = 0;
        status = FltReadFile( Instance,
                              FileObject,
                              &offset,
                              length,
                              buffer,
                              FLTFL_IO_OPERATION_NON_CACHED |
                              FLTFL_IO_OPERATION_DO_NOT_UPDATE_BYTE_OFFSET,
                              &bytesRead,
                              NULL,
                              NULL );

        if (NT_SUCCESS( status ) && (0 != bytesRead)) {

            notification->BytesToScan = (ULONG) bytesRead;

            //
            //  Copy only as much as the buffer can hold
            //

            RtlCopyMemory( &notification->Contents,
                           buffer,
                           min( notification->BytesToScan, SCANNER_READ_BUFFER_SIZE ) );

            replyLength = sizeof( SCANNER_REPLY );

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

            if (STATUS_SUCCESS == status) {

                *SafeToOpen = ((PSCANNER_REPLY) notification)->SafeToOpen;

            } else {

                //
                //  Couldn't send message
                //

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

    } finally {

        if (NULL != buffer) {

            FltFreePoolAlignedWithTag( Instance, buffer, 'nacS' );
        }

        if (NULL != notification) {

            ExFreePoolWithTag( notification, 'nacS' );
        }

        if (NULL != volume) {

            FltObjectDereference( volume );
        }
    }

    return status;
}

最好在内核中少读,在用户模式下读取整个文件,这需要将内核中的文件信息发送到必须读取的用户模式。

如果你想读取完整的文件并通过端口发送它,那么你应该注意 FltReadFile API 及其参数,有一个参数负责文件应该读取多少。您必须正确设置缓冲区大小以及标记池。

不需要那么复杂。 Microsoft 已经在内核中支持加密。查看 CNG 库,内核本身使用它来进行散列、密钥、安全相关的加密处理。 如果你真的坚持将文件内容发送到用户模式,我会提出一个更优雅的解决方案,那就是读取用户模式进程地址space中的文件。

  1. 打开文件并查询其大小。
  2. 使用您的用户模式进程的进程句柄调用 ZwAllocateVirtualMemory
  3. 读取分配的space中的文件内容。
  4. 向您的用户模式进程发送分配的地址以及大小和其他信息,例如文件名等。
  5. 等待用户模式进程完成处理并获得结果。
  6. 呼叫ZwFreeVirtualMemory to free your memory, or if you have a asynchronous model let the user-mode process free it's own memory by calling VirtualFree.

我会考虑的另一种选择是在内核中自行打开文件,然后调用 ZwDuplicateObject and create a handle to the file for my user-mode process. Lastly send the user-mode process the handle and let it work with the handle and read/query do what it wants. Again depending on your implementation the user-mode process could close the handle or you could wait and close it in the context of your user-mode process ( Using KeStackAttachProcess 以附加到其地址 space)。

不过,我会在内核中完成我能做的所有处理,因为一直切换上下文的成本很高。

祝你好运,
加布里埃尔