All Articles

A PART OF ANTI-VIRUS [Chapter 2: Introduction to File System Minifilter Drivers]

This page has been machine-translated from the original page.-

In this book, based on the Scanner File System Minifilter Driver sample code published in the official Windows Driver Samples repository1, I explain how real-time file scanning works in AntiVirus software for Windows.

However, for many readers, the term “file system minifilter driver” in Windows is probably unfamiliar.

So before reading Scanner sample code, this chapter organizes and explains the basic overview of Windows file system minifilter drivers.

A full treatment of minifilter drivers would easily exceed 100 pages.

Therefore, this chapter only covers the prerequisite concepts needed to read Scanner source code, and does not provide a full beginner guide to file system minifilter drivers or Filter Manager.

For details, please refer to Microsoft documentation below.


URL: About file system filter drivers

https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/about-file-system-filter-drivers


Table of Contents

What Is a File System Minifilter Driver?

In Windows, file system filter drivers running in kernel mode can monitor, modify, and filter file I/O operations.2

Windows provides two file system filter driver models: “legacy file system filter drivers” and “minifilter drivers.”3

Of these, legacy file system filter drivers are difficult to develop because implementation considerations are very complex.

They also have disadvantages compared to minifilters, such as not being unloadable while the system is running, so they are not recommended.4

The minifilter driver discussed in this book is also a file system filter driver, but it works as a client of File System Filter Manager (FltMgr.sys), which is loaded as a legacy file system filter driver.

File System Filter Manager is a kernel driver provided by the system to implement features needed by file system filter drivers.5

Because minifilter drivers can filter I/O indirectly through Filter Manager, they are generally more efficient and safer to implement than legacy file system filter drivers.

Registering Minifilter Drivers with File System Filter Manager

By using interfaces provided by File System Filter Manager, minifilter drivers can implement I/O request handling more easily and efficiently.

Minifilter drivers can filter IRP (I/O Request Packets)-based operations through this interface.

Unlike legacy file system filter drivers, minifilters do not need to implement all standard IRP dispatch functions. They can define callback routines only for I/O requests they want to filter.

For each target I/O request, minifilters can register both a “Preoperation Callback Routine” and a “Postoperation Callback Routine.”

This allows callbacks both when an I/O request is received and when completion of that I/O operation is notified.

The figure below (quoted from documentation) shows the I/O stack model using File System Filter Manager and minifilter drivers.7

I/O stack model (quoted from official documentation)

Minifilter Driver Hierarchy (Altitude)

As shown above, Filter Manager is loaded between Windows I/O Manager and file system drivers.

Each minifilter registered with Filter Manager has a value called Altitude.

Altitude defines the order in which minifilters receive and return IRP requests in the I/O stack.

When Filter Manager receives an I/O request, “Preoperation Callback Routine” runs from minifilters with higher Altitude first (descending order).

When Filter Manager receives completion of an I/O operation, “Postoperation Callback Routine” runs in reverse order, from lower Altitude first (ascending order).8

Altitude values are assigned within ranges based on filter type.

The filter type used to define this range is called a Load Order Group.

Mapping between Load Order Group and Altitude ranges is documented below.


URL: Load order groups and altitudes for minifilter drivers

https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/load-order-groups-and-altitudes-for-minifilter-drivers


For example, minifilter drivers used by AntiVirus software such as Microsoft Defender Antivirus belong to “FSFilter AntiVirus,” where Altitude is in range 320000 to 329999.

If you run fltmc on a machine where Microsoft Defender Antivirus is still installed, you can see WdFilter Altitude as 328010.

WdFilter Altitude

For minifilter drivers that must be officially installed on Windows, you need to request Load Order Group assignment from Microsoft.9

The list of filters assigned Altitude by Microsoft is published at the URL below and updated once or twice a year.


URL: Allocated filter altitudes

https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/allocated-altitudes


One reason these restrictions exist is to avoid situations where intended behavior of a minifilter is unintentionally not guaranteed.10

For example, if one minifilter scans data and another encrypts data, an encryption minifilter executing before the scanner on write requests could encrypt data before scanning, causing scanning problems.

Avoiding such issues and allowing each minifilter function to work properly is one key purpose of restricting Altitude ranges by Load Order Group.

Building a Small Minifilter Driver

In this chapter, as preparation for reading Scanner code in later chapters, we implement a simple minifilter driver.

Extract the sample code repository downloaded in Chapter 1 and open nullFilter.sln in [filesys] > [miniFilter] > [nullFilter] with Visual Studio.


URL: Windows-driver-samples

https://github.com/Microsoft/Windows-driver-samples


nullFilter is a simple minifilter driver that only registers itself with Filter Manager.

