Reverse Engineering a USB mouse (Updated 3rd May 2017)

This week my Zalman optical computer mouse died on me, I’ve had it for almost 4 years, so it did last a good while. The scroll wheel started to slip causing a very annoying issue of documents rolling about by themselves, so i threw it in the bin and went out and purchased a new one.

I ended up getting a new mouse from the local ASDA supermarket, sold under the name of ‘BlackWeb AYA gaming mouse’. Mostly because i thought it looked rather cool with it’s LED colour changing feature, as well as the fact that it was only £21.

(If you’re just looking for the software to control the BlackWeb AYA mouse LED from Linux, scroll to the bottom of the article and you’ll find the links to my github repository.)

IMG_20170325_183840

The packaging of the BlackWeb AYA mouse

The device features are advertised as;

  • 16.8 million colours
  • 12,800 dpi optical sensor
  • on the fly dpi switching
  • 1,000Hz polling rate
  • breathing light effect
  • macro manager
  • 6 programmable buttons
  • software included

What interested me is that the box didn’t mention any specific OS support (not even a mention of Windows), so i took a gamble hoping it might just have full Linux support. I was obviously wrong.

Upon first connecting it to the computer, the mouse immediately lit up red as the default colour, and it worked like any other generic HID device, requiring no special drivers to function. This is what appears in `dmesg

[    5.912200] usb 9-1: New USB device found, idVendor=3938, idProduct=1101
[    5.912202] usb 9-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[    5.912203] usb 9-1: Product: Blackweb  Optical Gaming Mouse
[    5.912204] usb 9-1: Manufacturer: Gaming Mouse
[    5.919435] input: Gaming Mouse Blackweb  Optical Gaming Mouse as /devices/pci0000:00/0000:00:13.0/usb9/9-1/9-1:1.0/0003:3938:1101.0002/input/input3
[    5.956392] hid-generic 0003:3938:1101.0002: input,hidraw1: USB HID v1.11 Mouse [Gaming Mouse Blackweb  Optical Gaming Mouse] on usb-0000:00:13.0-1/input0
[    5.961789] input: Gaming Mouse Blackweb  Optical Gaming Mouse as /devices/pci0000:00/0000:00:13.0/usb9/9-1/9-1:1.1/0003:3938:1101.0003/input/input4
[    6.013350] hid-generic 0003:3938:1101.0003: input,hiddev0,hidraw2: USB HID v1.11 Keyboard [Gaming Mouse Blackweb  Optical Gaming Mouse] on usb-0000:00:13.0-1/input1

The device listed under `lsusb

$ lsusb | grep 3938:1101
Bus 009 Device 002: ID 3938:1101

As you can see it is using the generic HID driver. The first thing i wondered, was how to change the LED colour, because the mouse only came with a CD containing software for a windows driver.

I searched through /sys and /dev for a while trying to find something representative of the colour red, hoping i may just be able to “echo” an RGB colour value to the mouse, but had no success.

I ended up finding a solution to manually set the mouse polling speed through the usbhid module,

/etc/modprobe.d/usbhid.conf

options usbhid mousepoll=1

Where 0=default, 1=1000Hz, 2=750Hz, 4=500Hz, 8=250Hz

So that was that out of the way.

Next i went ahead and had a look at the Windows software by setting up a Windows 10 virtual machine. After enabling USB pass through for the mouse, i installed the driver and opened the driver software, setting the mouse LED to a custom colour, and this set the LED as expected. The interesting thing was that after closing the virtual machine (and even powering off the host system), the mouse continued to stick to the same colour. So the mouse stored the colour value on the hardware itself as a persistent setting.

Screenshot_2017-03-25_18-52-12

The Windows driver software running in the virtual machine

I left it at that for a day, being a simple solution, until i came across a forum post mentioning Wireshark has USB sniffing capabilities. After alot of searching i realised it might be possible to reverse engineer the device to control it from Linux natively. Despite having never done anything like this before.

