Logo
blank Skip to main content

How to Develop a Windows File System Minifilter Driver: Complete Tutorial

The less code the better, right? Minifilters seem the perfect illustration of this. They can help many Windows developers spend less time writing code and lower the risk of introducing bugs.

In this article, we discuss how to develop minifilter drivers for Windows that will show open files in the debug output. To illustrate the simplicity and effectiveness of the minifilter approach, we use the example from our article on file system driver development.

This tutorial will be useful for C/C++ developers and Windows device driver developers that want to learn how to develop a simple file system minifilter driver.

In Windows, there are two types of filter drivers: legacy file system filter drivers and minifilter drivers. A legacy Windows file system filter driver can directly modify file system behavior and is called during each file system input/output (I/O) operation.

However, Microsoft currently favors minifilter drivers. A minifilter driver connects to the file system indirectly by registering all needed callback filtering procedures in a filter manager.

The latter is a Windows file system filter driver that gets activated and connects to the file system stack only when a minifilter is loaded. The filter manager calls the filtering procedures registered by a minifilter for a specific I/O operation.

In short, even though they donโ€™t have direct access to the file system, minifilters can still change system behavior. However, minifilters are much easier to write and use than their legacy alternatives. To illustrate this, we use an example filter from our previous Windows file system driver tutorial that displays the names of opened files in the debug output. Letโ€™s see how we accomplished this task with the help of a minifilter driver.

Need a custom driver to enhance your device and systems?

Reach out to our kernel and driver developers to improve your systemโ€™s stability, performance, and efficiency! 

Implementing a file system minifilter driver for Windows

Before we start developing a minifilter driver, we need to install Visual Studio 2019 with all available SDKs and the Windows Driver Kit (WDK). Then we can move to the key files composing our minifilter driver:

  • Main.cpp
  • FsMinifilter.cpp
  • FsMinifilter.inf

Main.cpp

To develop a file system minifilter driver for Windows, we first need to declare a global variable that will store the handle of our minifilter driver after itโ€™s registered, launched, and stopped:

C++
//
// The minifilter handle that results from a call to FltRegisterFilter
// NOTE: This handle must be passed to FltUnregisterFilter during minifilter unloading
//
PFLT_FILTER g_minifilterHandle = NULL;

Then we need to implement the DriverEntry function, which is our driverโ€™s entry point. We register our minifilter driver in this function:

C++
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
    //
    // register minifilter driver
    //
    NTSTATUS status = FltRegisterFilter(DriverObject, &g_filterRegistration, &g_minifilterHandle);
    if (!NT_SUCCESS(status))
    {
        return status;
    }
  
    // ...
}

Next, we need to call the FltStartFiltering function to launch our minifilter so it can actually start filtering I/O requests:

C++
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
    // ...
      
    //
    // start minifilter driver
    //
    status = FltStartFiltering(g_minifilterHandle);
    if (!NT_SUCCESS(status))
    {
        FltUnregisterFilter(g_minifilterHandle);
    }
      
    return status;
}

When registering the minifilter, we pass the address of the FLT_REGISTRATION struct object to the FltRegisterFilter function:

C++
//
// The FLT_REGISTRATION structure provides information about a file system minifilter to the filter manager.
//
CONST FLT_REGISTRATION g_filterRegistration =
{
    sizeof(FLT_REGISTRATION),      //  Size
    FLT_REGISTRATION_VERSION,      //  Version
    0,                             //  Flags
    NULL,                          //  Context registration
    g_callbacks,                   //  Operation callbacks
    InstanceFilterUnloadCallback,  //  FilterUnload
    InstanceSetupCallback,         //  InstanceSetup
    InstanceQueryTeardownCallback, //  InstanceQueryTeardown
    NULL,                          //  InstanceTeardownStart
    NULL,                          //  InstanceTeardownComplete
    NULL,                          //  GenerateFileName
    NULL,                          //  GenerateDestinationFileName
    NULL                           //  NormalizeNameComponent
};

Read also

