
Proof-of-Concept-Exploits für drei Schwachstellen im Firmware-Update-Mechanismus der Crucial-MX500-SSD, die Pufferüberläufe und potenzielle Codeausführung über ATA-Befehle ermöglichen.
Das betreffende Gerät ist jede SSD der MX500-Serie. Diese SSDs werden von einem Sillicon-Motion SM2259-Controller gesteuert (ältere Chargen hatten einen älteren Controller, Sillicon-Motion SM2258, aber der Hauptfokus dieses Dokuments liegt auf dem neueren).
SM2259 ist ein 4-Kanal SATA 6 Gb/s Mikrocontroller mit einer 32-Bit Little-Endian CPU basierend auf der ARC-Architektur.
Durch Beobachtung der aktuellsten Firmware zum Datum dieses Dokuments, nämlich M3CR046, wurden einige Probleme identifiziert und sowohl statisch als auch dynamisch bestätigt.
Alle Probleme wurden im Firmware-Update-Mechanismus des Controllers identifiziert, der dem Mikrocontroller-Handler des ATA PIO DOWNLOAD-MICROCODE (0x92)-Befehls entspricht, insbesondere in der Logik, die die Firmware mittels der Offset-Methode herunterlädt, was den Unterbefehlen 0x03 und 0x0E entspricht.
Alle in diesem Dokument behandelten Fehler wurden auf einer Crucial MX500 500 GB SSD (CT500MX500SSD1), SM2259H-AC-Controller mit M3CR046-FW und NY112-Flash-Chips, unter Verwendung eines PCs mit einer x86_64-CPU verifiziert.
Der FW-Code ist auf die Basisadresse 0x80020000 abgebildet, und der anfällige ATA-Handler befindet sich an Adresse 0x80024A9C. Eine dekompilierte Version der Funktion finden Sie unter resources/download_microcode_handler.c.
Für diejenigen, die sich lieber nicht mit den technischen Details befassen und stattdessen die Quintessenz verstehen möchten, verweise ich auf den FAQ-Abschnitt weiter unten.
Da M3CR046 mehrere Firmware-Images enthält, von denen das passende durch den Firmware-Update-Mechanismus ausgewählt wird (vielleicht abhängig von den tatsächlich verwendeten Flash-Chips oder anderen Hardwareeigenschaften), werden in diesem Dokument die Besonderheiten der ersten Firmware-Variante behandelt (da dies die Firmware ist, die auf unserem spezifischen Laufwerk unterstützt wird, und wir daher nur diese Firmware-Variante testen konnten). Die in diesem Dokument vorgestellten Fehler scheinen jedoch auf alle Firmware-Varianten zuzutreffen, aber bei der Reproduktion kann es zu einigen Unterschieden in den tatsächlichen Details kommen.
Dieses Problem betrifft Fälle, in denen der erste gesendete Block größer als 0x200 Sektoren ist. Wenn wir einen Blick in den ATA-Befehlshandler werfen, insbesondere auf die Logik, die ausgeführt wird, wenn die Blockgröße größer als die Sektorgröße ist und der Block der erste gesendete ist:

Dies setzt einige Variablen basierend auf dem nächsten Offset (der in unserem Fall, da wir bisher nur einen Block gesendet haben, der Länge des Blocks in Sektoren entspricht) und einer Variablen namens lower_bound_fw_offset, dem Block-Offset (d.h. Offset in Sektorgranularität) innerhalb des eingehenden Download-Images, an dem unser Firmware-Image erwartet wird. Dies ist ein hartcodierter Wert pro Firmware-Variante, der in unserem Fall (der ersten Variante) gleich 0 ist.
In diesem Fall kommt es beim Berechnen des Subtraktionsergebnisses für some_index zu einem Unterlauf, wodurch some_index bis zu 0xFFFF beträgt. Dies ist ein unerwartetes Verhalten, da basierend auf der Logik, die die Daten in den Download-Puffer verschiebt:

Beobachten wir, dass die Quelladresse, von der die Daten kopiert werden, angesichts des unerwarteten Werts für some_index möglicherweise nicht gültig ist.
Beim dynamischen Testen durch Senden einer Firmware-Update-Anfrage mit dem ersten Block größer als 0x200 Sektoren, hängt der Controller und sendet nicht einmal eine Antwort auf die ursprüngliche Anfrage. Dies ist konsistent und leicht reproduzierbar.
Es ist wahrscheinlich, dass dies auf einen ungültigen Verweis auf die berechnete Quelladresse zurückzuführen ist, der dann eine Ausnahme auslöst, die zum Hängen des Controllers führt. Dies wurde nicht bewiesen, sondern ist eher eine Vermutung, die das Hängen erklären könnte.
Das eingehende Download-Image (für M3CR046) hat eine Größe von 0x242400 Bytes, und in diesem Image befinden sich 3 interne Firmware-Images, von denen nur eines nach einem Firmware-Update-Prozess in den Flash geschrieben wird. Jedes dieser Images hat eine Größe von 0xC0C00 Bytes (oder 0x606 Sektoren).
Das bedeutet, dass der Firmware-Update-Mechanismus beim Extrahieren der korrekten Firmware-Kopie aus dem eingehenden Download-Image überprüfen muss, dass ihre Größe 0xC0C00 Bytes nicht überschreitet.
Der Controller versucht dies auch, aber es gibt einige Randfälle, die zu unerwartetem Verhalten führen können. Betrachten wir das folgende Snippet (das einen Teil des Codes mit dem vorherigen Fehler gemeinsam hat):

Wenn der aktuelle Block größer als 0x200 Sektoren ist und es nicht der erste Block in der Sequenz ist, werden 0x200 Sektoren (0x40000 Bytes) auf einmal kopiert. Dann gibt es eine Prüfung, deren Zweck es ist, die überschüssigen Bytes von der Anzahl der zu kopierenden Bytes abzuschneiden, wenn die Gesamtgröße des Firmware-Images higher_bound_fw_offset überschreitet (was in unserem Fall 0x606 Sektoren entspricht, da die Firmware-Größe genau dies sein sollte).
Diese Logik ergibt insgesamt Sinn, hat aber einen Fehler – wenn der letzte gesendete Block dazu führt, dass der nächste Offset zu hoch wird, so dass die überschüssige Anzahl von Bytes 0x200 Sektoren (oder 0x40000 Bytes) überschreitet, erhält curr_bytes_to_copy einen „negativen“ Wert, der auf etwa ~4GB (~0xFFFFFFFF) unterläuft. Wie zuvor gesehen, wird diese Variable verwendet, um die Anzahl der in den Download-Puffer zu übertragenden Bytes zu bestimmen.
Wenn wir einen Blick in r_maybe_some_efficient_data_transfer werfen, sehen wir das folgende Code-Stück:

Das bedeutet, dass die Kopiergröße auf 32MB begrenzt wird (von der ursprünglichen ~4GB Kopiergröße), aber das ist immer noch eine große Zahl, die auch zu einem undefinierten Verhalten führen kann, wenn der Speicherbereich ab 0x40000000 kleiner als 32MB ist.
Beim dynamischen Testen durch Senden von ATA-Blöcken, um einen Offset von 0x600 zu erreichen, und dann Senden eines großen Blocks der Größe 0x207 Sektoren, um den Unterlauf auszulösen, hängt der Controller erneut, wahrscheinlich aufgrund eines ungültigen Speicherzugriffs während des Kopiervorgangs.
Dieser Fehler ist interessanter als der vorherige, denn obwohl wir keinen kontrollierten Überschreibvorgang haben (sondern einen großen Überschreibvorgang, der möglicherweise eine Ausnahme auslöst, die den Controller hängen lässt), könnte die Funktion, die die Daten in den Download-Puffer verschiebt, tatsächlich so viele Daten übertragen, bevor sie abstürzt (wobei der Speicherbereich überschrieben wird, der sich direkt nach dem Download-Puffer im Hauptspeicher befindet), dann könnte das Verhalten des Ausnahme-Handlers möglicherweise basierend auf den überschriebenen Daten verändert werden. Dies könnte möglicherweise passieren, wenn beispielsweise der Ausnahme-Handler einen Zeiger aus dem überschriebenen Bereich liest und dann dorthin springt (dieser spezifische Fall ist nicht besonders wahrscheinlich, aber mit etwas mehr Forschung könnte so etwas entdeckt werden).