Because nullFilter has no callback function registration, it performs no filtering, as its name suggests.

First, look at the DriverEntry function, which is called first when the driver is loaded.

status = FltRegisterFilter(
    DriverObject,
    &FilterRegistration,
    &NullFilterData.FilterHandle 
);

FLT_ASSERT( 
    NT_SUCCESS( status ) 
);

if (NT_SUCCESS( status )) {

    status = FltStartFiltering( 
        NullFilterData.FilterHandle 
    );

    if (!NT_SUCCESS( status )) {
        FltUnregisterFilter( 
            NullFilterData.FilterHandle 
        );
    }
}

Here we focus on three functions used in DriverEntry.

  • FltRegisterFilter
  • FltStartFiltering
  • FltUnregisterFilter

Registering a Minifilter Driver with FltRegisterFilter

FltRegisterFilter11, called first, registers a minifilter driver using a pointer to FLT_REGISTRATION12.

It receives three arguments:

NTSTATUS FLTAPI FltRegisterFilter(
  [in]  PDRIVER_OBJECT         Driver,
  [in]  const FLT_REGISTRATION *Registration,
  [out] PFLT_FILTER            *RetFilter
);

The first member Driver receives the driver object pointer passed to DriverEntry.

The second member receives a pointer to FLT_REGISTRATION, and the third receives a pointer where the registered minifilter pointer is stored.

The FLT_REGISTRATION structure contains registration data required for minifilter registration.

In nullFilter, it is defined as follows.

CONST FLT_REGISTRATION FilterRegistration = {

 sizeof( 
    FLT_REGISTRATION 
 ),                       //  Size
 FLT_REGISTRATION_VERSION,//  Version
 0,                       //  Flags

 NULL,                    //  Context
 NULL,                    //  Operation callbacks

 NullUnload,              //  FilterUnload

 NULL,                    //  InstanceSetup
 NullQueryTeardown,       //  InstanceQueryTeardown
 NULL,                    //  InstanceTeardownStart
 NULL,                    //  InstanceTeardownComplete

 NULL,                    //  GenerateFileName
 NULL,                    //  GenerateDestinationFileName
 NULL                     //  NormalizeNameComponent

};

FLT_REGISTRATION specifies its own size and target Windows version (revision level).

Usually, FLT_REGISTRATION_VERSION defined in fltKernel.h is used.

FLT_REGISTRATION can also include ContextRegistration (context array) and OperationRegistration (IRP routine callbacks).

nullFilter does not register those in this stage, so context and callback registration are discussed later.

FilterUnload stores a pointer to the FilterUnloadCallback routine.

This routine is called when unloading a minifilter driver. If it is NULL, the minifilter cannot be unloaded.

In nullFilter, this is NullUnload, which calls FltUnregisterFilter.

If you build nullFilter after setting FilterUnloadCallback to NULL, fltmc unload fails with an error, as shown below.

When FilterUnloadCallback is NULL

FLT_REGISTRATION also registers instance-related callbacks such as InstanceSetupCallback and InstanceQueryTeardownCallback.

This chapter does not go deep into instances, but a minifilter attached to a specific volume at a specific Altitude is called a minifilter instance.13

In nullFilter, only InstanceQueryTeardownCallback is registered; the rest are NULL.

InstanceQueryTeardownCallback runs when detaching a minifilter instance.

Instances can be detached by FltDetachVolume14 or FilterDetach15, and InstanceQueryTeardownCallback can validate detach operations.

Starting Filter Processing with FltStartFiltering

After successful registration with FltRegisterFilter, call FltStartFiltering16 to start filtering.

This function receives a pointer to the minifilter registered by FltRegisterFilter.

NTSTATUS FLTAPI FltStartFiltering(
  [in] PFLT_FILTER Filter
);

When this request completes, Filter Manager treats the minifilter as active and starts I/O filtering.

If processing fails, nullFilter calls FltUnregisterFilter17 to unload.

When FltUnregisterFilter runs, registered callbacks are removed, contexts are deleted, and per-instance teardown callbacks are invoked.

Building and Installing nullFilter

Now build nullFilter in Visual Studio and load it into the VM.

Open nullFilter.sln and set [Windows SDK Version] in project properties to the installed SDK/WDK version.

Open property file

Specify SDK version

Then open [Inf2Cat] > [General] and enable [Use Local Time].

This avoids 22.9.7: DriverVer set to a date in the future (postdated DriverVer not allowed) in environments using Japan timezone.

Inf2Cat Use Local Time setting

After minimum property updates, build from [Build] > [Build Solution].

If build succeeds, output files are placed by default under ./x64/Debug/nullFilter relative to nullFilter.sln.

