For the past number of years I have been working on driver projects that are file system filter drivers. Many people are familiar with these kinds of drivers, and there is lots of information out on the internet about how to create them. My drivers have also been register filter drivers: they do much the same thing that file system filter drivers do, but for the registry. Unfortunately, there is not a lot of information out there about writing one of these. There is a brief article on osronline.com (written by a coworker of mine of one of these drivers), and there is a sample driver include as part of the Microsoft Windows Driver Kit samples. The lack of information out there leaves me feeling like I am the only developer foolish enough to be working on one of these drivers. I recently decided that I should help to remedy that lack of information available about these drivers, and hopefully entice a few more foolish developers into trying their hand at this.
This first article will be a very basic intro on building a registry filter driver that does nothing! But never fear, dear reader, there will be more in-depth information coming soon.
First, let’s talk generally about motivation. What is “filtering” the registry, and why in the world would I want to do that? A filter driver lets you see operating system calls for the registry before (and after) they actually get processed by the Windows registry code (Configuration Manager or CM) in the kernel. A filter driver lets you examine the calls passively, for example you might create a registry call logging utility like Sysinternals’ RegMon or Process Monitor. It also lets you actively filter the calls, changing the results if you feel inclined. For example, you might want to have a security product that prevents people from writing to certain keys. (Granted, you could do this with ACLs as well, but you get the idea.)
In the old days of yore (pre-Vista) this functionality was not really baked. In early operating systems it was not there at all, and you would be forced to hook into the system call table in order to try to write your filtering product. This could lead to all sorts of potential problems, of course, and Microsoft decided that to support these products they needed new APIs for registry filtering. These were added in Windows XP and later, but the initial releases only supported passive filtering. The ability to do active filtering came along in Vista and later, and continues to see improvements in later operating systems.
The basics of building a registry filter driver are that you need your driver to call CmRegisterCallbackEx during its DriverEntry routines, and then to call CmUnRegisterCallback during your DriverUnload routines. Registry filters are considered minifilters and are loaded based on Load Order Groups and Altitude, just as file system minifilters are. We won’t really discuss this much in this series, but to create your own you need to request an altitude from Microsoft which helps them determine in which order filters should be called.
So to build a bare-bones registry filter, first we call the registration function during our driver initialization as follows. Note that we have defined a callback routine (which we will implement later), and made up an altitude just for testing. We get back from the registry a “cookie” that we must use later on when we want to unregister our filter.
NTSTATUS RfRegistryCallback( __in PVOID CallbackContext, __in PVOID Argument1, __in PVOID Argument2 ); void RfUnload( __in PDRIVER_OBJECT pDriverObject ); LARGE_INTEGER g_CmCookie = { 0 }; extern "C" NTSTATUS DriverEntry( __in PDRIVER_OBJECT pDriverObject, __in PUNICODE_STRING pRegistryPath ) { UNREFERENCED_PARAMETER( pRegistryPath ); // Set up our unload routine pDriverObject->DriverUnload = RfUnload; // Register our callback with the system UNICODE_STRING AltitudeString = RTL_CONSTANT_STRING( L"360000" ); NTSTATUS status = CmRegisterCallbackEx( RfRegistryCallback, &AltitudeString, pDriverObject, NULL, &g_CmCookie, NULL ); if ( !NT_SUCCESS( status ) ) { // Failed to register - probably shouldn't succeed the driver intialization since this is critical } return status; }
Next we need to unregister our registry callback when our driver unloads:
void RfUnload( __in PDRIVER_OBJECT pDriverObject ) { UNREFERENCED_PARAMETER( pDriverObject ); PAGED_CODE(); NTSTATUS status = CmUnRegisterCallback( g_CmCookie ); if ( !NT_SUCCESS( status ) ) { // Failed to unregister - try to handle gracefully } }
Finally, we need a routine for the registry to call in our driver when something happens. For this first article, this routine isn’t really going to do anything, but it exists as a sample of what the callback function looks like, and what some of the parameters look like and how to handle them. In a future article we will look at a more graceful way to handle this callback routine.
NTSTATUS RfRegistryCallback( __in PVOID CallbackContext, __in PVOID Argument1, __in PVOID Argument2 ) { UNREFERENCED_PARAMETER( CallbackContext ); UNREFERENCED_PARAMETER( Argument2 ); REG_NOTIFY_CLASS Operation = (REG_NOTIFY_CLASS) (ULONG_PTR) Argument1; switch ( Operation ) { case RegNtPreCreateKeyEx: break; case RegNtPreOpenKeyEx: break; case RegNtKeyHandleClose: break; } return STATUS_SUCCESS; }
That’s it! At this point you have a working registry filter driver. You can install it on a machine (I just do this with the command line “sc create” and regedit for testing purposes). If you connect a kernel debugger and put a breakpoint on the RfRegistryCallback routine, you will see it’s getting called for all kinds of registry operations (there are a LOT of these going on in the system).