Close

Somebody set up us the bomb

A project log for Reverse-Engineering a low-cost USB CO₂ monitor

I'm trying to get data out of a relatively low-cost (80€) CO₂ monitor that appears to have a USB connection for data as well as for power

Henryk PlötzHenryk Plötz 04/17/2015 at 02:051 Comment

Luckily reverse engineering funky binary protocols is one of my specialities, so, let's see …

Data seems to arrive in 8-byte packets always. Indeed, this appears to be a property of what they did with the HID spec (more on that in a moment). The timing between 8-byte packets is irregular. In each packet there seem to be two groups of 4 byte each. The second byte of each group is always the same.

Getting more data by re-starting the Windows software makes things more confusing: The second byte of each group is still constant, but different now. It's numerically close though, so, time-based? This sucks.

Maybe I can learn something more from the USB HID device descriptor… (Intermission music playing while I google how to get the HID descriptor under Linux)

# echo 1-1.2:1.0 > /sys/bus/usb/drivers/usbhid/unbind
# lsusb -vd 04d9:a052

Bus 001 Device 014: ID 04d9:a052 Holtek Semiconductor, Inc.
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0         8
  idVendor           0x04d9 Holtek Semiconductor, Inc.
  idProduct          0xa052
  bcdDevice            1.00
  iManufacturer           1 Holtek
  iProduct                2 USB-zyTemp
  iSerial                 3 1.40
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           34
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.10
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      53
          Report Descriptor: (length is 53)
            Item(Global): Usage Page, data= [ 0x00 0xff ] 65280
                            (null)
            Item(Local ): Usage, data= [ 0x01 ] 1
                            (null)
            Item(Main  ): Collection, data= [ 0x01 ] 1
                            Application
            Item(Global): Logical Minimum, data= [ 0x00 ] 0
            Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
            Item(Local ): Usage Minimum, data= [ 0x00 ] 0
                            (null)
            Item(Local ): Usage Maximum, data= [ 0xff ] 255
                            (null)
            Item(Global): Report Count, data= [ 0x08 ] 8
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Main  ): Feature, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Logical Minimum, data= [ 0x00 ] 0
            Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
            Item(Local ): Usage Minimum, data= [ 0x00 ] 0
                            (null)
            Item(Local ): Usage Maximum, data= [ 0xff ] 255
                            (null)
            Item(Global): Report Count, data= [ 0x08 ] 8
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Main  ): Input, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Global): Logical Minimum, data= [ 0x00 ] 0
            Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
            Item(Local ): Usage Minimum, data= [ 0x00 ] 0
                            (null)
            Item(Local ): Usage Maximum, data= [ 0xff ] 255
                            (null)
            Item(Global): Report Count, data= [ 0x08 ] 8
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Main  ): Output, data= [ 0x02 ] 2
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Non_Volatile Bitfield
            Item(Main  ): End Collection, data=none
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0008  1x 8 bytes
        bInterval              10
Device Status:     0x0000
  (Bus Powered)
Now, that's just patently unhelpful. All it says is that there's report types that contain 8 items of values 0 through 255 each. In other words: 8 bytes. Yeah, I could have guessed that.

Okay, maybe looking more closely what the Windows software does will help me:

# modprobe usbmon
# wireshark -i usbmon1
will let me record (on the VM host) what the software does (in the VM guest) when started. And at least that's mildly enlightening:

which is an outgoing HID SET_REPORT with 8 bytes of data, after which the device starts sending reports of 8 bytes of data using INTERRUPT IN:

These 8-byte packets are then what I was seeing in the output of /dev/hidrawX.

Repeating different captures shows: The SET_REPORT generated by the windows software is different each time (though the DF byte at the end is constant). Also note that the device won't send anything before receiving this packet, so in order to gather data for analyzing on my Linux box I have to use the Windows software at least once after plugging the device in.

Discussions

Jarrett wrote 04/18/2015 at 17:21 point

Personally, I would go a more hardware-centric route for reverse engineering. If you check out that Russian link, there are two ICs that look sort of interesting. There's the big one, no datasheet available online, from my very brief searching. It'll be the brains. Then there's a small IC right near the USB port. It's very likely that that's just a bridge.

On the end of that bridge chip (pins 3 & 4), there are two lines that look like they might be going to the big IC. If you tap those with a logic analyser or BusPirate or similar, I would bet that it's an I2C, SPI, UART signal, something similar. And it will be a lot easier to decode the protocol there instead of dealing with USB junk, too.

That would be my strategy, anyway.

  Are you sure? yes | no