Learning how to reverse engineer a Windows USB driver: the Luxeed LED keyboard

In this article I will share with you the trials and tribulations that I went through as I reverse engineered a Windows driver to create a Linux one. The hardware which will be subjected to my hackery is the Luxeed LED keyboard. A keyboard with seemingly free controll over most of its multi-colored LEDs. After seeing this device on the digg, I knew I had to have one.

 

Corny disco keyboard? Perhaps. An insane amount of possibilities that can be had with a inexpensive keyboard with full visual feedback and open source drivers? Absolutely!

Of course, not everything in the world is free. This especially holds true to the source code to control this device. Regardless, I ordered a device and took it upon myself to learn how one would make a minimal driver. The end result of this will hopefully be a simple user space Linux application which can be used to controll the LEDs. Wish me luck!

Topics: 

Recommended Reading

If you are like me, and have no idea how USB devices and their drivers work, then here are some helpful guides to get you jump-started.

Java.net Article on Java and USB

Although the article later focuses on the Java aspects, I found the diagrams and introduction very helpful in gaining an understanding of USB devices and their structures.

Tutorial on USB  with Linux

A relatively old article which outlines some more Linux specific points concerning USB drivers.

Reverse engineering methods

Although I am sure that there are many more ways to reverse engineer windows drivers, I will outline the candidates that I found and evaluated below.

 

DebugFS and USBMon

I found a site with a seemingly perfect solution. By simply mounting the Linux debugfs and loading the usbmon module one can monitor the USB packets. This functionality is available in my default Kubuntu 7.04 installation without needing to recompile my kernel.

The idea is then that one launches VMWare (or similar virtualization software) to boot Windows. Once in Windows, you execute the functionality in the device which you would like to reproduce in the Linux driver. The USB packets are funnelled from VMWare through usbmon/debugfs, and one can then write the driver from this output that is produced.

This was the first method I pursued, but quickly found that keyboards and mice act as generic input devices in VMWare, no matter how hard you try to use a custom driver. Without being able to execute the blinking LED functionality under even Windows and finding many people with the same problem, I quickly discarded this alternative. I'm certain that this method would work great for any other non-keyboard device.

I found this idea from: http://www.quietearth.us/articles/2006/10/16/USB-Snoop-in-linux

 

USBSnoop and usbsnoop2libusb.pl

I found a very concise guide to using a native USB monitor driver/application under Windows. One would use the log files generated by this application to create a simple libusb skeleton driver through a perl parser. A seemingly optimal solution, however, it failed miserably for me. The application was buggy, and I could not get any interesting data sniffed. Most of the links on the site are broken, and some googling is required to find the software mentioned.

The guide: http://lindi.iki.fi/lindi/usb/usbsnoop.txt

 

SnoopyPro

While looking for a copy of the usbsnoop application, I found a new and improved version dubbed SnoopyPro. This application was able to reliably and easily capture USB packets and worked well. Lets use this one!

SnoopyPro project site: http://sourceforge.net/projects/usbsnoop/ 

 

 

Generating and working with USB data

In this section I will go through through the process of configuring and using the USB packet monitor SnoopyPro.

Generating USB data

Upon launching SnoopyPro one should first install the driver and service; this is done through File->Unpack Drivers and File->Install Service in the USB devices window.  Thereafter one can select various device addresses and their endpoints. I found the keyboard device address (windows device manager), and after toying around a bit, I found that the 1st endpoint (0x00) is used for basic keyboard functionality (typing, etc.). This works out of the box in Linux, so there was no need to look into this. The 2nd endpoint (0x01) seemed to be the one that the Windows application uses to execute commands that makes the keyboard blink. By right clicking this address, and selecting "install and restart", I was able to start capturing data.

