USB HID Down the rabbit hole: Logitech G435 dongle
Tags: usb, reverseengineering, hardware, reversing
Published: 2024-06-30 11:32:21

Last time, in the first post of this series, I explored the vendor interface of my Logitech Mouse, now let's explore the dongle of my headphones. As I said last time, it caught my attention that it can send "phone" HID events. Also, Logitech doesn't provide any official software for configuring the headphones(like the sidetone volume) or seeing their status(like the battery remaining).

{% embed https://twitter.com/fw_r3_t/status/1805693034950181110 %}

The dongle I have is the HDT647(PID 0acb). After a bit of research, the first thing that pop up is the [FCC Internal teardown images report](https://fccid.io/JNZA00150/Internal-Photos/11-Internal-Photo-JNZA00150-5359099). Unfortunately, the chips markings aren't readable, but the IC interfacing the USB port is clearly from Realtek, while the other one seems to be the [PAUxxxx SoCs from the defunct audiowise](https://web.archive.org/web/20220627071110/https://audiowise-t.com/) (Acquired by Airoha, a subsidiary of MediaTek).

{% embed https://twitter.com/fw_r3_t/status/1807083928458551475 %}

I didn't want to open the dongle, as opening it up is a little "destructive", but I couldn't resist the urge. So, after opening the dongle, I read the chips markings. The Realtek codec is the ALC4021 (which is used in ["similar" aplications](https://www.cabledo.com/google-usb-c-digital-to-3-5-mm-headphone-adapter-disassembly/)), no datasheet available. The other is the PAU1823, again used in  ["similar" applications](https://www.52audio.com/archives/88933.html) and no datasheet online(except one image and this [forum post,](https://www.pcbbar.com/thread-35320-1-1.html) which I cannot access the downloads as it requires an account). The last chip is a 512kb SPI flash (the [MX25V512E](https://mm.digikey.com/Volume0/opasdata/d220001/medias/docus/609/MX25V512E.pdf)), it can be for any of the other two chips.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x6lh7z6835d9pghepjjv.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5f54ojj0hl857bdzaxax.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ju5jd7g27s5m8wgy0pqh.png)

So it is basically a Bluetooth Audio Dongle. It seems that Lightspeed is just that, Bluetooth 5.2 with _their patented GreenRadio technology for low latency_. But I'm not very sure.

On the other hand, in the research I found a bunch of software programs for this dongle. There is the [official update software](https://support.logi.com/hc/en-us/articles/4407898529687-Download-G435-Gaming-Headset). There are also [these "unofficial" pairing(and more) tools](https://www.reddit.com/r/LogitechG/comments/ykshdk/comment/k3q9t11/) on the Logitech Reddit. These tools seem widely used, as I found the logs of these on o[ther Reddit posts](https://www.reddit.com/r/logitech/comments/17lykuk/g435_couldnt_connect_in_lightspeed/) and [even on GitHub!](https://github.com/duongchung1999/MerryLuxApp/blob/fe05ead83354d09db5424afa58058020759a87ff/Z_AllDll/DownloadAPI/DownloadAPI/bin/Debug/HDT647_160/log/20230318_text.log). Also, both the official and unofficial tools seem based on the same sources.

Finally, I also found a[ compatible dongle on eBay](https://www.ebay.com/itm/166620468237), which also provides a link to the previous "unofficial" tools.


## The USB interface

It has a good quantity of snd-usb-audio interfaces and an HID one.

HID descriptor

```
# Logitech G series G435 Wireless Gaming Headset
# 0x05, 0x0c,                    // Usage Page (Consumer Devices)       0
# 0x09, 0x01,                    // Usage (Consumer Control)            2
# 0xa1, 0x01,                    // Collection (Application)            4
# 0x85, 0x03,                    //  Report ID (3)                      6
# 0x15, 0x00,                    //  Logical Minimum (0)                8
# 0x26, 0x8c, 0x02,              //  Logical Maximum (652)              10
# 0x19, 0x00,                    //  Usage Minimum (0)                  13
# 0x2a, 0x8c, 0x02,              //  Usage Maximum (652)                15
# 0x75, 0x10,                    //  Report Size (16)                   18
# 0x95, 0x02,                    //  Report Count (2)                   20
# 0x81, 0x00,                    //  Input (Data,Arr,Abs)               22
# 0xc0,                          // End Collection                      24
# 0x05, 0x0b,                    // Usage Page (Telephony Devices)      25
# 0x09, 0x01,                    // Usage (Phone)                       27
# 0xa1, 0x01,                    // Collection (Application)            29
# 0x85, 0x05,                    //  Report ID (5)                      31
# 0x15, 0x00,                    //  Logical Minimum (0)                33
# 0x26, 0xbf, 0x00,              //  Logical Maximum (191)              35
# 0x19, 0x00,                    //  Usage Minimum (0)                  38
# 0x2a, 0xbf, 0x00,              //  Usage Maximum (191)                40
# 0x95, 0x02,                    //  Report Count (2)                   43
# 0x81, 0x00,                    //  Input (Data,Arr,Abs)               45
# 0xc0,                          // End Collection                      47
# 0x06, 0xc0, 0xff,              // Usage Page (Vendor Usage Page 0xffc0) 48
# 0x09, 0x01,                    // Usage (Vendor Usage 0x01)           51
# 0xa1, 0x01,                    // Collection (Application)            53
# 0x06, 0xc1, 0xff,              //  Usage Page (Vendor Usage Page 0xffc1) 55
# 0x85, 0x06,                    //  Report ID (6)                      58
# 0x15, 0x00,                    //  Logical Minimum (0)                60
# 0x26, 0xff, 0x00,              //  Logical Maximum (255)              62
# 0x09, 0xf0,                    //  Usage (Vendor Usage 0xf0)          65
# 0x75, 0x08,                    //  Report Size (8)                    67
# 0x95, 0x07,                    //  Report Count (7)                   69
# 0x81, 0x02,                    //  Input (Data,Var,Abs)               71
# 0x09, 0xf1,                    //  Usage (Vendor Usage 0xf1)          73
# 0x75, 0x08,                    //  Report Size (8)                    75
# 0x96, 0x02, 0x02,              //  Report Count (514)                 77
# 0x91, 0x02,                    //  Output (Data,Var,Abs)              80
# 0x09, 0xf2,                    //  Usage (Vendor Usage 0xf2)          82
# 0x75, 0x08,                    //  Report Size (8)                    84
# 0x95, 0x3f,                    //  Report Count (63)                  86
# 0xb1, 0x02,                    //  Feature (Data,Var,Abs)             88
# 0xc0,                          // End Collection                      90
# 0x06, 0xc3, 0xff,              // Usage Page (Vendor Usage Page 0xffc3) 91
# 0x09, 0x01,                    // Usage (Vendor Usage 0x01)           94
# 0xa1, 0x01,                    // Collection (Application)            96
# 0x06, 0xc4, 0xff,              //  Usage Page (Vendor Usage Page 0xffc4) 98
# 0x85, 0x34,                    //  Report ID (52)                     101
# 0x15, 0x00,                    //  Logical Minimum (0)                103
# 0x26, 0xff, 0x00,              //  Logical Maximum (255)              105
# 0x09, 0xf3,                    //  Usage (Vendor Usage 0xf3)          108
# 0x75, 0x08,                    //  Report Size (8)                    110
# 0x95, 0x14,                    //  Report Count (20)                  112
# 0x81, 0x02,                    //  Input (Data,Var,Abs)               114
# 0x09, 0xf4,                    //  Usage (Vendor Usage 0xf4)          116
# 0x75, 0x08,                    //  Report Size (8)                    118
# 0x95, 0x02,                    //  Report Count (2)                   120
# 0x91, 0x00,                    //  Output (Data,Arr,Abs)              122
# 0xc0,                          // End Collection                      124

```

The Consumer Control is for the volume up and down key press. The Telephony Devices I still cant find to trigger anything for this input event. And of course there are the Vendor Usage Page which are the most interesting ones. The usage of those is "strange", the only one used seems to be the **report 6** in a curious way: The total size is completely ignored, usually sending 64 bytes of data. For writing, a standard **send feature** is used, but for reading, some messages come as a input, so you have to listen to it, while others you have to issue a **get feature**.

## Reverse engineering the software
Both the program and the `AwToolLIB` are made in C# mono and shipped with some **obfuscation**(.NET reactor). At first, I thought it wasn't obfuscated and lost a lot of time trying to understand fake symbols and hashed string constants. But after removing the string hashing with .NET reactor slayer and knowing some symbols were fake, it was relatively "easy" navigating the code with ILSpy. This code is pretty complete, containing all the classes to interface with their different devices and functions. It also includes some internal resources, including what seems to be the firmware (file `headset_RTK`).

## Protocol
The protocol can be divided into two layers. The first layer is used to communicate with the Realtek driver to send commands to the Audio SoC, possibly through one of its serial interfaces (most probable UART, but it can also be SPI or I2C). The second layer is these commands.

###  RTK communication protocol
Messages are sent as a HID **send feature**. On every message sent, an ack is received as a HID input event. For example, an OK ack looks like this:

| **0**       | **1** |
|-------------|-------|
|           6 | 90    |
| _Report ID_ | _OK_  |


The messages I found so far are the following.

| **Message**        | **0** | **1** | **2** |     **3** |     **4** | **5** | **6** | **7** | **8** |
|--------------------|------:|------:|------:|----------:|----------:|------:|------:|------:|------:|
| _Enter Bootloader_ |     6 |     1 |     3 |         0 |         0 |     0 |     0 |     0 |     0 |
| _Exit Bootloader_  |     6 |     8 |     3 |         0 |         0 |     0 |     0 |     0 |     0 |
| _Send command_     |     6 |     4 |     3 | _CMD_LEN_ | _CMD_LEN_ | .     | .     | .     | .     |


The write command message is appended with the command at the end and requires entering the bootloader or strange and unusable behaviors will happen. Unfortunately, **entering the bootloader will stop the audio functionalities**.


### Commands
On top of the RTK protocol, the manufacturer has what it seems to be a proprietary command-response protocol. For sending a command, a **RTK write** is used and for reading the response, a hid **get feature** is used.

Here is a quickly done dump of all commands I found. Keep in mind that some of these commands append arguments at the end, and surely I messed up writing some of them. Also, the bytes 4 is used to write the length in bytes of the command after this byte (sometimes is this length - 1). The byte 3, if 0 and the total command length is more than 5, is used as a command counter which gets incremented until 128.
{% embed https://gist.github.com/endes0/21193c55e2e38c9b3e3a95492380059c %}

It is curious that all of them start with _[80,65]_. Most of the commands seem to follow the following format, with a lot of exceptions.

|   **0**   |   **1**   |        **2**       |              **3**             |               **4**              |    **5**   |
|:---------:|:---------:|:------------------:|:------------------------------:|:--------------------------------:|:----------:|
| Always 80 | Always 65 | Command "category" | Generally commands sent counter | Generally length after this byte | Command ID |

The commands category seems to be the following. Also, take it with a grain of salt.

| **Value** | **Category**                   |
|------|----------------------------|
|    0 | -                          |
|    1 | -                          |
|    2 | -                          |
|    3 | Debug                      |
|    4 | Audio                      |
|    5 | -                          |
|    6 | OTA                        |
|    7 | ?                          |
|    8 | Customer custom commands   |
|    9 | DSP                        |
|   10 | Noise cancellation         |
|   11 | NVDS (Non-volatile memory) |
|   12 | Touch                      |
|   13 | -                          |
|   14 | Wireless connection        |
|   15 | RF test                    |

The responses are another story that I might explore in a future blog post. The responses are read as 64 bytes blocks with an HID **get feature** at the report 6.

#### Bluetooth scan example
As an example, the following python snippet does a Bluetooth scan, listing all devices found.  

{% embed https://gist.github.com/endes0/e802a8a35fdf1fc27abf5db21e3a396b %}

#### Pairing sequence
Looking at the pairing software logs, I can extrapolate the following sequence to pair a dongle with a headset. To illustrate, let's say that the Bluetooth MAC address of the headsets is _[X0, X1, X2, X3, X4, X5]_. The bytes relative to the standard command <u>are underlined</u>. _Counter_ is the commands counter I mentioned on the protocol section.

- Enter Bootloader
- Sends LQ_DFU_BT_INQUIRY_SCAN (<u>80, 65, 14,</u> _counter_, 2, <u>240, 00</u>) to enumerate all the Bluetooth devices.
- Sends TX_DG_CREATE_CONNECT_EX (<u>80, 65, 14,</u> _counter_, 9, <u>229, 00</u>, _X5, X4, X3, X2, X1, X0_) to pair the dongle with the headsets.
- Sends REMOTE_GET_CONNECT_STATUS (<u>80, 65, 14,</u> _counter_, 1, <u>227</u>) to check that the pairing was successful. If it fails, it retries the previous step.
- Sends REMOTE_GET_MODE (<u>80, 65, 14,</u> _counter_, 1, <u>224</u>) to get the "mode", maybe related to the connection mode on the headset (Bluetooth or lightspeed)?. If it is 01, it skips to the last step.
- Sends REMOTE_SET_MODE (<u>80, 65, 14,</u> _counter_, 2, <u>225</u>, _01_) to set the mode to 01.
- Sends REMOTE_GET_MODE (<u>80, 65, 14,</u> _counter_, 1, <u>224</u>) to check that the mode has changed to 01.
- Sends OTA_CMD_GET_IMAGE_VERSION (<u>80, 65, 6,</u> _counter_, <u>4, 0, 52</u>, 01, 77, 67) to checks that the headset and the update tool (I think) firmware version are the same.

### So, can we read the battery level?

Yes, but as it requires sending a command, so entering the bootloader, a cut on the audio will be produced which is bothering. Maybe this is why Logitech doesn't support these headphones on their software programs?. Here was where I lost interest in this, as the main reason I started researching about these headphones was for knowing the battery level. There are a lot of commands and fun things still worth exploring, which can result in interesting information for implementing an app for these headsets, but this is it for now.