Getting an WDFDEVICE from a keyboard UnitId

So I have a keyboard identifier (UnitID from KEYBOARD_INPUT_DATA, which I think it should be unique per connected devices on the system) that I store for later use and I need to get the WdfDevice associated with it. What I thought is to loop through every device, check if it is of type keyboard and if so, get it’s UnitId and compare with the one that I have.

So, I tried to loop through every device that the driver registered, however, I found no function to loop through devices using pure Wdf (WDFDRIVER object), I had to get a POBJECT_DRIVER from the WDFDRIVER with WdfDriverWdmGetDriverObject and using NextDevice on the first DeviceObject. But now I’m stuck in converting the POBJECT_DEVICE back to WdfDevice, I only found a function to convert POBJECT_DEVICE to WdfDevice (WdfDeviceWdmGetDeviceObject). What should I use to convert it back? Is identifying a device by UnitId safe or should I identify it by the entire HID? Is there a better (in terms of efficiency) internal method to get a device from hid/unitid than looping through them myself? There is also a KeyboardIdentifier (made of 2 CHARs, Type and Subtype) in KEYBOARD_ATTRIBUTES, is that Unique?

Edit: Found how to convert the Wdm object back to a wdf object (WdfWdmDeviceGetWdfDeviceHandle). Looking at the docs, UnitId should be unique per plugged keyboard as it is the number in the device name (“\Device\KeyboardPortN”). My question about a more efficient method is still present tho.

WDF handles are private to your driver. An external driver doesn’t access another driver’s WDFDEVICE handle. An external driver doesn’t convert their PDEVICE_OBJECT into a WDFDEVICE. How do you even know that the other driver is a WDF driver. More specifically, no in box driver in the keyboard stack (i8042prt, kbdhidk, kbdclass) is WDF, they are all pure WDM, so there no WDFDEVICEs exist for them. Furthermore, you can’t acquire the lock for the list of device objects on the driver object, so you have a ticking time bomb in your code walking that list while a stateful pnp operation is in progress.

Now, down to the problem at hand. What bigger problem are you trying to solve? UnitIDs are a relic from the pre pnp days (and even then, not well implemented). There is no guaranteed consistency between them nor is there a way to understand the lifetime of the ID. How are you acquiring the UnitIDs outside of the keyboard stack? Once you have the specific keyboard stack in question, what are you going to do with it?

@Doron_Holan said:
WDF handles are private to your driver. An external driver doesn’t access another driver’s WDFDEVICE handle. An external driver doesn’t convert their PDEVICE_OBJECT into a WDFDEVICE. How do you even know that the other driver is a WDF driver. More specifically, no in box driver in the keyboard stack (i8042prt, kbdhidk, kbdclass) is WDF, they are all pure WDM, so there no WDFDEVICEs exist for them. Furthermore, you can’t acquire the lock for the list of device objects on the driver object, so you have a ticking time bomb in your code walking that list while a stateful pnp operation is in progress.

They’re created by my driver, I was searching for a similar way of PDRIVER_OBJECT’s DeviceObject and using the NextDevice property to loop through all devices created by MY driver but using Wdf (without having to get the WDM driver from the WDF driver and loop through PDEVICE_OBJECTs).

Now, down to the problem at hand. What bigger problem are you trying to solve? UnitIDs are a relic from the pre pnp days (and even then, not well implemented). There is no guaranteed consistency between them nor is there a way to understand the lifetime of the ID. How are you acquiring the UnitIDs outside of the keyboard stack? Once you have the specific keyboard stack in question, what are you going to do with it?

I’m not acquiring them outside the keyboard stack, but I need to keep the KEYBOARD_INPUT_DATA object from a stack and then process it later.
An example would be something like:

  • key ‘s’ is pressed on the keyboard
  • stop the “event” (do not process the key) and save the KEYBOARD_INPUT_DATA to process it later
  • after getting an ioctl from an external APP, process the keypress captured earlier. I need to identify the keyboard from which the keypress happened in the first place (I can use UnitId/KeyboardIdentifier/HID, whatever is best to identify a keyboard, that’s why I asked which should I use) and then process the keypress using that keyboard just like I would process it when it was pressed without cancelling it. The driver is a class filter, so it would run on all keyboards, that’s why I need to loop through the devices (which I already found how) to identify on which I should process the keypress.

So what would be an identifier to a device object that would stick and won’t change for the lifetime of the driver (so until driver is stopped or system shutdown/restart).

