Find which process is using the microphone, from a kernel-mode driver

Background

A while ago I was at Alex Ionescu’s house and we were discussing random Windows internals stuff. I learned that we both discovered cool things in the Windows Notification Framework (WNF). Alex and Gabrielle Viala presented their research on the topic at Black Hat USA 2018 (BHUSA2018) [1]. It is fairly comprehensive and will certainly be the most authoritative source of information on WNF in the near foreseeable future. Since I don’t do conference talks these days, I only discussed WNF it in my kernel training classes. In fact, I always tell the students that if they look a bit closer, it may result in original work that can be presented at conferences. Being the great reverse engineer that she is, Gabrielle was one of the very few persistent students who successfully completed the WNF exercises and made the prediction come true.

The BHUSA2018 presentation is currently unavailable to the public because there are some information disclosure bugs in WNF and Microsoft is fixing them. I will not repeat the details here. My intention here is to show an interesting usage of WNF for defensive purposes: from a kernel-mode driver, get notified when a process is using the microphone. Some details are intentionally omitted, but the determined readers will figure them out.

This blog entry is a modified version of the solution to one of the exercises in my class.

Prior research and related readings

Prior to BHUSA2018, the only source of information about WNF is [2, 3]. redplait reverse engineered parts of the main usermode WNF tracking structure and enumerated a list of supported events. Unfortunately it seemed that this novel research went mostly unnoticed. For those interested in esoteric Windows system-oriented details, I highly recommend you read his/her blog (I don’t know redplait’s gender).

I will update this with a link to the BHUSA2018 presentation when it is made available.

Brief introduction to WNF and its kernel interface

WNF is a pub/sub mechanism in the kernel that facilitates data exchange between various components. One component “publishes” the data and other components can be notified if they subscribe to the event. Think of it like a specialized message queue/broker. There are over 1,000 WNF events in Windows.

User-mode components can interact with WNF using either the native APIs or the Rtl wrappers. If you are interested in these APIs, you can either reverse engineer them yourself or wait for Alex and Gabrielle’s slides.

For kernel-mode drivers, it is possible to use WNF through the Zw-family of APIs. However, for our purposes, it is simpler to use the Ex ones:

  • ExQueryWnfStateData: query the published WNF data.
  • ExSubscribeWnfStateChange: subscribe to a WNF event.

I reverse engineered these functions and their prototypes can be seen in the code snippet below. They may be wrong so use at your own risk.

typedef NTSYSAPI NTSTATUS(NTAPI *pfnExSubscribeWnfStateChange)(
PVOID *wnfStruct,
PCWNF_STATE_NAME stateName,
ULONG eventMask,
PULONG changeStamp,
PVOID callback,
PVOID callbackContext);

typedef NTSYSAPI NTSTATUS(NTAPI *pfnSubscribeCallback)(
PVOID wnfStruct,
PCWNF_STATE_NAME stateName,
ULONG eventMask,
ULONG changeStamp,
PVOID typeId,
PVOID callbackContext
);

typedef NTSYSAPI NTSTATUS(NTAPI *pfnExQueryWnfStateData)(
PVOID wnfStruct,
PULONG changeStamp,
PVOID buf,
PULONG bufSize
);

