More Reverse Engineering

5 minute read

I did more reverse engineering on the DataRecorder Windows tool that comes with the TFA Klima-Logger hygrometer in order to find out which serial lines are used as input lines.

The evaluation version of IDA Pro Disassembler and Debugger did a great job here. The only thing I had to do was to load the executable DataRecorder.exe into the disassembler and find the places where the Win32 API function GetCommModemStatus gets called. A search for the function name revealed two sub-routines inside the huge pile of Assembler code. Looking at the references to data segment constants it was quite easy to determine which of the two sub-routines was the Assembler version of CI2CDriver::Sda() and which one was the Assembler version of CI2CDriver::Scl(). The first sub-routine was referencing an error message

Call to GetCommModemStatus in CI2CDriver::Sda() failed.

whereas the second was referencing the error message

Call to GetCommModemStatus in CI2CDriver::Scl() failed.

After identifying the two sub-routines I looked at their Assembler code and found the following suspicious fragment for CI2CDriver::Sda()

.text:0042CC30 ModemStat       = dword ptr -18h
...  
.text:0042CC84                 mov     [ebp+ModemStat], 0  
.text:0042CC8D                 lea     edx, [ebp+ModemStat]  
.text:0042CC90                 push    edx             ; lpModemStat  
...  
.text:0042CC9B                 call    ds:GetCommModemStatus  
...  
.text:0042CCF3                 mov     eax, [ebp+ModemStat]  
.text:0042CCF6                 and     eax, 10h  
.text:0042CCF9                 sub     eax, 10h  
.text:0042CCFC                 neg     eax  
.text:0042CCFE                 sbb     eax, eax  
.text:0042CD00                 inc     eax  
...  
.text:0042CD2B                 retn  

and this fragment for CI2CDriver::Scl():

.text:0042CC30 ModemStat       = dword ptr -18h  
...  
.text:0042CC84                 mov     [ebp+ModemStat], 0  
.text:0042CC8D                 lea     edx, [ebp+ModemStat]  
.text:0042CC90                 push    edx             ; lpModemStat  
...  
.text:0042CC9B                 call    ds:GetCommModemStatus  
...  
.text:0042CCF3                 mov     eax, [ebp+ModemStat]  
.text:0042CCF6                 and     eax, 20h  
.text:0042CCF9                 sub     eax, 20h  
.text:0042CCFC                 neg     eax  
.text:0042CCFE                 sbb     eax, eax  
.text:0042CD00                 inc     eax  
...  
.text:0042CD2B                 retn  

The function signature of GetCommModemStatus from the MSDN Library shows that the second parameter is a pointer to a long word:

BOOL GetCommModemStatus(HANDLE hFile, LPDWORD lpModemStat);

The out value parameter lpModemStat contains the pointer to the memory location that is referenced by ModemStat in the fragments above.
That led to the assumption that the first three lines of Assembler code, before the call to GetCommMomdemStatus, are preparing the call stack and the five lines of code after the call command are dealing with what was returned by the function from the out parameter. The interesting thing is that those result handling lines contain a constant 0x10h in one fragment and 0x20h in the other fragment. When looking at the MSDN Library description of the GetCommModemStatus function again it becomes clear which of the serial flag lines are used:

lpModemStat  
    [out] Pointer to a variable that specifies the current  
    state of the modem control-register values. This parameter  
    can be one or more of the following values.  

    Value   Meaning  
    MS_CTS_ON  
    0x0010  The CTS (clear-to-send) signal is on.  
    MS_DSR_ON  
    0x0020  The DSR (data-set-ready) signal is on.  

That means that the I2C’s serial clock SCL is read via the serial port’s DSR line and the I2C’s serial data SDA is read via the serial port’s CTS line.

The missing piece in the puzzle is whether a low voltage signal on the serial line means a logical 0 or whether it means a logical 1 for the SCL or the SDA signals. For that to find out it would have been sufficient to analyze the result code handling lines in the Assembler fragments above. Unfortunately I wasn’t sure about what exactly was happening on the arithmetic level. Therefore I decided to write a small Assembler program that performs the operations above and run it once with the DSR/CTS bits set and once with the bits cleared.

; tfa.s

section .data  
section .text  
    global main  
main:  
  mov     eax, 0h  
  and     eax, 10h  
  sub     eax, 10h  
  neg     eax  
  sbb     eax, eax  
  inc     eax  
exit:  
  mov     eax,1  
  xor     ebx,ebx  
  int     0x80  

The Assembler file can be compiled with NASM under Linux by using the following command:
   nasm -f elf  tfa.s
Afterwards it must be linked into an executable by using
   gcc -o tfa tfa.o
In order to see what is happening, the GNU Debugger was used with the following command sequence:

$>gdb sda  
(gdb) break main  
(gdb) run  
(gdb) set disassembly-flavor intel  
(gdb) disassemble main  
(gdb) stepi  
16              mov     eax, 0h  
(gdb) print/x $eax  
$1 = 0x0  
(gdb) stepi  
18              and     eax, 10h  
(gdb) print/x $eax  
$2 = 0x0  
(gdb) stepi  
20              sub     eax, 10h  
(gdb) print/x $eax  
$3 = 0x0  
(gdb) stepi  
22              neg     eax  
(gdb) print/x $eax  
$4 = 0xfffffff0  
(gdb) stepi  
24              sbb     eax, eax  
(gdb) print/x $eax  
$5 = 0x10  
(gdb) stepi  
26              inc     eax  
(gdb) print/x $eax  
$6 = 0xffffffff  
(gdb) stepi  
30              mov     eax,1   ;system call number (sys_exit)  
(gdb) print/x $eax  
$7 = 0x0  

The result was when the eax register is loaded with 0x10h then the final value of the eax register is 1 and when it gets loaded with value 0x00h then the final value is 0.
That means that the arithmetic Assembler operations return 0 when the serial line flags are cleared and 1 when the flags are set, i.e. no inversion is done here.

With the findings from the reverse engineering session above it is clear now which serial port lines are used for which I2C communication lines:

SCL Input  - DSR (High - High)  
SDA Input  - CTS (High - High)  
SCL Output - DTR (High - Low)  
SDA Output - RTS (High - Low)