More Reverse Engineering
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)