
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.