/ #c #linux 

Missile Launcher Code: A Libusb Userspace Linux Driver


Back in my undergrad career we had to take operating systems classes, which were arguably some of my favorite… we got to play with Linux and C. Aside from breaking the kernel multiple times, one of the projects involved writing a driver for some 3rd party device: the Dream Cheeky USB Missile Launcher. It comes with a CD that includes a driver and software to control the launcher, so to speak. Looking at the reviews it’s clear that the software is buggy and the driver does not even work most of the time; more importantly, it is only supported on Windows! Nerf wars are a de facto part of IT and software engineer workplaces, so having a working launcher is pretty critical. I’m going to explain the driver I wrote to control this in Linux, hopefully in enough detail that you can leave and write your own USB drivers.

The code I’m presenting is written to be a userspace driver, not a kernel module. I am also using the libusb-1.0 API, which is a major rework of the previous 0.9 version, so make sure you have the correct version. You can find the full source code file in the link on the top right of the code-boxes posted below, so I’m going to focus on specific sections here.

Getting information about our USB device

The first part to consider is how we connect to a device. This is done by getting a handle to the device through its vendor and product ID, respectively VID:PID. Typing lsusb in terminal shows us connected devices, and if it’s not totally foreign it will show the name of the device or manufacturer as well. Using this we get the proper codes of our missile launcher:

VID:PID Codes

#define IN 0x81
#define OUT  0x02
#define VENDOR  0x1941 /* Use to open the device handle */
#define PRODUCT 0x8021

