Wednesday, March 9, 2016

Reversing Corsair Sabre LED control and writing PyUSB controller

EDIT: On /r/ReverseEngineering, I was made aware of this project https://github.com/ccMSC/ckb Someone did a more thorough job of reversing what CLE does. Go check it out!

I bought a Corsair Sabre gaming mouse. (http://gaming.corsair.com/en-us/sabre-rgb-gaming-mouse) The LED’s are supposed to improve my gaming performance. That’s a joke. I haven’t really gamed much with it. I decided to figure out how I can change LED’s from Linux or via scripting. The mouse has four RGB LED’s. One is for DPI indication. Three are customizable (with pattern, solid color, and other ways). You can use Corsair CLE (Corsair Utility Engine ) to customize the colors.  CLE is only for Windows.


Once you customize your LED’s in CLE, it the software keeps communicating with the mouse via USB to change the colors or keep it one color. I know this because when I kill CLE or plug-in my mouse on a computer with no CLE, it changes colors back to default.


My first thought was to use Frida (www.frida.re) then hook into CLE to just send colors I want to. This turned out to be much harder than I thought. After going through IDA, CheatEngine, and Immunity Debugger, I was unable to figure out how I could use the software. That’s failure on my side. I kinda gave up on that. I decided to make it easier and just analyze USB communication instead. The benefit of this is, I can also make it work with Linux.


First, watch this:


I started by downloading and installing USBlyzer (http://www.usblyzer.com/). FYI: Wireshark also can parse USB packets.
I clicked through the Device Tree window to find my device.


After trial and error, I realized that Interface 3 is the place where LED change communication occurs. I clicked different USB Input Device checkboxes to see if me changing colors using CLE had any impact. Interface 3 was impacted by color changes.


I started sniffing again on Interface 3 to see what else I could gather. There are three LED’s I can control via the software (CLE).


First thing I did was change LED for Zone 1. I started sniffing then clicked the color next to “Selected.”
I changed it from #112233 to #AABBCC.


Packet’s that were sent
For Seq 0004, I observed AABBCC being sent.


I followed similar process for other two (scroll wheel and front) LED Zones.

After collecting data, this is what I found this:
Packet has 64 bytes total
Start to end:
07 22 04 01 //Not sure what this is for yet. I didn’t care enough about it.
//XX XX XX are R G B values in hex
01 XX XX XX // for front LED
02 XX XX XX // for LED under the logo
03 XX XX XX // for DPI indicator (yes, you can change it!)
04 XX XX XX // for scroll wheel LED
00*44 // Rest is just 00’s.


That was pretty simple.


We now know what we need to send but we still need more info from USBlyzer.
Notice that the program is doing Control Transfer. In the bottom half window, you can select URB and see more details.
Out of all that, we care about bmRequestType, bRequest, wValue, wIndex, and wLength. The reason we care about that is because we’ll also be doing Control Transfer, using PyUSB. You can read more here https://github.com/walac/pyusb/blob/master/docs/tutorial.rst#talk-to-me-honey and https://github.com/walac/pyusb/blob/master/usb/core.py#L997


PyUSB will make it easier for us to communicate with the USB device. You can download it from here: https://walac.github.io/pyusb/ It was pretty easy to install on Linux.


After looking at the examples, this is what I came up a script.


led_control.py

import usb.core
import usb.util


#call this first
def use_usb():
       global dev
       dev = usb.core.find(idVendor=0x1b1c,idProduct=0x1b14) #Found this using lsusb
       dev.detach_kernel_driver(3) #Remember interface 3?
       usb.util.claim_interface(dev,3)
       dev.set_interface_altsetting(interface=3,alternate_setting=0)


#call this last
def stop_usb():
       usb.util.release_interface(dev,3)
       dev.attach_kernel_driver(3)


start = [0x07, 0x22, 0x04, 0x01] #again, not sure what this is for. Didn't look into it.
end = [0x00] * 44


#pass in colors = {1:[r,g,b],2:[r,g,b],3:[r,g,b],4:[r,g,b]
#example:
#colors = {1:[0,0,255],2:[0,255,0],3:[255,0,0],4:[0,0,0]}
def change_colors(colors):
       data = start + [1] + colors[1] + [2] + colors[2] + [3] + colors[3] + [4] + colors[4]
       dev.ctrl_transfer(bmRequestType=0x21, bRequest=0x09, wValue=0x0300, wIndex=0x0003, data_or_wLength=data,timeout=1000)


I can now just import led_control in another python script then change colors.

This is how I use it:

import led_control
led_control.use_usb()
colors = {1:[0,0,255],2:[0,255,0],3:[255,0,255],4:[0,0,0]}
led_control.change_colors(colors)
led_control.stop_usb()


I am new to USB and I have never written anything with PyUSB before. Feel free to recommend what I could have done differently. If you can make the led_control script better, please do so.

If there are inaccuracies in the the post, please leave a comment.