Reverse Engineering the USB Driver for the Corsair Virtuoso SE Headset

Search for a command to run...

No comments yet. Be the first to comment.
Server Wazuh Dashboard via docker behind NGINX Proxy Manager Set up a docker network. Change the subnet to whatever you want. docker network create \ --driver=bridge \ --subnet=10.0.1.0/24 \ --gateway=10.0.1.1 \ wazuh_npm Clone and e...

This is a tutorial on how to flash Tasmota firmware on the WiFi version of the Sonoff Zigbee Bridge Pro. Use this tutorial at your own risk. It's not my fault if you brick your device. Notable Identifiers on the boardSN-NG-ZBP-01WW220300ZB Bridge-P F...

error: android-udev: signature from "Anatol Pomozov (Arch Linux developer account) <anatolik@archlinux.org>" is unknown trust :: File /var/cache/pacman/pkg/android-udev-20231104-1-any.pkg.tar.zst is corrupted (invalid or corrupted package (PGP signat...

Go to chrome://flags/ Search for Tab Groups Save and Sync Change to Enabled Restart Chrome Create a new Tab Group Right-click the group name and select Save Group The group will now save to the Bookmarks Toolbar Follow the same process on all oth...

DO NOT CONNECT THE SWITCH TO AC POWER AT ANY POINT THE CASE IS OPEN. Bridge the Nano's GND and Reset pins. The TX/RX pins don't need to be swapped. NanoS31 3.3VVCC RXRX TXTX GNDGND Hold the button on the side, plug it into the computer...
12/17/22: My goal is to have the ability to control the LEDs of the headset via the program ckb-next and sidetone with HeadsetControl
This device uses the Slipstream Wireless Dongle, and I won't be. I'm figuring this out as I go, and adding a wireless dongle (that neither program has worked on yet) will add a layer of complexity to something I already don't know how to do. For now, USB only.
1/2/23: As you can see in the "USB Description Overview w/ lsusb" section, I've managed to map out my best estimation of what each nibble does. I am working solely on ckb-next integration for LED control at this stage. The unfortunate issue I face is that I was hoping I could build off of the bragi protocol already implemented in ckb-next, but it looks like it isn't compatible.
1/3/23: When I first started out, I focused on the packets sent to control functionality. With the new info that it isn't bragi compatible, I have to decipher the initiation packets sent when iCUE first recognizes the headset. Yay me.
How to setup Wireshark for the purpose of USB packet capture on liquidctl wiki
beyondlogic: USB in a NutShell
liquidctl Wiki Techniques for analyzing USB protocols
A Reddit post outlining the reverse engineering of a Corsair Keyboard
ckb-next wiki on the Corsair Protocol
liquidctl wiki on the Corsair Commander Core
Device ID: 0x0a3d
dev_address determined from the "GET DESCRIPTOR Response DEVICE" Packet, which is like the fourth one sent in in Wireshark, depending on how many USB devices are passed to the VM. For me, #1 was the VirtualBox mouse driver.
Wireshark Filter:
((((usb.device_address == 2)) && (usb.data_len == 64)) ) && (usb.irp_info.direction == 0x0)
Bash output in OSS-Code looks pretty when put in a JSON file.
lsusb -v -d 1b1c:0a3d verbose output for the specific device
Bus 001 Device 008: ID 1b1c:0a3d Corsair CORSAIR VIRTUOSO SE USB
1b1c:0a3d = Vendor ID, unique to manufacturer (usually)
1b1c:0a3d = Product ID, unique to device
Ref: USB HID Appendix
USB device required by OS to explain descriptor. Not all fields are required.
idVendor 0x1b1c Corsair
iProduct 2 CORSAIR VIRTUOSO SE USB
MaxPower 150mA
bInterfaceProtocol 0 "none," not a keyboard or mouse
The headset has 4 interfaces starting at 0. Interface 3 is the HID device to control the LEDs and other settings.
The host computer initiates everything. Interrupts in USB are seen by the host at a certain poll rate and know, based on this descriptor, what that interval is, and what packet size is expected. In this case, 64 bytes.
bEndpointAddress 0x81 EP 1 IN Endpoint 1 IN, Most significant bit sent 8
The host is telling the device to send it data.
bEndpointAddress 0x01 EP 1 OUT Endpoint 1 OUT, Most significant bit sent 0
The host is giving the device stuff to do.
0000 [1b 00] [e0 a6 c5 1d 82 91 ff ff] [00 00 00 00] [09 00]
0010 [00] [01 00] [02 00] [01] [01] [40 00 00 00] {[02] [08] [(02 11 00
0020 00 00 00 00 00 00 00 00 00 00 00) 00 00 00 00 00
0030 00 00 00 00 00 00 00 00 00 00 00] [00 00 00 00 00
0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0050 00 00 00 00 00 00 00 00 00 00 00]}
91 bytes on the wire, this specific frame is the first interrupt_out sent to the headset.
I broke the bytes up in square brackets by function, according to Wireshark. Curly brackets indicate the HID data. I'll break down each square bracket by field name indicated in Wireshark.
usbpcap_header_len
irp_id
usbd_status
function
irp_info
bus id
device address
endpoint
transfer_type
packet data length
report id
vendor data
padding (only first 15 bytes have been observed to be used)
not specified
Only items 11, 12, and 13 are relevant HID data.
There are 33 of these.... Hopefully they'll help decipher the Interrupt_out mappings.
Pkt Purpose Out IN
[0] Report ID 02 01
[1] Vendor Data 08 00
[2] Commands Echo
[3] Feature Select
[4] Data (1)
[5] Data (2)
[6] Unknown (2)
[7] Unknown (3)
[8] Red
[9] Mic (1)?
[10] Mic (2)
[11] Green
[12] Charge LED
[13] Mic (3)
[14] Blue
Possibly indicates what feature of the headset is being modified
*** Seen in Initialization ***
01
02
0d Sleep Enabled Status
09
08
05
*** Seen while expirementing with settings ***
06 RGB
0e Sleep wait time
*** Seen Ocasionally with no user input ***
12
Indicates the feature being written to/read from
00 LED
02 Brightness
0d Sleep toggle
0e Sleep time
I'm pretty sure these two bytes are the values passed by whatever setting is altered. I have only seen them change when changing the sleep time and brightness.
Case of the Battery: I started a capture when I knew the headset wasn't fully charged. The reply packets indicate that Data (2) was the only byte that changed slowly over a few minutes. "XX 03" seems to indicate that "XX" (data 1) is the battery level, and "03" (data 2) maybe indicates that data 1 is outputting battery info. TODO: VERIFY SETTING INDICATOR DURING CHARGE
09 00 Mic LED color
XX 03 Battery Level
XX = byte changes
Mic (2) ff
Mic (3) 00
Microphone is Muted and "Disabled LED When Mic is Active" does nothing.
Mic LED is RED
Mic (2) 00
Mic (3) ff
Microphone is not Muted and "Disabled LED When Mic is Active" is disabled.
Mic LED is Green
Mic (2) 00
Mic (3) 00
Microphone is not Muted and "Disabled LED When Mic is Active" is enabled.
Mic LED is off
Mic (2) ff
Mic (3) ff
unknown
Mic LED is yellow
I think Mic (1) indicates that the Green RGB contol byte is being used to control the charge LED rather than the main logo LEDs. "e1"
Data (1) is 09. Also, the button on the microphone controls the mute/unmute bytes.
Microphone LED is also full RGB which can be observed when switching the headset to wireless mode and may be independently controllable, but iCUE won't let users change this.
Sleep on, 2 packets. Sleep off, 1 packet. Change sleep time, 2 packets.
When sleep is turned on, the ("when to start" setting in iCUE) sleep time must be re-assigned every time. The two Data Bytes change when the sleep time value has been changed. I think the Setting Indicator byte flips to 0d to toggle the actual sleep function since all other values are zeros at this point, and it is present for the first sleep-on packet and the only sleep-off packet. The byte goes to 0e only after the first sleep-on packet to reassign the sleep time to the newly re-enabled sleep function.
Order of operations
Turn sleep on in iCUE
Packet 1 = Feature Select 0d , Data Bytes ff
Packet 2 = Feature Select 0e , Data Bytes specify the when to start time
Turn sleep off in iCUE
Packet 1 = Feature Select 0d , Data Bytes 00
When the headset is connected VIA USB and fully charged, this led flashes. The speed at which interrupt-out packets are sent corresponds to the speed of the LED pulsing. When it's solid green, it sticks to 80. This LED is also full RGB which can be observed when switching the headset to wireless mode and may be independently controllable, but iCUE won't let users change this.
This byte is the dedicated brightness of the charge led.
You can send datum to this byte to change the intensity, but it is overwritten by, assumably, the firmware.
Six packets are sent to the device when using the dedicated brightness slider.
No contention here. Pretty obvious which bytes control the primary LED just by toggling them.
There are no packets sent from the host for the following functions: Sidetone and its volume slider, 7.1/Stereo, lighting link presets, EQ presets, Voice Prompts, Battery indicator in the status bar.