The vendor and product codes are found when running lsusb, and are the two numbers presented before the name of the device (####:####). The first two defines, IN and OUT, define the endpoint address for the USB device. This is the address that is used to send data (IN) to the device, and to read (OUT) data from it. To get these, we need to look at both the IN and OUT bEndpointAddress by typing sudo lsusb -v, which shows verbose USB device information. You – may – need sudo for this unless you have proper rules set in your /etc/udev/rules.d/permissions file; no, you won’t break anything by running it as root. It will print out a LOT of output depending on how many devices are connected, so lets trim that down a bit.

To get info about our device, run sudo lsusb -v | grep -A 50 “Dream”, assuming you don’t have any other “dream” (dream cheeky) devices connected, to find the device in lsusb’s output and display the next 50 lines. Scrolling through we see something like the following:

Endpoint Descriptor:
  bLength                 7
  bDescriptorType         5
  bEndpointAddress     0x81  EP 1 IN
  bmAttributes            2
    Transfer Type            Bulk
    Synch Type               None
    Usage Type               Data
  wMaxPacketSize     0x0200  1x 512 bytes
  bInterval               0
Endpoint Descriptor:
  bLength                 7
  bDescriptorType         5
  bEndpointAddress     0x02  EP 2 OUT
  bmAttributes            2
    Transfer Type            Bulk
    Synch Type               None
    Usage Type               Data
  wMaxPacketSize     0x0200  1x 512 bytes
  bInterval               0
which makes it very clear what our IN and OUT defines come from.

The next part shows what codes to send the device. These can be found if you try to run the software and sniff the packets using something like Wireshark, and seeing what codes sent to the device from the windows driver, or you can just try looking them up! To save you the hassle, please refer below:

Missile Launcher Action Codes
static uint8_t UP[] = {0x01, 00, 00, 00, 00, 00, 00, 00};
static uint8_t DOWN[] = {0x02, 00, 00, 00, 00, 00, 00, 00};
static uint8_t LEFT[] = {0x04, 00, 00, 00, 00, 00, 00, 00};
static uint8_t RIGHT[] = {0x08, 00, 00, 00, 00, 00, 00, 00};
static uint8_t STOP[] = {00, 00, 00, 00, 00, 00, 00, 00};

Now for the juicy parts.

Initializing libusb
	libusb_device_handle *h_dev;
	libusb_context *session;
	libusb_device **list = 0;
	unsigned char c;

	if (getuid() != 0) {
		fprintf(stderr, "! Program can only be run as root.\n");
		exit(EXIT_FAILURE);
	}

	libusb_init(&session);
	libusb_set_debug(session, 3); /* Set debug to the highest level */

Three things we need, as shown in the code block: a handle to the device, a USB session, and a list to store the USB devices. The char c is for sending commands to the device which is in the full source.

  • libusb_device_handle: This is a pointer to the device, it is how we communicate with the launcher and pass it to our other functions.
  • libusb_context: Our session for the runtime of the program allowing us to use libusb dynamically while another module may be using it as well.
  • libusb_device: This is a structure to represent a USB device connected to our system. We show it as a **list, because we need a list of pointers to multiple devices.

While this is a userspace driver and does not need to be loaded by the kernel, we still need a root user to run it and have full access to the device. By using getuid() we can tell if we are in root (UID of zero), and quit if not.
To actually be able to do ~anything with our program, we must initialize libusb, and although optional, set the debug level for it with libusb_init(), and libusb_set_debug().

I initialized my libusb session! Now what?

Now we have to actually find our USB device, and then claim the interface to it so we can read and write data on it! We first need to get how many USB devices are currently connected to the system.

Get connected devices
/* Get all of the USB devices on the computer */
devcount = libusb_get_device_list(session, &list);
if (devcount <= 0) {
	fprintf(stderr, "! Missile Launcher is not connected!\n");
	libusb_free_device_list(list, 1);
	return h_dev;
}

The function libusb_get_device_list() returns an integer representing how many devices are connected to the system. If it returns 0 or less, obviously nothing is connected or we ran into an error, so return a device handle of NULL. If not, we can continue and use the device count to iterate through each device as shown below.

Iterating through devices
	/* Iterate through all of the USB devices in the list */
	for (i = 0; i < devcount; i++) {
		if ((libusb_get_device_descriptor(list[i], &ddesc)) < 0) {
			fprintf(stderr, "! Cannot find device descriptor. \n");
			libusb_free_device_list(list, 1);
			return h_dev;
		}
		if ((ddesc.idVendor == VENDOR && ddesc.idProduct == PRODUCT)) {
			printf("* Found Missile Launcher *\n\tUSB#%d\n\tVendor ID: 0x%x\n\tProduct ID: 0x%x\n", i, ddesc.idVendor, ddesc.idProduct);

			h_dev = libusb_open_device_with_vid_pid(session, VENDOR, PRODUCT);
			break;
		}
	}
Had we not retrieved the number of devices, we would not know how many to go through and check. This is where the VID and PID codes we found earlier come into play! First we get information about the USB device (similar to lsusb, but in our code now), and if that does not work we cannot possibly check what device it is, thus we must abort returning a NULL device handle.

Once we have a descriptor about the device, which is a struct provided by libusb, we can directly compare the VID and PID to those of our device. This process continues until the device is found or the list is completely looked through.
Let’s say that we did find the device! We can now get a handle to the missile launcher with libusb_open_device_with_vid_pid(), passing in our codes.

I have a device handle! Can I write to my device now? Not yet - claim the interface.

A device handle lets us know exactly which device to communicate with, but it will not let us read or write to it until we have claimed the interface.

This is exactly what our claim_interface() function will allow us to do. Unfortunately, we cannot directly claim the interace for a USB device many times from the start. Why? The Linux kernel generally has its own generic USB drivers running, which will automatically attach to USB devices and try to mount them or give you access to them. When you plug a flash drive in, the kernel driver automatically enumerates the USB devices, finds it, and attaches a kernel driver that provides communication capabilities. We must detach that driver from our device before attaching our own driver on it.

Of course, first check if there really is a kernel driver active on the device, and if so try to detach it succesfully. If everything passes, we can finally claim this device as our own!

Detaching the kernel driver
if (libusb_kernel_driver_active(h_dev, 0) == 1) {
  printf("\tTrying to remove Kernel driver from USB...\n");
  if (libusb_detach_kernel_driver(h_dev, 0) == 0)
      printf("\tSuccessfully removed Kernel driver!\n");
  else {
      fprintf(stderr, "! Could not detach the kernel driver, exiting.\n");
      return ret;
  }
}

Final step: Sending the commands to the device

We now have everything needed to communicate with the device. The libusb API provides several functions to send and recieve data to/from the device; in this case, the function to use is libusb_control_transfer(). Shown below is a nice wrapper function we can use to call this, and pass it in the codes we found earlier to control the launcher.

int missile_usb_sendcommand(libusb_device_handle *handle, uint8_t *seq)
{
	if (libusb_control_transfer(handle, 0x21, 9, 0x00000200, 0x00, seq, 8, 1000) != 0)
		return -1;

	return 0;
}
The seq is one of the five codes the launcher accepts, and is passed in along with some information, specifically the size of the transfer: 8 bytes. This will now send commands and let you control the Dream Cheeky USB Misile Launcher!
More importantly, this outlines a general method to access USB devices and the steps required to succesfully communicate with them. Enjoy hacking on your own devices!