Aus reinem Interesse und Spaß am tüfteln habe ich mir mal den Kilometerstand vom Convers+ vorgenommen.
Letztlich ist es mir gelungen die Kodierung desselben zu knacken, sprich den Verschlüsselungs/Entschlüsselungs-Algorithmus
Nur so zur Einstumming ein paar Bilder:
Zur Technik:
Das Convers+ (IPC) speichert seinen Kilometerstand in einem externen EEPROM (3) vom Typ 24C16 ab. Dieses hat eine Kapazität von 2 KByte und schafft mindestens 1 Million Schreibzyklen. Neben dem Gesamtkilometerzähler ist dort auch der Tageskilometerzähler und viele andere Parameter und Daten vom Convers+ gespeichert.
Der Zentralprozessor (1) ist ein MAC7116 von Freescale, ein typischer Vertreter in Automotive-Elektronik-Komponenten. Der EEPROM ist via I2C mit diesem verbunden. Beim Booten, also wenn Strom aufs IPC gelegt wird liest der Prozessor den Inhalt des EEPROMs ein und prüft ob alles passt. In den darin enthaltenen Werten weiss das IPC dann wie und was es anzuzeigen hat (Sprache, Meilen/KM, Fahrzeugoptionen, Kilometerstände, usw.).
Während der Fahrt erhält die Firmware ein Signal über den MS-CAN Bus welches anzeigt, welche Wegstrecke zurückgelegt wurde. Und das geht so:
Das BCM sendet in CAN ID 040 alle 50 ms einen Wegstreckenwert in Byte D6 (wenn man die Bytes von rechts nach links aufbaut, also D7..D0, das vorletzte). Ändert sich der Wert nicht, steht das Fahrzeug. Bewegt es sich, egal ob forwärts oder rückwärts, ist der Wert der nachfolgenden Nachricht höher als der vorherige. Da es sich um einen 1-Byte Wert handelt (Wertebereich 0x00 bis 0xFF) findet oberhalb 0xFF ein Überlauf zu 0x00 statt, d.h. der Wert beginnt wieder ab 0x00 so als hätte man die Stellen ab Position drei weggeschnitten. Die Differenz der Änderung ist gleichzeitig ein Maß für die Geschwindigkeit, dennoch wird dieses Byte ausschließlich für die Entfernungsdarstellung verwendet.
Ich habe mir, nachdem ich das Byte aus CAN-Logs mittels der Binärbaum-Methode isoliert hatte, einen kleinen Generator programmiert, der ein CANHacker Tracefile erzeugt in dem nur diese ID und die für Zündung gesendet wird. Damit habe ich ermittelt das Differenzwerte bis 0xC8 vom IPC akzeptiert werden, größere Werte aber keine Änderung zur Folge haben und diese vermutlich unplausibel wären.
Somit konnte ich den Kilometerzähler hochlaufen lassen und die Werte des EEPROM immer wieder einlesen und vergleichen. Dabei viel mir auf das nach einem Poweron die Nachkommastelle des Tageskilometerzählers immer 0 war, egal was vorher da stand. Es wurde auf, bzw. abgerundet. Damit hatte ich die Vermutung das der Kilometerstand nur relativ selten abgespeichert wird, was auch die Beschreibbarkeitszyklen des EEPROM anders garnicht hergäben.
Relativ schnell war damit klar das sich der Speicherort für den Kilometerstand 20 Bytes ab der EEPROM-Adresse 0x774 befand:
Ein zusätzlicher, 4 Byte langer an Position 0x788. Hiermit kam ich aber nicht endgültig ans Ziel, nur nahe dran. Ich tüftelte eine ganze Weile mit Bekannten an der Verschlüsselung. Entscheidend war das Disassemblen der IPC-Firmware, die den wahren Algorithmus dann preis gab.
Der Kilometerstand wird in fünf 4 Byte-Gruppen kodiert. Jede 4 Byte Gruppe ist mit einer Prüfsumme (CRC) und einem Parity-Bit (Gerade/Ungerade Anzahl von 1-Bits im Wert) gesichert. Die Prüfsummen kommen aus einer CRC-Table mit 16 Einträgen aus dem 1 MByte großen Firmwareteil (hier z.B. "7M2T-14C026-AG") des IPC:
Ich will das Verfahren anhand eines Beispiels erläutern. Ich nehme folgende Daten aus einem EEPROM wo das IPC den Kilometerstand 249.510 km anzeigt:
0x774: 0x79, 0xD9, 0xFF, 0x26,
0x778: 0x79, 0xD9, 0xFF, 0xA2,
0x77C: 0x79, 0xD9, 0xFF, 0x23,
0x780: 0x79, 0xD9, 0xFF, 0xA4,
0x784: 0x79, 0xD9, 0xFF, 0x25
Zuerst lesen wir das erste WORD (2 Byte, Big Endian) ab Adresse 0x774 = 0x79D9 und das zweite WORD ab Adresse 0x776 = 0xFF26 in einen Buffer. Zur Berechnung der CRC gehen wir so vor:
DWORD km1sum = 0x00000000
WORD km2sum = 0x0000
WORD w1 = 0x79D9
WORD w2 = 0xFF26
WORD km1 = calc_crc(w1, w2); # km1=0x079D, 0x079D, 0x079D, 0x079D, 0x079D
WORD km2 = calc_parity(w1, w2); # km2=0x0027, 0x0023, 0x0024, 0x0025, 0x0026
km1sum = km1sum + km1;
km2sum = km2sum + km2;
function calc_crc:
BYTE ub = w2 / 0x100 # ub = 0xFF
WORD w = w1 / 0x10 # w = 0x079D
ub = NOT ub # ub = 0x00
ub = ub AND 0xF0 # ub = 0x00
ub = ub * 0x100 # ub = 0x00
w = w OR ub # w = 0x079D
BYTE crc = 0x0F;
for (i = 0; i < 4; i++)
crc = CRCTABLE[crc] XOR (w AND 0x0F)
w = w / 0x10
# i=0: crc=0x08, w=0x0079
# i=1: crc=0x08, w=0x0007
# i=2: crc=0x06, w=0x0000
# i=3: crc=0x09, w=0x0000
endfor
# die Prüfsumme ist 0x09
if w1 AND 0x0F) == crc # (w1 AND 0xF) = 0x09
return w # 0x079D (=upper 12 Bits of w1)
else
return 0xFFFF
endif
function calc_parity:
BYTE ub = (w2 AND 0xFF); # w2=0x0026, w2=0x00A2
if ODD_PARITY(ub) # ist parität von ub ungerade? 0x26=0b00100110 = 3 einsen = Ungerade parität
return (ub AND 0x7F) + 1; # ub AND 0x7F = 0x26 + 1 = 0x27
else
return 0
endif
Alles anzeigen
Das ganze wird dann noch 4mal wiederholt, jeweils für die folgenden 4 Byte-Gruppen. Am Ende haben wir dann:
# km1sum=0x2611
# km2sum=0x00B9
DWORD dw = (km1sum * 0x80) + km2sum # dw=0x130A39
WORD km = (dw / 5) + 1 # km=0x3CEA6 = 249510
Mehr ist es nicht
Somit ist dann auch klar das nur alle 200 Meter ein Kilometerstand ins EEPROM gespeichert wird, daher die Division durch 5.
Durch den Trick, einen die sich häufig ändernden niedrigen Werte des Kilometerstandes über 5 Speicherstellen zu verteilen, erhöht sich die Beschreibbarkeit des EEPROM um diesen Faktor. Somit wird faktisch eine Million Kilometer Gesamtreichweite sichergestellt. Die im IPC angezeigten Werte liegen im RAM desselben und halten natürlich immer den aktuellen Wert.
Um nun aus einem Wunsch-Kilometerstand die richtige Bytefolge zu machen muss man den Algorithmus nur "rumdrehen". Lesen und schreiben lässt sich das EEPROM mit jedem X-Beliebigen I2C Programmer für wenige Cent auf Ebay. Je nach Programmer auch ohne den EEPROM auszulöten. Darüber hinaus ist es auch möglich diesen mittels UDS über den CAN-Bus zu ändern. Hierzu schreibt man einen Fake-Bootloader fürs IPC, gaukelt ein Update vor und lässt den Bootloader im RAM ausführen. Dieser ändert dann das ab IO-Map Adresse 0x6000000 gemappte I2C EEPROM ab, macht einen Reset und schwub, hat man seinen gewünschten Kilometerstand.
Naja, zumindest im IPC. Der wird aber aus Sicherheitsgründen wohl noch in ein paar anderen Modulen abgelegt. Das aber nur dazu wie einfach es ist einen Tacho zu manipulieren.