How to Hook 64-Bit Code from WOW64 32-Bit Mode

Maintain seamless compatibility between your 32- and 64-bit applications to enhance your system security and integrity with our step-by-step guide! 

Learn more

The FltRegisterFilter function contains special callback procedures for loading, initiating, and disabling the driver. Weโ€™ll get back to these procedures a bit later.

Right now, we need to pass the array of the FLT_OPERATION_REGISTRATION  structures describing pre-operation and post-operation procedures for a specific I/O operation to the FLT_REGISTRATION structure. For our minifilter, registering a pre-operation callback for the IRP_MJ_CREATE operation will be enough:

C++
//
// Constant FLT_REGISTRATION structure for our filter.
// This initializes the callback routines our filter wants to register for.
//
CONST FLT_OPERATION_REGISTRATION g_callbacks[] =
{
    {
        IRP_MJ_CREATE,
        0,
        PreOperationCreate,
        0
    },
  
    { IRP_MJ_OPERATION_END }
};

FsMinifilter.cpp

This file contains all callback procedures needed for the operation of our minifilter driver.

A minifilter driver can register pre- and post-operation procedures for every I/O operation. These procedures are pretty similar to dispatch and completion procedures of a regular filter driver.

We need to implement a pre-operation procedure for the IRP_MJ_CREATE operation in which we output the file names:

C++
FLT_PREOP_CALLBACK_STATUS FLTAPI PreOperationCreate(
    _Inout_ PFLT_CALLBACK_DATA Data,
    _In_ PCFLT_RELATED_OBJECTS FltObjects,
    _Flt_CompletionContext_Outptr_ PVOID* CompletionContext
)
{
    //
    // Pre-create callback to get file info during creation or opening
    //
  
    DbgPrint("%wZ\n", &Data->Iopb->TargetFileObject->FileName);
  
    return FLT_PREOP_SUCCESS_NO_CALLBACK;
}

Next, we need to implement the InstanceFilterUnloadCallback procedure for unloading our minifilter. In this procedure, we call the FltUnregisterFilter function.

Note: Microsoft documentation strongly recommends registering this procedure so that the filter manager can unload the minifilter when needed:

C++
NTSTATUS FLTAPI InstanceFilterUnloadCallback(_In_ FLT_FILTER_UNLOAD_FLAGS Flags)
{
    //
    // This is called before a filter is unloaded.
    // If NULL is specified for this routine, then the filter can never be unloaded.
    //
  
    if (NULL != g_minifilterHandle)
    {
        FltUnregisterFilter(g_minifilterHandle);
    }
  
    return STATUS_SUCCESS;
}

The two other procedures โ€” InstanceSetupCallback and InstanceQueryTeardownCallback โ€” are needed for our driver to be able to connect to and disconnect from all disk partitions. These procedures are just stubs that do nothing and return STATUS_SUCCESS:

C++
NTSTATUS FLTAPI InstanceSetupCallback(
    _In_ PCFLT_RELATED_OBJECTS  FltObjects,
    _In_ FLT_INSTANCE_SETUP_FLAGS  Flags,
    _In_ DEVICE_TYPE  VolumeDeviceType,
    _In_ FLT_FILESYSTEM_TYPE  VolumeFilesystemType)
{
    //
    // This is called to see if a filter would like to attach an instance to the given volume.
    //
  
    return STATUS_SUCCESS;
}
  
NTSTATUS FLTAPI InstanceQueryTeardownCallback(
    _In_ PCFLT_RELATED_OBJECTS FltObjects,
    _In_ FLT_INSTANCE_QUERY_TEARDOWN_FLAGS Flags
)
{
    //
    // This is called to see if the filter wants to detach from the given volume.
    //
  
    return STATUS_SUCCESS;
}

FsMinifilter.inf

When creating a driver project, Visual Studio generates an INF file: a configuration file containing all information that the operating system needs for installing a minifilter driver. We need to change some template values in the INF file generated by Visual Studio:

  1. Set Class and ClassGuid values for the minifilter driver. The ClassGuid value can be generated with the help of Visual Studio, while the Class value can be chosen in Windows documentation for minifilter development.

