Date Thu 25 October 2018 By Gwaby Category ReverseEngineering. Tags Windows WNF

This blogpost briefly presents the Windows Notification Facility and provides a write-up for a nice exercise that was given by Bruce Dang during his workshop at Recon Montreal 2018.

Preamble This post was mainly written few days after the end of Bruce Dang's Recon Montreal training but I decided to postpone its publication for various reasons. I almost forgot about it until Alex reminded me that it was still pending. As Bruce already wrote a really nice post about WNF (if you missed it, I encourage you to read it now) I felt that mine wouldn't bring anything new to the table and that there wasn’t much point for me to finish it properly but someone didn't really give me the choice... :')

Introduction Some time ago, Bruce Dang invited five BlackHoodie ladies to attend his Windows Kernel Rootkit training at Recon Montreal. Barbie, Priya, Oryan, Aneal and I had the chance to be there during these four days of intensive work. In this blog post I won't describe the content of the class (trust me, it was great) but I will focus on one of the exercises I really enjoyed: reversing and (mis)using WNF!

Input data I didn't know this component at all and very few information on it is available on the Internet. The only input at my disposal was the following prompt: 14. Reverse engineer the following Windows kernel functions. The result of this exercise will be used in the next exercise. • ExSubscribeWnfStateChange • ExQueryWnfStateData 15. Write a driver that gets notified when an application is using the microphone. Print its pid. Hints: • check contentDeliveryManager_Utilities.dll for the WNF StateName. • some interesting info is available here: http://redplait.blogspot.com/2017/08/wnf-ids-from-perfntcdll.html

Some background The Windows Notification Facility, or WNF, is a (not really well-known) kernel component used to dispatch notifications across the system. It can be used either in kernel-mode or in user-land with a set of exported (but obviously not documented) API functions and related data structures. An application may subscribe a specific type of event (identified by a StateName) in order to be notified every time a change to its state occurs (which can be associated with StateData). On the other hand, a publisher component is responsible for providing the data that will be sent with the notification and to trigger the event. It should be noted that WNF state names can be instanced (Scope) to a single process, a silo (Windows Container), or to the whole machine. For example, if the application is running inside a silo, it will only be notified for Silo-scoped events happening inside its own container. In this blog post, I won't speak about the user-land mechanisms involved when using high level API: they are kind of out of scope for the exercise and the explanations would make the blog post a bit too heavy. Alex Ionescu and I gave an in-depth talk about WNF in both of its flavors at BlackHat USA 2018, which should be posting the videos and slides sometime in November 2018 (the release of which is pending some vulnerabilities to be addressed by MSRC).