The first thing to do was check which USB Bus the mouse was connected on, `lsusb‘ provides that information, and in my case it was Bus 009. So heading into Wireshark, i set the sniffer interface to usbmon9.

To avoid getting alot of spam information in the packet capture from other devices, i used a second connected mouse to perform any clicking whilst the packet capture was running, and also setting a filter rule within Wireshark like so:

usb.bus_id == 9 && usb.device_address == 2

The first step was to open the driver software, set a new colour for the LED, and then stop the packet capture. To my surprise i spotted the RGB colour value of the newly set colour in a datastream heading from the host to the USB mouse.

Screenshot_2017-03-25_18-37-13

The colour i had set (as hexadecimal RGB) was FF8041

0000   c0 63 e4 97 01 88 ff ff 53 02 00 02 09 00 00 00  .c......S.......
0010   52 38 d5 58 00 00 00 00 9b 0c 01 00 8d ff ff ff  R8.X............
0020   08 00 00 00 08 00 00 00 21 09 07 03 01 00 08 00  ........!.......
0030   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0040   07 0a 01 00 ff 80 41 00                          ......A.

The relevant part of this datastream is:

0000   07 0a 01 00 ff 80 41 00

The next step is to understand what the other information does. The colour value is surrounded by 00’s, assuming these are separators/padding. We then have 3 bytes left at the beginning, which i could only assume is a command to set the LED to the colour that follows.

I managed to whip up a small python script using PyUSB to interface with the mouse, that allowed this command to be sent to the mouse from within Linux natively.

Here’s an example showing how the request/command can be replayed, to tell the mouse to turn the LED blue:

import usb.core
import usb.util

# find the device
dev = usb.core.find(idVendor=0x3938, idProduct=0x1101)

# detach the kernel driver
dev.detach_kernel_driver(1)
usb.util.claim_interface(dev,1)
dev.set_interface_altsetting(interface=1,alternate_setting=0)

# set a hardcoded colour
colors = [0x00, 0x00, 0xFF]

# concatenate the colours into the expected 8 bytes
data = [0x07, 0x0a, 0x01, 0x00] + colors + [0x00]

# send the data to the mouse
dev.ctrl_transfer(bmRequestType=0x21, bRequest=0x09, wValue=0x0307, wIndex=0x0001, data_or_wLength=data,timeout=1000)

# reclaim the device
usb.util.release_interface(dev,1)
dev.attach_kernel_driver(1)

The values for bmRequestType, bRequest, wValue and wIndex were all present in Wireshark for the captured frame. So simply filling them in the same here appeared to work.

To my amazement the mouse LED instantly turned blue! But there was a small issue. Upon rebooting / powering off the machine, or simply hotplugging the device, meant the mouse would reset to its default colour each time (or the last colour that was set from within the virtual machine).

I realised there must be another command which tells the mouse to store a persistent colour. So going through the packet capture log once again, trying anything that contained a datastream seemed like a logical approach.

Under Windows when setting the LED colour, the mouse flashes 5 times momentarily, and i hadn’t seen this from setting the LED colour through a PyUSB script.

I’d almost given up, after several hours of trying absolutely everything in the packet capture, but eventually found it:

Screenshot_2017-03-25_18-36-48

0000   40 38 f6 a8 01 88 ff ff 53 02 00 02 09 00 00 00  @8......S.......
0010   52 38 d5 58 00 00 00 00 a6 ef 03 00 8d ff ff ff  R8.X............
0020   08 00 00 00 08 00 00 00 21 09 07 03 01 00 08 00  ........!.......
0030   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0040   07 04 01 28 00 00 00 00                          ...(....

Sending these 8 bytes to the mouse, triggered the LED to flash 5 times!

0000   07 04 01 28 00 00 00 00

Hotplugging the mouse, now kept the colour persistent. Simply adjusting the ‘data’ variable to contain these 8 bytes worked flawlessly to store the colour onto the hardware as a persistent setting. I suppose it is like a “save” command.

data = [0x07, 0x04, 0x01, 0x28, 0x00, 0x00, 0x00, 0x00]
IMG_20170325_183810

The mouse set to a custom LED colour!

 

I ended up putting together a slightly better script to set and store the LED colour of the mouse. You can get the code from my github repository here:

https://github.com/Jigoku/ayatool

Or by cloning the repository directly from a terminal:

$ git clone git@github.com:Jigoku/ayatool.git

It’s as simple as running the below command, where each argument is the RGB value to set.

# ./ayatool.py 50 100 255

UPDATE:

I have since started working on a PyQt based interface to control more features easily, which includes a colorpicker widget, after cloning the git repository you can launch this by running:

# ./ayatool_qt.py

It still needs some improvements, but should be useful for changing the settings more easily (including profile slots and polling rate!).

121e5fd8-1fda-11e7-9967-bc6128445a67

If this helped you with the BlackWeb AYA mouse on Linux (or helped with you reversing a different USB mouse) I’d love to hear about it! Cheers.

Advertisements
This entry was posted in Hardware, Linux, Reverse Engineering, Uncategorized and tagged , , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s