0% found this document useful (0 votes)
939 views43 pages

The Windows Notification Facility

The document discusses the Windows Notification Facility (WNF), which is a kernel notification mechanism introduced in Windows 8. It describes WNF internals like APIs, structures, state names and scopes. It also covers how to register, publish, consume and subscribe to WNF state data and notifications.

Uploaded by

huyphamxt1992
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
939 views43 pages

The Windows Notification Facility

The document discusses the Windows Notification Facility (WNF), which is a kernel notification mechanism introduced in Windows 8. It describes WNF internals like APIs, structures, state names and scopes. It also covers how to register, publish, consume and subscribe to WNF state data and notifications.

Uploaded by

huyphamxt1992
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 43

The Windows Notification

Facility
The Most Undocumented Kernel Attack Surface Yet

Gabrielle Viala @pwissenlit Black Hat USA


Alex Ionescu @aionescu 2018
About Gabrielle Viala
 Gaby - @pwissenlit on twitter

 Reverse engineer at Quarkslab

 Playing with the Windows Internals

 Member of the BlackHoodie organization board

 Quite new in the field so I don’t have much record yet ;)


About Alex Ionescu
 VP of EDR Strategy and Founding Architect at CrowdStrike

 Co-author of Windows Internals 5th-7th Editions

 Reverse engineering NT since 2000 – was lead kernel developer of ReactOS

 Instructor of worldwide Windows internals classes

 Author of various tools, utilities and articles

 Conference speaker at SyScan, Infiltrate, Offensive Con, Black Hat, Blue Hat, Recon, …

 For more info, see www.alex-ionescu.com or hit me up on Twitter @ aionescu


▪ WNF Internals
▪ APIs
▪ Structures
▪ Tools & Extensions

▪ WNF Attack Surface


Talk Outline ▪ Subscribers and Callbacks
▪ Some “Fuzzing” Results

▪ Interesting/Sensitive WNF State Names


▪ System State
▪ User Behavior

▪ Using WNF to Change System State


▪ Process Migration

▪ Key Takeaways & Future Research


WNF Internals & APIs
What is WNF?
 The Windows Notification Facility is a pubsub (Publisher/Subscriber) user/kernel notification mechanism that
was added in Windows 8, in part to solve some long-standing design constraints in the OS, as well as to serve as
a basis for Mobile-based/App-centric Push Notifications similar to iOS/Android

 Its key differentiator is that it is a blind (basically, registration-less) model which allows for out-of-order
subscription vs. publishing

 By this, we mean that a consumer can subscribe to a notification even before the notification has been published by its
producer

 And that there is no requirement for the producer to ‘register’ the notification ahead of time

 On top of this, it also supports persistent vs. volatile notifications, monotonically increasing unique change stamp
IDs, payloads of up to 4KB for each notification event, a thread-pool-based notification model with group-based
serialization, and a security model that is both scope based and implements Windows Security Descriptors
through the standard DACL/SACL mechanism [Run-On Sentence Pwnie Award]
Why does WNF exist?
 The canonical example is a driver wanting to know if the volumes have been mounted for RW access yet

 To indicate this, Autochk (Windows’ fsck) signals an event called VolumesSafeForWriteAccess

 But to signal an event, you need to first create it

 However, how can we know if Autochk has run, before Autochk has created the event for us wait on?

 Ugly solution: Sit in a sleep() loop checking for the presence of the event – once the event is known to exist, wait on it

 After a Windows application exits, all handles are closed – and once an object has no handles, it is destroyed

 So who would event keep the event around?

 Without WNF, the solution is to have the kernel create the event before any drivers can load, and have Autochk
open it like a consumer would, but instead, be the one to signal it instead of waiting on it
WNF State Names
 In the WNF world, a state name is a 64-bit number – but there’s a trick to it – it has an encoded structure

 A state name has a version, a lifetime, a scope, a data permanence flag, and a unique sequence number

typedef struct _WNF_STATE_NAME_INTERNAL


{
ULONG64 Version:4;
ULONG64 NameLifetime:2;
ULONG64 DataScope:4;
ULONG64 PermanentData:1;
ULONG64 Unique:53;
} WNF_STATE_NAME_INTERNAL, *PWNF_STATE_NAME_INTERNAL;

 This data is only accessible if we XOR the 64-bit number with a magic constant:

#define WNF_STATE_KEY 0x41C64E6DA3BC0074