I was able to verify that I had the correct end point by simply launching the Luxeed keyboard control application, clicking the button that causes the keyboard to blink, and making sure that the only time the endpoint received/sent data was at that moment. Once certain that I had the correct one, I cleared the window,  un/replugged in the keyboard, and restarted the listener. With this clean slate, I launched the Luxeed control application again, selected the feature to make the keyboard blink in a rainbow pattern, and then clicked the stop icon in the sniffing window. I selected all the rows in the window - clicked expand - re-selected all rows - and hit copy. I then pasted in the text  to a file which I later sent to my Linux machine. This is all the data that we will need to make a rudimentary driver!

 

Since the SnoopyPro output format differs from the original USBSnoop one cannot use the perl script to generate a skeleton driver from the log. One could of course hack the perl script to work with the new format, however, at this point I decided to do the output->code step manually as a learning exercise.

A short introduction to libusb

In this section, I will take you through the steps necessary to query the Luxeed keyboard device using the programming language C and libusb.

A simple libusb program

The first thing that should be done is to install the libusb development package. This varies from distribution to distribution, but the package to install is generally called something like libusb-dev.

You should also bookmark the libusb API page as it is an invaluable source of information concerning the library, and ultimately, the tool which you will use to make the keyboard blink.

To use the Luxeed keyboard we need to find its Vendor ID and Product ID. This information is used by the program to query the correct device, and you can think of it as a unique identifier for this particular brand and model.

Connect the keyboard, and then execute the following command in a shell as root: lsusb -v

Among the output you should be able to spot the entries corresponding to the keyboard:

...

idVendor           0x534b
idProduct          0x0600
bcdDevice           30.01
iManufacturer           1 Luxiium Lighting@Technology,Inc
iProduct                2 LED_KeyBoard
...
 

As you can see above, 0x534B is the Vendor ID and 0x0600 is the Product ID.

Now it's time to code! Let's assume that you have a basic C program:

#include <stdio.h>

int main (int argc,char **argv)
{
  printf("Lets look for the Luxeed LED keyboard...\n");
  return 0;
}
 

To use libusb we need to first add the libusb header file to the top of the file: 

#include <usb.h>
 

Also, we need a function to search for the keyboard device:

static struct usb_device *findKeyboard(uint16_t vendor, uint16_t product)
{
  struct usb_bus *bus;
  struct usb_device *dev;
  struct usb_bus *busses;

  usb_init();
  usb_find_busses();
  usb_find_devices();
  busses = usb_get_busses();

  for (bus = busses; bus; bus = bus->next)
    for (dev = bus->devices; dev; dev = dev->next)
      if ((dev->descriptor.idVendor == vendor) && (dev->descriptor.idProduct == product))
        return dev;

  return NULL;
}
 

To avoid redundancy, the explanations to the above function calls are explained in the libusb api. However, the general gist of it is that it initializes the libusb subsystem, iterates over all the USB busses and their devices until it finds a device with the idVendor and idProduct matching the keyboard. If one is found, we return its device structure, else null.

Let's replace the contents of the main function with the following. It will call the function we just added and output the result.

printf("Trying to locate the Luxeed LED keyboard...");

// Call the findKeyboard function and store the returned results.
// The function takes the vendor id and product id of the desired keyboard as arguments
// so that it knows what to look for
struct usb_device *dev;
dev = findKeyboard(0x534B,0x0600);

//If the keyboard wasn't found the function will have returned NULL
if (dev == NULL) {
    fprintf(stderr, "Not found!\n");
    return 1;
}
//If it gets this far, it means it found the keyboard
printf("Found it! Now make me blink already!\n");
return 0;
 
 

Now, it's time to compile and try this out. Save the file as find_keyboard.c and simply execute gcc as you normally would, but with the added parameter -lusb so that it will link to the libusb library.

gcc -o find_keyboard find_keyboard.c -lusb
 
Download the complete source code here: find_keyboard.c

All done! That wasn't hard at all was it? :)

Please keep in mind that I am also new to libusb, so if you know a better way of doing or explaining things, then please don't hesitate to contact me and I will update the page and and accredit you.

Making the keyboard blink!

Coming soon :)