6

I'm writing an Autohotkey script to toggle Listen to this device for my microphone, without interacting with a GUI.

listen to this device

I thought it would be a simple registry key being modified so I used RegShot to find the key:

Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Capture\{My-Microphone's-UUID}\Properties

The key is called {24dbb0fc-9311-4b3d-9cf0-18ff155639d4},1 (On all computers).

And the value when toggling the Listen to this device changes like this: (The 0's change to f's)

xxxxxxxxxxxxxxxx0000xxxx
xxxxxxxxxxxxxxxxffffxxxx

But when I check the GUI, I see that the Listen to this device tick-box has been ticked but I can't actually hear anything from my mic, when I un-tick it, click apply, re-tick it and apply again, I hear my mic. So I thought I might need DllCall or PostMessage here, like what message was sent or what dll was called when I click apply but I couldn't find anything on it on the Internet. I don't know how to make Windows understand that this setting has changed.

Please teach me how to reverse engineer this with x64dbg.

Shayan
  • 111
  • 7
  • 1
    If I search for the identified registry key 24dbb0fc-9311-4b3d-9cf0-18ff155639d4 I am getting some interesting results pointing to the MMDevice API. See for example here. – Robert Mar 17 '20 at 08:53
  • 1
    Apparently multimediasoft offers a library (paid) Audio Sound Recorder for .NET which "emulates" this feature. – 0xec Mar 17 '20 at 09:53
  • 3
    What I would do is this: 1. See what process that UI resides in (use Spy++ for that to get PID from the HWND you showed.) I'd guess that it'd be one of rundll32 procs. 2. Then use a debugger (x64Dbg would work) and set a breakpoint on ntdll.ZwSetValueKey in that proc (before that UI is shown). You may want to make a conditional bp to catch when your registry value is written. It will be in a 2nd parameter as UNICODE_STRING*. 3. Run the proc until bp triggers. 4. After then just walk thru the code with your debugger and see what they are doing there. No guesswork needed. – c00000fd Mar 18 '20 at 21:32
  • 1
    @c00000fd +1 or alternatively just use ProcMon (be sure to configure symbols) and filter on RegSetValue then doubleclick the entry and inspect the callstack which might be easier than using a debugger... – Remko Mar 21 '20 at 21:53
  • I tried hard and for very long but I couldn't do it, it's hard for me as a beginner who never reversed anything with x64dbg. I watched a lot of video tutorials as well but to no avail. – Shayan May 04 '20 at 10:36
  • 1
    I first thought of GetSystemMetrics/SystemParametersInfo/WM_SETTINGSCHANGE, but it turns out those are not used for what you want/need. What I am wondering is whether you are actually interested in achieving that functionality or whether it's important to you how to reverse engineer such stuff?! Given we're on RE.SE I'd assume it's the latter, but I'd like to know as I think that in this case it might be possible to substitute reverse engineering with knowledge about Win32 programming. It would be normal, at least, for that configuration change to be broadcast somehow. – 0xC0000022L May 06 '20 at 13:30
  • 1
    Probably you should make use of this to gain a better understanding of the underlying stuff. I really doubt that the dialog you are showing above implements any of the actual meat. mixerSetControlDetails and MIXERCONTROL look relevant, for example. – 0xC0000022L May 06 '20 at 13:57
  • I'd like to learn how to RE Windows, so I can RE my future projects! I will take a through look into those, thanks! :3 – Shayan May 06 '20 at 22:39
  • For reference, multimediasoft uses the BASSmix library to implement the feature. – 0xec May 07 '20 at 07:18
  • @TechLord Could you take a look at this please? – Shayan May 09 '20 at 20:02
  • 1
    I am personally not very comfortable discussing ways in which a microphone or a webcam can be enabled silently without their permissions, since this mimics "malware-like" behavior. I'm sure that your intentions are good, and I do not want to start a discussion regarding the ethics of this but I only tried to help since I was tagged. There was no requirement mentioned at the time, that the user's mic needed to be enabled silently . So, nothing personal here, and thank you for your understanding. :) – TechLord May 12 '20 at 00:25
  • You're right, "ENABLING" a mic or cam silently is bad, but we're talking about "LISTEN TO THIS DEVICE" which means the user will be able to hear themselves, that's all. idk what you mean about "your intentions are good". Also, once a mic is plugged in, it's ENABLED at all times anyways. Also what's about "WITHOUT THEIR PERMISSION"? I said I want to use it with a hotkey at the very beginning of the question. If one's not bothered using a GUI each time, they can just make a shortcut of the sound settings and use that anyways. – Shayan May 12 '20 at 11:22
  • 1
    @Shayan with RCE being a two-edged sword of sorts, you have to understand that some of the aspects you ask about could raise questions. Personally I didn't see it the way TechLord took it, but don't feel offended by their view. – 0xC0000022L May 12 '20 at 13:38
  • I understand, everyone has their own point of view and I respect that, I was just clearing things up for future viewers because TechLord's comment could potentially hold them back from writing an answer. – Shayan May 12 '20 at 13:50
  • @0xC0000022L Hello! Why can't I start another bounty on this question? Technically I should be able to, but the "Start a bounty" doesn't appear. – Shayan May 28 '20 at 16:49
  • 1
    @Shayan I think the absolute minimum of a bounty is 50. I don't know how much you offered, but the rule is: If you have already offered a bounty on the question before, the minimum offer is double your last offer (source). So there you have it. – 0xC0000022L May 28 '20 at 19:13
  • I am also very curious to know if this is possible. I'm using several virtual audio cable recording devices in Win 10 that are always on feeding data to an application. Sometimes I want to hear them through my speakers. But, sometimes I don't. It is a lot of clicks to check/un-check "Listen to this device" on each one. Anxiously looking for more info on a solution here. Thanks! – bhall Jan 28 '21 at 16:52