Here’s how we set those values in our example:

C++
;; ...
[Version]
Class    = "Bottom"
ClassGuid = {21D41938-DAA8-4615-86AE-E37344C18BD8}
;; ...
  1. Set the LoadOrderGroup value in accordance with the Class value chosen earlier:
C++
;; ...
[MiniFilter.Service]
LoadOrderGroup = "FSFilter Bottom"
;; ...
  1. Set the Instance1.Altitude value, which determines the order in which a specific minifilter driver will be loaded within a particular Class (at your discretion):
C++
;; ...
Instance1.Altitude = "47777"
;; ...

With these three files composed, we successfully created a minifilter driver and can now move to building and installing it.

Related project

Improving a Windows Audio Driver to Obtain a WHQL Release Signature

Explore how Apriorit’s expertise in driver development helped our client to improve their audio driver’s performance and stability, earning them a WHQL Release Signature.

Project details

Installing a minifilter driver

Now we need to install the driver using the INF file. To start, stop, and remove the driver, we use the Service Control (SC) utility (sc.exe).

Letโ€™s start with installing the driver.

  1. To install the minifilter driver, right-click the INF file and select the Install option:
01

 

Figure 1. Installing a minifilter driver

  1. Check if the driver was installed properly and then start it:
Starting a minifilter driver

 

Figure 2. Starting a minifilter driver

  1. To stop or delete our minifilter driver, we use the SC utility:
stopping the driver

 

Figure 3. Stopping and deleting a minifilter driver

As you can see, our minifilter driver is quite easy to work with. But can it actually do the job? Letโ€™s find out!

Checking the performance of our minifilter driver

To see if our code works as itโ€™s supposed to, weโ€™ll use the following tools for minifilter development:

Once the minifilter driver is up and running, open the DebugView utility to see the names of all opened files:

debug output monitoring

 

Figure 4. Debug output monitoring

We also need to launch the DeviceTree utility to see how our minifilter driver connects to volume devices. As itโ€™s the filter manager and not the minifilter itself that connects directly to the file system, in the device tree of our file system, weโ€™ll see the filter manager but no minifilters:

device tree

 

Figure 5. Our filter manager in the device tree

As you can see, building and attaching a file system minifilter driver takes much less effort than working with legacy filter drivers.

If you want to learn more about drivers, check out our another article that explains how to develop virtual disk driver for any Windows version.

Read also

Controlling and Monitoring a Network with User Mode and Driver Mode Techniques: Overview, Pros and Cons, WFP Implementation

Take charge of your network traffic! Optimize your network management and get insights on implementing powerful user-mode and driver-mode techniques from a guide by Apriorit experts. 

Learn more

Conclusion

Minifilter drivers can provide the same results as legacy file system filter drivers but require less effort to develop. When working with minifilters, you can implement filters for precisely the I/O operations you need for the task at hand.

Thanks to their simplicity, flexibility, reliability, and great performance, minifilters are the main filter driver development approach that Microsoft recommends for Windows systems. In this Windows filter driver development tutorial, we showed you the basics of creating one yourself. And if you want to learn how to test drivers, check out our article on Windows driver testing basics.

At Apriorit, we have a team of creative kernel and driver development experts and file system minifilter developers who have mastered the art of building device drivers and minifilters of any complexity. Leverage our expertise in Windows driver model development.

Looking for a dedicated driver development team?

Benefit from our extensive 20-year track record in specialized driver development to reinforce your product and broaden its features!

Have a question?

Ask our expert!

Tell us about
your project

...And our team will:

  • Process your request within 1-2 business days.
  • Get back to you with an offer based on your project's scope and requirements.
  • Set a call to discuss your future project in detail and finalize the offer.
  • Sign a contract with you to start working on your project.

Do not have any specific task for us in mind but our skills seem interesting? Get a quick Apriorit intro to better understand our team capabilities.