Capture a USB HID Descriptor Safely with usbhid-dump

A keyboard, mouse or game controller sends bytes you cannot see, and usbhid-dump pulls the raw HID report descriptor so you can actually read them. Allow about ten minutes for a first capture. The examples use the usbutils package installed here, version 017, with usbhid-dump reporting version 1.4.

Checkpoint: This guide captures a descriptor by default. It does not start a live input stream unless you explicitly add -e stream or -es.

1. Check the installed command

Run the version and help commands as your ordinary user. Neither detaches a kernel driver or reads from a device:

$ usbhid-dump --version
usbhid-dump 1.4
$ usbhid-dump --help
Usage: usbhid-dump [OPTION]...

The exact copyright and help text can vary by build. The checks that matter are the version number and the documented defaults: entity descriptor and a 60,000 millisecond stream timeout.

On this installed build, the package is usbutils 1:017-3build1. Do not silently apply instructions written for a different release if an option or output detail matters to your investigation.

2. Find the device and its bus address

Use lsusb to list devices before selecting one:

$ lsusb
Bus 002 Device 003: ID 054c:0ce6 Example USB device

Write down the bus and device numbers from the line you intend to inspect. The example values above are placeholders, so replace 002:003 with values from your own output. If the device exposes several HID interfaces, you can narrow the capture further in the next step.

Bus and device addresses change after unplugging, replugging or rebooting. Treat an old address as stale evidence and run lsusb again rather than guessing.

3. Capture one report descriptor

Use -a bus:device to select the device and -e descriptor to make the requested entity explicit:

$ sudo usbhid-dump --address=002:003 --entity=descriptor
002:003:000:DESCRIPTOR         1730000000.123456
 05 01 09 02 A1 01 09 01 A1 00 ...

Root access is commonly required because the program uses libusb to claim the interface. Omit sudo if your device permissions already grant access. The output is one chunk: a header line, followed by hexadecimal bytes. The interface number in the header is 000 in this illustrative output, not a guarantee about your device.

For a real capture, the descriptor bytes should continue until the whole report descriptor has printed. An empty result usually means the selection matched no HID interface, the device disappeared, or access was denied. Check the command's exit status and repeat lsusb before changing filters.

Checkpoint: You now have a descriptor without opening a live stream. Save a copy only after checking the output really is from the intended bus and device:

$ sudo usbhid-dump -a 002:003 -ed > hid-descriptor.txt
$ sed -n '1,4p' hid-descriptor.txt
002:003:000:DESCRIPTOR         1730000000.123456
 05 01 09 02 A1 01 ...

Shell redirection creates or truncates hid-descriptor.txt. Use a new filename if an earlier capture matters. The command does not touch the device's firmware or persistent configuration, but it does temporarily claim the selected interface while it runs.

4. Narrow the selection when several interfaces match

Use --model=vid:pid when you know the vendor and product IDs from lsusb. Both values are hexadecimal:

$ sudo usbhid-dump --model=054c:0ce6 --entity=descriptor

You can also select an interface number with --interface=NUMBER:

$ sudo usbhid-dump -m 054c:0ce6 -i 1 -ed

5. Inspect a stream only with a safe boundary

A stream capture is different from a descriptor capture. --entity=stream keeps running until you press Ctrl-C or the timeout expires, and it detaches the kernel driver from the selected interface while active. The default timeout is 60 seconds:

$ sudo usbhid-dump -a 002:003 -es -t 5000
002:003:000:STREAM             1730000000.234567
 00 00 00 00 00 00 00 00

The timeout is in milliseconds, so 5000 means five seconds. A zero timeout means infinity. Prefer a short timeout while learning the device, and stop it with Ctrl-C once you have enough data.

Warning: Never start an unrestricted stream dump while the selected device might be the keyboard controlling your terminal. The program can detach that keyboard, leaving you unable to type Ctrl-C. The installed manual's recovery is to stop input and wait for the timeout; the keyboard should then be reattached and control returned. If you set -t 0, there is no automatic timeout at all, so never use that value for a first test from a keyboard-controlled session.

For a visible sign that transfers are arriving, add --stream-feedback: it prints a dot to standard error for each transfer dumped. To start with output paused, use --stream-paused; USR1 and USR2 pause and resume stream output.

6. Read the output and recover from errors

Each output chunk starts with BUS:DEVICE:INTERFACE:ENTITY TIMESTAMP. The timestamp is seconds since the Unix epoch. DESCRIPTOR identifies the complete report descriptor. STREAM identifies an input report, though a report larger than the endpoint packet size can span multiple chunks.

If access fails, retry without changing the device selection first and read the diagnostic carefully. Then check whether your account has permission to access the USB device. Use sudo only for this diagnostic command, not for anything else done with the saved text. If no interface matches, confirm the hexadecimal spelling of the ID and the decimal spelling of the interface number.

If a stream command stops after the timeout, that is expected. If it stops after Ctrl-C, the interface should be released and the kernel driver reattached. Check that the keyboard, mouse or other input device works before starting another capture. If it does not, unplugging and reconnecting the device is a practical recovery step, but it can change its bus address and interrupt any application using it.

Done means