Verify these three files are generated.

  • nullFilter.sys
  • nullFilter.inf
  • nullFilter.cat

These are all required components of a driver package.18

nullFilter.sys is the minifilter PE file.

nullFilter.inf contains driver installation configuration, including supported OS versions and catalog file name.

nullFilter.cat contains cryptographic hashes of files in the driver package, used by Windows to verify driver integrity.

The catalog file itself must also be digitally signed.

Copy these files to the VM.

Then right-click nullFilter.inf and run [Install].

Install using INF file

If installation succeeds, NullFilter.sys is copied to C:\Windows\System32\drivers, and driver information is registered in service registry.

Service registry after installation

The registry includes driver type, name, image path, startup settings, and more.

For example, the [Instance] key includes attachment settings and Altitude.

Instance key information

After installation, load minifilter with fltmc19.

Run elevated Command Prompt and execute fltmc.

At this point nullFilter is installed but not yet loaded to Filter Manager, so it does not appear in the list.

Check loaded filters with fltmc

Then run fltmc load nullFilter.

Now nullFilter is registered and loaded as a minifilter.

Altitude appears as 370020, as configured in registry during installation.

Load nullFilter

Then use fltmc attach to attach the loaded nullFilter instance to a volume.

You can confirm volume names with fltmc volumes.

Attach nullFilter instance

Finally, run fltmc unload nullFilter to unload it and verify it disappears from fltmc list.

Using DbgPrint

So far, nullFilter performs no filtering.

To deepen understanding, we now modify nullFilter gradually and verify behavior in the VM.

First, add DbgPrintEx20 to NullUnload and NullQueryTeardown to verify callbacks run during detach and unload.

This code is published in branch 01-add-debug-comment of the repository below.


URL: MFLab 01-add-debug-comment

https://github.com/kash1064/MFLab/tree/01-add-debug-comment


Build and reinstall this code in the VM. Use Debug build configuration for this section.

After reinstalling, launch Dbgview.exe (Sysinternals Suite) in VM and enable [Capture Kernel] and [Enable Verbose Kernel Output] from [Capture].

Change Dbgview capture settings

Now detaching or unloading with fltmc shows debug output from the added DbgPrintEx calls.

Confirm callback behavior with DbgPrintEx

Registering IRP Routines and Callback Functions

Next, add filtering behavior by registering IRP routines and callback functions.

To do this, set OperationRegistration in FLT_REGISTRATION to a pointer to an array of FLT_OPERATION_REGISTRATION.

In this example, we register IRP_MJ_CREATE with callbacks FuncPreCreate and FuncPostCreate.

const FLT_OPERATION_REGISTRATION Callbacks[] = {

    { IRP_MJ_CREATE,
      0,
      FuncPreCreate,
      FuncPostCreate},

    { IRP_MJ_OPERATION_END }
};

{ IRP_MJ_OPERATION_END } is mandatory as the last element.21

Both callbacks in this test only print target file names.

/* same code as source */

As shown in source, minifilter driver uses FltGetFileNameInformation22 and FltParseFileNameInformation23 to query/parse file information and access it through FLT_FILE_NAME_INFORMATION24.25

The changed code is published in branch 02-add-irp-callback-routine.


URL: MFLab 02-add-irp-callback-routine

https://github.com/kash1064/MFLab/tree/02-add-irp-callback-routine


Build and attach as before. Although it now has callbacks and is no longer truly nullFilter, names are unchanged.

After attachment, you can confirm both FuncPreCreate and FuncPostCreate are called and file names are output for IRP_MJ_CREATE.

Debug output by FuncPreCreate and FuncPostCreate

Attaching Context to Files

Finally, add file context functionality to the minifilter.

A context is a structure a minifilter can attach to Filter Manager objects.26

As long as the target object exists, context can be accessed consistently and used across I/O operations.

One reason AntiVirus minifilter drivers attach contexts is performance improvement.

For example, data calculated in a Preoperation callback can be stored in context and reused in Postoperation callback.

To attach context, set ContextRegistration in FLT_REGISTRATION to an array of FLT_CONTEXT_REGISTRATION27.

For this test, add the structures below.

typedef struct _DATA_CONTEXT {

    UNICODE_STRING message;

} DATA_CONTEXT, * PDATA_CONTEXT;

const FLT_CONTEXT_REGISTRATION ContextRegistration[] = {

    { FLT_FILE_CONTEXT,
      0,
      NULL,
      sizeof(DATA_CONTEXT),
      'galF' },

    { FLT_CONTEXT_END }
};

FLT_CONTEXT_END must be the final element.

