USB HID Reverse Engineering for beginners

THIS GUIDE IS WORK IN PROGRESS! SUGGESTIONS AND CONTRIBUTIONS ARE WELCOME!

Introduction

This a newbie friendly guide for reverse engineering USB HID “vendor protocol” for devices such as RGB accessories (gaming peripherals, lights), toys, sound cards and other devices with proprietary control software.

Don’t care about the technical mumbo-jumbo?

Skip to “Capturing USB packet data” section to just capture USB packets for someone else to analyze and reverse engineer.

DISCLAIMER!

This guide skips and simplifies a lot of details. I’m not an expert in USB or USB HID so there are almost certainly some errors or misconseptions in here. I’m not responsible if you damage or brick any devices while using this guide!


USB basics

What is USB HID?

USB HID stands for Universal Serial Bus Human Interface Device. It’s a USB standard meant for communicating with devices such as keyboards, mice, controllers and similar devices involving human interaction.

You can read more about USB HID in detail from the following sources if you feel adventurous:

How do USB Human Interface Devices communicate?

There are several types of communication between a computer and a USB HID device.

Report Descriptors

This the device’s way of telling what it is and how it listens and talks in a very basic way.
The reports describe which interfaces and endpoints the device has.
The reports don’t contain vendor specific proprietary protocols which are usually not publicly documented (unless reverse engineered).

Interfaces and endpoints

USB devices usually have at least one interface and one endpoint. Interfaces are used to separate different functionalities of a USB device. E.g. Interface 0 could be for device configuration and information, Interface 1 could be for Mouse data and Interface 3 could be for Keyboard data.

Endpoints define the communication type such as transfer type and direction. Endpoint 0 is usually used for Control transfers (as mandated by the USB spec).

USB Request Blocks (URBs)

URBs are a way for operating systems to structuring USB packet data. URBs contain information about the direction and type of various transfers and the data (e.g “wValue”, “leftover capture data” or “HID data” depending on the transfer type and direction)

Here are a few examples of transfer types with simple explanations:

  • USB Request Blocks (URBs):
    • Interrupt transfers (URB_INTERRUPT): A common way to send and receive small amounts of data with minimal latency. Usually interrupt transfers are used for button presses, status updates and sometimes even for device settings (like RGB LED values).
    • Control transfers (URB_CONTROL): A common way to send and receive device settings and features with more complex structure. There transfers are non-time-critical and usually (unperceivably) slower than interrupt transfers.
    • Isochronous transfers (URB_ISOCHRONOUS): A way to send and receive time-dependent and steady stream of data such as video or audio. These kind of transfers are rarely used to send or receive device settings.
    • Bulk transfers (URB_BULK): A way to send and receive non-time-critical bursts of data such as images or files. These kind of transfers are probably never used to send or receive device settings.

Capturing USB packet data

There are various tools which can be used for capturing or “sniffing” USB packet data. I recommend using Wireshark with USBPcap/usbmon/libpcap since this guide contains step-by-step instruction based on those.

You will need some software to capture and analyze the USB packet data. Here are my recommendations which will be shown in more detail in this guide.

USB packet monitoring/sniffing

  • Windows: USBPcap (Included in the Wireshark installer!)
  • Linux: usbmon (built-in to most Linux distributions)
  • MacOS: libpcap

Packet capture and analyzer

  • Wireshark: Recommended! Compatible with Windows, Linux and MacOS.

Spreadsheet application (optional)

Hypervisor software

You may need a Windows virtual machine if the manufacturer’s official software is not available for Linux or MacOS. This is usually the case so I’ve gathered some resources below.

You’ll need a copy of Windows 10 or 11 which you can download here for free:

Here are some hypervisor software options for running a Windows VM on Linux or MacOS:

NOTE: Use your favorite web browser and search engine to look up instructions on how to install a Windows VM with your selected hypervisor software.

USB packet capture preparations

Windows host with Wireshark and USBPcap (no virtual machine)

Follow these steps if you are using Wireshark, USBPcap and Windows without a virtual machine.