State Name Lifetime
 A WNF state name can be well-known, permanent, persistent, or temporary (WNF_STATE_NAME_LIFETIME)

 The first-three lifetimes are related to the registry location where the state information will be kept

 Well-known names live in HKLM\SYSTEM\CurrentControlSet\Control\Notifications

 Permanent names live in HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Notifications

 Persistent names live in HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\VolatileNotifications

 Cannot register a well-known name – these are relied upon by the kernel and must be provisioned in the registry

 Permanent and persistent names require SeCreatePermanentPrivilege, just like global named objects

 Persistent names persist beyond process (registrar’s) exit while permanent names persist beyond reboot
State Scopes
 The data scope determines the first security boundary around a WNF state name – who has access/visibility to it

 A state name’s can have system scope, session scope, user scope, process scope or machine scope

 Other than providing a security boundary, WNF scopes can also be used to provide instantiated data for the same name

 Kernel bypasses state access checks, while TCB-privilege allows cross-scope access of WNF state names

 System and machine scoped names are global – there is no scope identifier (they use a different scope map)

 Session scoped names use the session ID as the identifier

 User scoped names use the user SID as the identifier

 Process scoped names use the EPROCESS object address as the identifier
Sequence Numbers
 To guarantee uniqueness, each state name has a unique 51-bit sequence number associated with it

 Well-known names have a 4-character family tag with the remainder 21 bits used as the unique identifier

 Permanent names have an increasing sequence number seeded off the registry value “SequenceNumber”

 Persistent names and volatile names share an increasing sequence number seeded off a runtime global value

 This data is then managed on a per-silo (container) basis and available in PspHostSiloGlobals->WnfSiloState

 Internally, within Microsoft, each WNF name then has a ‘friendly’ identifier that is used in code – sometimes this is
stored in a global sharing the same name

 nt!WNF_BOOT_DIRTY_SHUTDOWN – 0x1589012fa3bc0875 => 0x544f4f4200000801

 BOOT1, Well-Known Lifetime, System Scope, Version 1


Registering a WNF State Name
 Other than well-known names, as previously mentioned, a WNF state name can be registered at runtime:

NTSTATUS
ZwCreateWnfStateName (
_Out_ PWNF_STATE_NAME StateName,
_In_ WNF_STATE_NAME_LIFETIME NameLifetime,
_In_ WNF_DATA_SCOPE DataScope,
_In_ BOOLEAN PersistData,
_In_opt_ PCWNF_TYPE_ID TypeId, // This is an optional way to get type-safety
_In_ ULONG MaximumStateSize, // Cannot be above 4KB
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor // *MUST* be present
);

 Can also use ZwDeleteWnfStateName to delete the registered state name (other than for well-known ones)
Publishing WNF State Data
 To modify WNF state name data, the following system call can be used

NTSTATUS
ZwUpdateWnfStateData (
_In_ PCWNF_STATE_NAME StateName,
_In_reads_bytes_opt_(Length) const VOID* Buffer,
_In_opt_ ULONG Length, // Must be less than MaximumSize when registered
_In_opt_ PCWNF_TYPE_ID TypeId, // Optionally, for type-safety
_In_opt_ const PVOID ExplicitScope, // Process handle, User SID, Session ID
_In_ WNF_CHANGE_STAMP MatchingChangeStamp, // Expected current change stamp
_In_ LOGICAL CheckStamp // Enforce the above or silently ignore it
);

 Can also use ZwDeleteWnfStateData to wipe the current state data buffer
Consuming WNF Data
 To query WNF state name data, the following system call can be used instead – most parameters are like update

NTSTATUS
ZwQueryWnfStateData(
_In_ PCWNF_STATE_NAME StateName,
_In_opt_ PCWNF_TYPE_ID TypeId,
_In_opt_ const VOID* ExplicitScope,
_Out_ PWNF_CHANGE_STAMP ChangeStamp,
_Out_writes_bytes_to_opt_(*BufferSize, *BufferSize) PVOID Buffer,
_Inout_ PULONG BufferSize // Can be 0 to receive the current size
);

 The real power, however, is that both the Update and Query APIs don’t actually need a registered state name

 If the name is non-temporary, and the caller has sufficient access, the name instance can be live-registered!
