
CVE-2018-4185: iOS 11.2-11.2.6 Offenlegung von Kernel-Zeigern, eingeführt durch Apples Meltdown-Abschwächung.
iOS 11.2 führte einen Kernel-Informationsleck ein, mit dem der kASLR-Slide bestimmt werden konnte.
Das Problem war das Ergebnis einer neu hinzugefügten Funktion, __ARM_KERNEL_PROTECT__, die
versehentlich dazu führte, dass die Adresse der Kernel-Funktion Lel0_synchronous_vector_64_long im
Register x18 erschien, wenn die Werte der Register eines Threads mit thread_get_state abgerufen
wurden. Das Problem wurde entdeckt, als Kernel-Zeiger in iOS-App-Absturzprotokollen auftauchten.
In iOS 11.2 führte Apple auf arm64 eine Funktion namens __ARM_KERNEL_PROTECT__ ein. Laut einem
Kommentar in osfmk/arm64/proc_reg.h:
__ARM_KERNEL_PROTECT__ ist eine Funktion, die vor möglichen
architektonischen oder mikroarchitektonischen Schwachstellen schützen soll,
die es Kernen ermöglichen könnten, EL1-exklusive Zuordnungen im EL0-Modus zu
lesen/auf sie zuzugreifen. Dies wird erreicht, indem so viele Zuordnungen
wie möglich entfernt werden, wenn der Kern vom EL1-Modus in den EL0-Modus
wechselt, und diese Zuordnungen wiederhergestellt werden, wenn der Kern vom
EL0-Modus in den EL1-Modus wechselt.
Das heißt, beim Übergang von EL1 (Kernel-Modus) zu EL0 (Benutzermodus) werden so viele Kernel- Zuordnungen wie möglich entfernt. Dies sollte die mögliche Angriffsfläche gegen Kernel-Speicher- zuordnungen bei der Ausnutzung mikroarchitektonischer Schwachstellen wie Spectre oder Meltdown begrenzen.
Wenn man sich den Diff zwischen den XNU-Versionen 4570.20.62 und 4570.31.3 ansieht, findet man eine
Reihe neuer Verweise auf das Register x18 in der Datei osfmk/arm64/locore.s im Zusammenhang mit __ARM_KERNEL_PROTECT__. Insbesondere sieht man, dass der
Ausnahmebehandlungsvektor Lel0_synchronous_vector_64, der bei einem Systemaufruf (Anweisung
svc #0) aufgerufen wird, nun wie folgt aussieht:
.text
.align 7
Lel0_synchronous_vector_64:
MAP_KERNEL
BRANCH_TO_KVA_VECTOR Lel0_synchronous_vector_64_long, 8
Das Makro BRANCH_TO_KVA_VECTOR ist wie folgt definiert:
.macro BRANCH_TO_KVA_VECTOR
#if __ARM_KERNEL_PROTECT__
/*
* Find the kernelcache table for the exception vectors by accessing
* the per-CPU data.
*/
mrs x18, TPIDR_EL1
ldr x18, [x18, ACT_CPUDATAP]
ldr x18, [x18, CPU_EXC_VECTORS]
/*
* Get the handler for this exception and jump to it.
*/
ldr x18, [x18, #($1 << 3)]
br x18
#else
b $0
#endif /* __ARM_KERNEL_PROTECT__ */
.endmacro
Dieses Makro führt einen indirekten Sprung zur tatsächlichen
Ausnahmebehandlungsvektor-Implementierung, Lel0_synchronous_vector_64_long, indem ein Zeiger
auf diese Funktion in das Register x18 geladen wird. Beachten Sie jedoch, dass diese
Überschreibung von x18 stattfindet, bevor die Userspace-Register von der Funktion
fleh_dispatch64 gespeichert werden, die von Lel0_synchronous_vector_64_long aufgerufen wird.
Das bedeutet, dass x18 beim Speichern der Benutzerregister tatsächlich ein Zeiger auf
Lel0_synchronous_vector_64_long ist und nicht der ursprüngliche Wert aus dem Userspace.
Obwohl x18 bei der Rückkehr von Ausnahmen gelöscht wird, ist das Speichern eines Kernel-Zeigers
im Benutzerregisterzustand problematisch, da thread_get_state verwendet werden kann, um den
gespeicherten Zustand der Benutzerregister zurück in den Userspace zu kopieren, einschließlich
des Werts von Register x18. Ein Thread muss lediglich thread_get_state für sich selbst
aufrufen und sich den gemeldeten Wert von x18 ansehen, um die Adresse der Funktion
Lel0_synchronous_vector_64_long zu erhalten. Dadurch wird es trivial, den kASLR-Slide zu
bestimmen, indem man den so erhaltenen Wert von x18 von der statischen Adresse von
Lel0_synchronous_vector_64_long abzieht.
Wie oben erwähnt, ist die Ausnutzung trivial: Rufen Sie einfach die Funktion thread_get_state
auf, sehen Sie sich den Wert für Register x18 an und ziehen Sie die statische Adresse der
Kernel-Funktion Lel0_synchronous_vector_64_long davon ab.
Ich entdeckte dieses Problem am 26. Februar 2018, nachdem ich einen Kernel-Zeiger im Register
x18 eines iOS-App-Absturzprotokolls bemerkte. Eine schnelle Überprüfung zeigte, dass derselbe
Wert in Register x18 jedes Absturzprotokolls auf dem Gerät erschien, was auf einen schwerwiegenden
Informationsleck hindeutete.
Als nächstes versuchte ich durch Experimente herauszufinden, was genau mit Register x18 los war.
Ich setzte einen Haltepunkt in einer leeren iOS-App und verwendete lldb, um den Wert von Register
x18 zu lesen, wodurch ich bestätigte, dass der Leck nicht auf abstürzende Anwendungen beschränkt
war. Als nächstes versuchte ich, den Wert von x18 mit Inline-Assembly zu lesen, und stellte fest,
dass der erhaltene Wert nicht mit dem Wert übereinstimmte, den der Debugger bei Verwendung eines
Befehls wie reg read x18 anzeigte. Dies deutete darauf hin, dass der Leck möglicherweise wirklich
in thread_get_state lag und dass Register x18 tatsächlich keinen Kernel-Zeiger enthielt,
während die CPU im Userspace ausgeführt wurde. Ein schneller Proof-of-Concept, der den Wert von
x18 mit thread_get_state las, bestätigte, dass diese Funktion tatsächlich die Quelle des Lecks
war.
Ich meldete das Problem am 26. Februar 2018, demselben Tag, an dem ich es entdeckte, an Apple.
Von Brandon Azad