See step-by-step instructions

Check the USB Vendor ID and Product ID of your device

Open a PowerShell window and run the following command to get the VID and PID values.

PS C:\Users\User> Get-PnpDevice -Class HIDClass -PresentOnly | Select-Object FriendlyName, InstanceID

FriendlyName                          InstanceId
------------                          ----------
HyperX Pulsefire Dart                 HID\VID_0951&PID_16E1&MI_02\8&5C80A98&0&0000
HyperX Pulsefire Dart                 HID\VID_0951&PID_16E1&MI_01&COL02\8&34377B15&0&0001
HyperX Pulsefire Dart                 USB\VID_0951&PID_16E1&MI_02\7&8A381C0&0&0002
HyperX Pulsefire Dart                 USB\VID_0951&PID_16E1&MI_01\7&8A381C0&0&0001
HyperX Pulsefire Dart                 USB\VID_0951&PID_16E1&MI_00\7&8A381C0&0&0000

Look for your device’s name. Let’s use “HyperX Pulsefire Dart” as an example.

HyperX Pulsefire Dart                 USB\VID_0951&PID_16E1&MI_00\7&8A381C0&0&0000

We can see the Vendor ID 0951 and Device ID 16E1 from one of the matching lines.

TIP: If the output only shows “USB Input Device” and “HID-compliant … device” lines then try to search the VID and PID values online to find out if that’s your device. You can use something like “USB 0951:16E1” as the search query.

Starting packet capture with Wireshark

Open Wireshark, double-click the cogwheel next to USBPcap1, uncheck “Capture from all devices connected” and check and click Start.

The USBPcap is listed under Capture

Wireshark USBPcap1 options

A new view will open which shows live capture data from USBPcap1. This will view the packet data of all newly connected USB devices.

Disconnect and reconnect the USB device you wish to capture. You should see new packets appear soon after reconnecting the device.

Capturing from USBPcap1

You may want to apply a filter like usb.device_address == Y (where Y is the second number in the source/destination column “X.Y.Z”) to only capture packets related to a specific device. This is especially useful if you need to connect new USB device after starting the capture but don’t want to capture the packets related to that device.

After confirming that the capture works, stop the current capture by clicking Stop in Wireshark. Discard any unsaved captures if prompted.

You can now proceed to the next section about capturing and saving USB packets for inspection.

Linux host with Wireshark, usbmon and virt-manager

Follow these steps if you are using Wireshark, usbmon and virt-manager with a Windows virtual machine.

See step-by-step instructions

Check the USB Vendor ID and Product ID of your device

Open a terminal window and run lsusb like shown:

$ lsusb | grep -vi 'hub'
Bus 00X Device 00X: ID xxxx:xxxx Manufacturer name Device name
...

Look for your device’s name. Let’s use “HyperX Pulsefire Dart” as as example.

Bus 005 Device 008: ID 0951:16e1 Kingston Technology HyperX Pulsefire Dart

We can see that the device’s Vendor ID is 0951 and Product ID is 16e1 and that it is connected to the Bus 5 and Device Address 8.
NOTE: We will need these numbers later when filtering the USB packet capture with Wireshark and usbmon!

Load usbmon

Most Linux distributions ship with the usbmon kernel mode preinstalled.
You can manually load the module with the modprobe command like shown.

$ sudo modprobe usbmon
[sudo] password for user:

$ ls /dev/usbmon*
/dev/usbmon0  /dev/usbmon1  /dev/usbmon2  /dev/usbmon3  /dev/usbmon4  /dev/usbmon5  /dev/usbmon6

Temporarily allowing non-root access to usbmon device

Allow “others” to read the usbmonX device. Replace X with the Bus number you got previously from lsusb.

SECURITY WARNING! This command will expose the whole Bus to ALL USERS AND PROCESSES on the machine until you reboot. This could have serious security implications.

$ sudo chmod o+r /dev/usbmonX

Starting packet capture with Wireshark

Open Wireshark and choose the same usbmon device by double-clicking.

The usbmon devices are listed under Capture