WNF Notifications
 So far, we’ve assumed that the consumer knows when to call the API – but there are ‘blocking’ reads as well

 This works using a notification system, closer to a true pub-sub model

 First, the process must register an event (ZwSetWnfProcessNotificationEvent)

 Then use ZwSubscribeWnfStateChange, specifying an event mask -> receive a subscription ID on output

 1 -> Data Arrival 10 -> Name Destroyed (these are called data notifications)

 2 -> Data Subscriber Arrived, 4 -> Meta Subscriber Arrived, 8 -> Generic Subscriber Arrived (these are called meta notifications)

 Then, wait on the event that was registered

 Whenever it is signaled, ZwGetCompleteWnfStateSubscription, which returns an WNF_DELIVERY_DESCRIPTOR

 But these low-level APIs have a problem (thanks Gabi!) – only a single per-process notification event can exist
High Level API
 When it comes to notifications, things get complicated – so Rtl provides a simpler interface:

NTSTATUS
RtlSubscribeWnfStateChangeNotification (
_Outptr_ PWNF_USER_SUBSCRIPTION* Subscription,
_In_ WNF_STATE_NAME StateName,
_In_ WNF_CHANGE_STAMP ChangeStamp,
_In_ PWNF_USER_CALLBACK Callback,
_In_opt_ PVOID CallbackContext,
_In_opt_ PCWNF_TYPE_ID TypeId,
_In_opt_ ULONG SerializationGroup,
_In_opt_ ULONG Unknown);

 Uses single Ntdll.dll-managed event queue with a callback system – no need for using any system calls
Notification Callback
 Behind the scenes, the contents of the WNF_DELIVERY_DESCRIPTOR are converted into the callback
parameters

typedef NTSTATUS (*PWNF_USER_CALLBACK) (


_In_ WNF_STATE_NAME StateName,
_In_ WNF_CHANGE_STAMP ChangeStamp,
_In_opt_ PWNF_TYPE_ID TypeId,
_In_opt_ PVOID CallbackContext,
_In_ PVOID Buffer,
_In_ ULONG BufferSize);

 For each registration, an entry is entered into the RtlpWnfProcessSubscriptions global pointer, which has a
LIST_ENTRY of WNF_NAME_SUBSCRIPTION structures (we will play with these a bit later)

 Each one of these, in turn, has a LIST_ENTRY of WNF_USER_SUBSCRIPTION structures, with the callback and context
Kernel API
 WNF also provides almost identical functionality to kernel-mode callers as well, both through the exported system
calls (which can be used from a driver) as well as through high-level APIs in the executive runtime

 ExSubscribeWnfStateChange -> Given state name, mask, and a callback + context, receives subscription handle

 Callbacks receive the signaled name, event mask, change stamp, but not the buffer or its size

 ExQueryWnfStateData receives the subscription handle, and reads the currently active state data

 Each callback ends up calling this to actually get the data associated with the notification

 For both kernel and user-mode subscriptions, WNF creates a WNF_SUBSCRIPTION data structure to track it

 But some fields won’t be filled out for user-mode – like the Callback/Context – since this is in Ntdll.dll struct instead
WNF Data Structures
WNF Structure
Analyzing with Tools & WinDbg
WinDBG Custom Extension
 Basically does the same things as !wnf command

 Does not rely on private symbols and should (hopefully) work smoothly
kd> !wnfhelp
[WnfDbg] extension commands help:
> !wnfsm [Address] = Displays the structure of a _WNF_SCOPE_MAP object.
The address can either be a scope map or an eprocess (in case of multiple silo).
If no address are provided, it will search for the generic scope map with nt!PspHostSiloGlobals.
> !wnfsi [Address] [0/1/2] = Displays the structure of a _WNF_SCOPE_INSTANCE object.
> !wnfsilist <Address> [0/1/2] = List all the scope instances in a list_entry.
> !wnfni [Address] [0/1/2] = Displays the structure of a _WNF_NAME_INSTANCE object.
> !wnfnilist <Address> [0/1/2] = List all the name instances in a list_entry.
> !wnfsd <Address> = Displays the structure of a _WNF_STATE_DATA object and dump the data.
> !wnfsub [Address] [0/1/2] = Displays the structure of a _WNF_SUBSCRIPTION object.
> !wnfsublist <address> [offsetList] [0/1/2] = List all the subscriptions in a list_entry
[offsetList] indicates the offset of the list to parse in the subscription.
By default, it will parse ProcessSubscriptionListEntry.
If the address provided is the base address of a list_entry, it will parse this list.
> !wnfctx [Address] [0/1/2] = Displays the structure of a _WNF_PROCESS_CONTEXT object.
> !wnfctxlist [Address] [0/1/2] = Displays the structure of a _WNF_SCOPE_INSTANCE object.
If no address is provided, it will list the process contexts pointed by nt!ExpWnfProcessesListHead
> !wnfctxhead = Prettyprints the list head pointed by nt!ExpWnfProcessesListHead
> !wnfname <StateName> = Displays the WNF state name information.
> !wnfwhat <Address> = Determines the type of the structure at the provided address and dumps its structure.
> !wnfhelp = meh... >_>‘

