evilbeast 发表于 2023-10-24 15:08

外星人笔记本键盘USB协议逆向

本帖最后由 evilbeast 于 2023-10-24 15:11 编辑

---
### 前言

我朋友一台 dell g16 购买时直接安装了linux系统,但是linux上没有官方的键盘控制中心,所以无法控制键盘灯光,于是我就想着能不能逆向一下键盘的协议,然后自己写一个控制键盘灯光的程序。我自己的外星人笔记本是m16,所以我就先从m16开始逆向。



### USB协议分析

通过 chatgpt 得知,AlienFX设备通常通过USB接口连接到计算机。键盘的灯光控制是通过HID (人机接口设备) 协议进行的。当你使用AlienFX软件时,这些程序会发送特定的命令到键盘,告诉它如何设置灯光效果。

现在wireshark已经支持HID协议的解析,所以我们可以直接使用wireshark来分析USB协议。在安装wireshark是需要勾选安装`USBPcap`



打开wireshark,选择USBPcap1



设置要捕获的usb设备,然后点击start



打开 `alienware command center`,设置键盘灯光,在灯光效果除设置颜色为红色,然后点击应用。




然后我们就可以在wireshark中看到usb协议的数据包了, 我们可以看到有两个数据包,一个是发送数据包,一个是接收数据包。可以通过设置过滤器来过滤掉接收数据包,只看发送数据包,过滤设置为 `usb.src == "host"`



发现仅仅是改了一个按键的颜色,就发送了很多数据包,而且每个数据包的长度都不一样,这是因为每个数据包都是一个命令,而且每个命令的长度都不一样,所以我们需要找到每个命令的格式,然后才能解析出每个命令的含义。

### 验证数据包

这个时候我们需要写一个测试程序,来分析哪一个包让键盘改变了颜色,然后再分析这个包的格式。这里我们使用python将数据重发到usb设备,然后观察键盘的变化。

```python
import logging
import time

import usb
from usb import USBError


class AlienwareUSBDriver:
    VENDOR_ID = 0xd62
    PRODUCT_ID = 0xc2b0

    SEND_BM_REQUEST_TYPE = 0x21
    SEND_B_REQUEST = 0x09
    SEND_W_VALUE = 0x3cc
    SEND_W_INDEX = 0x0

    PACKET_LENGTH = 63

    def __init__(self):
      self._control_taken = False
      self._device = None

    def acquire(self):
      """ Acquire control of the USB controller."""
      if self._control_taken:
            return

      self._device = usb.core.find(idVendor=AlienwareUSBDriver.VENDOR_ID, idProduct=AlienwareUSBDriver.PRODUCT_ID)

      if self._device is None:
            logging.error("ERROR: No AlienFX USB controller found; tried VID {}, PID {}"
                        .format(AlienwareUSBDriver.VENDOR_ID, AlienwareUSBDriver.PRODUCT_ID))

      try:
            self._device.set_configuration()
      except USBError as exc:
            logging.error("Cant set configuration. Error : {}".format(exc.strerror))

      try:
            usb.util.claim_interface(self._device, 0)
      except USBError as exc:
            logging.error("Cant claim interface. Error : {}".format(exc.strerror))

      self._control_taken = True
      logging.debug("USB device acquired, VID={}, PID={}".format(hex(AlienwareUSBDriver.VENDOR_ID),
                                                                   hex(AlienwareUSBDriver.PRODUCT_ID)))

    def release(self):
      if not self._control_taken:
            return

      try:
            usb.util.release_interface(self._device, 0)
      except USBError as exc:
            logging.error("Cant release interface. Error : {}".format(exc.strerror))

      try:
            self._device.attach_kernel_driver(0)
      except USBError as exc:
            logging.error("Cant re-attach. Error : {}".format(exc.strerror))

      self._control_taken = False
      logging.debug("USB device released, VID={}, PID={}".format(hex(AlienwareUSBDriver.VENDOR_ID),
                                                                   hex(AlienwareUSBDriver.PRODUCT_ID)))

    def write_packet(self, pkt):
      if not self._control_taken:
            return

      try:
            num_bytes_sent = self._device.ctrl_transfer(
                self.SEND_BM_REQUEST_TYPE, self.SEND_B_REQUEST,
                self.SEND_W_VALUE, self.SEND_W_INDEX,
                pkt, 0)

            logging.debug("wrote: {}, {} bytes".format(pkt, len(pkt)))
            if len(pkt) != num_bytes_sent:
                logging.error("writePacket: intended to write {} of {} bytes but wrote {} bytes"
                              .format(pkt, len(pkt), num_bytes_sent))

            return num_bytes_sent
      except USBError as exc:
            logging.error("writePacket: {}".format(exc))

```

其中设备信息可以在wireshark中查看



```python
VENDOR_ID = 0xd62
PRODUCT_ID = 0xc2b0
```

在使用 `device.ctrl_transfer` 发送数据时需要指定 `bmRequestType`, `bRequest`, `wValue`, `wIndex`,这些信息也可以在wireshark中查看



