ApriorIT

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 talk about developing file system minifilters 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 Windows file system minifilter driver development tutorial will be useful for C/C++ developers and Windows device driver developers.

Contents:

Implementing a file system minifilter driver

     Main.cpp

     FsMinifilter.cpp

     FsMinifilter.inf

Installing a minifilter driver

Checking the performance of our minifilter driver

Conclusion

 

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.

Related services

Kernel and Driver Development

Implementing a file system minifilter driver

Before we start the process of Windows file system driver development, 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

First, we need to declare a global variable that will store the handle of our minifilter driver after it’s registered, launched, and stopped:

//
// 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:

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:

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:

//
// 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
};

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:

//
// 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 }
};

Read also:
How to Hook 64-Bit Code from WOW64 32-Bit Mode

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: 

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:

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:

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:

;; ...
[Version]
Class    = "Bottom"
ClassGuid = {21D41938-DAA8-4615-86AE-E37344C18BD8}
;; ...
  1. Set the LoadOrderGroup value in accordance with the Class value chosen earlier:
;; ...
[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):
;; ...
Instance1.Altitude = "47777"
;; ...

With these three files composed, we can now move to building and installing our minifilter.

Read also:
Developing a Simple Display-only Driver for a Graphics Adapter: A Practical Example

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 minifilter driver works as it’s supposed to, we’ll use the following tools:

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.

Related services

Outsource Software Development in C/C++

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.

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. Get in touch with us to start discussing your next ambitious project.

 

Let's talk

4000 chars left
Attach a file
Browse
By clicking Send you give consent to processing your data

Book an Exploratory Call

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.

Book time slot

Contact Us

P: +1 202-780-9339
E: [email protected]

8 The Green, Suite #7106, Dover, DE 19901
United States

D-U-N-S number: 117063762

btnUp