Recently, I have an obsession with USB HID devices. So let's go through this rabbit hole. These devices send a descriptor which specifies the format of the "packets" they send or receive. Most fields are defined in the standard, so your hid driver can interact with the device out of the box. Some devices implement curious fields, like my headphones can send phone "events" like calls.
Most of them implement custom vendor specific fields. This is where it gets interesting.
Let's introduce my laptop mouse (below is Jaime playing with it), it's a Logitech M185. Almost all the Logitech hid devices implement their proprietary standard hid++, which is well-known. Mine is the exception.
[21837.663373] usb 2-2: new full-speed USB device number 9 using xhci_hcd
[21837.815411] usb 2-2: New USB device found, idVendor=046d, idProduct=c542, bcdDevice= 3.02
[21837.815419] usb 2-2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[21837.815422] usb 2-2: Product: Wireless Receiver
[21837.815424] usb 2-2: Manufacturer: Logitech
[21837.819645] input: Logitech Wireless Receiver Mouse as /devices/pci0000:00/0000:00:14.0/usb2/2-2/2-2:1.0/0003:046D:C542.000D/input/input38
[21837.819801] hid-generic 0003:046D:C542.000D: input,hidraw0: USB HID v1.11 Mouse [Logitech Wireless Receiver] on usb-0000:00:14.0-2/input0
Describe the bug
Adapter CU0019 not recognised (same on windows Logitech utilities). Tried multiple of these adapters.
It uses the Telink TLSR8366. While Telink openly publishes a lot of tools, documents and code on their website, it is an amalgam of things which don't do a good job explaining themselves, so most of the time I was like "huh?". Oh, they also implement their own ISA called TC32, which is undocumented. Fortunately, GitHub users trust1995 and rgov have done a fairly decent job of implementing it on Ghidra.
Ghidra processor specification for the Telink TC32
Telink TC32 Processor Specification for Ghidra
This repository contains a fairly complete processor specification for the Telink TC32 architecture, used by all of Telink's System-On-Chips. The work herein is based on Ryan Govostes' work and extended with various fix-ups and actual P-code implementation.
Right now decompilation is working well with several tested TC32 ELFs.
Usage
Copy the Telink_TC32 repository to Ghidra/Processors. Restart Ghidra
Afterwards, when importing a TC32 binary, when prompted for the binary's "Language", select the "Telink_TC32" processor.
For analysing Telink ELFs, I use the following process (using some plugins from my GhidraPlugins repo):
Import the binary, do not Auto-Analyse
Run the fix_funcnames.py plugin
Run the disas_symbols.py plugin
Run the Auto-Analysis, without call convention identification
Parse the register header file into the Data Type Manager (Grab the Telink SDK, redefine the REG_ADDR%X macros in register_82XX.h as integers instead of as pointers)
Export the register/defines values just imported from…
The HID descriptor, apart from the standard mouse report, exposes a vendor report with ID 5. This report has a feature (aka input and output interface) of 7 bytes. Reading from it returns nothing.
I started the house by the roof, instead of trying to fuzz the vendor report, I tried to dump the firmware "the hardware way". These chips have a SWIRE pin, which is a proprietary protocol for debugging and accessing the memory. This interface requires a Telink EVK tool, which isn't cheap, but GitHub user pvvx has written some tools for reading and writing the memory. The 826X tools are compatible with the 836x series.
After adapting the tools to use my FTDI ft2332HL board and dumps the memory and soldering cables to the reset and Swire pin (fortunately these pins aren't connected to anything), the dumper kinda worked. It was very unstable, but the data seemed good (it wasn't). Seeing the KNLT, Logitech, Wirless Receiver and TLSR8366 strings was a good confirmation that it wasn't just garbage.
I could have wasted countless days trying to work with this dump, hopefully, I decided to do things right and started fuzzing the HID vendor report using a fastly written Python script. The results were, let's say, very verbose. A lot of send commands responded with data. Also, I thought I bricked the device as the mouse stopped working, luckily a reset fixed it.
On the results instantaneously something catches my attention. When the fuzzer was changing the data of the second byte, the received data was like sliding.
My intuition was that we were reading memory!. I quickly write a dumper and Tachan! We got a more stable, easy and correct dump.
Beyond are the scripts I made for dumping, they are crappy and require the hid-tools package.
Analyzing the firmware dump
After renaming the symbols of the startup code according to the names on the boot c startup assembly code of the SDKs, I found that a constant was different from those. After a quick GitHub code search, VOILA here are the possible SDK they used.
I'm lazy, I wanted to generate a Fidb for Ghidra to automatically recognize functions from the SDK, especially the ones related to USB, but I didn't want to setup and compile the SDK. Fortunately, there was a precompiled bin with its symbols on one of the repos.
A great find
After generating a Fidb and doing an analysis, for me, it was clear to me that the firmware was a slightly modified version of the 8366_dongle project on that repo(I already had my suspicions at the moment I opened the disassembly of the bin).
After a little bit of digging, tachan! This seems to be the code that handles the vendor HID Report.
voidusb_host_cmd_proc(u8*pkt){externu8host_cmd[8];externu8host_cmd_paring_ok;u8chn_idx;u8test_mode_sel;u8cmd=0;staticemi_flg;if((host_cmd[0]==0x5)&&(host_cmd[2]==0x3)){host_cmd[0]=0;dongle_host_cmd1=host_cmd[1];if(dongle_host_cmd1>12&&dongle_host_cmd1<16){//soft paringhost_cmd_paring_ok=0;rf_paring_tick=clock_time();//update paring timeif(dongle_host_cmd1==13){//kb and mouse tolgethermouse_paring_enable=1;keyboard_paring_enable=1;}elseif(dongle_host_cmd1==14){//mouse onlymouse_paring_enable=1;}elseif(dongle_host_cmd1==15){//keyboard onlykeyboard_paring_enable=1;}}elseif(dongle_host_cmd1>0&&dongle_host_cmd1<13)//1-12:����EMI{emi_flg=1;cmd=1;irq_disable();reg_tmr_ctrl&=~FLD_TMR1_EN;//rf_stop_trx ();chn_idx=(dongle_host_cmd1-1)/4;test_mode_sel=(dongle_host_cmd1-1)%4;}}if(emi_flg){emi_process(cmd,chn_idx,test_mode_sel,pkt,dongle_cust_tx_power_emi);}}
/vendor/dongle/dongle_emi.c
Mouse Device ID
I think I also found the memory address where the current paired Mouse is stored(custom_binding[0]): 0x809160. I can't confirm it as I don't have another mouse and the value is a little bit off for me.
Potentially, this can be used to send a USB HID read memory and obtain the current mouse ID.
Ghidra symbols
Here are all the symbols I found, it can be imported with the ImportSymbolsScript.py.
USB HID custom commands
So, after analyzing the firmware, fuzzer output and doing some test, I found the following USB HID set feature commands.
1
2
3
4
5
6
7
Description
0xD
0x3
-
-
-
-
-
Software pairing: Mouse and keyboard
0xE
0x3
-
-
-
-
-
Software pairing: Mouse
0xF
0x3
-
-
-
-
-
Software pairing: Keyboard
0x1
0x3
-
-
-
-
-
EMI: channel low, mode carrier
0x2
0x3
-
-
-
-
-
EMI: channel low, mode cd
0x3
0x3
-
-
-
-
-
EMI: channel low, mode rx
0x4
0x3
-
-
-
-
-
EMI: channel low, mode tx
0x5
0x3
-
-
-
-
-
EMI: channel medium, mode carrier
0x6
0x3
-
-
-
-
-
EMI: channel medium, mode cd
0x7
0x3
-
-
-
-
-
EMI: channel medium, mode rx
0x8
0x3
-
-
-
-
-
EMI: channel medium, mode tx
0x9
0x3
-
-
-
-
-
EMI: channel high, mode carrier
0xA
0x3
-
-
-
-
-
EMI: channel high, mode cd
0xB
0x3
-
-
-
-
-
EMI: channel high, mode rx
0xC
0x3
-
-
-
-
-
EMI: channel high, mode tx
0xC0
addr&0xff
(addr>>8)&0xff
-
-
-
-
Memory: read 32 bits from addr + 0x800000
0xC1
addr&0xff
(addr>>8)&0xff
dat
-
-
-
Memory: write 8 bits dat to addr + 0x800000
0xC2
addr&0xff
(addr>>8)&0xff
dat&0xff
(dat>>8)&0xff
-
-
Memory: write 16 bits dat to addr + 0x800000
0xC3
addr&0xff
(addr>>8)&0xff
dat&0xff
(dat>>8)&0xff
(dat>>16)&0xff
(dat>>24)&0xff
Memory: write 32 bits dat to addr + 0x800000
0xC4
addr
-
-
-
-
-
Memory: read analog address addr
0xC5
addr
-
dat
-
-
-
Memory: write 8 bits dat at analog address addr
0xCC
0xF0
0x5A
-
-
-
-
Misc: "renumerates USB devices"
Notes: Take the italics entries with a grain of salt, as I didn't test it. Byte 0 is always the report ID, in this case 5.
It seems that software pairing is broken, Keyboard and Mouse pairing command always return success while the other 2 never succeed. Also, all the pairing commands disconnect the mouse, and it won't work until restarting the dongle.
Issuing the "renumerate" command will connect the device as a USB printer "Telink Semiconductor USB DevSys" with VID 248A and PID 5320. Maybe this is the "USB programming mode" for interfacing with Telink BDT tools? Taking a look at the sources of web BDT tool, the PID doesn't seem to match. So I thought it wasn't.
The analog read I think it reads the "3.3V analog registers" referenced in the datasheet.
Another "great" dump
Before I said that when the device renumerates as "Telink Semiconductor USB DevSys" maybe it is for the BDT tools, well after launching the desktop tools on Windows, it connects, so it is.
This is great as we can have total access to all the memory spaces and also some debugging functions.
Let's just say that the memory access tool, well, umh, it's not great. The CORE access it also seems to start at address 0x800000, but if we read 0x808000 it seems to contain errors, or maybe another program.
The analog read works like the USB HID analog read, the flash read always returns 0xFF (In theory this chip doesn't have flash at all) and unfortunately the OTP read doesn't work. This is bad news, as the OTP memory is likely to have something.