Data Structures There are a lot of structures involved in the WNF and here is a simplified view of their relationship in memory: An event, or an instance of a WNF State Name, is represented in memory by a WNF_NAME_INSTANCE structure. These structures are sorted in binary trees and linked to the scope in which the event happens. The scopes determine what information a component is able to see or access. They also enable the instantiation of different data for the same State Name. There are five possible types of scope that are defined as follows: typedef enum _WNF_DATA_SCOPE { WnfDataScopeSystem = 0x0 , WnfDataScopeSession = 0x1 , WnfDataScopeUser = 0x2 , WnfDataScopeProcess = 0x3 , WnfDataScopeMachine = 0x4 , } WNF_DATA_SCOPE ; The scopes, identified with WNF_SCOPE_INSTANCE structures, are stored by type in doubly linked lists and their heads are kept in a WNF_SCOPE_MAP that is silo specific. When a component subscribes to a WNF State Name, a new WNF_SUBSCRIPTION structure is created and added to the linked list belonging to the associated WNF_NAME_INSTANCE . If the subscriber is using a low-level API (such as the one that is described below), a callback is added in the WNF_SUBSCRIPTION and called when the component needs to be notified. A WNF_PROCESS_CONTEXT object keeps track of all the different structures involved for a specific subscriber process. It also stores the KEVENT used to notify the process. This context is either accessible via the EPROCESS object or by crawling the doubly linked list pointed by nt!ExpWnfProcessesListHead . Below you'll find a representation of these connections. In case you're wondering what the 0x906 refers to, this is related to the fact that all of the structures used by WNF have a tiny header (a common occurrence in Windows' File System-related data structures) that describes the structure type and size: typedef struct _WNF_CONTEXT_HEADER { CSHORT NodeTypeCode ; CSHORT NodeByteSize ; } WNF_CONTEXT_HEADER , * PWNF_CONTEXT_HEADER ; This header is pretty handy when debugging as it's quite easy to spot the objects in memory. Here are some node type codes for the WNF structures: #define WNF_SCOPE_MAP_CODE ((CSHORT)0x901) #define WNF_SCOPE_INSTANCE_CODE ((CSHORT)0x902) #define WNF_NAME_INSTANCE_CODE ((CSHORT)0x903) #define WNF_STATE_DATA_CODE ((CSHORT)0x904) #define WNF_SUBSCRIPTION_CODE ((CSHORT)0x905) #define WNF_PROCESS_CONTEXT_CODE ((CSHORT)0x906)

Reverse time Now that we have some background, let's start the exercises! The first part was to actually reverse the following functions in order to understand their purpose: ExSubscribeWnfStateChange

ExQueryWnfStateData ExQueryWnfStateData NTSTATUS ExQueryWnfStateData ( _In_ PWNF_SUBSCRIPTION Subscription , _Out_ PWNF_CHANGE_STAMP ChangeStamp , _Out_ PVOID OutputBuffer , _Out_ OPULONG OutputBufferSize ); This function is quite simple as it only performs two actions. First it retrieves the WNF_NAME_INSTANCE from the Subscription with ExpWnfAcquireSubscriptionNameInstance . Then, it reads the data stored in it with ExpWnfReadStateData and tries to copy it in Buffer . If this buffer is too small, it will only write the size needed in OuputBufferSize and return with STATUS_BUFFER_TOO_SMALL . For the record, all WNF State Names store their data in memory under a WNF_STATE_DATA structure. This structure holds various metadata such as the data size and the number of times it has been updated (called the ChangeStamp ). A pointer to the WNF_STATE_DATA is kept directly in the WNF_NAME_INSTANCE , as shown below. Alex would also like me to point out that WNF State Names can be marked as persistent, which means that the data (and change stamp) will be retained accross reboots (obviously by using a secondary data store). More details on this will be available in our presentation!

Let's code! Basically, with the functions we reversed, we should be able to register a new subscription and be notified as any other legitimate application using WNF :) However we are still missing one element: finding the required WNF State Name for microphone input. I will only detail the parts of the driver that are relevant to interacting with WNF. If you are interested in driver development on Windows, you might want to take a look at the Windows Driver Kit documentation and their samples, or better yet, directly attend one of Bruce's training courses ;) Looking for the right WNF State Name As a hint for retrieving the WNF State Name, Bruce provided a link to a blog post and the name of a library ( contentDeliveryManager_Utilities.dll ). In their blog entry, Redplait defined several State Names used by WNF. Unfortunately, the one we are looking for is not listed. However, that still gives us a good start, for we now know what WNF State Names look like. One naive approach to find what we are looking for is to grep for one of the blog's WNF State Names in contentDeliveryManager_Utilities.dll and hope that other IDs will be around... Luckily, this works pretty well! By following the cross reference of the matched pattern in IDA, we can reach a full list of WNF State Names referenced in the DLL. Each entry in this list comes with its name and description which is really handy for our purposes! (For further information, this list is used by GetWellKnownWnfStateByName ). We now just have to look for the one specific to the microphone (remember the exercise? :p) .rdata:00000001800E3680 dq offset WNF_AUDC_CAPTURE .rdata:00000001800E3688 dq offset aWnf_audc_captu ; "WNF_AUDC_CAPTURE" .rdata:00000001800E3690 dq offset aReportsTheNu_0 ; "Reports the number of, and process ids "... // “Reports the number of, and process ids of all applications currently capturing audio. // Returns a WNF_CAPTURE_STREAM_EVENT_HEADER data structure” It should be noted that the same table is also available with the``perf_nt_c.dll`` library that is included with Windows Performance Analyzer. Subscribing to the event To subscribe a new event, we just have to call ExSubscribeWnfStateChange in our driver with the WNF State Name we found out from above. This function is exported but not defined in any header, therefore we have to manually declare it ourselves by pasting the definition from above. Note that ntoskrnl.lib contains the import library stub, so there is no need to retrieve its address by hand (thanks Alex for the protip ;)). The only thing that needs to be done here is to call the function with the right parameters: NTSTATUS CallExSubscribeWnfStateChange ( VOID ) { PWNF_SUBSCRIPTION wnfSubscription = NULL ; WNF_STATE_NATE stateName ; NTSTATUS status ; stateName . Data = 0x2821B2CA3BC4075 ; // WNF_AUDC_CAPTURE status = ExSubscribeWnfStateChange ( & wnfSubscription , & stateName , 0x1 , NULL , & notifCallback , NULL ); if ( NT_SUCCESS ( status )) DbgPrint ( "Subscription address: %p