3 Answers3

3

AFAIK, knowing what registry keys are used is not always enough, because the registry is just a place to store things like preferences. Setting a registry value may not have any immediate effect on a device. The real source of truth of how a device is configured is the device itself.

From my experience control panels tend to be lightweight GUIs hosted by the OS, and the process that does any real work is the daemon that the GUI talks to over IPC.

Looking over the recorded API calls in API Monitor we can see that the control panel sends RPC messages to the AudioSrv service. Using sc queryex in Command Prompt, you can find the PID of the svchost (service host) that's hosting the instance of this service.

From there, doing a string search in IDA, we find the string "ListenTo" being used by some of the AudioSrv code. It could be a string used for debugging, but that would be my first place to do some static analysis in IDA or set a breakpoint on with my debugger.

There are some tools that can help with figuring out which code is run when you perform some action. Ultimap in CheatEngine comes to mind, you can find tutorials for it online. You can also perform tracing of the process in x64dbg and look for any syscalls, which is generally interesting because it indicates that the process is asking the kernel for something (e.g. control a device).

Ultimately, this is probably a lot of work just to get to a hacky solution, so you might explore alternatives e.g. creating a virtual device driver.

P. Private
  • 180
  • 3
  • 14
0

Unfortunately I cannot answer how to find this information using x64dbg. However this question has been asked multiple times over the years and never got a definitive answer, so here's a solution anyway:

This can be done by acquiring the device via IMMDeviceEnumerator::GetDevice and opening & editing the IMMDevice:PropertyStore directly.

In my experience, IPropertyStore::SetValue will take care of notifying the AudioSrv as well. IPropertyStore::Commit is seemingly not required for events to fire.

Here is a Python sample using comtypes and pycaw:

from comtypes.automation import VT_BOOL
from comtypes.persist import STGM_READWRITE

from pycaw.api.mmdeviceapi.depend import PROPVARIANT from pycaw.utils import AudioUtilities

CHECKBOX_GUID = "{24DBB0FC-9311-4B3D-9CF0-18FF155639D4}" CHECKBOX_PID = 1

Can be found in the registry

DEVICE_GUID = "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"

if name == "main": enumerator = AudioUtilities.GetDeviceEnumerator() dev = enumerator.GetDevice(f"{{0.0.1.00000000}}.{DEVICE_GUID}")

store = dev.OpenPropertyStore(STGM_READWRITE)
if store is None:
    print("failed to open property store")
    exit(1)

for j in range(store.GetCount()):
    pk = store.GetAt(j)
    if str(pk.fmtid) != CHECKBOX_GUID or pk.pid != CHECKBOX_PID:
        continue

    new_input = PROPVARIANT(VT_BOOL)
    new_input.union.boolVal = True
    store.SetValue(pk, new_input)
    break

