· 5 min read ·

What the Packets Tell You: USB Reverse Engineering with Wireshark

Source: lobsters

There is a certain category of hardware that ships with no public documentation, a Windows-only driver signed by a vendor you have never heard of, and absolutely no Linux support. If you want to make that hardware do anything useful outside its intended environment, you reverse engineer it. Using Wireshark to capture and decode USB traffic is one of the cleanest ways to start.

But the capture is the easy part. The harder part is reading what you caught.

The Plumbing: usbmon and USBPcap

On Linux, the kernel exposes USB traffic through the usbmon module. Load it with modprobe usbmon and Wireshark will show you USB bus interfaces alongside your network interfaces, named usbmon0, usbmon1, and so on. usbmon0 captures across all buses; the numbered ones correspond to specific USB host controllers.

modprobe usbmon
wireshark &

On Windows, the equivalent is USBPcap, which installs a kernel filter driver that hooks into the USB stack and feeds packets to the WinPcap/Npcap interface Wireshark already knows how to talk to. It has historically required a reboot and elevated privileges. The Linux path is cleaner for this kind of work.

Once you are capturing, every USB transaction your device performs shows up in Wireshark’s packet list. The columns that matter most immediately are the transfer type, the direction (host-to-device or device-to-host), the endpoint number, and the data payload.

Descriptors First

Before you look at any application-level traffic, read the descriptors. When you plug in a USB device, the host controller enumerates it by issuing a sequence of control transfers to endpoint zero. These transfers read the device descriptor, configuration descriptor, interface descriptors, endpoint descriptors, and for HID devices, the HID report descriptor. All of this happens before the operating system loads any driver.

In Wireshark, filter for usb.transfer_type == 0x02 to isolate control transfers, then look for GET DESCRIPTOR requests with a Descriptor Index of 0. The responses contain the raw descriptor bytes. Wireshark decodes these for you in the packet detail pane, breaking out fields like bDeviceClass, bMaxPacketSize0, idVendor, and idProduct.

The vendor and product IDs (idVendor / idProduct) are your first clue. Cross-reference them against the USB ID repository or run lsusb -v on Linux. Sometimes the vendor string in iManufacturer and iProduct is more informative than the IDs themselves.

For HID devices, the HID report descriptor is the single most valuable artifact you can extract from the capture. It encodes the structure of every report the device sends or receives: field widths, value ranges, usage pages, logical minimums and maximums. Wireshark decodes HID report descriptors if you right-click the relevant response and apply the HID dissector. Alternatively, paste the raw bytes into Frank Zhao’s USB Descriptor Decoder for a human-readable breakdown.

Transfer Types and What They Mean

USB has four transfer types, and knowing which one your device uses for which purpose constrains your interpretation considerably.

Control transfers (endpoint 0) are used for enumeration and device-specific commands. Many simpler devices do everything over control transfers, sending vendor-specific requests with bRequest values that encode commands.

Interrupt transfers are the workhorse of HID. Despite the name, they are polled at a fixed interval by the host, not triggered by interrupts on the device side. A USB mouse sends position and button data via interrupt transfers every 1-8ms depending on its configured polling rate. The payload structure matches the HID report descriptor.

Bulk transfers have no guaranteed timing but are error-corrected and are used for devices where throughput matters more than latency: printers, scanners, USB storage.

Isochronous transfers guarantee bandwidth but not delivery, which is why USB audio and video use them. Dropped packets are better than late packets for streaming media.

When you open a capture and see a device doing almost all its work over interrupt transfers on endpoint 1 IN, you are almost certainly looking at a HID device. If you see bulk transfers on endpoints 1 and 2, you might be looking at a CDC device or something custom. This narrows down what documentation to look for and what class driver to hook into.

Reading the Application Protocol

Once enumeration traffic is out of the way, the substantive communication starts. In Wireshark, filter to a specific endpoint with usb.endpoint_number == 1 and sort by time. Look at the data payloads as hex, and start building a mental map.

Press a button on your device, then find the corresponding packet in the capture. Change a setting, see what control transfer the host sends. The mapping between user actions and packet contents is usually not obfuscated, just undocumented.

Some patterns appear repeatedly:

  • A leading byte that acts as a report ID or command discriminator
  • Big-endian or little-endian 16-bit integers for sensor values
  • Bitmasks for button states
  • Fixed-length padding to fill out a report size

For devices that communicate over vendor-specific control transfers, the bRequest, wValue, wIndex, and data fields together form the command space. Systematically vary them and watch what changes. libusb makes this scriptable:

import usb.core

dev = usb.core.find(idVendor=0x1234, idProduct=0x5678)
dev.set_configuration()

# Send a vendor-specific control request
dev.ctrl_transfer(
    bmRequestType=0x40,  # vendor, host-to-device, device
    bRequest=0x01,
    wValue=0x0000,
    wIndex=0x0000,
    data_or_wLength=b'\x00'
)

Combining pyusb scripting with Wireshark captures of the official driver doing the same operation is how you build confidence that you have the protocol right.

The Limits of Traffic Analysis

Not everything survives capture intact. Isochronous endpoints drop data when the bus is stressed. Bulk transfers that span multiple packets can be hard to reassemble mentally without Wireshark’s stream following. And if the device does any encryption or checksum validation, you see the ciphertext or the validated bytes rather than the plaintext commands.

Some devices also behave differently under usbmon observation than they do in production, though this is rare and usually a driver issue rather than the device detecting the capture.

One class of problem that Wireshark alone cannot solve is when the vendor driver does preprocessing in userspace before sending packets. If you are capturing at the kernel USB layer, you see what goes over the wire, not what the application thinks it sent. For devices that rely heavily on this kind of abstraction, you may need to combine USB capture with binary analysis of the driver or userspace library.

Going Further

Once you understand the protocol well enough, the next step is usually one of:

  • Writing a hidraw or libusb-based userspace driver
  • Submitting a kernel driver if the device is common enough to warrant it
  • Writing a udev rule to set permissions and load the right module automatically

For HID devices specifically, Linux’s hidraw interface lets you read and write raw HID reports from userspace without needing kernel privileges or a custom driver. Combined with the report descriptor you extracted during enumeration, this is often enough to build a complete userspace implementation.

Tools like usbhid-dump can extract HID descriptors and reports without Wireshark, which is useful for quick sanity checks. hidapitester lets you send and receive HID reports interactively. For more complex protocol exploration, USB Proxy can sit between a device and host and mutate traffic in flight, which is useful for testing hypotheses about what specific bytes control.

Reverse engineering USB devices sits at an interesting intersection of protocol knowledge, traffic analysis, and experimentation. Wireshark provides the view into the bus. What you do with that view depends on knowing enough USB to read what is in front of you.

Was this interesting?