Introduction

Hot-pluggable device is now a big threat to IT security. In this article, we will try to develop a user-mode application to detect device change on the system, i.e. plug-in a USB drive, iPod, USB wireless network card, etc. The program can also disable any newly plugged devices. We will get a basic idea on how this works and talk about its limitations at the end of this article.

How to detect hardware change?

Well, in fact, Windows OS will post WM_DEVICECHANGE upon device change. All we need to do is to add a handler to handle this event.

Well, this is not bad, since at least you will know when an extra "disk" is mounted/unmounted, and you can get the affected drive letter by using the DEV_BROADCAST_VOLUME.dbcv_unitmask. The downside is that you won't know what physical device has actually been plugged into the system.

API: RegisterDeviceNotification()

To get notified on other types of device changes, or to get notified if you are running as a service and don't have a top-level window, you have to call RegisterDeviceNotification() API. For example, to get notified upon interface change, you can do the following.

Pay extra attention to line 8, the NotificationFilter.dbcc_classguid. See Doron Holan's blog

A PnP device is typically associated with two different GUIDs, a device interface GUID and a device class GUID.

A device class GUID defines a broad category of devices. If you open up device manager, the default view is "by type." Each type is a device class, where each class is uniquely ID's by the device class GUID. A device class GUID defines the icon for the class, default security settings, install properties (like a user cannot manually install an instance of this class, it must be enumerated by PNP), and other settings. The device class GUID does not define an I/O interface (see Glossary), rather think of it as a grouping of devices. I think a good clarifying example is the Ports class. Both COM and LPT devices are a part of the Ports class, yet each has its own distinct I/O interface which are not compatible with each other. A device can only belong to one device class. The device class GUID is the GUID you see at the top of an INF file.

A device interface GUID defines a particular I/O interface contract. It is expected that every instanceof the interface GUID will support the same basic set of I/Os. The device interface GUID is what the driver will register and enable/disable based on PnP state. A device can register many device interfaces for itself, it is not limited to one interface GUID. If need be, the device can even register multiple instances of the same GUID (assuming each have their own ReferenceString), although I have never seen a real world need for this. A simple I/O interface contract is the keyboard device interface to the raw input thread. Here is the keyboard device contract that each instance of the keyboard device interface GUID must support.

You can see the current list of device classes and device interface classes at the following registries:

It seems by using the dbcc_name, we can know what device has been plugged into the system. Sadly, the answer is NO, dbcc_name is for OS internal use and is an identity, it is not human readable. A sample of dbcc_name is as follows:

Now, by using this decoded information, we can get the device description or device friendly name by two methods:

Read the registry directly: for our example dbcc_name, it will be, \\HKLM\SYSTEM\CurrentControlSet\Enum\USB\Vid_04e8&Pid_503b\0002F9A9828E0F06

Use SetupDiXxx

API: SetupDiXxx()

Windows has a set of API to allow an application to retrieve hardware device information programmatically. For example, we can get the device description or device friendly name with the dbcc_name. The flow of the program is roughly as follows:

Use SetupDiGetClassDevs() to get a handle of device info set HDEVINFO, you can think of the handle as a directory handle.

Use SetupDiEnumDeviceInfo() to enumerate all the device in the info set, you can think of this operation as a directory listing. Upon each iteration, we will get a SP_DEVINFO_DATA, you can think of this handle as the file handle.

During the enumeration, use SetupDiGetDeviceInstanceId() to read the instance ID for each device, you can think of this operation as reading file attribute. The instance ID is in the form of "USB\Vid_04e8&Pid_503b\0002F9A9828E0F06", very similar to the dbcc_name.

If the instance ID match the dbcc_name, we call SetupDiGetDeviceRegistryProperty() to retrieve the description or friendly name

Disable a device

Suppose you have the correct HDEVINFO and SP_DEVINFO_DATA (actually, we save the dbcc_name as the tree node extra data and retrieve that data when we right click on the device icon and then call SetupDiGetClassDevs and SetupDiEnumDevicInfo), the flow to disable a device is as follows:

In fact, the DIF codes are a bit complicated, and you will have to call SetupDiSetClassInstallParams() with different structures for different DIF codes. For more information, see MSDN "Handling DIF Codes".

Minor Issue

I experience multiple DBT_DEVICEARRIVAL/DBT_DEVICEREMOVECOMPLETE messages on the same insertion/removal of my USB wireless card.

Limitations

Obviously, the program can only detect device changes when the application is running, e.g. device plugged in before the system power-up, or before the application starts up will not be detected. But this can be solved by saving the current state at a remote computer and then checking the difference during application starts up.

We can disable the device, but that is all we can do. We can't access control the device against the logon user, nor we can provide read only access. IMHO, I think this can only be fixed if we re-implement the whole program as a kernel mode filter driver.

History

June 19th, 2006: Initial release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.