
empty_list - exploit para p0 issue 1564 (CVE-2018-4243) iOS 11.0 - 11.3.1 kernel r/w
empty_list - exploit para a issue 1564 da p0 (CVE-2018-4243) iOS 11.0 - 11.3.1 r/w do kernel @i41nbeer
BUG: getvolattrlist recebe um argumento bufferSize controlado pelo usuário através da syscall fgetattrlist.
Ao alocar um buffer do kernel para serializar a lista de atributos, existe o seguinte comentário:
/*
O problema é que o código não trata corretamente o caso em que o tamanho do buffer fornecido pelo usuário é menor que o tamanho do cabeçalho solicitado. Se passarmos ATTR_CMN_RETURNED_ATTRS, chegaremos ao seguinte código:
/* Return attribute set output if requested. / if (return_valid) { ab.actual.commonattr |= ATTR_CMN_RETURNED_ATTRS; if (pack_invalid) { / Only report the attributes that are valid */ ab.actual.commonattr &= ab.valid.commonattr; ab.actual.volattr &= ab.valid.volattr; } bcopy(&ab.actual, ab.base + sizeof(uint32_t), sizeof (ab.actual)); }
Não há verificação se o buffer alocado é grande o suficiente para conter pelo menos isso.
Exploração: Espero publicar uma análise mais detalhada sobre isso; estas são algumas notas preliminares sobre como o exploit funciona:
O bug dá a você a capacidade de escrever 8 bytes zero além do final de uma alocação kalloc.16. Embora pareça que você possa controlar alguns bits nesses bytes, não tenho certeza se isso é realmente possível, então concentrei-me em explorar como se estivesse escrevendo um ponteiro NULL além do final.
Isso é uma primitiva bastante limitada, então o primeiro passo é tentar enumerar as possíveis coisas que você poderia fazer:
No final, escolhi a primeira opção. Existem então dois requisitos adicionais:
Escolhi mirar a struct ipc_port, que possui um campo de contador de referência como sua segunda dword, cumprindo assim o primeiro requisito. No entanto, ela não é alocada em kalloc.16; em vez disso, vive em sua própria zona (ipc_ports).
Isso significa que temos que alinhar um bloco da zona kalloc.16 logo antes de um bloco de ipc_ports e, então, fazer o overflow a partir da última alocação kalloc.16 no bloco kalloc.16 para a primeira em ipc_ports.
Há dois truques que podemos usar para facilitar isso:
Inversão da Freelist: Alocações de zona virão primeiro de páginas intermediárias (parcialmente cheias). Isso significa que, se simplesmente começarmos a liberar e alocar objetos k.16 em algum lugar no meio do groom, eles não serão reutilizados até que a página intermediária atual esteja cheia ou vazia.
Isso apresenta um desafio porque as freelists de páginas novas são preenchidas de forma semirrandômica, de modo que suas alocações vão de dentro para fora:
| 9 8 6 5 2 1 3 4 7 10 | <-- example "randomized" allocation order from a fresh all-free page
Isso significa que nossas páginas intermediárias finais de k.16 e ports ficarão mais ou menos assim:
| - - - 5 2 1 3 4 - - | - - - 4 1 2 3 5 - - | kalloc.16 ipc_ports
Se usarmos o overflow para corromper uma entrada da freelist, teremos um panic se ela for alocada, então precisamos evitar isso.
O truque é que, controlando a ordem de alocação e liberação, podemos inverter as freelists de modo que as páginas intermediárias finais fiquem mais ou menos assim: | 1 4 - - - - - 5 3 2 | 2 5 - - - - - 4 3 1 | kalloc.16 ipc_ports
Neste ponto, é muito mais provável que consigamos liberar um kalloc.16 e realocá-lo para o overflow, de modo que possamos atingir o primeiro qword de um ipc_port.
Alocações seguras contra overflow: Como provavelmente haverá muitas alocações candidatas das quais teremos que fazer overflow antes de atingir o alvo (que está bem no final, logo antes do ipc_port), precisamos garantir que os objetos alocados na página kalloc.16 sejam seguros para corromper com um ponteiro NULL.
Eu uso descritores ool_port de mensagens mach para isso, já que NULL é um valor válido.
Fluxo do Exploit: Fazemos o groom para inverter as freelists de kalloc.16 e começamos a tentar fazer overflow para dentro de um ipc_port.
Conhecemos a faixa aproximada de nomes de mach ports que contêm o port a ser corrompido; após cada tentativa de overflow, verificamos cada um desses ports para ver se o port foi corrompido. Um efeito colateral da corrupção bem-sucedida é que o campo io_active do port será definido como zero. Podemos detectar isso sem causar efeitos colaterais usando o método MIG mach_port_kobject.
Depois de encontrar o port corrompido, precisamos fazer com que uma referência seja adquirida e liberada nele; e, mais importante, precisamos que o caminho de código que faz isso não verifique o campo io_active. mach_port_set_attributes fará isso por nós.
Agora transformamos nossa escrita de ponteiro NULL além do final de um kalloc.16 em um mach port pendurado (dangling) :)
Causamos um gc de zona, com o objetivo de fazer a memória do port ser reutilizada como uma página kalloc.4096. Primeiro, fazemos com que ela seja reutilizada como um descritor ool_ports, onde o campo ip_context se sobrepõe a um send right que enviamos a nós mesmos para um port canário. Isso nos permite descobrir o endereço aproximado de nossos objetos no kernel. Em seguida, substituímos o ool_desc por um pipe buffer e, com um pouco de ajuste, conseguimos descobrir onde o mach port pendurado está na memória.
Construímos um kernel task port falso ali e então fazemos a limpeza.
Confiabilidade: O exploit funciona, o que era meu objetivo :) A confiabilidade é algo em torno de 30%, talvez. Tudo depende da rapidez com que você consegue fazer o overflow inicial e o loop de testes. Se algo mais vier e alocar ou liberar em kalloc.16, você aumenta a probabilidade de corromper uma entrada da freelist ou outra coisa e causar um panic.
Tenho certeza de que o exploit pode ser tornado mais confiável; só o levei até o ponto em que demonstrei que esse bug é explorável. Se você quiser usar isto como ponto de partida e demonstrar como melhorar a confiabilidade, adoraria ler um post de blog! Imagino que isso envolveria monitorar as alocações de kalloc.16 e entender quais são os casos de falha e como podem ser evitados.
As taxas de sucesso parecem ser mais altas quando o dispositivo foi reiniciado e deixado ocioso por um tempo.
Limpeza: Se o exploit funcionar, ele deve se limpar e não causar panic no dispositivo. O kernel task port falso permanecerá ativo.
Use as funções em kmem.h para ler e escrever memória do kernel. Persista um send-right para tfp0 lá se quiser manter o acesso à memória do kernel depois que este processo sair.
Testei em: iPod Touch 6G, iPhone 6S, iPhone SE, iPhone 7, iPhone 8 Deve funcionar no iOS 11 até o iOS 11.3.1