
CVE-2018-4248: Grenzüberschreitendes Lesen in libxpc während der Zeichenfolgenserialisierung.
xpc-string-leak ist ein Proof-of-Concept-Exploit für einen Out-of-Bounds-Speicherlesefehler in libxpc.
Dieser Exploit nutzt die Sicherheitslücke aus, um außerhalb der Grenzen liegenden Heap-Speicher von diagnosticd zu lesen,
einem nicht-sandboxierten Root-Prozess mit der Berechtigung task_for_pid-allow.
Unter macOS 10.13.5 und iOS 11.4 überprüft die Funktion _xpc_string_deserialize nicht, ob der
deserialisierte String die korrekte Länge hat, bevor mit _xpc_string_create ein XPC-String-Objekt erstellt wird.
Dies kann zu einem Heartbleed-artigen Out-of-Bounds-Heap-Lesen führen, wenn der XPC-String
anschließend in eine andere XPC-Nachricht serialisiert wird.
Hier ist die Implementierung von _xpc_string_deserialize, dekompiliert mit IDA:
OS_xpc_string *__fastcall _xpc_string_deserialize(OS_xpc_serializer *xserializer)
{
OS_xpc_string *xstring; // rbx@1
char *string; // rax@4
char *contents; // [rsp+8h] [rbp-18h]@1
size_t size; // [rsp+10h] [rbp-10h]@1 MAPDST
xstring = 0LL;
contents = 0LL;
size = 0LL;
if ( _xpc_string_get_wire_value(xserializer, (const char **)&contents, &size) )
{
if ( contents[size - 1] || (string = _xpc_try_strdup(contents)) == 0LL )
{
xstring = 0LL;
}
else
{
xstring = _xpc_string_create(string, size - 1);
LOBYTE(xstring->flags) |= 1u;
}
}
return xstring;
}
_xpc_string_deserialize ruft zunächst _xpc_string_get_wire_value auf, um einen Zeiger auf die
Stringdaten sowie die serialisierte Größe des Strings (wie im String-Header angegeben) abzurufen.
_xpc_string_deserialize überprüft dann, ob der String am Ende seiner angegebenen Größe einen Null-Terminator
hat, überprüft jedoch nicht, ob sich nicht früher in den Daten ein Null-Terminator befindet.
Schließlich wird eine Kopie des Strings auf dem Heap erstellt und das OS_xpc_string-Objekt mit
_xpc_string_create angelegt.
Hier ist der dekompilierte Code für _xpc_string_create:
OS_xpc_string *__fastcall _xpc_string_create(const char *string, size_t length)
{
OS_xpc_string *xstring; // rax@1
xstring = (OS_xpc_string *)_xpc_base_create(&OBJC_CLASS___OS_xpc_string, 16LL);
if ( (((_DWORD)length + 4) & 0xFFFFFFFC) + 4 < length )
_xpc_api_misuse("Unreasonably large string");
xstring->wire_length = ((length + 4) & 0xFFFFFFFC) + 4;
xstring->string = string;
xstring->length = length;
return xstring;
}
_xpc_string_create vertraut dem Wert der von _xpc_string_deserialize übergebenen Länge und setzt die
entsprechenden Felder im OS_xpc_string-Objekt. An diesem Punkt kann der deserialisierte String ein
length-Feld aufweisen, das größer ist als die tatsächlich allokierten Stringdaten.
Theoretisch könnte dies genutzt werden, um Speicherkorruption in Diensten auszulösen, die die Länge
des Strings mittels xpc_string_get_length abfragen, aber dieses Muster scheint unüblich. Eine
weniger leistungsfähige, aber praktischere Exploit-Strategie besteht darin, den String erneut
serialisieren und an uns zurücksenden zu lassen, was uns ein Heartbleed-artiges Fenster in den Speicher
des Opferprozesses verschafft.
Dies ist die Implementierung von _xpc_string_serialize:
void __fastcall _xpc_string_serialize(OS_xpc_string *string, OS_xpc_serializer *serializer)
{
int type; // [rsp+8h] [rbp-18h]@1
int size; // [rsp+Ch] [rbp-14h]@1
type = *((_DWORD *)&OBJC_CLASS___OS_xpc_string + 10);
_xpc_serializer_append(serializer, &type, 4uLL, 1, 0, 0);
size = LODWORD(string->length) + 1;
_xpc_serializer_append(serializer, &size, 4uLL, 1, 0, 0);
_xpc_serializer_append(serializer, string->string, string->length + 1, 1, 0, 0);
}
Der length-Parameter des OS_xpc_string wird während der Serialisierung vertrauenswürdig behandelt,
was bedeutet, dass viele Bytes aus dem Heap in die serialisierte Nachricht gelesen werden. Wenn der
deserialisierte String kürzer war als seine gemeldete Länge, wird die Nachricht mit Heap-Daten
außerhalb der Grenzen gefüllt.
Wir sind immer noch auf XPC-Dienste beschränkt, die einen Teil der XPC-Nachricht an den Client
zurückspiegeln, aber das ist viel häufiger. Beispielsweise ist diagnosticd unter macOS und iOS ein
vielversprechender Kandidat, der zudem nicht sandboxiert ist, root läuft und task_for_pid-Berechtigungen
besitzt. Diagnosticd ist für die Verarbeitung von Diagnosenachrichten (z. B. von os_log generierte
Nachrichten) und deren Streaming an interessierte Clients zuständig. Indem wir uns registrieren,
um unseren eigenen Diagnose-Stream zu empfangen, und dann eine Diagnosenachricht mit einem kürzeren
als erwarteten String senden, können wir eine Momentaufnahme einiger Daten im Heap von diagnosticd
erhalten, was bei der Erlangung von Codeausführung im Prozess helfen kann.
Zum Bauen führen Sie make aus. Siehe oben in der Makefile für verschiedene Bauoptionen.
Führen Sie den Exploit aus, indem Sie die Größe des Lecks in der Befehlszeile angeben:
$ ./xpc-string-leak 0x40
0x2000000000000000 0xe00007ff39bf0992
0x00007fff56858570 0x00007fff7ed23d0e
0x0000000000000000 0x0000000000000000
0x00007fff7ed52be2 0x00007fff7ed29ed6
Die Leckgröße muss ein Vielfaches von 8 und mindestens 16 sein.
Der xpc-string-leak-Code wird als Public Domain veröffentlicht. Als Höflichkeit bitte ich darum, dass Sie, falls Sie diesen Code referenzieren oder verwenden, mich als Quelle angeben.
Ich entdeckte diesen Fehler Anfang 2018 (Januar oder Februar), vergaß dann aber, ihn zu untersuchen, bis Mai. Ich meldete das Problem am 9. Mai an Apple, es erhielt die CVE-Nummer CVE-2018-4248 und wurde am 9. Juli in iOS 11.4.1 und macOS 10.13.6 behoben.
Brandon Azad