In this episode of the series, we are going to take a little side trip to explore contexts in a registry filter driver. There are two main types of context in a registry filter. The first is the most simple and is called a callback context. This is simply a pointer value that you can initialize during a pre-operation callback and it gets passed to you in your post-operation callback. The PVOID member is in all of the pre-op callback structures, such as REG_CREATE_KEY_INFORMATION, starting with Vista. The field name is CallContext, and you simply set it during pre-op and use it during post-op. Cm does not manage this object’s lifetime in any way for you. It doesn’t automatically behave one way if the call succeeds vs. fails, or anything like that. So you have to take care to handle those things yourself. Following is a very simple pre-create/open callback, all it does is allocate a structure and set the CallContext so that the post- callback will receive it.
NTSTATUS RfPreCreateOrOpenKeyEx( __in PVOID CallbackContext, __in PVOID Argument1, __in PREG_CREATE_KEY_INFORMATION CallbackData ) { UNREFERENCED_PARAMETER( CallbackContext ); UNREFERENCED_PARAMETER( Argument1 ); PAGED_CODE(); // Allocate a new callback context and have it passed back to us when the open key completes PMY_CONTEXT pContext = (PMY_CONTEXT) ExAllocatePoolWithTag( PagedPool, sizeof(MY_CONTEXT), 'xCyM' ); if ( pContext ) { // Put some meaningless data in our context that we can verify later pContext->Data = 42; CallbackData->CallContext = pContext; } return STATUS_SUCCESS; }
The code for the post- callback is almost as simple, but now we will examine the second kind of context: the object context. Registry filters can attach a bit of data to any registry object. This may be useful for creating some data during key-open that would be used during later operations such as a value enumerate. You can put anything in here, and Cm will pass it to you in the ObjectContext field of every subsequent callback for that object. Multiple registry filters can each store their own context, and the system differentiates between these by using the “Cookie” value that is returned when you register your filter with Cm.
For our example, we will take the context that was generated in the pre-create/open, and if the create/open was successful, we will attempt to attach this same context to the object. If the operation was not successful, or we were unable to set the object context, then we need to free the context ourselves.
NTSTATUS RfPostCreateOrOpenKeyEx( __in PVOID CallbackContext, __in PVOID Argument1, __in PREG_POST_OPERATION_INFORMATION CallbackData ) { UNREFERENCED_PARAMETER( CallbackContext ); UNREFERENCED_PARAMETER( Argument1 ); PAGED_CODE(); if ( CallbackData->CallContext ) { if ( STATUS_SUCCESS == CallbackData->Status ) { // Attach the context we allocated to the opened object NTSTATUS status; status = CmSetCallbackObjectContext( CallbackData->Object, &g_CmCookie, CallbackData->CallContext, NULL ); if ( NT_SUCCESS( status ) ) { InterlockedIncrement( &g_nObjectContexts ); } else { ExFreePool( CallbackData->CallContext ); } } else { ExFreePool( CallbackData->CallContext ); } } return STATUS_SUCCESS; }
Once the context is attached to the object, Cm now will manage it’s lifetime for us, giving us a callback whenever it needs the context to be cleaned up. (Cm makes this callback either when the object is closed, or when your filter is unloaded.)
NTSTATUS RfCallbackObjectContextCleanup( __in PVOID CallbackContext, __in PVOID Argument1, __in PREG_CALLBACK_CONTEXT_CLEANUP_INFORMATION CallbackData ) { UNREFERENCED_PARAMETER( CallbackContext ); UNREFERENCED_PARAMETER( Argument1 ); PAGED_CODE(); if ( CallbackData->ObjectContext ) { // Make sure that our context looks like we expect it to PMY_CONTEXT pContext = (PMY_CONTEXT) CallbackData->ObjectContext; ASSERT( 42 == pContext->Data ); ExFreePool( CallbackData->ObjectContext ); InterlockedDecrement( &g_nObjectContexts ); } return STATUS_SUCCESS; }
The object context is a powerful way to help you generate information about an object that you will need later, and attach it to the object. It’s nothing you couldn’t have written yourself by managing your own storage of pointers to context, and linking the object pointer to each one, but it’s just so nice when Cm does it for you. ๐
One additional note should be made. Cm will also provide you the context of a root object when a relative create/open is done, in the RootObjectContext field.
Also note that I have put in some testing and debugging code, keeping track of how many contexts have been created vs. freed, and making sure that they all get cleaned up when the driver is unloaded. This test code is not strictly necessary, but helped to reassure me that the correct things were happening.
Would you consider teaching how to query the name of the accessed keys and its key value?
I’m kinda stuck trying to read these unicode strings, since the offsetting seems to be off.
@Thiago, part 2 of this series talks about getting the full name of the opened key. Once you have that, you can just build an OBJECT_ATTRIBUTES and call ZwOpenKey to get a key handle, and then ZwQueryValueKey to query a value that lives within that key.
Omg, that’s true, i totally missed this. Thanks for pointing out, now using your method it works flawlessly.
Thanks for the post. Very well written and helpful. What happens if you create a reg value in the driver from the registry callback? ๐ Who sees that? Many thanks.
I think this article Supporting Layered Registry Filtering Drivers will help answer that. Basically, only the registry filters below yours in altitude should see the operations your filter performs.