By Yarden Shafir, Senior Security Engineer
WNF (Windows Notification Facility) is an undocumented notification mechanism that allows communication inside processes, between processes, or between user mode processes and kernel drivers. Similar to other notification mechanisms like ETW (Event Tracing for Windows) and ALPC (Advanced Local Procedure Call), WNF communication happens over different “channels,” each representing a unique provider or class of information.
Offensive engineers already found several uses for WNF. Alex Ionescu and Gabrielle Viala reported information leaks and denial-of-service bugs that were fixed by Microsoft; @modexpblog demonstrated code injection through WNF names, which is undetected by pretty much all EDR products; and Alex Plaskett of NCC Group demonstrated how attackers could use WNF allocations to spray the kernel pool.
WNF is used extensively by Windows components to send or receive information about software and hardware states. It can be used in the same way by 3rd party applications to learn about the state of the system, send messages to Windows processes or drivers or as an internal communication channel between processes or drivers (though WNF is undocumented and therefore not meant to be used by 3rd parties at all).
Reading and interpreting WNF state names
In the WNF world, these previously mentioned “channels” are called state names. WNF state names are 64-bit numbers that are composed of:
- Version
- Lifetime:
- Well-known, predefined in the system
- Permanent, persist in the registry across boot sessions
- Persistent, persist in memory, but not across boot sessions
- Temporary, only exist in the context of the process and is gone when the process terminates
- Scope: System, Session, User, Process, or Physical Machine
- Permanent data bit
- Unique sequence number
These attributes are combined into the state name using this bit layout:
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;
Until recently, this mechanism was almost exclusively meant for hardware components such as the battery, microphone, camera, and Bluetooth, and held very little interest for defensive engineers. But that is beginning to change with the recent addition of several new state names used by the kernel code integrity manager, which now uses WNF to notify the system about interesting code integrity events that might be useful to security tools.
While still undocumented and unrecommended for general use, it may be time for defenders to start looking further into WNF and its potential benefits. Starting from Windows 10, WNF now offers several added well-known state names that get notified by the kernel Code Integrity Manager (CI.dll). This component is responsible for all kernel hashing, signature checks, and code integrity policies, which is rich information for all security products.
How do we find out about those names? Well, to dump all well-known state names on the machine, you’d have to install the Microsoft SDK, then run WnfNameDumper to retrieve all WNF names defined in perf_nt_c.dll and dump their human-friendly names, IDs, and descriptions into a file, which would look like this:
{“WNF_CELL_UTK_PROACTIVE_CMD”, 0xd8a0b2ea3bcf075},
// UICC toolkit proactive command notification for all slots. SDDL comes from ID_CAP_CELL_WNF_PII
// and UtkService in %SDXROOT%\src\net\Cellcore\packages\Cellcore\Cellcore.pkg.xml
{“WNF_CELL_UTK_SETUP_MENU_SLOT0”, 0xd8a0b2ea3bce875},
// UICC toolkit setup menu notification for slot 0. SDDL comes from ID_CAP_CELL_WNF_PII
// and UtkService in %SDXROOT%\src\net\Cellcore\packages\Cellcore\Cellcore.pkg.xml
{“WNF_CELL_UTK_SETUP_MENU_SLOT1”, 0xd8a0b2ea3bdd075},
// UICC toolkit setup menu notification for slot 1. SDDL comes from ID_CAP_CELL_WNF_PII
// and UtkService in %SDXROOT%\src\net\Cellcore\packages\Cellcore\Cellcore.pkg.xml
etc., etc., etc…
In Windows version 22H2, the Windows SDK contains just over 1,400 well-known state names. Many of those names can be revealing, but for now, we’ll focus on the WNF_CI (code integrity) names:
{“WNF_CI_APPLOCKERFLTR_START_REQUESTED”, 0x41c6072ea3bc2875},
// This event signals that AppLockerFltr service should start.
{“WNF_CI_BLOCKED_DRIVER”, 0x41c6072ea3bc1875},
// This event signals that an image has been blocked from loading by PNP
{“WNF_CI_CODEINTEGRITY_MODE_CHANGE”, 0x41c6072ea3bc2075},
// This event signals that change of CodeIntegrity enforcement mode has occurred.
{“WNF_CI_HVCI_IMAGE_INCOMPATIBLE”, 0x41c6072ea3bc1075},
// This event signals that an image has been blocked from loading as it is incompatible with HVCI.
{“WNF_CI_SMODE_CHANGE”, 0x41c6072ea3bc0875},
// This event signals that change of S mode has occurred.
In this version we can see five state names with the prefix WNF_CI, all generated by the Code Integrity manager, and each one has a helpful description telling us what it’s used for. And unlike most other WNF names, here we see a few events that could be helpful to defensive engineers:
- WNF_CI_APPLOCKERFLTR_START_REQUESTED – Signals that AppLockerFltr service should start
- WNF_CI_BLOCKED_DRIVER – Signals that a driver has been blocked from loading by HVCI (Hypervisor-protected Code Integrity) because it was found in the block list
- WNF_CI_CODEINTEGRITY_MODE_CHANGE – Signals that a change of CodeIntegrity enforcement mode has occurred
- WNF_CI_HVCI_IMAGE_INCOMPATIBLE – Signals that an image has been blocked from loading as it is incompatible with HVCI, most likely because it has regions that are both writable and executable or allocates memory from the executable non-paged pool
- WNF_CI_SMODE_CHANGE – Signals that a change of S mode has occurred
Usually, the buffers passed into WNF state names are a mystery, and their contents must be reverse-engineered. But in this case, Microsoft exposes the data passed into one of the state names in the public Microsoft Symbol Server, accessed through symchk.exe and symsrv.dll:
typedef struct _WNF_CI_BLOCKED_DRIVER_CONTEXT { /* 0x0000 */ struct _GUID Guid; /* 0x0010 */ unsigned long Policy; /* 0x0014 */ unsigned short ImagePathLength; /* 0x0016 */ wchar_t ImagePath[1]; } WNF_CI_BLOCKED_DRIVER_CONTEXT, *PWNF_CI_BLOCKED_DRIVER_CONTEXT;
We can get some information from the Code Integrity manager, which could be useful for EDR products. Some of this can also be found in the Microsoft-Windows-CodeIntegrity ETW (Event Tracing for Windows) channel, as well as other interesting events (which deserve a post of their own). Still, some of the data in these WNF names can’t be found in any other data source.
Now if we update our SDK version to the preview build (25336 during the writing of this post), we can see a few other WNF state names that haven’t been released to the regular builds yet:
{“WNF_CI_APPLOCKERFLTR_START_REQUESTED”, 0x41c6072ea3bc2875},
// This event signals that AppLockerFltr service should start.
{“WNF_CI_BLOCKED_DRIVER”, 0x41c6072ea3bc1875},
// This event signals that an image has been blocked from loading by PNP
{“WNF_CI_CODEINTEGRITY_MODE_CHANGE”, 0x41c6072ea3bc2075},
// This event signals that change of CodeIntegrity enforcement mode has occurred.
{“WNF_CI_HVCI_IMAGE_INCOMPATIBLE”, 0x41c6072ea3bc1075},
// This event signals that an image has been blocked from loading as it is incompatible with HVCI.
{“WNF_CI_LSAPPL_DLL_LOAD_FAILURE”, 0x41c6072ea3bc3075},
// This event signals that a dll has been blocked from loading as it is incompatible with LSA running
// as a protected process.
{“WNF_CI_LSAPPL_DLL_LOAD_FAILURE_AUDIT_MODE”, 0x41c6072ea3bc3875},
// This event signals that an unsigned dll load was noticed during LSA PPL audit mode.
{“WNF_CI_SMODE_CHANGE”, 0x41c6072ea3bc0875},
// This event signals that change of S mode has occurred.
Here, we see two new state names that add information about PPL-incompatible DLLs being loaded into the Local Security Authority Subsystem Service (LSASS). LSASS, the OS authentication manager, runs as a PPL (Protected Process Light). This ensures that the process isn’t tampered with and is only running code signed with the correct signature level.
Investigating LSASS protections with WNF
Microsoft has been trying to make LSASS run as a PPL for a while now. Still, it couldn’t fully enable it because of compatibility issues with products that require full access to LSASS, including the injection of different plugins. However, they’re attempting to still protect LSASS as much as possible from credential stealers like Mimikatz, while still allowing users the option to turn LSASS back to a regular process.
Since Windows 8.1, there is an option to run LSASS as a PPL in audit mode. This means the system still treats it as a normal process but logs any operation that would have been blocked if it ran as a PPL. In Windows 11 it runs as a regular PPL by default, with an option to run it in audit mode exposed through the registry and the security center (in Preview builds)
So this is where our two new state names come in:
WNF_CI_LSAPPL_DLL_LOAD_FAILURE gets notified when LSASS is running as a regular PPL, and a DLL isn’t signed according to the PPL requirements is blocked from loading into the process. And WNF_CI_LSAPPL_DLL_LOAD_FAILURE_AUDIT_MODE gets notified when LSASS is running as a PPL in audit mode and loads a DLL that would have been blocked if it was running as a normal PPL.
Endpoint Detection & Response (EDR) tools can be alerted about all DLL loads through a documented kernel callback. The image load notify routine does include cached signing information inside IMAGE_INFO in the fields ImageSignatureLevel and ImageSignatureType, however this information may not always be available, and the callback isn’t notified about blocked DLL loads. Blocked DLL loads are interesting as they indicate what could be an exploitation attempt (or an organization trying to load their plugin written in 2003 into LSASS).
So, while none of these new state names contain any information that is exceptionally interesting to EDRs, they do have some interesting data that security products could find useful and, at a minimum, add some visibility or save the EDR some work.
And, of course, there is already one user for some of these WNF_CI state names: The Windows Defender command-line tool MpCmdRun.exe. MpSvc.dll, one of the DLLs loaded into MpCmdRun.exe, subscribes to two WNF state names: WNF_CI_CODEINTEGRITY_MODE_CHANGE and WNF_CI_SMODE_CHANGE. Whenever they are notified, the DLL queries them to get the new values and updates its internal configuration accordingly.
Other pieces of the system subscribe to these state names too. I used WinDbg commands to extract this list from my own system:
- The DcomLaunch service registers to WNF_CI_SMODE_CHANGE, WNF_CI_BLOCKED_DRIVER and WNF_CI_APPLOCKERFLTR_START_REQUESTED
- Utcsvc service (through utcsvc.dll) registers to WNF_CI_SMODE_CHANGE
- SecurityHealthService.exe registers to WNF_CI_SMODE_CHANGE
- Msteams.exe registers to WNF_CI_SMODE_CHANGE
- PcaSvc service (through PcaSvc.dll) registers to WNF_CI_HVCI_IMAGE_INCOMPATIBLE and WNF_CI_BLOCKED_DRIVER – this is the service responsible for displaying the pop-up message when your favorite vulnerable driver won’t load on your HVCI-enabled system.
Currently, no process subscribes to the new LSA (Local Security Authority) state names (WNF_CI_LSAPPL_DLL_LOAD_FAILURE and WNF_CI_LSAPPL_DLL_LOAD_FAILURE_AUDIT_MODE), but since those are still in the preview stage that isn’t very surprising and I’m sure we’ll be seeing some subscriptions to it in the future.
Explore the possibilities with new WNF info
Windows has empowered security enthusiasts on both the offensive and defensive sides with newly attainable information in WNF. By expanding beyond the historical scope and adding state names to WNF, researchers have a more transparent view of how things operate. Over time, it’s likely you’ll see security researchers correlate this information with other events and processes to showcase novel security research!
Here, we’ve provided a quick introduction to WNF and its new features along with a simple example of how it can be used to investigate LSASS. If you’re interested in more details on WNF internals and its offensive capabilities, Alex Ionescu and Gabrielle Viala presented it in detail in a BlackHat 2018 talk. They later published a blog post and a collection of useful scripts.