A new view will open which shows live capture data from usbmonX. This will view the packet data of all devices connected to the same Bus.

You can filter the capture to only show packets related to the device of your choice by applying a filter like usb.device_address == X where X is the Device number from lsusb.

Wireshark capture with a device address filter

Next test redirecting the USB device to the Windows virtual machine while the Wireshark capture is running.

Redirecting the USB device to the virtual machine

Open Virt-Manager and start up the Windows virtual machine and redirect the USB to the virtual machine like shown.

WARNING! Make sure you have another mouse/keyboard connected if you’re redirecting a mouse/keyboard device as you won’t be able to control the host OS and Wireshark using it.

Virt-Manager -> Virtual Machine -> Redirect USB device Select USB devices for redirection

You should see the device appear in Windows after selecting it from the list and clicking Close.

Check Wireshark to ensure the packet capture is still working

You should see new packets in the packets list after connecting the USB device to the virtual machine.

There should also be some “GET DESCRIPTOR” packets from the moment when the USB device was transferred from Linux to Windows. You may have to scroll up to find the “GET DESCRIPTOR” packets if the list is constantly being filled new new packets from/to the device.

After confirming that the capture still works while connected to Windows, stop the current capture by clicking Stop in Wireshark. Discard any unsaved captures if prompted.

You can now proceed to the next section about capturing and saving USB packets for inspection.

MacOS host with Wireshark, libpcap and UTM

Follow these steps if you are using Wireshark, libpcap and UTM with a Windows virtual machine.

See step-by-step instructions

This section needs your help! I don’t have the required hardware to create this section.

See the following sources for help:

Other hypervisors and packet capture software

You’re on your own. Read the manuals and use your favorite search engine to look up instructions on how to do “USB device passthrough” or “USB redirection”.

Capturing and saving USB packets for inspection

This is the second most time consuming step of reverse engineering a USB HID device’s protocol. There are multiple ways to gather packets for inspection and comparison but the easiest one is to just change one setting per capture.

This is what I usually do when working with a new device:

  1. Close any manufacturer’s official software, if opened.
  2. Start Wireshark USB capture
  3. Open manufacturer’s official software
    NOTE: Some software require’s you to click the device from a list before it starts being controlled thus producing packets.
  4. Wait 10s to ensure the manufacturer’s official software has sent and received all initialization packets.
  5. Change a single setting like RGB LED color, on/off toggles or sliders.
    NOTE: Prefer using nice and easily recognizable values like “0” and “100” or with RGB colors use “#AABBCC” or if manual values aren’t possible then start with the brightest red ("#FF0000") and brightest blue ("#0000FF").
  6. Close the manufacturer’s official software
    NOTE: Sometimes you have to close the software from the task bar or task manager to completely close it.
  7. Stop the Wireshark capture
  8. Save the showed packets to a file: File -> Export specified packets
    NOTE: Make sure the “Packet Range” settings are at “Displayed” and “All packets” to not save packets from any other device.
    Also use a clear name so you can remember and understand which capture contains what when you later inspect them.
    E.g. “mouse_all_leds_static_100_bright_red.pcapng”
  9. Go to Step 1 and change the same setting as during the previous capture except use a different value.
  10. Optional: You can also do multiple changes during a single capture if you keep track of the timing and the values.
    E.g. Set value to 1, wait 10s, set value to 2. The same 10s delay should be visible in the capture thus making it easier to differentiate between actions.

Experiment with multiple setting and value combinations. The more combinations you do, the more easily you can understand which packet and byte does what.
Sometimes it’s also a good idea to do two different captures with the exact same actions so you can double-check that a specific packet is what you’re really after.

Move to the next section when you think you’ve gathered enough captures. 2-3 captures should be enough to start reverse engineering the vendor protocol.


Inspecting the USB capture data

For this part you’ll have to understand how bytes work and how USB device vendor protocols usually work.

