Loading a new file into IDA for the second time was easier. Again, I had to prepare it with
upx -d HIDApi.dll
and this time all functions were already nicely labeled for me. And again I spent a lot of time being stupid, starting here:
This is the ReadUSB function in HIDApi.dll, but it doesn't appear to be doing much more than calling ReadFile. Well, unless you notice the call to sub_10001320 in the bottom right box. And, yep, that function is important all right:
It seems to operate on 8 bytes, does a couple of xors, shifts, has magic constants and loops. About three hours of manual work leads to this equivalent Python function:
def decrypt(key, data): cstate = [0x48, 0x74, 0x65, 0x6D, 0x70, 0x39, 0x39, 0x65] shuffle = [2, 4, 0, 7, 1, 6, 5, 3] phase1 =  * 8 for i, o in enumerate(shuffle): phase1[o] = data[i] phase2 =  * 8 for i in range(8): phase2[i] = phase1[i] ^ key[i] phase3 =  * 8 for i in range(8): phase3[i] = ( (phase2[i] >> 3) | (phase2[ (i-1+8)%8 ] << 5) ) & 0xff ctmp =  * 8 for i in range(8): ctmp[i] = ( (cstate[i] >> 4) | (cstate[i]<<4) ) & 0xff out =  * 8 for i in range(8): out[i] = (0x100 + phase3[i] - ctmp[i]) & 0xff return out
Yeah, it's a decryption function for a (bad) encryption system. It operates in several phases:
- In the first box, the input is taken from [eax+1] through [eax+8] and shuffled around. [eax+1] ends up at [eax+3], [eax+2] ends up at [eax+5], etc. Note that the indices in the Python version are zero-based.
- The second box iterates over a key stored at byte_1001A750 and XORs it into the data.
- The third box shifts the entire data by 3 bits to the right, using [esp+18h+arg_0] as a temporary store for wrapping around. [eax+8] becomes ([eax+8] >> 3) | ([eax+7] << 5); [eax+7] becomes ([eax+7] >> 3) | ([eax+6] <<5); etc.
- The fourth box does something with a buffer starting at [esp+18h+var_10], using esi as the loop index. At first I assumed that would be some kind of cipher state, that's why I called it cstate in the Python code. It was initialized with fixed values in the first box, and here, in the fourth box, it's nibble swapped and written into a buffer at [esp+18h+var_8], without changing the original state. I called that ctmp in the python version.
- The sixth box finally calculates the end result of the data in [eax+1] ff. by subtracting [esp+14h+var_8] ff. from it (using ecx as the loop index).
And now the satisfying part: Applying this function to some data I sniffed earlier yields:
41 00 00 41 0D 00 00 00 43 0C 9F EE 0D 00 00 00 42 12 87 DB 0D 00 00 00 6D 03 E2 52 0D 00 00 00 6E 4E C7 83 0D 00 00 00 71 03 39 AD 0D 00 00 00 50 03 38 8B 0D 00 00 00 57 22 C0 39 0D 00 00 00Yeah \o/
There's the 0D line terminator we were promised, the checksum matches, and among other there are 42 and 50 data types. 0x338 is decimal 824, which matches the CO₂ readout at the time the sample was taken. I'm reasonably sure that the temperature wasn't 12.87°C though.
Now all we have to do is get the device to send data without the Windows tool and figure out how to interpret the wealth of data we're receiving.