Turn off the &#$@ lights!
A few days ago I got fed up again with the lights of my headset blinking while I was playing a game. I don’t use my headset if I don’t need to so it was laying on my desk, blinking and being annoying. I had double checked the settings of the SteelSeries application but lighting was turned off.
Diving deeper I found that the application uses a provider system that supports different applications and games that you could individually turn on or off.
Turning the provider off for the specific game resolved my issue and also made me realize two things were possible:
- You can get game state from certain games without writing an integration
- You can directly control RGB lighting of USB peripherals
Since I don’t use my headset often but I do use my keyboard a lot I want to try to come up with some nice ways of integrating lighting and certain events. I’m thinking about game events, meeting reminders, system notifications etc.
In this series we’ll look at how you can reverse engineer the lighting protocol used for your keyboard and write some code to control it.
Capturing USB packets
First thing we need to do is be able to capture USB packets so we can see what’s going on. You might want to download Wireshark and make sure that you check the box to install the included USBPcap. If you install USBPcap manually you don’t have the ability to select the interfaces in Wireshark.
Once you start it up, provided you have at least one USB controller, you will see a list of your USB controllers.
When you click the cog icon next to a capture interface, you get a dialog with a possibly long list of USB devices attached.
Since we want to focus on one device, we want our capture to be as specific as possible. As you can see the list of devices is pretty long and complex. I plugged in my keyboard into one of the front ports of my PC and it showed up as a new device (I saw a higher number appear so went with that).
As you can see in the screenshot, the keyboard I have is implemented as a composite device which means it is implemented as multiple devices that are communicating with the computer. I guess it makes sense to implement and control the keyboard and the lighting as different devices. Each will show up in the capture log as a different path. Since this is the first USB interface and the device with ID 15 the keyboard devices will show up as “1.15.x”, where x is the actual device index.
Now we have to figure out what data would be useful. When you start capturing packets, the log will first show that a few informational packets will get sent to the host device at 1.15.0, and if you start typing it will list communication between the computer and 1.15.1. One packet from the keyboard to the computer on keypress, one back to acknowledge and also two packets when the key is released.
💡 Adding the “HID Data” column
In the screenshot below you can see I have added the HID Data column.
In order to do that you have to:
1. Click on a packet to see the contents in the lower pane
2. Scroll down a bit until you see the HID Data
3. Right click on the row that says “HID Data” and select “Apply as column”, the shortcut for that is
CTRL + SHIFT + I
Since everything is working now, it’s time to fire up the keyboard software and see what it’s doing. Upon starting the Ducky software it starts sending a lot of messages to devices 1.15.3 and 1.15.4. Since the packets for 1.15.3 have no data in them and because it says “in” instead of “out” (I’m assuming that’s the transaction mode), I will apply a filter so Wireshark only shows packets for the device that we really want, the RGB lighting module.
usb.dst == "1.15.4" shows that a whopping 491 packets get sent to the device to put it in some kind of, I guess, configuration mode. This might be important so I’ll save the filtered list for later and restart the capture. I saved it as JSON because that’s the only way the HID Data was in a usable format and that way I can easily parse it later if I want to generate some gigantic 2D byte array of packets.
Before this experience I had set my keyboard to “Backlighting Off”. Now let’s restart the capture with the filter still active and see what we get. I figured I would try out “Reactive Mode” since I know that lights up a single key when you press it, but in my test it only showed a few packets to activate the mode and nothing afterwards. Not useful since we want to directly control the lights.
After trying a few modes I finally found a mode called “Equalizer Mode” that kept sending packets to the device. In the screenshot below you can see four unique packets and then a list of packets that have mostly zeroes and start repeating at the end of the page. The first four packets are also sent at the end when the Ducky software was launching so that confirms my idea that those are some kind of activation packets and the rest start out with the same prefix so they must control the lights.
Stay tuned for part 2 where we will dive deeper into the individual packets and write some code to send them to the device. Then we can see if this works or if we really need the 491 packets we captured when the software was starting up!