" , Subscription_addr ); return status ; } Defining the callback As we saw previously, ExSubscribeWnfStateChange takes among its parameters a callback that will be called every time the event is triggered. This callback will be used to get and process event data relative to the notification. The callback prototype looks like that: NTSTATUS notifCallback ( _In_ PWNF_SUBSCRIPTION Subscription , _In_ PWNF_STATE_NAME StateName , _In_ ULONG SubscribedEventSet , _In_ WNF_CHANGE_STAMP ChangeStamp , _In_opt_ PWNF_TYPE_ID TypeId , _In_opt_ PVOID CallbackContext ); To obtain the data in our callback, we have to call ExQueryWnfStateDataName . Once again this function is exported but not defined in any header so we have to define it ourselves: NTSTATUS ExQueryWnfStateData ( _In_ PWNF_SUBSCRIPTION Subscription , _Out_ PWNF_CHANGE_STAMP CurrentChangeStamp , _Out_writes_bytes_to_opt_ ( * OutputBufferSize , * OutputBufferSize ) PVOID OutputBuffer , _Inout_ PULONG OutputBufferSize ); [...] We need to call this API twice: once to get the size needed to allocate a buffer for the data and the second time to actually retrieve the data. NTSTATUS notifCallback (...) { NTSTATUS status = STATUS_SUCCESS ; ULONG bufferSize = 0x0 ; PVOID pStateData ; WNF_CHANGE_STAMP changeStamp = 0 ; status = ExQueryWnfStateDataFunc ( Subscription , & changeStamp , NULL , & bufferSize ); if ( status != STATUS_BUFFER_TOO_SMALL ) goto Exit ; pStateData = ExAllocatePoolWithTag ( PagedPool , bufferSize , ' LULZ ' ); if ( pStateData == NULL ) { status = STATUS_UNSUCCESSFUL ; goto Exit ; } status = ExQueryWnfStateDataFunc ( Subscription , & changeStamp , pStateData , & bufferSize ); if ( NT_SUCCESS ( status )) DbgPrint ( "## Data processed: %S

" , pStateData ); [...] // do stuff with the data Exit : if ( pStateData != nullptr ) ExFreePoolWithTag ( pStateData , ' LULZ ' ); return status ; } Cleaning the mess when unloading the driver If you blindly try the code above, you'll get an ugly blue screen of death as I painfully learnt the first time I tried the exercise and unloaded my driver! :P We need to delete our subscription beforehand. For this purpose, we can call ExUnsubscribeWnfStateChange in the driver unload routine (and make sure that PWNF_SUBSCRIPTION wnfSubscription is made into a global variable). PVOID ExUnsubscribeWnfStateChange ( _In_ PWNF_SUBSCRIPTION Subscription ); VOID DriverUnload ( _In_ PDRIVER_OBJECT DriverObject ) { [...] ExUnsubscribeWnfStateChange ( g_WnfSubscription ); } Quite an epic fail All we have to do now is to start the driver, enable Cortana, play a little bit with it and wait for the event to trigger. ... Aaaand...! Nothing! My exercise result totally failed as I forgot that I didn't have a sound card on my Virtual Machine (probably the reason why I couldn’t start any of the sound related apps..? >.>’) and most importantly, due to my host configuration, I couldn't make it work at all (don't ask). Still, in order to ensure that my driver works correctly I had to choose another event (sigh) and went for WNF_SHEL_DESKTOP_APPLICATION_STARTED . This notification is signaled anytime a desktop application launches. In return, it simply outputs the application name that was started. With this WNF State Name, it was pretty straightforward to get some results: