Subscribe a notification from WNF

# Introduction

In Black Hat USA 2018, Alex Ionescu ([@aionescu](https://twitter.com/aionescu)) unveiled a new toy: Windows Notification Facility.

The Windows Notification Facility: Peeling the Onion of the Most Undocumented Kernel Attack Surface Yet - Black Hat USA 2018 | Briefings Schedule
https://www.blackhat.com/us-18/briefings/schedule/index.html#the-windows-notification-facility-peeling-the-onion-of-the-most-undocumented-kernel-attack-surface-yet-11626

It seems that the current version of Windows kindly delivers push notifications to any user-mode processes and kernel drivers at tons of important events such as process creation, start menu tile installation, WiFi connection status change, and etc.

In the BH talk, he also introduced a tool *Mach2*, that can control thousands of prerelease features in Windows through WNF.

riverar/mach2: Feature Control Multi-tool
https://github.com/riverar/mach2

Based on the code of Mach2 and some simple kernel-debugging technique, let me introduce a small program to subscribe WNF and explain how I created it.

# Analysis of the publisher code

Mach2 consumes two WNF-related system calls: `NtUpdateWnfStateData` and `NtQueryWnfStateData`.  I think these names explain well: `NtUpdateWnfStateData` publishes a new state, which can be queried by `NtQueryWnfStateData`.

From the BH demo, I already knew Edge navigation pushes a notification and it can be subscribed.  Actually it would be a good entrypoint of WNF, so let's analyze how Edge pushes a notification.

First, launch Edge and set a breakpoint at `NtUpdateWnfStateData` in Edge's manager process and content process.

```
0: kd> vertarget
Windows 10 Kernel Version 17134 MP (2 procs) Free x64
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 17134.1.amd64fre.rs4_release.180410-1804
Machine Name:
Kernel base = 0xfffff803`ec2aa000 PsLoadedModuleList = 0xfffff803`ec658170
Debug session time: Sat Aug 25 13:55:33.753 2018 (UTC - 7:00)
System Uptime: 0 days 0:24:36.324

0: kd> !process 0n4784 0
Searching for Process with Cid == 12b0
PROCESS ffffa185845d8080
    SessionId: 1  Cid: 12b0    Peb: 3692e32000  ParentCid: 031c
    DirBase: 27740000  ObjectTable: ffffdb09aa262e00  HandleCount: 1247.
    Image: MicrosoftEdge.exe
0: kd> !process 0n4484 0
Searching for Process with Cid == 1184
PROCESS ffffa18584cb9580
    SessionId: 1  Cid: 1184    Peb: bd30256000  ParentCid: 031c
    DirBase: 69880000  ObjectTable: ffffdb09aa8a0440  HandleCount: 1014.
    Image: MicrosoftEdgeCP.exe

0: kd> bp /p ffffa185845d8080 nt!NtUpdateWnfStateData
0: kd> bp /p ffffa18584cb9580 nt!NtUpdateWnfStateData
0: kd> bl
 0 e fffff803`ec73cf0c     0001 (0001) nt!NtUpdateWnfStateData
     Match process data ffffa185`845d8080
 1 e fffff803`ec73cf0c     0001 (0001) nt!NtUpdateWnfStateData
     Match process data ffffa185`84cb9580
```

When I did some navigation on the browser, the breakpoint at the manager process was hit.  Looking at the callstack below, you can see `EMODEL!CTabWindow::_OnNavigationComplete` is calling `ntdll!RtlPublishWnfStateData`.  So obviously Edge pushes a notification when navigation is completed.

```
Breakpoint 0 hit
nt!NtUpdateWnfStateData:
fffff803`ec73cf0c 4c8bdc          mov     r11,rsp
0: kd> !process -1 0
PROCESS ffffa185845d8080
    SessionId: 1  Cid: 12b0    Peb: 3692e32000  ParentCid: 031c
    DirBase: 27740000  ObjectTable: ffffdb09aa262e00  HandleCount: 1253.
    Image: MicrosoftEdge.exe
0: kd> kL
Child-SP          RetAddr           Call Site
ffff848d`8332fa88 fffff803`ec463343 nt!NtUpdateWnfStateData
ffff848d`8332fa90 00007ffb`aa89d764 nt!KiSystemServiceCopyEnd+0x13
00000036`933fe7b8 00007ffb`aa87ca0b ntdll!NtUpdateWnfStateData+0x14
00000036`933fe7c0 00007ffb`893f3385 ntdll!RtlPublishWnfStateData+0x4b
00000036`933fe830 00007ffb`8945345b EMODEL!CTabWindow::_OnNavigationComplete+0x395
00000036`933fea60 00007ffb`8944fe61 EMODEL!Microsoft::WRL::Details::DelegateArgTraits<long (__cdecl ABI::Windows::Foundation::ITypedEventHandler_impl<ABI::Windows::Foundation::Internal::AggregateType<ABI::WebRuntime::BrowsingContext * __ptr64,ABI::WebRuntime::IBrowsingContext * __ptr64>,ABI::Windows::Foundation::Internal::AggregateType<ABI::WebRuntime::NavigationCompletedEventArgs * __ptr64,ABI::WebRuntime::INavigationCompletedEventArgs * __ptr64> >::*)(ABI::WebRuntime::IBrowsingContext * __ptr64,ABI::WebRuntime::INavigationCompletedEventArgs * __ptr64) __ptr64>::DelegateInvokeHelper<ABI::Windows::Foundation::ITypedEventHandler<ABI::WebRuntime::BrowsingContext * __ptr64,ABI::WebRuntime::NavigationCompletedEventArgs * __ptr64>,<lambda_24c54b165d2c35dbde8442e015c21012>,-1,ABI::WebRuntime::IBrowsingContext * __ptr64,ABI::WebRuntime::INavigationCompletedEventArgs * __ptr64>::Invoke+0x1b
00000036`933fea90 00007ffb`8940981e EMODEL!Microsoft::WRL::InvokeTraits<-2>::InvokeDelegates<<lambda_8860911b6575297232878b65e442a3ec>,Windows::Foundation::ITypedEventHandler<WebRuntime::BrowsingContext * __ptr64,WebRuntime::NavigationCompletedEventArgs * __ptr64> >+0x65
00000036`933feaf0 00007ffb`89410159 EMODEL!WebRuntime::BrowsingContext::InvokeNavigationCompletedEvent+0xee
00000036`933feb70 00007ffb`8940fbfa EMODEL!BrowserCouplingIsoSink::HandleDocumentComplete+0xe5
00000036`933febe0 00007ffb`8940f449 EMODEL!BrowserCouplingIsoSink::LCIEWinProc+0x37a
00000036`933fec90 00007ffb`8c5fcccf EMODEL!LCIEBrowserCouplingIsoSink_WinProc+0x1a9
00000036`933fed40 00007ffb`894ae9ff edgeIso!IsoDispatchMessageToArtifacts+0x54f
00000036`933fee50 00007ffb`894abf92 EMODEL!LCIEDispatchToArtifactsOnThread+0xf
00000036`933fee80 00007ffb`a4802cc6 EMODEL!Microsoft::WRL::Details::DelegateArgTraits<long (__cdecl ABI::Windows::System::IDispatcherQueueHandler::*)(void) __ptr64>::DelegateInvokeHelper<Microsoft::WRL::Implements<Microsoft::WRL::RuntimeClassFlags<2>,ABI::Windows::System::IDispatcherQueueHandler,Microsoft::WRL::FtmBase>,<lambda_8904907745c6387a83a814456134490b>,-1>::Invoke+0x12
00000036`933feeb0 00007ffb`a47c8c7a CoreMessaging!Windows::System::DispatcherQueue::DeferInvokeCallback+0x16
00000036`933feee0 00007ffb`a47ed0ac CoreMessaging!System__Action$CallbackThunk+0xea
00000036`933fef50 00007ffb`a47eca69 CoreMessaging!Microsoft::CoreUI::Dispatch::DeferredCall::Callback_Dispatch+0x22c
00000036`933fefc0 00007ffb`a47d288f CoreMessaging!Microsoft::CoreUI::Dispatch::DeferredCallDispatcher::Callback_OnDispatch+0xe9
00000036`933ff020 00007ffb`a47d0d18 CoreMessaging!Microsoft::CoreUI::Dispatch::EventLoop::Callback_RunCoreLoop+0x60f
00000036`933ff110 00007ffb`a47cce09 CoreMessaging!Microsoft::CoreUI::Dispatch::UserAdapter::OnUserDispatch+0x1e8
00000036`933ff1e0 00007ffb`a47ccc08 CoreMessaging!Microsoft::CoreUI::Dispatch::UserAdapter_DoWork+0xf9
00000036`933ff2c0 00007ffb`aa5d6cc1 CoreMessaging!Microsoft::CoreUI::Dispatch::UserAdapter_WindowProc+0x58
00000036`933ff2f0 00007ffb`aa5d6693 USER32!UserCallWinProcCheckWow+0x2c1
00000036`933ff480 00007ffb`a47cfef4 USER32!DispatchMessageWorker+0x1c3
00000036`933ff510 00007ffb`a47d006a CoreMessaging!Microsoft::CoreUI::Dispatch::UserAdapter::DispatchNextUserQueueItem+0x154
00000036`933ff5c0 00007ffb`a47d1174 CoreMessaging!Microsoft::CoreUI::Dispatch::UserAdapter::RunIntegratedLoop+0x5a
00000036`933ff680 00007ffb`a47d2fc7 CoreMessaging!Microsoft::CoreUI::Dispatch::UserAdapter::Callback_HostModeRun+0x78
00000036`933ff6c0 00007ffb`a47fe815 CoreMessaging!Microsoft::CoreUI::Dispatch::EventLoop::Callback_Run+0x177
00000036`933ff740 00007ffb`a47c636b CoreMessaging!Microsoft::CoreUI::Messaging::MessageSession$R::Microsoft__CoreUI__IExportMessageSession_Impl::Run+0x35
00000036`933ff780 00007ffb`a4842c0c CoreMessaging!Microsoft::CoreUI::IExportMessageSession$X__ExportAdapter::Run+0x4b
00000036`933ff820 00007ffb`a483f826 CoreMessaging!Windows::System::DispatcherQueue::RunLoop+0x64
00000036`933ff880 00007ffb`a7ea3034 CoreMessaging!Windows::System::DispatcherQueueController::DispatcherQueueThreadProc+0x136
00000036`933ff8f0 00007ffb`aa871431 KERNEL32!BaseThreadInitThunk+0x14
00000036`933ff920 00000000`00000000 ntdll!RtlUserThreadStart+0x21
```

Let's take a closer look at what is being passed from emodel.dll to ntdll.dll, by setting a breakpoint at the call instruction to `RtlPublishWnfStateData` as follows.

```
0: kd> ub 00007ffb`893f3385
EMODEL!CTabWindow::_OnNavigationComplete+0x36d:
00007ffb`893f335d 488bcf          mov     rcx,rdi
00007ffb`893f3360 ff156ad45600    call    qword ptr [EMODEL!_imp_wcslen (00007ffb`899607d0)]
00007ffb`893f3366 4c8d0c4502000000 lea     r9,[rax*2+2]
00007ffb`893f336e 4c897c2420      mov     qword ptr [rsp+20h],r15
00007ffb`893f3373 4c8bc7          mov     r8,rdi
00007ffb`893f3376 33d2            xor     edx,edx
00007ffb`893f3378 488b0d89965b00  mov     rcx,qword ptr [EMODEL!WNF_EDGE_LAST_NAVIGATED_HOST (00007ffb`899aca08)]
00007ffb`893f337f ff15d3d55600    call    qword ptr [EMODEL!_imp_RtlPublishWnfStateData (00007ffb`89960958)]
0: kd> bc*
0: kd> bp /p ffffa185`845d8080 00007ffb`893f337f
0: kd> bl
 0 e 00007ffb`893f337f     0001 (0001) EMODEL!CTabWindow::_OnNavigationComplete+0x38f
     Match process data ffffa185`845d8080
0: kd> g
Breakpoint 0 hit
EMODEL!CTabWindow::_OnNavigationComplete+0x38f:
0033:00007ffb`893f337f ff15d3d55600    call    qword ptr [EMODEL!_imp_RtlPublishWnfStateData (00007ffb`89960958)]
```

Now we're in EMODEL.  Let's see what we have right now.

```
1: kd> ub . l10
EMODEL!CTabWindow::_OnNavigationComplete+0x34c:
0033:00007ffb`893f333c 488b93a8130000  mov     rdx,qword ptr [rbx+13A8h]
0033:00007ffb`893f3343 488d4da0        lea     rcx,[rbp-60h]
0033:00007ffb`893f3347 e8b0560800      call    EMODEL!CUString::CUString (00007ffb`894789fc)
0033:00007ffb`893f334c 90              nop
0033:00007ffb`893f334d 44397c2430      cmp     dword ptr [rsp+30h],r15d
0033:00007ffb`893f3352 7c32            jl      EMODEL!CTabWindow::_OnNavigationComplete+0x396 (00007ffb`893f3386)
0033:00007ffb`893f3354 488b7dc0        mov     rdi,qword ptr [rbp-40h]
0033:00007ffb`893f3358 4885ff          test    rdi,rdi
0033:00007ffb`893f335b 7429            je      EMODEL!CTabWindow::_OnNavigationComplete+0x396 (00007ffb`893f3386)
0033:00007ffb`893f335d 488bcf          mov     rcx,rdi
0033:00007ffb`893f3360 ff156ad45600    call    qword ptr [EMODEL!_imp_wcslen (00007ffb`899607d0)]
0033:00007ffb`893f3366 4c8d0c4502000000 lea     r9,[rax*2+2]
0033:00007ffb`893f336e 4c897c2420      mov     qword ptr [rsp+20h],r15
0033:00007ffb`893f3373 4c8bc7          mov     r8,rdi
0033:00007ffb`893f3376 33d2            xor     edx,edx
0033:00007ffb`893f3378 488b0d89965b00  mov     rcx,qword ptr [EMODEL!WNF_EDGE_LAST_NAVIGATED_HOST (00007ffb`899aca08)]
1: kd> r
rax=000000000000000e rbx=0000019af44cbf90 rcx=04810a28a3bc08f5
rdx=0000000000000000 rsi=0000019af4e43f1c rdi=0000019af4e4e280
rip=00007ffb893f337f rsp=00000036933fe830 rbp=00000036933fe930
 r8=0000019af4e4e280  r9=000000000000001e r10=00000fff736ec53a
r11=5555500000000001 r12=0000000000000000 r13=00007ffb899802d8
r14=0000019af3fb1de0 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
EMODEL!CTabWindow::_OnNavigationComplete+0x38f:
0033:00007ffb`893f337f ff15d3d55600    call    qword ptr [EMODEL!_imp_RtlPublishWnfStateData (00007ffb`89960958)] ds:002b:00007ffb`89960958={ntdll!RtlPublishWnfStateData (00007ffb`aa87c9c0)}
```

The first parameter is a strange 64bit integer *04810a28a3bc08f5*, that was stored at `EMODEL!WNF_EDGE_LAST_NAVIGATED_HOST`.  This should represent the structure `WNF_STATE_NAME`, which was used in Mach2.  I don't remember well, but Alex explained this is not just a random number but generated from some human-friendly information and xor'ed with a magic number (probably to obfuscate?).  Anyway, what is important here is *04810a28a3bc08f5* is a notification ID and you can get it from the symbol `EMODEL!WNF_EDGE_LAST_NAVIGATED_HOST`.

The second parameter is just 0 and I don't know what it means.

To understand the third and fourth parameter, the function call to `wcslen` at 00007ffb\`893f3360 is a great hint.  After `wcslen`, the fourth parameter r9 is set to rax (= return value from wcslen) * 2 + 2 at 00007ffb\`893f3366.  So r9 is the size of a UTF-16 string buffer.  And, the third parameter r8 is moved from rdi that was passed to `wcslen`.  So the third parameter is a string.  Let's check it out.

```
1: kd> du /c 100 @r8
0000019a`f4e4e280  "www.kernel.org"
```

Great.  It's confirmed that Edge is surely pushing a hostname to WNF.

# Hand-decompile the subscriber code

Mach2 does not depend on subscribing part of WNF, but we already learned `NtQueryWnfStateData`, `NtUpdateWnfStateData`, and `RtlPublishWnfStateData` are all implemented in ntdll.  It's natural that a subscribing function is also implemented in ntdll.dll.  Let's start with querying ntdll's functions in the symbol.

```
1: kd> x ntdll!*WnfState*
00007ffb`aa8821e0 ntdll!RtlTestAndPublishWnfStateData (void)
00007ffb`aa842144 ntdll!RtlpSubscribeWnfStateChangeNotificationInternal (void)
00007ffb`aa87c9c0 ntdll!RtlPublishWnfStateData (void)
00007ffb`aa8a29f3 ntdll!RtlQueryWnfStateData$filt$0 (void)
00007ffb`aa8a2c47 ntdll!RtlQueryWnfStateDataWithExplicitScope$filt$0 (void)
00007ffb`aa89bbd0 ntdll!ZwGetCompleteWnfStateSubscription (<no parameter info>)
00007ffb`aa89d4d0 ntdll!ZwSubscribeWnfStateChange (<no parameter info>)
00007ffb`aa89b730 ntdll!ZwCreateWnfStateName (<no parameter info>)
00007ffb`aa89b8d0 ntdll!NtDeleteWnfStateName (<no parameter info>)
00007ffb`aa89b730 ntdll!NtCreateWnfStateName (<no parameter info>)
00007ffb`aa89b8d0 ntdll!ZwDeleteWnfStateName (<no parameter info>)
00007ffb`aa89bbd0 ntdll!NtGetCompleteWnfStateSubscription (<no parameter info>)
00007ffb`aa89d4d0 ntdll!NtSubscribeWnfStateChange (<no parameter info>)
00007ffb`aa89ca10 ntdll!ZwQueryWnfStateNameInformation (<no parameter info>)
00007ffb`aa89b8b0 ntdll!ZwDeleteWnfStateData (<no parameter info>)
00007ffb`aa89c9f0 ntdll!NtQueryWnfStateData (<no parameter info>)
00007ffb`aa89d750 ntdll!NtUpdateWnfStateData (<no parameter info>)
00007ffb`aa89ca10 ntdll!NtQueryWnfStateNameInformation (<no parameter info>)
00007ffb`aa89c9f0 ntdll!ZwQueryWnfStateData (<no parameter info>)
00007ffb`aa89b8b0 ntdll!NtDeleteWnfStateData (<no parameter info>)
00007ffb`aa89d750 ntdll!ZwUpdateWnfStateData (<no parameter info>)
00007ffb`aa842100 ntdll!RtlSubscribeWnfStateChangeNotification (<no parameter info>)
00007ffb`aa842970 ntdll!RtlUnsubscribeWnfStateChangeNotification (<no parameter info>)
00007ffb`aa89d730 ntdll!ZwUnsubscribeWnfStateChange (<no parameter info>)
00007ffb`aa881440 ntdll!RtlQueryWnfStateDataWithExplicitScope (<no parameter info>)
00007ffb`aa87b540 ntdll!RtlQueryWnfStateData (<no parameter info>)
00007ffb`aa89d730 ntdll!NtUnsubscribeWnfStateChange (<no parameter info>)
```

Easy.  Obviously `ntdll!NtSubscribeWnfStateChange` is a system call entrypoint to subscribe WNF.  The next step is to set a breakpoint at the kernel system call `nt!NtSubscribeWnfStateChange`.

The function `nt!NtSubscribeWnfStateChange` is aggressively called from many places even without doing any operations.  I picked up one of those callers `RPCRT4!LoadFirewallExtensionIfNecessary` because it looks straightforward to hand-decompile.  Here's its snippet calling `ntdll!RtlSubscribeWnfStateChangeNotification` that ends up calling `nt!NtSubscribeWnfStateChange`.

```
RPCRT4!LoadFirewallExtensionIfNecessary+0x21:
00007ffb`5b1db45d 488b15bc750e00  mov     rdx,qword ptr [RPCRT4!WNF_RPCF_FWMAN_RUNNING (00007ffb`5b2c2a20)]
00007ffb`5b1db464 4c8d05b5dc0500  lea     r8,[RPCRT4!FwManInstalledStateChangeNotification (00007ffb`5b239120)]
00007ffb`5b1db46b 488364242000    and     qword ptr [rsp+20h],0
00007ffb`5b1db471 488d4c2450      lea     rcx,[rsp+50h]
00007ffb`5b1db476 4533c9          xor     r9d,r9d
00007ffb`5b1db479 c705ed2e100002000000 mov dword ptr [RPCRT4!gFwExtensionState (00007ffb`5b2de370)],2
00007ffb`5b1db483 ff15372c0e00    call    qword ptr [RPCRT4!_imp_RtlQueryWnfStateData (00007ffb`5b2be0c0)]
00007ffb`5b1db489 8bd0            mov     edx,eax
00007ffb`5b1db48b 85c0            test    eax,eax
00007ffb`5b1db48d 0f88b5db0600    js      RPCRT4!LoadFirewallExtensionIfNecessary+0x6dc0c (00007ffb`5b249048)

RPCRT4!LoadFirewallExtensionIfNecessary+0x57:
00007ffb`5b1db493 448b442450      mov     r8d,dword ptr [rsp+50h]
00007ffb`5b1db498 4c8d0d81dc0500  lea     r9,[RPCRT4!FwManInstalledStateChangeNotification (00007ffb`5b239120)]
00007ffb`5b1db49f 488b157a750e00  mov     rdx,qword ptr [RPCRT4!WNF_RPCF_FWMAN_RUNNING (00007ffb`5b2c2a20)]
00007ffb`5b1db4a6 488d0ddb2b1000  lea     rcx,[RPCRT4!gFwManStateNotification (00007ffb`5b2de088)]
00007ffb`5b1db4ad c744243801000000 mov     dword ptr [rsp+38h],1
00007ffb`5b1db4b5 8364243000      and     dword ptr [rsp+30h],0
00007ffb`5b1db4ba 488364242800    and     qword ptr [rsp+28h],0
00007ffb`5b1db4c0 488364242000    and     qword ptr [rsp+20h],0
00007ffb`5b1db4c6 ff15ec2b0e00    call    qword ptr [RPCRT4!_imp_RtlSubscribeWnfStateChangeNotification (00007ffb`5b2be0b8)]
00007ffb`5b1db4cc 8bd0            mov     edx,eax
00007ffb`5b1db4ce 85c0            test    eax,eax
00007ffb`5b1db4d0 0f8872db0600    js      RPCRT4!LoadFirewallExtensionIfNecessary+0x6dc0c (00007ffb`5b249048)
```

You can see it calls `RtlQueryWnfStateData` with `WNF_RPCF_FWMAN_RUNNING` first, and then passes a 32bit integer stored in rsp+50h to `ntdll!RtlSubscribeWnfStateChangeNotification`.  The function `RPCRT4!FwManInstalledStateChangeNotification` should be a callback function from WNF.

Considering Windows x64 calling convention, you can assume `RtlQueryWnfStateData` takes five parameters and `RtlSubscribeWnfStateChangeNotification` takes eight.  You can confirm it from the 32bit assembly.  In addition, the import library ntdll.lib in Windows Kit contains the symbols name `_RtlQueryWnfStateData@24` and `_RtlSubscribeWnfStateChangeNotification@36`, from which you can know the total size of parameters of each API. This is also a great hint.

Let's take a look at 32bit assembly of the callback function `RPCRT4!FwManInstalledStateChangeNotification`.

```
rpcrt4!FwManInstalledStateChangeNotification:
4efbede0 8bff            mov     edi,edi
4efbede2 55              push    ebp
4efbede3 8bec            mov     ebp,esp
4efbede5 837d1000        cmp     dword ptr [ebp+10h],0
4efbede9 0f85bd080200    jne     rpcrt4!FwManInstalledStateChangeNotification+0x208cc (4efdf6ac)

