How are you, gentlemen

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/26/2015 at 18:400 Comments

Now that I've had my breakthrough I can leisurely stroll around the rest of the code for completeness sake. This should show me how and what data is being sent to the device to initiate data reporting.

First up: WriteUSB in HIDApi.dll. Unsurprisingly this calls an encryption function sub_100011E0 immediately before doing WriteFile, which looks very much like the decryption function but in reverse (Actually that's an assumption: I haven't properly analyzed it):

During my interactive testing with the IDA debugger I've never seen WriteUSB called though, so it's not related to device initialization and therefore not of immediate interest. In some of the documentation I saw functionality described that would allow you to send a calibration offset to the device to correct for sensor drift over time, so that's probably what WriteUSB will be used for. I might have to come back to it at a later time.

USBInit is disappointingly short:

(I've labeled byte_1001A750, which you might remember from the last post, as usb_enc_key.)

This function simply initializes the memory where the encryption key will be stored with a fixed value. I've never seen this value used, so it must be overwritten somewhere and the initial SET_REPORT must be generated.

After looking through the cross references on usb_enc_key I found this code in FindUSB:

It does two things: First it calls sub_100010A0 with a pointer to usb_enc_key, then it prepares arguments to HidD_SetFeature and calls it. The call to HidD_SetFeature gets three arguments: esi (was set somewhere before this code part, I assume it's a reference to the device to use), a pointer to esp+…+var_C and the number 9. esp+…+var_C seems to be a buffer of 9 bytes which first is initialized to zero in three steps (mov [esp+…+var_c], eax; mov [esp+…+var_8]+eax; mov [esp+…+var_4], al; where eax contains 0) and then the usb_enc_key is copied into offset 1 of it. So it contains a zero byte followed by usb_enc_key. Funnily enough we'll later see the same structure when we write Linux code.

sub_100010A0 now is what actually initializes usb_enc_key with some pseudo-random stuff. It starts with a call to _time, has lots of magic numbers, and xors, and calls to _localtime. I post it here only for completeness' sake, I haven't actually looked at it in depth:

So, still what remains is getting data reporting started with Linux code. We'll look at both Python (because that's my preferred language) and Perl (because that's what FHEM is written in) implementations.