A single byte holds 8 bits which can be used to represent a number between 0 and 255.
This means that in some cases a single byte in the data could mean up to 255 different things (like RGB values for each primary color).
E.g. The RGB color magenta (#FF00FF) is a combination of red (#FF0000) and blue (#0000FF) which is (255, 0, 255) in decimal or 11111111 00000000 11111111 in binary (bits).

The Wireshark capture represents each byte as a hexadecimal (hexadecimal 00-FF is same as Decimal 0-255). You might also see hexadecimal values with 0x prefix like 0xFF outside of Wireshark.

Pick two packet capture (pcapng) files to compare. Start with packet captures which involve changing the same setting with two or move values.
E.g. RGB LED colors, slider from 0 to 100 or an on/off toggle.

Open one PCAPNG file at a time into Wireshark and begin by filtering out all packets received from the device. We initially only want to know how to talk to the device.

Read this page to understand how Wireshark display filters work: https://www.wireshark.org/docs/wsug_html_chunked/ChWorkBuildDisplayFilterSection.html

You can apply the filter (usb.src == host) to only show packets sent to the device or (usb.dst == host) to only show packets received from the device.

Next you should look for URB_CONTROL and URB_INTERRUPT packets. These are usually used for setting changes when sent to or received from the device.

You can apply a filter for the usb.transfer_type field to only show specific transfer types.

  • (usb.transfer_type == 0)= Only show URB_ISOCHRONOUS packets (Isochronous transfers, you can probably ignore these)
  • (usb.transfer_type == 1) = Only show URB_INTERRUPT packets (Interrupt transfers)
  • (usb.transfer_type == 2) = Only show URB_CONTROL packets (Control transfers)
  • (usb.transfer_type == 4) = Only show URB_BULK packets (Bulk transfers, you can probably ignore these)

Example filters:

  • Only show packets sent to the device: (usb.src == host)
  • Only show packets received from the device: (usb.dst == host)
  • Only show interrupt transfers sent to the device: (usb.src == host) && (usb.transfer_type == 1)
  • Only show control transfers sent to the device: (usb.src == host) && (usb.transfer_type == 2)
  • Only show interrupt transfers received from the device: (usb.dst == host) && (usb.transfer_type == 1)
  • Only show control transfers received from the device: (usb.dst == host) && (usb.transfer_type == 2)

Inspecting URB_CONTROL and URB_INTERRUPT packets

URB_CONTROL and URB_INTERRUPT packets usually contain one of the following pieces of data:

  • Leftover capture data (usb.capdata)
  • HID data (usbhid.data)
  • Data fragment (usb.data_fragment)

Use the filter (usb.capdata || usbhid.data || usb.data_fragment) to only show packets which probably contain settings data.

You can also filter by contents if you think the data you’re looking for is in a predictable format (e.g. RGB color AABBCC).
Example filter for RGB value AABBCC: (usb.capdata contains aa:bb:cc) || (usbhid.data contains aa:bb:cc) || (usb.data_fragment contains aa:bb:cc)

You can also apply any of the fields as a column so you can more easily skim through each packet and possibly identify expected values by eye.

Open Wireshark column preferences: Edit -> Preferences -> Appearance -> Columns -> (+) Add a new column (click the plus button as many times as you need new columns) Then double click the “New column” field to rename the new column and then double click the empty “Fields” field to select which field the column displays. Enter one of the above mentioned filter/field names (e.g. usb.capdata) and click Apply.

Wireshark column preferences

Wireshark capture view with data columns added

TIP: You can change the appearance of each field or even (temporarily) hide unneeded columns quickly by right clicking a field on the header line. You can re-check fields to show them again without having to go to the preferences.

Wireshark capture view column selections

Visual comparison of multiple packets and captures

When you’ve identified interesting packets possibly containing settings data, copy the “Data Fragment”, “Leftover capture data” or “HID data” values as Hex dumps and paste them into a spreadsheet or some application where you can easily compare each hex dump side by side.

IMPORTANT: Also write down the bmRequestType, bRequest, wIndex and wValue values if the packet’s transfer type is URB_CONTROL! You will need those later!

Wireshark copy field value as Hex dump

The clipboard should now contain a piece of text similar to this (the amount of lines and bytes vary):

0000   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0010   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0020   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0030   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

Paste the text into a spreadsheet application like Libreoffice Calc, Google Sheets or Microsoft Excel, format all cells of the sheet to be “Plain text” (so 00’s don’t become just 0’s) and use the “Data - split text to columns” feature to format each byte into a separate cell.

NOTE: Use CTRL+SHIFT+V to paste as plain text or without formatting!

Step-by-step instructions for common spreadsheet applications

See step-by-step instructions with LibreOffice Calc

LibreOffice split text to columns

See step-by-step instructions with Google Sheets

Press Ctrl+A to select all columns and rows and then format all cells as plain text.

Google Sheets format cells as plain text

Then split the hex dump bytes into columns by spaces.

Google Sheets split text to columns

The end result should look like this.

Google Sheets split text to columns end result

See step-by-step instructions with Microsoft Excel Online

Press Ctrl+A to select all columns and rows and then format all cells as plain text.

Microsoft Excel Online format cells as plain text

Then split the hex dump bytes into columns by spaces.

Microsoft Excel Online split text to columns

The end result should look like this.

Microsoft Excel Online split text to columns end result

Compare each capture and hex dump

Then start comparing each Hex dump byte-by-byte. Look for similar packets between two captures and highlight their differences.

You can also try to match two Hex dump from two captures assuming that the order of packets is the same between multiple captures. Not all software send packets in a predictable order!

It also helps to start highlighting each different first changing byte with a different color in the beginning. So first changing byte “0x0a” is green, “0x1c” is blue and so on..

Here are some real-world examples for you to take example from.

A USB gaming mouse with two RGB LEDs. Only the LED color was changed between the two captures and RGB values were easily recognizable from the data. We can also guess that the last byte is the brightness value as 0x64 hex is 100 in decimal. Spreadsheet highlight example 1

A USB soundcard with two inputs has a 3-step (off/75Hz/150Hz) switch for toggling the high-pass filter setting per input. The switch was toggled to 75Hz, then 150Hz and off in one capture. This is a good example where you don’t necessarily have to restart capture after a single action. Spreadsheet highlight example 2

Need help?

Got a tricky device with difficult to understand protocol? Send me some packet captures and I’ll see if I can help you. Maybe I could even improve this guide from new knowledge.
You can find my contact details on the About page on my website.

Sending our own packets to the device

You think it’s time to start talking to the device on your own without the manufacturer’s official software? That’s where libusb comes into play!

I recommend using this Python boilerplate script for your initial testing. You’ll need to adjust the script to match your device’s details and specific protocol.

Requirements (Windows/Linux/MacOS):

Boilerplate script for sending settings: pyusb_write_example.py

Change all details where “FIXME” is mentioned and fill the URB_CONTROL values if the device is controlled with Control transfer instead of Interrupt transfers.
And be very careful with the payload / data fragment values or you could potentially brick the device for ever.
The hex dump / spreadsheet bytes need to be formatted as 0x prefixed hexbytes for Python.
E.g. 0a 00 01 02 03 -> [0x0a, 0x00, 0x01, 0x02, 0x03]

NOTE: Running the script requires administrator/root access!

Reading settings and/or responses from the device

There are many ways devices respond to the host or periodically send status updates to the host.

Common ways to read from USB device are

  • Sending an Interrupt transfer and immediately receiving an interrupt transfer back
  • Sending a SET_REPORT request and reading a received GET_REPORT response
  • Sending a GET_REPORT request and reading a received Interrupt transfer immediately after
  • Randomly receiving an interrupt transfer from the device (a heartbeat or status update)

Usually the responses as are long as the requests (e.g. send 64 bytes, receive 64 bytes) and many devices repeat back exactly what was send on success. Some devices may repeat back what was sent but replace the first byte with something else (to perhaps indicate success or failure).


Sources and credits

License

CC0

The contents of this repository are licensed with Creative Commons — CC0 1.0 Universal license unless explicitly stated otherwise. Zero rights reserved.