Edit: I got another idea, which I can do with less system calls, I’ll store in the device context an unique id (incremented with each device) and look up after the device by that. The only problem if the number would overflow, but that would require a lot of device plug/unplug during a single session before that happens.

at the time of the keypress processing where you stash the event (either the service callback or the read completion routine), you know the stack that reported the keypress. Save the WDFDEVICE for that stack alongside the KEYBOARD_INPUT_DATA. Then when you retrieve the keypress to replay later, you have everything you need to report up the right stack. When the WDFDEVICE is removed (i.e. the stack is being torn down) you can walk through the list of saved KEYBOARD_INPUT_DATAs and remove the entries that are associated with the to be deleted WDFDEVICE).

1 Like

@Doron_Holan said:
at the time of the keypress processing where you stash the event (either the service callback or the read completion routine), you know the stack that reported the keypress. Save the WDFDEVICE for that stack alongside the KEYBOARD_INPUT_DATA. Then when you retrieve the keypress to replay later, you have everything you need to report up the right stack. When the WDFDEVICE is removed (i.e. the stack is being torn down) you can walk through the list of saved KEYBOARD_INPUT_DATAs and remove the entries that are associated with the to be deleted WDFDEVICE).

When the event is stopped, I do not keep a copy of the input data on the driver, I send it to the app and the app sends it back when it needs to be processed (so I do not keep a lot of data in the driver). It’s my bad that I forgot to mention this, sorry. Went out with attaching a variable to each WdfDevice’s context that I auto-increase when a new device gets created, it’s the easiest solution I could though of that would not matter if a device has the same vendor id/guid/any other type of ID (and do not have to deal with strings (eg. a whole HID, which should be unique)).

have you considered the security implications of such a design?

As Mr Bond implies, that means you are trusting the user-mode code. Remember that user-mode code is not secure. It’s trivially easy to hijack a user-mode process. You shouldn’t be injecting user-mode data into a stream where trusted kernel data is expected.

