
CVE-2018-4280: Vulnerabilidade de substituição de porta Mach no launchd no iOS 11.2.6 levando a escape da sandbox, escalada de privilégios e bypass de codesigning.
Blanket é uma ferramenta de escape de sandbox voltada para iOS 11.2.6, embora a vulnerabilidade principal só tenha sido corrigida no iOS 11.4.1. Ela explora uma vulnerabilidade de substituição de porta Mach no launchd (CVE-2018-4280), bem como várias vulnerabilidades menores em outros serviços, para executar código dentro do processo ReportCrash, que não tem sandbox, roda como root e possui a permissão task_for_pid-allow. Isso concede ao blanket controle sobre todos os processos em execução no telefone, incluindo os críticos de segurança como amfid.
O exploit consiste em várias etapas. Este README explicará a vulnerabilidade principal e as etapas do escape de sandbox passo a passo.
Ao pesquisar relatórios de falhas no iOS, descobri uma vulnerabilidade de substituição de porta Mach no launchd. Ao travar de uma maneira específica, um processo pode fazer o kernel enviar uma mensagem Mach para o launchd que faz com que o launchd desaloque excessivamente um direito de envio para uma porta Mach em seu namespace IPC. Isso permite que um invasor se faça passar por qualquer serviço do launchd que ele possa consultar para o resto do sistema, o que abre inúmeras possibilidades de escalada de privilégios.
Essa vulnerabilidade também está presente no macOS, mas acioná-la no iOS é mais difícil devido a verificações no launchd que garantem que a mensagem de exceção Mach venha do kernel.
O launchd multiplexa vários manipuladores de mensagens Mach diferentes em sua porta principal, incluindo um manipulador MIG para mensagens de exceção. Se um processo enviar uma mensagem mach_exception_raise ou mach_exception_raise_state_identity para sua própria porta bootstrap, o launchd receberá e processará essa mensagem como uma exceção de nível de host.
Infelizmente, o tratamento dessas mensagens pelo launchd é defeituoso. Se o tipo de exceção for EXC_CRASH, o launchd desalocará as portas de thread e tarefa enviadas na mensagem e depois 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:```C
kern_return_t __fastcall
catch_mach_exception_raise( // (a) The service routine is
mach_port_t exception_port, // called with values directly
mach_port_t thread, // from the Mach message
mach_port_t task, // sent by the client. The
exception_type_t exception, // thread and task ports could
mach_exception_data_t code, // be arbitrary send rights.
mach_msg_type_number_t codeCnt)
{
__int64 __stack_guard; // ST28_8@1
kern_return_t kr; // w0@1 MAPDST
kern_return_t result; // w0@4
__int64 codes_left; // x25@6
mach_exception_data_type_t code_value; // t1@7
int pid; // [xsp+34h] [xbp-44Ch]@1
char codes_str[1024]; // [xsp+38h] [xbp-448h]@7
__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 ( current_audit_token.val[5] ) // (b) If the message was sent by
{ // a process with a nonzero PID
result = KERN_FAILURE; // (any non-kernel process),
} // the message is rejected.
else
{
if ( codeCnt )
{
codes_left = codeCnt;
do
{
code_value = *code;
++code;
__snprintf_chk(codes_str, 0x400uLL, 0, 0x400uLL, "0x%llx", code_value);
--codes_left;
}
while ( codes_left );
}
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_port(thread); // (c) The "thread" port sent in
if ( kr ) // the message is deallocated.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
kr = deallocate_port(task); // (d) The "task" port sent in the
if ( kr ) // message is deallocated.
{
_os_assumes_log(kr);
_os_avoid_tail_call();
}
if ( exception == EXC_CRASH ) // (e) If the exception type is
result = KERN_FAILURE; // EXC_CRASH, then KERN_FAILURE
else // is returned. MIG will
result = 0; // deallocate the ports again.
}
*__stack_chk_guard_ptr;
return result;
}
Isto é o que o código faz:
1. Esta função é a rotina de serviço Mach para mensagens de exceção `mach_exception_raise`: ela é
invocada diretamente pelo sistema Mach quando o launchd processa uma mensagem de exceção Mach
`mach_exception_raise`. Os argumentos para a rotina de serviço são analisados da mensagem Mach e,
portanto, são controlados pelo remetente da mensagem.
2. Em (b), o launchd verifica se a mensagem de exceção Mach foi enviada pelo kernel. O token de
auditoria do remetente contém o PID do processo de envio no campo 5, que será zero apenas para o
kernel. Se a mensagem não foi enviada pelo kernel, ela é rejeitada.
3. As portas de thread e tarefa da mensagem são desalocadas explicitamente em (c) e (d).
4. Em (e), o launchd verifica se o tipo de exceção é `EXC_CRASH` e retorna `KERN_FAILURE` se
for. A intenção é garantir que não sejam tratadas mensagens `EXC_CRASH`, presumivelmente para que
o ReportCrash seja invocado como o manipulador de corpos. No entanto, retornar `KERN_FAILURE` neste ponto
fará com que as portas de tarefa e thread sejam desalocadas novamente quando a mensagem de exceção for
limpa posteriormente. Isso significa que essas duas portas serão desalocadas em excesso.