NTSTATUS SubscribeCallback(
PVOID wnfStruct,
PCWNF_STATE_NAME stateName,
ULONG eventMask,
ULONG changeStamp,
PVOID typeId,
PVOID callbackContext
) {
UNREFERENCED_PARAMETER(stateName);
UNREFERENCED_PARAMETER(eventMask);
UNREFERENCED_PARAMETER(changeStamp);
UNREFERENCED_PARAMETER(typeId);
UNREFERENCED_PARAMETER(callbackContext);

UNICODE_STRING fn;
ULONG cStamp;
RtlInitUnicodeString(&fn, L"ExQueryWnfStateData");
auto ExQueryWnfStateData =
(pfnExQueryWnfStateData)MmGetSystemRoutineAddress(&fn);

PUCHAR buf = (PUCHAR)ExAllocatePool(PagedPool, PAGE_SIZE);
ULONG bufSize = PAGE_SIZE;
auto status = ExQueryWnfStateData(wnfStruct, &cStamp, buf, &bufSize);

__debugbreak(); // you can view the data here 🙂
ExFreePool(buf);
return status;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT drvObj, PUNICODE_STRING regPath) {
UNREFERENCED_PARAMETER(drvObj);
UNREFERENCED_PARAMETER(regPath);
UNICODE_STRING fn;
PVOID handle;
WNF_STATE_NAME stateName;

RtlInitUnicodeString(&fn, L"ExSubscribeWnfStateChange");
// WNF_AUDC_CAPTURE
stateName.Data[0] = 0xa3bc4075;
stateName.Data[1] = 0x02821b2c;
auto ExSubscribeWnfStateChange =
(pfnExSubscribeWnfStateChange)MmGetSystemRoutineAddress(&fn);
auto status = ExSubscribeWnfStateChange(
&handle, &stateName, 3, NULL, SubscribeCallback, (PVOID)1);

return status;
}

For audio data, the relevant WNF event name is WNF_AUDC_CAPTURE. Its description is: Reports the number of, and process ids of all applications currently capturing audio. Returns a WNF_CAPTURE_STREAM_EVENT_HEADER data structure.

You may wonder how I knew that WNF_AUDC_CAPTURE was an interesting event. Well, it was not magic. I extracted the WNF event names and description from an in-box DLL, ContentDeliveryManager_Utilities.dll (alternatively, you can also get them from perf_nt_c.dll as redplait did), and skimmed through the descriptions. How did I know that DLL had the WNF event names? Binary search and persistence.

The next task is to figure out who publishes the WNF data and how it is structured. After some chasing, I discovered that the user-mode audio service is the publisher. The exact function is is audiosrv!CCaptureNotifier::PublishCaptureAudioStatus. Here you can see the debug session where I dumped the data and the show that the SpeechRuntime.exe process was using my microphone (it’s actually Cortana).

rax=000001d246b135a0 rbx=000001d246b31ba8 rcx=02821b2ca3bc4075
rdx=0000000000000000 rsi=000001d246b31ba8 rdi=0000000000000000
rip=00007ffab50bcafe rsp=0000004378ffd670 rbp=0000004378ffd770
r8=0000004378ffd6e0 r9=0000000000001000 r10=000001d246b135a0
r11=0000004378ffe660 r12=0000000000000000 r13=00007ffab511dac0
r14=000001d246b31a70 r15=0000000000000001 iopl=0 nv up ei pl zr na
po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
audiosrv!CCaptureNotifier::PublishCaptureAudioStatus+0xc2:
0033:00007ffa`b50bcafe call qword ptr [audiosrv!_imp_RtlPublishWnfStateData (00007ffa`b5120d88)] ds:002b:00007ffa`b5120d88={ntdll!RtlPublishWnfStateData (00007ffa`c11e0b10)}
1: kd> dd @r8
00000043`78ffd6e0 00000001 00000001 00000190 00000000
...
1: kd> !process 190 1
Searching for Process with Cid == 190 PROCESS
ffffa6090d27b5c0 SessionId: 1 Cid: 0190 Peb: dd39a15000 ParentCid: 0304
DirBase: 5742d000 ObjectTable: ffffb7094dba58c0 HandleCount: 787.
Image: SpeechRuntime.exe

And that’s how we find out which process is using the microphone, from a kernel-mode driver. Surely you can imagine the defensive applications…

If you are interested in this kind of stuff, please consider attending one of my training courses. Click HERE for the current course schedule.

References

[1] Ionescu, A. and Viala, G. 2018. The Windows Notification Facility: Peeling the Onion of the Most Undocumented Kernel Attack Surface Yet. (Aug. 2018).
[2] WNF IDs from perf_nt_c.dll: 2017. http://redplait.blogspot.com/2017/08/wnf-ids-from-perfntcdll.html.
[3] WNF notifiers: 2012. http://redplait.blogspot.com/2012/09/wnf-notifiers.html.