- For most of these commands, the inputted address can either be the base address of the object or the base of a list_entry in the structure.
- Specify the verbosity level with 0, 1 or 2.
WnfCom
 (Small) python module that can be used to communicate via WNF

 Enables reading and writing data for existing name instances

 Can also create a temporary state name (server style ☺)

 Got a “client” side that is notified anytime an update happens for a specific name instance

 Easy peasy to use:

>>> from wnfcom import Wnfcom >>> from wnfcom import Wnfcom
>>> wnfserver = Wnfcom() >>> wnfclient = Wnfcom()
>>> wnfserver.CreateServer() >>> wnfclient.SetStateName("41c64e6da5559945")
[SERVER] StateName created: 41c64e6da5559945 >>> wnfclient.Listen()
>>> wnfserver.Write(b“potato soup") [CLIENT] Event registered: 440
Encoded Name: 41c64e6da5559945, Clear Name: 6e99931 [CLIENT] Timestamp: 0x1 Size: 0xb
Version: 1, Permanent: No, Scope: Machine, Data:
Lifetime: Temporary, Unique: 56627 00000000: 70 6F 74 61 74 6F 20 73 6F 75 70
State update: 11 bytes written potato soup
WnfDump
 Command-line C utility that can be used to discover information about WNF state names

 -d ➔ Dump all WNF state names using registry-based enumeration, displaying vital information

 Add –v ➔ Verbose output, which includes a hexdump of the WNF state data

 Add –s ➔ Security descriptors, which includes an SDDL string of the WNF state name’s permissions

 -b ➔ Brute-force temporary WNF state names (will show how we do this soon)

 -i ➔ Show information about a specific single WNF state name

 -r ➔ Read a particular WNF state name

 -w ➔ Write data into a particular WNF state name

 -n ➔ Register for notifications as a subscriber for the given WNF state name
StateNameDiffer
 Yet another small python script to dump or diff Well-Known State Names in DLLs

Allows quickly spotting differences across versions

Dump output can be used in other scripts (Alex’s wnfTool, wnfCom, etc.)

$ python .\StateNamediffer.py -h
usage: StateNamediffer.py [-h] [-dump | -diff] [-v] [-o OUTPUT] [-py] file1 [file2]
Stupid little script to dump or diff wnf name table from dll
positional arguments:
file1
file2 Only used when diffing
optional arguments:
-h, --help show this help message and exit
-dump Dump the table into a file
-diff Diff two tables and dump the difference into a file
-v, --verbose Print the description of the keys
-o OUTPUT, --output OUTPUT Outputs the output file (Default: output.txt)
-py, --python Change the output language to python (by default it's c)
WNF Attack Surface
The Privileged Disclosure
 When reading the thousands of WNF state names that exist on the system, I noticed several that had interesting-
looking buffers

 Including some that had what looked to be like pointers or other privileged data

 I did several repros on multiple machines and in some cases discovered heap, stack, and other privileged
information data/information disclosures across privilege boundaries

 Submitted case(s) with MSRC in July – fixed in November! (sorry I am not James Forshaw it takes 90+ days)

 WNF_AUDC* events leak(ed) 4KB of stack space!

 The main underlying problem is the same as you’ve seen in past research from j00ro, taviso, and others…

 Certain WNF state names contain encoded data structures with various padding/alignment issues

 Or in certain cases, bona fide uninitialized memory (such as due to sizeof misuse or other scenarios)
Discovering State Names and Permissions
 The first approach was to discover all the possible state names that could perhaps be manipulated maliciously

 For well-known names, permanent names, and persisted names, this is doable by enumerating the registry keys

 We can then associate friendly names with the well-known names if we obtain Microsoft’s database

 There are a few places where this can be found ☺

 Then, we can also look in the registry for their security descriptor (this is the first thing in the data buffer)

 The security descriptor is a bit tricky because it doesn’t have an Owner or Group, so technically it’s ‘invalid’

 We manually fix this up to address the problem with a fake owner/group

 But for temporary names – they’re not in the registry, and only kernel has the data structures for them (!wnf)
Discovering Volatile Names
 Volatile names are actually not that hard to brute force

 The version is always 1

 The lifetime is WnfTemporaryStateName

 The permanent flag is always zero (temporary names can’t have permanent data)

 The scope is one of the 4 possible scopes

 But the sequence number is 51 bits, Alex!

 Well…. recall that sequence numbers are monotonically increasing

 And for volatile names, the sequence is reset to 0 each boot

 So we can use ZwQueryWnfStateNameInformation with the WnfInfoStateNameExist class up to ~1M…


Brute Forcing Security Descriptors
 Volatile state names only have security descriptors in kernel memory – no way to query this without !wnf

 But we can infer if we have read access by trying to read ☺

 We can infer write access by trying to write!

 But you saw how well that went

 So there’s a trick: remember that we can enforce a matching change stamp

 Set this to 0xFFFFFFFF – the match check is made after the access check, so the error value leaks the writeable state

 Doesn’t give us the whole security descriptor, but we can run the tool at different privileges to get some idea of
AC/Low IL/User/Admin/SYSTEM
Dumping Kernel/User Subscribers
 All subscriptions done by a process are in the WNF_PROCESS_CONTEXT as a LIST_ENTRY of WNF_SUBSCRIPTION

 Kernel subscriptions are basically owned by the System process

 We can use !list to dump the Callback and CallbackContext in WNF_SUBSCRIPTION for the System Process

 Note that in some cases, the event aggregator (CEA.SYS) is used which hides the real callback(s) in the context

 We can repeat this for user-mode processes as well, but the Callback will be NULL, as these are user subscribers

 So we need to attach to user-space, get the RtlpWnfProcessSubscriptions table, go over the


WNF_NAME_SUBSCRIPTION’s and then for each one, dump the list of WNF_USER_SUBSCRIPTION structures,
each of which has a callback

 Unfortunately, this symbol is a static which means that it is not in public symbols (but can be found by disassembling)

 Certain user-mode callbacks also use the event aggregator (EventAggregation.dll) which stores callback in context
Interesting/Sensitive WNF State Names
Inferring System State and User Behavior with WNF
 Some WNF IDs can be used because the reveal interesting information about the machine state:

 WNF_WIFI_CONNECTION_STATUS – Wireless connection status (many more interesting WNF* IDs)

 WNF_BLTH_BLUETOOTH_STATUS – Similar, but for Bluetooth (also WNF_TETH_TETHERING_STATE)

 WNF_UBPM_POWER_SOURCE – Indicates battery power vs. AC adapter (WNF_SEB_BATTERY_LEVEL has battery level)

 WNF_CELL_* – On Windows Phone (lol), has information such as network, number, signal strength, EDGE vs 3G, etc.

 Others can be used to infer user behaviors

 WNF_AUDC_CAPTURE/RENDER – Indicates (including the PID) the process that is capturing/playing back audio

 WNF_TKBN_TOUCH_EVENT – Indicates each mouse click, keyboard press, or touch screen press

 WNF_SEB_USER_PRESENT / WNF_SEB_USER_PRESENCE_CHANGED – Utilizes Windows’ own user-presence learning


Avoiding Standard Notification APIs
 Even in situations where certain user actions already have documented APIs for receiving a notification, these
APIs may generate event log/EDR/audit data

 Corresponding WNF IDs may exist (and sometimes may even be more descriptive) for the same actions

 For example, WNF_SHEL_(DESKTOP)_APPLICATION_(STARTED/TERMINATED) provides information on both


modern application launches (including the actual package name that was launched) through DCOM, as well as
regular Win32 application launches

 As long as the applications were created through ShellExecute (and/or interactively through Explorer.exe or CLI)

 Other examples are when there is a user-mode API, but no kernel-mode equivalent

 WNF_EDGE_LAST_NAVIGATED_HOST – Indicates each URL the user types in (or clicks on) in Edge

 WNF_SHEL_LOCKSCREEN_ACTIVE – Indicates the lock screen is active


Controlling the System with WNF
 Some WNF IDs can be written to in order to effect change on the system

 WNF_FSRL_OPLOCK_BREAK receives a list of PIDs (and the number/size) and terminates each of them!

 There is a similar API for waiting on the process(es) to exit/resume instead

 Others we haven’t quite figured out yet, but look pretty interesting

 WNF_SHEL_DDC_(WNS/SMS)_COMMAND – The buffer size is 4KB which indicates potential for parsing bugs

 In a similar vein, there are also certain WNF IDs that indicate that certain things should happen/be done

 WNF_CERT_FLUSH_CACHE_TRIGGER – Flushes the certificate store

 WNF_BOOT_MEMORY_PARTITIONS_RESTORE – Stores the original memory partitions

 WNF_RTDS_RPC_INTERFACE_TRIGGER_CHANGED – Potentially starts RPC-Interface Trigger Started Services


Using WNF to Change System State
Injecting Code with WNF
 Common techniques for injecting code into other processes include

 WriteProcessMemory – directly injecting code

 File Mappings (Section Objects) – mapping a section object into the target, or writing into an already-mapped section

 Atom Objects – storing data into an atom and then having the target request the atom data

 Window Messages – using messages such as WM_COPYDATA and DDE messages to cause the target process to get data

 GUI Objects – Changing the title of a Window (or that of its class) to data we wish to have in the target’s memory

 Using WNF provides yet another way of transferring data into a target process

 Either by re-using one of its existing WNF IDs that it reads (especially if it’s stored in a persistent fashion in the process)

 Or by directing the process to call Rtl/ZwQueryWnfStateData on a particular WNF ID of interest


Modifying Callbacks/Contexts for Code Redirection
 Injecting memory is one part of the problem, but to redirect control flow, common solutions include

 APCs

 Remote Threads

 Changing Thread Context

 Modifying the “window long” of a window to get a custom callback/context associated with the window handler

 Another approach, however, can be to parse the WNF_USER_SUBSCRIPTION’s of a remote process (and these
are linked to WNF_NAME_SUBSCRIPTION’s, which are linked off the RtlpWnfProcessSubscriptions)

 The callback function can be changed (be wary of CFG) and then the WNF payload + size will be parameters 5 and 6

 Alternatively, the callback context can be changed (which is often a V-Table or has another embedded function pointer)
Conclusion
Key Takeaways
 The Windows Notification Facility is an extremely interesting, well-designed addition to the Windows 8+ kernel

 It provides lots of useful functionality to various system applications, services, and drivers

 It also acts as the basis for more advanced functionality and notification frameworks

 Unfortunately, it is highly undocumented, and provides no real visibility into its behavior

 Other than a WinDBG extension which doesn’t work since the WNF symbols are private

 And some limited ETW events that only work for one particular set of callers

 Its ability to persist data across reboots also makes it even more interesting for misuse

 There is no magic intellectual property in WNF internals – Microsoft is supposed to publish the symbols to make
!wnf work
Key Takeaways (cont)
 WNF has grown beyond just providing notifications

 Did its designer really mean for it to be used as a way to kill processes from kernel-mode?

 Should it really be used to store PII and every URL ever visited in a world-readable WNF state name?

 Do teams that use WNF at Microsoft fully understand the implications, boundaries, etc?

 Such as needing to initialize memory and treating data the same way you’d treat system call parameters

 Because it can be used to transfer data from one boundary to another, and because callbacks are involved, both
parsing errors as well as code redirection/injection attacks are possible using more novel techniques than usual

 Obviously, this implies permissions to exist in the first place, so this is an EDR-evasion technique more than anything

 How long until we start seeing WNF state names with pointers in them? :-/

 Defenders – start fuzzing, building visibility tools, and poking at WNF – at the very least, you’ll have fun!
Future Research
 A big chunk of WNF events all start with SEB_ which represents the System Events Broker

 SystemEventsBrokerServer.dll and SystemEventsBrokerClient.dll are the user-mode high-level APIs

 It may be that some of these SEB events are then internally parsed by SEB’s clients, masking some true consumers

 Many of the registered kernel-mode and user-mode callbacks are owned by CEA.SYS or EventAggregation.dll

 These are part of the “Event Aggregation Library”, which allows you to have start/stop callbacks when a certain set of
events have accumulated above a certain threshold, or multiple WNF events are happening at the same time, or in a given
sequence, or when at least one out of a group of WNF events have occurred

 Essentially a finite state machine around WNF event IDs that callers can register for

 So the real consumers are hidden behind the event aggregation library

 We are releasing the tools this weekend on GitHub


THANK YOU!

Q&A

Common questions

Powered by AI

Using WNF as an alternative to standard notification APIs offers benefits such as enhanced flexibility and reduced system overhead, as WNF does not necessarily produce event log entries that standard APIs might generate, thereby minimizing audit trails and improving performance . However, drawbacks include significant documentation gaps and the lack of visibility into WNF’s internal workings, which can complicate development and security assessments . Furthermore, misuse potential is high, as the broad access scope and persistent data capabilities inherent in WNF might lead to security vulnerabilities if not properly controlled . Careful consideration of these trade-offs is necessary to ensure a secure and efficient implementation.

Debugging WNF-related functionalities is challenging due to the lack of visibility and documentation around WNF symbols and internal behaviors, as these symbols are private and not publicly disclosed . Developers can overcome these challenges by utilizing tools such as the WinDBG custom extensions that facilitate the examination of WNF structures, though these tools do not rely on private symbols and often provide limited insights . Additionally, employing tools like WnfDump and WnfCom can help infer WNF state behaviors and security descriptors, providing developers with essential debugging information to address potential issues with WNF implementations .

The WnfCom python module provides functionalities for interacting with the Windows Notification Facility by enabling developers to read and write data for existing state name instances, create temporary state names, and be notified of updates for specific state name instances . When using WnfCom, a developer can set up both server and client interactions, where the server manages state updates, and the client listens for changes, thus facilitating efficient state management and communication between different components of an application .

The design of the Windows Notification Facility, which includes capabilities for persistent data and widespread notifications, raises significant security concerns. Its ability to persist data across reboots can be misused for unauthorized data retention or tracking . Furthermore, the facility's integration with both kernel and user-mode operations can be exploited for unauthorized process management or data leakage. As WNF provides a mechanism to transfer data across boundaries, it creates vulnerabilities for potential injection or code redirection attacks . This necessitates the establishment of robust permissions and monitoring to mitigate misuse and protect system integrity.

The Windows Notification Facility ensures the uniqueness of state names by associating a unique 51-bit sequence number with each state name . This sequence number varies with the type of state name: well-known names use a 4-character family tag with a 21-bit unique identifier, whereas permanent and persistent names have escalating sequence numbers from registry and runtime values respectively . This structuring guarantees that each state name is distinct across different system-wide notifications.

The ability to modify callbacks and contexts in WNF subscriptions poses significant security implications as it introduces an attack vector for code injection. By altering these components within the WNF_USER_SUBSCRIPTION structures, attackers can redirect the flow of execution to malicious code . Common techniques for such exploitation might involve changing the callback function to a malicious address or modifying the callback context that carries relevant execution data . Given WNF's system-wide interaction ability, such modifications can lead to wide-reaching impacts, making robust validation and access control measures crucial to mitigating this risk.

WNF state name lifetimes are divided into four categories: well-known, permanent, persistent, and temporary . Well-known names are stored in HKLM\SYSTEM\CurrentControlSet\Control\Notifications, permanent names in HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Notifications, and persistent names in HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\VolatileNotifications . Well-known names are pre-provisioned by the kernel, while permanent and persistent names require specific privileges to register due to their extended lifetimes .

A WNF state name is a 64-bit number with an encoded structure designed to represent various attributes such as version, lifetime, scope, data permanence, and a unique sequence number . The purpose of a WNF state name is to identify different notifications or state changes within the Windows Notification Facility, allowing different parts of the system or applications to react to these changes or subscribe for updates .

The integration of the Windows Notification Facility in applications can impact system performance by increasing resource demands through state name registration and management activities. When applications frequently register or query state names, they may add overhead by increasing memory usage for tracking these notifications via WNF_SUBSCRIPTION structures . Additionally, poorly designed application logic that excessively reacts to state changes could lead to performance degradation due to frequent processing cycles triggered by WNF notifications . Developers must balance notification responsiveness with efficient resource use to optimize system performance.

A developer can use the Windows Notification Facility to subscribe to state changes through APIs such as ExSubscribeWnfStateChange, which requires a state name, mask, callback, and context to receive a subscription handle . This handle is used to manage the subscription, allowing the developer to capture notifications and query the active state data through ExQueryWnfStateData. The system maintains WNF_SUBSCRIPTION structures to track these subscriptions, differentiating between those initiated in kernel mode and user mode .

You might also like