rpcrt4!FwManInstalledStateChangeNotification+0xf:
4efbedef 33c0            xor     eax,eax

rpcrt4!FwManInstalledStateChangeNotification+0x11:
4efbedf1 5d              pop     ebp
4efbedf2 c21c00          ret     1Ch

rpcrt4!FwManInstalledStateChangeNotification+0x208cc:
4efdf6ac e80a750000      call    rpcrt4!LoadFirewallExtensionInternal (4efe6bbb)
...
```

It ends with `ret 1C`, which means it's stdcall and the total size of parameters is 0x1C bytes.  If you set a breakpoint at any WNF callback function and check the first parameter value, you'll see it's the value of notification ID.

So, the prototypes should be something like this:

```
NTSTATUS NTAPI WnfCallback(uint64_t, void*, void*, void*, void*, void*);

extern "C" {
  NTSTATUS NTAPI RtlQueryWnfStateData(uint32_t*,
                                      uint64_t,
                                      decltype(WnfCallback),
                                      uint64_t);
  NTSTATUS NTAPI
  RtlSubscribeWnfStateChangeNotification(void*,
                                         uint64_t,
                                         uint32_t,
                                         decltype(WnfCallback),
                                         size_t,
                                         size_t,
                                         size_t,
                                         size_t);
}
```

# Monitor Edge navigation

We've got a notification ID 04810a28a3bc08f5 for Edge navigation, and the function prototypes.  I created a simple console program using these information and uploaded its source code to the gist.  To build, clone it and just run `NMAKE` on the "Native Tools Command Prompt" of your Visual Studio.

https://gist.github.com/msmania/472912cd6e9ab067be3211ba3f5f0f9e

Below is the screenshot.  Both 32bit and 64bit process can receive the same notifications from 64bit Edge.  It looks like InPrivate mode does not push notifications.

![wnf.png](https://qiita-image-store.s3.amazonaws.com/0/165275/666e23e9-8143-1e71-ecb6-aea195f126cb.png)

# Wrapping Up

I believe it's worth spending some time on analyzing or fuzzing these WNF as Alex mentioned.  Even without putting on a security hat, there may be an interesting notification you can use to build your tool though using this kind of undocumented APIs is not supported.