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.
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.
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:
First, we need to declare a global variable that will store the handle of our minifilter driver after it’s registered, launched, and stopped:
Then we need to implement the DriverEntry function, which is our driver’s entry point. We register our minifilter driver in this function:
Next, we need to call the FltStartFiltering function to launch our minifilter so it can actually start filtering I/O requests:
When registering the minifilter, we pass the address of the FLT_REGISTRATION struct object to the FltRegisterFilter function:
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:
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:
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:
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:
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:
- 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:
- Set the LoadOrderGroup value in accordance with the Class value chosen earlier:
- 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):
With these three files composed, we can now move to building and installing our minifilter.
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.
- To install the minifilter driver, right-click the INF file and select the Install option:
- Check if the driver was installed properly and then start it:
- To stop or delete our minifilter driver, we use the SC utility:
As you can see, our minifilter driver is quite easy to work with. But can it actually do the job? Let’s find out!
To see if our minifilter driver works as it’s supposed to, we’ll use the following tools:
- Sysinternals DebugView utility for monitoring the debug output
- OSR DeviceTree utility for displaying devices and drivers
Once the minifilter driver is up and running, open the DebugView utility to see the names of all opened files:
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:
As you can see, building and attaching a file system minifilter driver takes much less effort than working with legacy filter drivers.
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.