Actual context assignment is implemented in FuncPostCreate.

First, try to retrieve existing file context with FltGetFileContext28.

status = FltGetFileContext(
    Data->Iopb->TargetInstance,
    Data->Iopb->TargetFileObject,
    &dataContext
);

If not found, allocate and set context with FltAllocateContext29 and FltSetFileContext30.

status = FltAllocateContext(
    NullFilterData.FilterHandle,
    FLT_FILE_CONTEXT,
    sizeof(DATA_CONTEXT),
    PagedPool,
    &dataContext
);

status = FltSetFileContext(
    Data->Iopb->TargetInstance,
    Data->Iopb->TargetFileObject,
    FLT_SET_CONTEXT_KEEP_IF_EXISTS,
    dataContext,
    NULL
);

In this sample, if file name is test.txt, write File is test.txt!! into context; otherwise write Not valid file.

Code is published in branch 03-add-file-context.


URL: MFLab 03-add-file-context

https://github.com/kash1064/MFLab/tree/03-add-file-context


After building, reinstalling, and creating test.txt, context is attached. On later IRP_MJ_CREATE requests for the same file, text in context can be referenced.

Attach context to test.txt

Inspecting Minifilter Information from Debugger

So far we implemented callbacks and file context attachment on top of nullFilter.

Finally, attach a kernel debugger to VM and inspect minifilter information.

After attaching debugger, run !fltkd.filters first.

!fltkd31 is a debugger extension for minifilter driver debugging.

!fltkd.filters shows loaded minifilters, Altitudes, and volume-attached instances.

Result of !fltkd.filters

Then run !fltkd.filter <minifilter address> using nullFilter address found in !fltkd.filters to dump detailed configuration.

Result of !fltkd.filter

Likewise, run !fltkd.instance <instance address> with instance address found via !fltkd.filters to dump instance information.

Result of !fltkd.instance

Volume information for attached instances can also be checked with !fltkd.volumes and !fltkd.volume <volume address>.

Result of !fltkd.volumes

Chapter Summary

This chapter explained introductory information about registration and implementation of Windows file system minifilter drivers through the nullFilter sample driver.

In the next chapter, we explain implementation of AntiVirus software for Windows through Scanner sample code.

Table of Contents


  1. Windows Driver Samples - scanner: https://github.com/microsoft/Windows-driver-samples/tree/main/filesys/miniFilter/scanner

  2. About file system filter drivers https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/about-file-system-filter-drivers

  3. Windows Internals 7th Ed. Vol.2 p.653-654 (Andrea Allievi, Alex Ionescu)

  4. Advantages of the filter manager model https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/advantages-of-the-filter-manager-model

  5. Windows Internals 7th Ed. Vol.2 p.654

  6. Filter Manager concepts https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/filter-manager-concepts

  7. Filter Manager concepts https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/filter-manager-concepts

  8. Requesting filter altitude identifiers https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/minifilter-altitude-request

  9. Windows Kernel Programming, Second Edition p.388

  10. FltRegisterFilter https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltregisterfilter

  11. FLT_REGISTRATION structure https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-fltregistration

  12. Filter Manager concepts https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/filter-manager-concepts

  13. FltDetachVolume https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltdetachvolume

  14. FilterDetach https://learn.microsoft.com/ja-jp/windows/win32/api/fltuser/nf-fltuser-filterdetach

  15. FltStartFiltering https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltstartfiltering

  16. FltUnregisterFilter https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltunregisterfilter

  17. Components of a driver package https://learn.microsoft.com/ja-jp/windows-hardware/drivers/install/components-of-a-driver-package

  18. Fltmc.exe https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/development-and-testing-tools

  19. DbgPrintEx https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/wdm/nf-wdm-dbgprintex

  20. FLT_OPERATION_REGISTRATION structure https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-fltoperation_registration

  21. FltGetFileNameInformation https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltgetfilenameinformation

  22. FltParseFileNameInformation https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltparsefilenameinformation

  23. FLT_FILE_NAME_INFORMATION structure https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-fltfilenameinformation

  24. Windows Kernel Programming, Second Edition p.398

  25. Managing contexts in a minifilter https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/managing-contexts-in-a-minifilter-driver

  26. FLT_CONTEXT_REGISTRATION structure https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/ns-fltkernel-fltcontext_registration

  27. FltGetFileContext https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltgetfilecontext

  28. FltAllocateContext https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltallocatecontext

  29. FltSetFileContext https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ddi/fltkernel/nf-fltkernel-fltsetfilecontext

  30. !fltkd extension https://learn.microsoft.com/ja-jp/windows-hardware/drivers/ifs/development-and-testing-tools