nvs
  • 1
0

Using nvs' answer as a starting point, I was able to figure out how to also set the device that should be listening to the input.

Here's my code, also using pycaw:

from typing import Optional
from comtypes import GUID
from comtypes.automation import VT_BOOL, VT_LPWSTR, VT_EMPTY
from comtypes.persist import STGM_READWRITE
from pycaw.api.mmdeviceapi import PROPERTYKEY

from pycaw.api.mmdeviceapi.depend import PROPVARIANT from pycaw.utils import AudioUtilities

#Hardcoded values LISTEN_SETTING_GUID = "{24DBB0FC-9311-4B3D-9CF0-18FF155639D4}" CHECKBOX_PID = 1 LISTENING_DEVICE_PID = 0

#Values you can change: microphone_name = 'Microphone (USB-MIC)' listening_device_name = 'Speakers (5- GSA 70 Main Audio)' #Set to None to use the default playback device enable_listening = False

def main(): store = get_device_store(microphone_name) if store is None: print("failed to open property store") exit(1)

set_listening_checkbox(store, enable_listening)
set_listening_device(store, listening_device_name)

#Write to the checkbox property def set_listening_checkbox(property_store, value:bool): checkbox_pk = PROPERTYKEY() checkbox_pk.fmtid = GUID(LISTEN_SETTING_GUID) checkbox_pk.pid = CHECKBOX_PID

new_value = PROPVARIANT(VT_BOOL)
new_value.union.boolVal = value
property_store.SetValue(checkbox_pk, new_value)

#Write to the device property def set_listening_device(property_store, output_device_name:Optional[str]): if output_device_name is not None: listening_device_guid = get_GUID_from_name(output_device_name) else: listening_device_guid = None

device_pk = PROPERTYKEY()
device_pk.fmtid = GUID(LISTEN_SETTING_GUID)
device_pk.pid = LISTENING_DEVICE_PID

if listening_device_guid is not None:
    new_value = PROPVARIANT(VT_LPWSTR)
    new_value.union.pwszVal = listening_device_guid
else:
    new_value = PROPVARIANT(VT_EMPTY)

property_store.SetValue(device_pk, new_value)

#Gets the device store from the device name def get_device_store(device_name:str): device_guid = get_GUID_from_name(device_name) enumerator = AudioUtilities.GetDeviceEnumerator() dev = enumerator.GetDevice(device_guid)

store = dev.OpenPropertyStore(STGM_READWRITE)
return store

#This is just a helper method to turn a device name into a GUID. def get_GUID_from_name(device_name:str) -> str: input_devices = get_list_of_active_coreaudio_devices("input") for device in input_devices: if device.FriendlyName == device_name: return device.id output_devices = get_list_of_active_coreaudio_devices("output") for device in output_devices: if device.FriendlyName == device_name: return device.id raise ValueError("Device not found!")

#Helper method to get all (active) devices def get_list_of_active_coreaudio_devices(device_type:str) -> list: import comtypes from pycaw.pycaw import AudioUtilities, IMMDeviceEnumerator, EDataFlow, DEVICE_STATE from pycaw.constants import CLSID_MMDeviceEnumerator

if device_type != "output" and device_type != "input":
    raise ValueError("Invalid audio device type.")

if device_type == "output":
    EDataFlowValue = EDataFlow.eRender.value
else:
    EDataFlowValue = EDataFlow.eCapture.value
# Code to enumerate devices adapted from https://github.com/AndreMiras/pycaw/issues/50#issuecomment-981069603

devices = list()
device_enumerator = comtypes.CoCreateInstance(
    CLSID_MMDeviceEnumerator,
    IMMDeviceEnumerator,
    comtypes.CLSCTX_INPROC_SERVER)
if device_enumerator is None:
    raise ValueError("Couldn't find any devices.")
collection = device_enumerator.EnumAudioEndpoints(EDataFlowValue, DEVICE_STATE.ACTIVE.value)
if collection is None:
    raise ValueError("Couldn't find any devices.")

count = collection.GetCount()
for i in range(count):
    dev = collection.Item(i)
    if dev is not None:
        if not ": None" in str(AudioUtilities.CreateDevice(dev)):
            devices.append(AudioUtilities.CreateDevice(dev))

return devices

if name == "main": main()