Yes, it does trust the user-mode code, but only with keyboard inputs, the maximum the user-mode code will be able to do is:

  1. Cancel Key press events (which would turn the keyboard useless until you close the app somehow)
  2. Send Key press events (which I agree that it can make things run, but if the app (let’s say not my app, but an evil app that takes advantage of this “security hole”) is already running into the machine and can already send key press events or use the winapi to hook into the keyboard event stack and modify inputs there.
    This is why I do not really see it as a security hole. I do not execute code coming from the user-mode app, I just take a list of keys that it wants pressed and then execute them.
    Edit: For security purposes (let’s say we don’t want someone to use a modified version of the app to send keyboard input on the prompt “Are you sure you want to run this as an administrator?” to open their software as an administrator) I can make the file on which IOCTL are processed accessible to only administrator, so the app would have to already run as an administrator, in which there is no more point to inject key strokes, as you already have full system access.

Can’t it also monitor keyboard input sequences like ctrl + alt + del followed by a string of interesting chars?

@MBond2 said:
Can’t it also monitor keyboard input sequences like ctrl + alt + del followed by a string of interesting chars?

Theoretically it can, but as I said, if the app is already running as an administrator it already has pretty much full access to the computer and there would be no more point in trying to ‘hack’ things using keyboard inputs when it can do it directly. Same story would be with keyboard devices from Logitech/Razer and so on that supports macro keys, as the app(running in user-mode) sends those inputs to the driver. So you can already make a keyboard press ctrl + alt + del followed by a string of interesting chars with more than half of the keyboards on the market.

I’m not thinking about sending them, so much as collecting them. Running as an admin on one machine at some time it one thing, but actually having the password of a user is quite another.

1 Like

> @MBond2 said: > I’m not thinking about sending them, so much as collecting them. Running as an admin on one machine at some time it one thing, but actually having the password of a user is quite another. Ok, I get your point now and I do have some questions about that. If the app run once as an admin, wouldn’t that allow the app to create a service that would run the app as an admin every time without a UAC prompt (I do have an app, not related to this project that does exactly that, the app installer needs to run as an admin so it can create a service that can run the app at startup as admin without a prompt every time you boot the PC)? If the app (which would be an evil app from a third party trying to take advantage of my driver) is already running as an administrator, does it even needs the windows user password anymore? The app has already full access to the system, to some functions through some loopholes, but it has. It can do almost anything to the system. There is already security issues in windows which lets you take advantage of those things, without needing to intercept keyboards keys(see https://tinyapps.org/blog/201304110700_dump_passwords.html ). And even then, one could use raw input or hook the keyboard to make a keylogger. There are a lot of methods to do that. To get a user password without using an exploit, an app can show a fake UAC prompt. I do not really expect someone to use this to compromise a system as there are better alternatives, but I see your point. One way would be to send the driver the settings (which keys should replace which key) and then store that in the driver and do the change there. However that’ll limit what I can do on key press at just replacing it with other keys, but I wanted to implement more than that, and interact directly with some apps when a specific key on a specific device was pressed (eg. start recording on OBS). So there is not really other way I can do this, as to achieve my functionality I need to do it this way, however I’m open to ‘security’ suggestions if there are better alternatives or a way to make sure an evil app couldn’t take advantage of the driver to do things it can’t already.

Sure, this system might already be compromised, but what about the next one. Administrators on AD joined machines are likely to have at least some access to other machines in the AD and likely admin access on at least some of those too. That’s why having the actual password is much more valuable than just having your code run as admin on a single machine.

Let’s leave aside the idea that ‘there are lots of other security holes, so it won’t matter if I make a new one’.

remember the whole point of the ctrl+alt+del or secure action sequence (SAS) is to prevent ordinary programs from being able to use techniques like keyboard hooks or raw input to interfere with entering or log passwords.

@MBond2 said:
Sure, this system might already be compromised, but what about the next one. Administrators on AD joined machines are likely to have at least some access to other machines in the AD and likely admin access on at least some of those too. That’s why having the actual password is much more valuable than just having your code run as admin on a single machine.

Let’s leave aside the idea that ‘there are lots of other security holes, so it won’t matter if I make a new one’.

remember the whole point of the ctrl+alt+del or secure action sequence (SAS) is to prevent ordinary programs from being able to use techniques like keyboard hooks or raw input to interfere with entering or log passwords.

As I said, the only other way to achieve what I want to achieve for my driver is for the app to tell the driver that it should replace key “a” with “key d”, which the app wouldn’t know anymore what key was pressed, it would act just as a “control panel” for the driver. However, that would limit what I can achieve with my driver, as I want to allow it to interact directly with third-party apps (eg. start recording/switch scene on OBS, open new tab in chrome) on a key press, which wouldn’t be possible if the app doesn’t know x key was pressed on that keyboard.
Would it be possible (is there any API) for a driver to know if a user-desktop is opened or a system-desktop (by user-desktop I mean the normal desktop where apps run and by system-desktop I mean the ctrl-alt-delete page, change password page, login page and so on)? If yes, than I could just use that and do not pass data to the APP when a system-desktop is active and the problem should be gone.

I don’t know of any way to know at the keyboard input level where they input is destined. I asked if you have considered the security implications of what you are doing. If you have, and they are acceptable for your use case, then by all means continue. Please take appropriate precautions and if you plan to distribute this product appropriate warning to IT staff

1 Like

@MBond2 said:
I don’t know of any way to know at the keyboard input level where they input is destined. I asked if you have considered the security implications of what you are doing. If you have, and they are acceptable for your use case, then by all means continue. Please take appropriate precautions and if you plan to distribute this product appropriate warning to IT staff

I do plan to include warnings of this stuff and what can be do with it, and yes, I have considered the security implications, however that doesn’t mean I do not want to find every possibility to get rid of them. Well, my question wasn’t really “get the target on which the keys are gonna be sent”, but rather than “what is the currently displayed desktop?” (is it user’s X desktop, or a system desktop). I remember reading some time ago that microsoft uses a totally different desktop (let’s say system’s desktop) to display the login page/UAC prompts. So my question was if there is a way to know if a “system” desktop is currently active/opened, then I could just stop sending data to apps until that is gone (I’m not sure if this is out of my mind or I remember reading it somewhere, but isn’t raw input windows API also processed using IOCTL’s from the kbdclass driver?, so if apps couldn’t process rawinput while in UAC, then it means there should be a way to do this).

I have not worked directly on this in some years, but there are clearly multiple layers here. A keyboard provides input (scancodes) via some interface like PS2, USB or RDP. These are electrical or virtual and anyways enter the stack at the very bottom. win32k.sys implicitly trusts input from these sources, as they are meant to come from hardware or remote psudo hardware; but send input is different. It is known to come from a UM process attempting to control another UM process. I have not looked into the code in a long time, but I believe that is was in the XM / server 2003 days that the security holes were fixed with respect to secure desktop access