```python
OUT_BM_REQUEST_TYPE = 0x21
OUT_B_REQUEST = 0x09
OUT_W_VALUE = 0x3cc
OUT_W_INDEX = 0x0
```

尝试将wireshark中的数据包发送到键盘,通过测试发现,其中一条数据包发送后,Q键的灯光才会改变

```python
if __name__ == '__main__':
    device = AlienwareUSBDriver()
    device.acquire()

    data = bytes.fromhex('cc8c020073072f46121278b56519a6f9661799e568127ab7691aaaff6a2aaaff6c2aaaff6e137fbf7019a6f9711aaaff8608334b8708334b8808334b2bfc0000')
    device.write_packet(data)
```

### 数据包格式分析

通过分析得知,每次改变颜色,awcc 会通过`CC 8C 02 00` 命令把所有按键的颜色发送一遍 ,经过多次测试,发现每个按键的颜色都是由三个字节表示,分别是 `R` `G` `B`,所以我们可以通过改变这三个字节来改变按键的颜色。包格式如下:



经过多次尝试后,将整个键盘的对应序号,得到如下表格

| Key         | Code | Key       | Code | Key       | Code | Key       | Code |
|-------------|------|-----------|------|-----------|------|-----------|------|
| esc         | 1    | f4      | 5    | u         | 0x31 | lshift    | 0x52 |
| f1          | 2    | f5      | 6    | i         | 0x32 | z         | 0x54 |
| f2          | 3    | f6      | 7    | o         | 0x33 | x         | 0x55 |
| f3          | 4    | f7      | 8    | p         | 0x34 | c         | 0x56 |
| f8          | 9    | 3         | 0x18 | [         | 0x35 | v         | 0x57 |
| f9          | 0xa| 4         | 0x19 | ]         | 0x36 | b         | 0x58 |
| f10         | 0xb| 5         | 0x1a | \\      | 0x38 | n         | 0x59 |
| f11         | 0xc| 6         | 0x1b | a         | 0x3f | m         | 0x5a |
| f12         | 0xd| 7         | 0x1c | s         | 0x40 | ,         | 0x5b |
| home      | 0xe| 8         | 0x1d | d         | 0x41 | .         | 0x5c |
| end         | 0xf| 9         | 0x1e | f         | 0x42 | /         | 0x5d |
| del         | 0x10 | 0         | 0x1f | g         | 0x43 | rshift    | 0x5f |
| `         | 0x15 | -         | 0x20 | h         | 0x44 | up      | 0x73 |
| 1         | 0x16 | =         | 0x21 | j         | 0x45 | lctrl   | 0x65 |
| 2         | 0x17 | back      | 0x24 | k         | 0x46 | fn      | 0x66 |
| tab         | 0x29 | caps      | 0x3e | l         | 0x47 | lwin      | 0x68 |
| q         | 0x2b | enter   | 0x4b | ;         | 0x48 | lalt      | 0x69 |
| w         | 0x2c | space   | 0x6a | '         | 0x49 | ralt      | 0x70 |
| e         | 0x2d | rwin      | 0x6e | right   | 0x88 | rctrl   | 0x71 |
| r         | 0x2e | ralt      | 0x70 | down      | 0x87 | left      | 0x86 |
| t         | 0x2f | microphone| 0x14 | voice0    | 0x11 | voice+    | 0x13 |
| y         | 0x30 | voice-    | 0x12 | voice+    | 0x13 | voice-    | 0x12 |


可以用一个例子来验证上面的keymap是否正确

```python
if __name__ == '__main__':
    device = AlienwareUSBDriver()
    device.acquire()


    keymap = {
      'esc': 1, 'f1': 2, 'f2': 3, 'f3': 4, 'f4': 5, 'f5': 6, 'f6': 7, 'f7': 8, 'f8': 9, 'f9': 0xa, 'f10': 0xb, 'f11': 0xc, 'f12': 0xd, 'home': 0xe, 'end': 0xf,
      'del': 0x10, '`': 0x15, '1': 0x16, '2': 0x17, '3': 0x18, '4': 0x19, '5': 0x1a, '6': 0x1b, '7': 0x1c, '8': 0x1d, '9': 0x1e, '0': 0x1f, '-': 0x20, '=': 0x21,
      'back': 0x24, 'microphone': 0x14, 'tab': 0x29, 'q': 0x2b, 'w': 0x2c, 'e': 0x2d, 'r': 0x2e, 't': 0x2f, 'y': 0x30, 'u': 0x31, 'i': 0x32, 'o': 0x33, 'p': 0x34,
      '[': 0x35, ']': 0x36, '\\': 0x38, 'voice0': 0x11, 'caps': 0x3e, 'a': 0x3f, 's': 0x40, 'd': 0x41, 'f': 0x42, 'g': 0x43, 'h': 0x44, 'j': 0x45, 'k': 0x46,
      'l': 0x47, ';': 0x48, '\'': 0x49, 'enter': 0x4b, 'voice+': 0x13, 'lshift': 0x52, 'z': 0x54, 'x': 0x55, 'c': 0x56, 'v': 0x57, 'b': 0x58, 'n': 0x59, 'm': 0x5a,
      ',': 0x5b, '.': 0x5c, '/': 0x5d, 'rshift': 0x5f, 'up': 0x73, 'voice-': 0x12, 'lctrl': 0x65, 'fn': 0x66, 'lwin': 0x68, 'lalt': 0x69, 'space': 0x6a, 'ralt': 0x70,
      'rwin': 0x6e, 'rctrl': 0x71, 'left': 0x86, 'down': 0x87, 'right': 0x88
    }

    def get_key_bytes(a, b, a_color, b_color):
      header = bytes.fromhex('cc8c0200')
      a_bytes = (a << 24 | a_color).to_bytes(4, byteorder='big')
      b_bytes = (b << 24 | b_color).to_bytes(4, byteorder='big')

      data = header + a_bytes + b_bytes
      out = data + bytes(64 - len(data))
      return out


    chars = list(keymap.keys())
    a = 0
    a_color = 0
    b = 0
    b_color = 0
    for i, k in enumerate(chars):
      key = keymap
      a = key
      a_color = 0xff0000
      b = 0
      b_color = 0
      if i > 0:
            b = keymap]
            b_color = 0x00ff00
      device.write_packet(get_key_bytes(a, b, a_color, b_color))
      time.sleep(0.3)

    a_color = 0x00ff00
    b_color = 0x00ff00
    device.write_packet(get_key_bytes(a, b, a_color, b_color))
