
Exploit de Ian Beer para CVE-2017-2370 (r/w de memória do kernel no iOS 10.2)
// ianbeer
exploit de leitura/escrita arbitrária no kernel para CVE-2017-2370 no iOS 10.2
Testado apenas em iPod Touch 6G 14C92 - outros dispositivos/firmwares não funcionarão imediatamente!
*** o bug *** mach_voucher_extract_attr_recipe_trap é uma armadilha mach que pode ser chamada de qualquer contexto. É um código totalmente novo, adicionado no iOS 10.
kern_return_t mach_voucher_extract_attr_recipe_trap(struct mach_voucher_extract_attr_recipe_args *args) { ipc_voucher_t voucher = IV_NULL; kern_return_t kr = KERN_SUCCESS; mach_msg_type_number_t sz = 0;
if (copyin(args->recipe_size, (void *)&sz, sizeof(sz))) <---------- (a)
return KERN_MEMORY_ERROR;
if (sz > MACH_VOUCHER_ATTR_MAX_RAW_RECIPE_ARRAY_SIZE)
return MIG_ARRAY_TOO_LARGE;
voucher = convert_port_name_to_voucher(args->voucher_name);
if (voucher == IV_NULL)
return MACH_SEND_INVALID_DEST;
mach_msg_type_number_t __assert_only max_sz = sz;
if (sz < MACH_VOUCHER_TRAP_STACK_LIMIT) {
/* keep small recipes on the stack for speed */
uint8_t krecipe[sz];
if (copyin(args->recipe, (void *)krecipe, sz)) {
kr = KERN_MEMORY_ERROR;
goto done;
}
kr = mach_voucher_extract_attr_recipe(voucher, args->key,
(mach_voucher_attr_raw_recipe_t)krecipe, &sz);
assert(sz <= max_sz);
if (kr == KERN_SUCCESS && sz > 0)
kr = copyout(krecipe, (void *)args->recipe, sz);
} else {
uint8_t *krecipe = kalloc((vm_size_t)sz); <---------- (b)
if (!krecipe) {
kr = KERN_RESOURCE_SHORTAGE;
goto done;
}
if (copyin(args->recipe, (void *)krecipe, args->recipe_size)) { <----------- (c)
kfree(krecipe, (vm_size_t)sz);
kr = KERN_MEMORY_ERROR;
goto done;
}
kr = mach_voucher_extract_attr_recipe(voucher, args->key,
(mach_voucher_attr_raw_recipe_t)krecipe, &sz);
assert(sz <= max_sz);
if (kr == KERN_SUCCESS && sz > 0)
kr = copyout(krecipe, (void *)args->recipe, sz);
kfree(krecipe, (vm_size_t)sz);
}
kr = copyout(&sz, args->recipe_size, sizeof(sz));
done:
ipc_voucher_release(voucher);
return kr;
}
Aqui está a estrutura de argumentos (controlada a partir do espaço do utilizador)
struct mach_voucher_extract_attr_recipe_args { PAD_ARG_(mach_port_name_t, voucher_name); PAD_ARG_(mach_voucher_attr_key_t, key); PAD_ARG_(mach_voucher_attr_raw_recipe_t, recipe); PAD_ARG_(user_addr_t, recipe_size); };
recipe e recipe_size são ponteiros do espaço do utilizador.
No ponto (a) quatro bytes são lidos do ponteiro recipe_size (espaço do utilizador) para sz.
No ponto (b), se sz for menor que MACH_VOUCHER_ATTR_MAX_RAW_RECIPE_ARRAY_SIZE (5120) e maior que MACH_VOUCHER_TRAP_STACK_LIMIT (256), sz é usado para alocar um buffer no heap do kernel.
No ponto (c), copyin é chamado novamente para copiar memória do espaço do utilizador para aquele buffer que foi alocado agora, mas em vez de passar sz (o tamanho validado que foi alocado) args->recipe_size é passado como o tamanho. Isto é o ponteiro do espaço do utilizador para o tamanho, não o tamanho!
Isto leva a um overflow do heap do kernel completamente controlado. Note que o código na verdade não pode funcionar corretamente :)
*** o exploit ***
Eu miro buffers de mensagens mach pré-alocados que são alocados via kalloc. Os primeiros 4 bytes são um campo de tamanho que é usado para determinar onde no buffer ler e escrever uma mensagem. Ao corromper este campo podemos fazer com que mensagens mach sejam lidas e escritas fora dos limites da alocação kalloc que sustenta o kmsg.
Há uma pequena complicação: o kmsg pré-alocado de uma porta só será usado para envios reais de mach_msg pelo kernel (não para respostas a métodos MIG, por exemplo). Isto torna um pouco mais complicado obter conteúdo controlado suficiente dentro deles.
Um tipo de mensagem mach que o kernel envia com muitos dados controlados pelo utilizador é uma mensagem de exceção, enviada quando uma thread crasha.
O ficheiro load_regs_and_crash.s contém assembly ARM64 que carrega os registos de propósito geral ARM64 com o conteúdo de um buffer de modo que, quando crasha, a mensagem de exceção contém esse buffer de dados (cerca de 0x70 bytes são controlados).
Ao sobrescrever o campo ikm_size da porta para apontar para o cabeçalho de outra porta, podemos ler e escrever o cabeçalho de outra porta e descobrir onde ela está na memória. Podemos então libertar essa segunda porta e realocar um user client no seu lugar, que também podemos ler e escrever.
Leio o ponteiro vtable do user client e uso a técnica do gadget OSSerializer::serialize conforme detalhado em [https://info.lookout.com/rs/051-ESQ-475/images/pegasus-exploits-technical-details.pdf] para chamar uma função arbitrária com dois argumentos controlados.
Eu chamo uuid_copy que chama memmove(arg0, arg1, 0x10). Ao apontar arg0 ou arg1 para dentro do próprio user client (que podemos ler recebendo a mensagem de exceção), podemos ler e escrever memória arbitrária do kernel em blocos de 16 bytes.