USB Sniffing and Programming
  • Posted on April 5, 2018

USB Sniffing and Programming

This post is going to cover how to take a USB device and write software that can interact with the device without having publicly available documentation. I came about writing this post when I found an old AverMedia RECental USB button in a box of electronics. I wanted to repurpose the button and this is the result of that process. The device used while writing this tutorial was a very simple in that it only sent two different interrupts (pressed / released) along with a method to create LED animations. I only worked with interrupts and control transfers; I didn’t look into bulk transfers.

Sniffing USB Traffic

The first task is to figure out what the USB device is actually outputting along with what is being sent to the USB device. Before sniffing USB traffic, you should install all drivers and software that the manufacturer of the USB device suggests even if it’s optional. These additional programs will help you see the full picture of how the device functions and what data is sent to the USB device. Without the additional software you may not be able to fully see how the device functions. In my case, the software sent data to the USB device to manage and animate the LEDs on the device.

It’s fairly simple to sniff USB traffic using Wireshark with USBmon or USBPcap on Linux or Windows. During the Wireshark installation you will be asked if you want to install USB monitoring / capturing. Additional details can be found on the Wireshark website about USB Capturing. After starting Wireshark, you want to start capturing packets for the specific device.

If you see multiple USB capture devices, you need to determine which hub your USB device is on. For Linux and usbmon run the command lsusb and find the Bus number for the device. For Bus 003 you will capture data from usbmon3. As for using Windows and USBPcap, you can identify the USBPcap interface by running the command USBPcapCMD.exe (located in “C:\Program Files\USBPcap”) without any arguments. The previous command will display a new window with the USBPcap interface along with the USB Hub and devices that are attached to it (the first column is not the USBPcap number).

USBPcap List Devices

If you’re new to using Wireshark, there is an abundance of resources online to get you started, but the application is fairly straight forward for basic use. For a quick overview, you can read the post Wireshark Interface Overview.

At this point, you simply want to start capturing USB traffic through Wireshark. Depending on the device, the best method of capturing data will vary. Generally you want to try to limit capturing to one specific event to make it easier to decode. Taking a joystick as an example, you would press a single button multiple times, add a packet comment, and then press a different button multiple times, write a comment and repeat. The reason for producing the same event multiple times is to ensure the data is the same for the same event and no sequential or timing information is passed.

Overview of the USB Specification

The USB specification for transferring information between the USB device and computer isn’t too complex if you’re just trying to figure out how a USB device works and to hack together a program. The specification has four different types of transfers (Control, Interrupt, Isochronous, and Bulk). Along with the transfer types, there are a few different types of descriptors that identify the device and the available communication methods. There are two websites I highly recommend for additional USB information, Beyond Logic and Jungo.

Control Transfers

Control transfers are generally used to get the USB device into a state that it can be functional. The majority of configuration happens through Control Transfers but other information can also be passed through this transfer. Control Transfers can travel in both directions and successful requests will always have at least two packets of data. Control transfers will have at least three packets for requests that send a payload.

Interrupt Transfers

Interrupt transfers are packets sent without a known interval or timing (unlike Isochronous). The USB device will queue interrupt requests until the host requests the information from the device. Interrupt transfers are used when a mouse moves, a button is pressed, etc.

Isochronous Transfers

Isochronous transfers are time dependent transfers and only work on full-speed and high-speed devices. Since keeping sync with time is more important, isochronous transfers will not try to reattempt failed transfers. Isochronous transfers are seen when using a USB audio interface, USB phone, etc.

Bulk Transfers

Bulk transfers are for large payloads that are not time sensitive. Bulk transfers are seen when sending or receiving data to a printer or a scanner. These transfers have error correction and detection ensuring that the transfer is transmitted without error.

Descriptors and Endpoints

USB devices have many different descriptors that identify the USB device itself and the available communication methods and endpoints. There are four descriptors to focus on, Configuration, Interface, Alternative Settings, and Endpoints. There can be multiple configuration descriptors, but there has to be at least one and the majority of USB devices only have one. The configuration descriptor allows devices to vary based on the environment they are in (such as how they are powered). The configuration descriptor then identifies the interfaces that are available. The interface descriptor groups together endpoints that are to be used. Interfaces can have alternative settings that affects what endpoints will actually be used. Endpoints define the communication method between the host and the device, the direction of communication, max packet size, and additional transfer information.

Programming with LibUSB

LibUSB is a cross platform USB library. To get started, you want to download libusb apt install libusb-1.0-0-dev which is different than libusb-dev. Below are some snippets of code that will hopefully help you get started with programming an application that interacts with your USB device.

The first program is the basic setup of the libusb library. The code has two #define statements that identify the USB vendor and product ID we are searching for along with a function that will find the device. This could easily be modified to by dynamic values or determined by iterating through all devices. You can find the vendor and product ID by running lsusb on Linux or inside of the device manager in Windows. The IDs are generally displayed in hexadecimal format. Here’s a list of all USB vendor ids.

#include <stdio.h>
#include <stdlib.h>
#include <libusb-1.0/libusb.h>