```

效果如下:


这样我们就可以根据需要,来动态设置每个按键的颜色了。

### 其他命令

其中还有清除灯光的命令,格式如下:

```
cc8c1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
```

关闭波动灯光的命令,格式如下:

```
cc8c0500010101010101010101010101010101010101010101010101010101010101010101010101000000000100010101010101010101010101010100000000
cc8c0600000101010101010101010101010101000000000000010001010101010101010101000100000000000101000101010001000100010100010000000000
```

开启波动灯光的命令,格式如下:

```
cc800305000001010101000000000000050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
```

### 最后

在分析过程中,也是花费了不少时间, 主要是 `alineware command center` 在全键盘设置成一个颜色时,会发送很多数据包,而且每个键对应的颜色值还不一样,我以为有特殊的算法

比如我设置成绿色时,发送的数据包如下:



他会为每个键随机生成颜色相近的值, 这些值同样是以十六进制表示的RGB颜色代码。我们可以解析这些代码来看看这些颜色是否相似。

以下是解析的颜色及其RGB值:

1. 00 3F 11 - RGB(0, 63, 17)
2. 00 FF 4A - RGB(0, 255, 74)
3. 00 8E 29 - RGB(0, 142, 41)
4. 00 91 2A - RGB(0, 145, 42)
5. 00 A3 2F - RGB(0, 163, 47)
6. 00 9E 2D - RGB(0, 158, 45)
7. 00 9B 2D - RGB(0, 155, 45)
8. 00 99 2C - RGB(0, 153, 44)
9. 00 A0 2E - RGB(0, 160, 46)
10. 00 91 2A - RGB(0, 145, 42)
11. 00 8E 29 - RGB(0, 142, 41)
12. 00 9B 2D - RGB(0, 155, 45)
13. 00 CC 3B - RGB(0, 204, 59)
14. 00 8E 29 - RGB(0, 142, 41)
15. 00 FF 4A - RGB(0, 255, 74)

从这些解析的值可以看出,这些颜色大多是绿色调,但是其中有不同的亮度和饱和度。例如,00 FF 4A是一个明亮的绿色,而00 8E 29是一个相对较深的绿色。

总的来说,这些颜色都是绿色调,并且大部分的颜色是相似的。不过,其中的一些颜色(如00 FF 4A)会显得明显更亮和饱和。所以,这些颜色大部分是相似的。

abpyu 发表于 2023-10-25 09:05

所以,根据楼主的测试来看,这个对于厂商来说,再简单不过的,但是厂商就是不做
为什么不做?
打心眼里认为选择非windows系统的无非就是因为省掉win系统的钱
这就是厂商的态度!
就是没态度!
鄙视这种厂商

fortytwo 发表于 2023-10-24 15:50

可以有耐心的!这种不是明文的协议,虽然不带混淆加密,但是分析起来还是很花时间。

猫携 发表于 2023-10-24 15:28

厉害,新技能get

Nevatu 发表于 2023-10-24 15:38

厉害厉害!!

heyuyanxop 发表于 2023-10-24 15:42

这个牛皮啊

dodoxia 发表于 2023-10-24 15:47

大神!!!顶礼膜拜!

6699902a 发表于 2023-10-24 15:51

学习了。谢谢大神分享{:1_893:}

JuncoJet 发表于 2023-10-24 16:00

买不起 我键盘都是白嫖的

LuckyClover 发表于 2023-10-24 16:21

呐,这就叫专业

比海还大的沙滩 发表于 2023-10-24 16:39

学习了。
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 外星人笔记本键盘USB协议逆向