#define VENDOR 0x0000
#define PRODUCT 0x0000

int find_device(int vendor, int product, libusb_device **output, libusb_device_handle **handle) {
	libusb_device **list;
	libusb_device *device = NULL;
	struct libusb_device_descriptor desc;

	int status = 0, res = 0;
	ssize_t i = 0;
	ssize_t cnt = libusb_get_device_list(NULL, &list);

	*output = NULL;
	*handle = NULL;

	if(cnt < 0) {
		fprintf(stderr, "No USB devices discovered.\n");
		status = 1;
		goto cleanup;
	}

	for(i = 0; i < cnt; ++i) {
		device = list[i];
		res = libusb_get_device_descriptor(device, &desc);

		if(res != 0) {
			fprintf(stderr, "[%d] Error getting device descriptor.\n", res);
			continue;
		}

		if(desc.idVendor == vendor && desc.idProduct == product) {
			*output = device;
			break;
		}
	}

	if(*output != NULL) {
		res = libusb_open(device, handle);
		if(res == LIBUSB_SUCCESS) {
			status = 0;
		}else{
			status = 2;
			fprintf(stderr, "[%d] Failed opening USB device.\n", res);
		}
	}else{
		status = 3;
	}

	cleanup:
	libusb_free_device_list(list, 1);
	return status;
}


int main(int argc, char *argv[]) {
	libusb_device *device = NULL;
	libusb_device_handle *handle = NULL;
	int status = 0;

	libusb_init(NULL);

	status = find_device(VENDOR, PRODUCT, &device, &handle);
	if(status != 0) {
		fprintf(stderr, "[%d] Failed to find device.\n", status);
		goto cleanup;
	}

	//
	// Add USB Logic Here
	//

	libusb_close(handle);

	cleanup:
	libusb_exit(NULL);
	return status;
}

This function iterates through all the descriptors to find interrupt endpoints to use. Wireshark should display the endpoint (an integer) in the packets you previously captured, but for endpoints that haven’t been used or to programmatically find endpoints, use the function below.

void get_endpoints(libusb_device *device, uint8_t *endpoint_in, uint8_t *endpoint_out) {
	int i = 0, j = 0, k = 0;

	struct libusb_device_descriptor desc = NULL;
	struct libusb_config_descriptor *config = NULL;
	const struct libusb_endpoint_descriptor *endpoint = NULL;

	libusb_get_device_descriptor(device, &desc);
	libusb_get_config_descriptor(device, 0, &config);

	for(i = 0; i < config->bNumInterfaces; ++i) {
		for(j = 0; j < config->interface[i].num_altsetting; ++j) {
			for(k = 0; k < config->interface[i].altsetting[j].bNumEndpoints; ++k) {
				endpoint = &config->interface[i].altsetting[j].endpoint[k];

				if ((endpoint->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) & LIBUSB_TRANSFER_TYPE_INTERRUPT) {
					if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) {
						*endpoint_in = endpoint->bEndpointAddress;
					} else {
						*endpoint_out = endpoint->bEndpointAddress;
					}
				}
			}
		}
	}

	libusb_free_config_descriptor(config);
	return;
}

Before you can use a device you must claim the interfaces you plan on using. Below are two functions to claim and release all interfaces for a device handle. The functions will return 0 on success or will return a LIBUSB_ERROR value. Before claiming or releasing interfaces, you might have to detach the kernel driver if you’re using Linux.

int claim_interfaces(libusb_device *device, libusb_device_handle *handle) {
	int i = 0, res = 0;
	struct libusb_config_descriptor *config = NULL;

	res = libusb_get_config_descriptor(device, 0, &config);
	if(res != 0) {
		goto cleanup;
	}

	for(i = 0; i < config->bNumInterfaces; ++i) {
		res = libusb_claim_interface(handle, i);
		if(res != LIBUSB_SUCCESS) {
			for(i; i >= 0; --i) {
				libusb_release_interface(handle, i);
			}

			fprintf(err, "[%d] Failed claiming interface.", res);
			break;
		}
	}

	cleanup:
	libusb_free_config_descriptor(config);
	return res;
}

int release_interfaces(libusb_device *device, libusb_device_handle *handle) {
	int i = 0, res = 0;
	struct libusb_config_descriptor *config = NULL;

	res = libusb_get_config_descriptor(device, 0, &config);
	if(res != 0) {
		goto cleanup;
	}

	for(i = 0; i < config->bNumInterfaces; ++i) {
		libusb_release_interface(handle, i);
	}

	cleanup:
	libusb_free_config_descriptor(config);
	return res;
}

Additional Information

If you followed the tutorial above with Linux, you may notice that you need root access to run your program. This is because you don’t have a udev with proper permissions created. I haven’t looked enough into udev to write a section about it yet, but the information on Oracle should get you started.

When compiling your program that uses libusb add the argument -lusb-1.0 to gcc. The libusb-1.0-0-dev is different than the libusb package available. The header you want to use is libusb-1.0/libusb.h.