
um framework Ghidra para engenharia reversa do kernelcache do iOS
Este framework é o produto final da minha experiência em engenharia reversa de Kernelcache. Normalmente procuro por vulnerabilidades auditando manualmente o kernel e suas extensões e automatizei a maioria das coisas que realmente queria ver no Ghidra para acelerar o processo de reversão, o que se mostrou eficaz e economiza muito tempo. O framework funciona em iOS 12/13/14/15 e em macOS 11/12 (tanto kernelcache quanto KEXT único) e foi disponibilizado ao público com a intenção de ajudar pessoas a começar a pesquisar sobre o kernel iOS sem a dificuldade de preparar seu próprio ambiente. Como acredito, este framework (incluindo o conjunto de ferramentas que fornece e com algum conhecimento básico de IOKit) é suficiente para começar a hackear o Kernelcache.
O framework é inteiramente escrito em Python e pode ser estendido para construir outras ferramentas. Ele fornece algumas APIs básicas que você pode usar em quase qualquer projeto e economizar tempo de leitura do manual verboso. Sinta-se à vontade para ler as funcionalidades principais no diretório utils/.
Ghidra é bom quando se trata de analisar Kernelcaches, mas como outras ferramentas de RE, requer algum trabalho manual. ghidra_kernelcache fornece um bom ponto de entrada para corrigir coisas no início e até mesmo durante a engenharia reversa, proporcionando assim uma saída do descompilador com boa aparência.
Existe um projeto semelhante feito por @_bazad no IDAPro chamado ida_kernelcache que fornece um bom ponto de entrada para pesquisadores que desejam trabalhar com a imagem do kernel no IDA. Meu framework se parece um pouco com o trabalho de Brandon e vai além, fornecendo muito mais recursos para tornar o processo de trabalho com o kernelcache menos doloroso.
::externalMethod() e ::getTargetAndMethodForIndex().Esses recursos são feitos como ferramentas separadas que podem ser executadas por atalhos de teclado ou clicando em seus ícones na barra de ferramentas.
Clone o repositório:```sh git clone https://github.com/0x36/ghidra_kernelcache.git
**Nota importante**: O projeto foi testado no Ghidra 10.1_PUBLIC e 10.2_DEV e não é compatível com versões anteriores.
Vá para *`Windows → Script Manager`,* clique em *`script Directory`,* depois adicione *`ghidra_kernelcache`* à lista de diretórios.
Vá para *`Windows → Script Manager`,* na listagem de *scripts*, vá para a categoria *`iOS→kernel`* e marque os plug-ins vistos lá, eles aparecerão na Barra de Ferramentas do GHIDRA.
No diretório [logos/](https://github.com/0x36/ghidra_kernelcache/tree/master/logos), você pode colocar seus próprios logos para cada ferramenta.
## Simbolização do kernelcache do iOS
`ghidra_kernelcache` requer, na primeira etapa, [iometa](https://github.com/Siguza/iometa/) (feito por [@s1guza](https://twitter.com/s1guza)), uma ferramenta poderosa que fornece informações de classes C++ no binário do kernel. O ótimo é que funciona como um binário independente, então a saída pode ser importada para seu framework de RE favorito apenas analisando-a. Meu framework pega a saída do iometa e a analisa para simbolizar e corrigir tabelas virtuais.
### Uso
Após descomprimir o kernel, execute os seguintes comandos:```sh
$ iometa -n -A /tmp/kernel A10-legacy.txt > /tmp/kernel.txt
# if you want also to symbolicate using jtool2
$ jtool2 --analyze /tmp/kernel
Carregue o kernelcache no Ghidra, NÃO USE importação em lote, carregue-o como imagem Mach-O.
Após o Kernelcache ser carregado e auto-analisado, clique no ícone mostrado na barra de ferramentas ou pressione Meta-Shift-K, depois coloque o caminho completo da saída do iometa que é /tmp/kernel.txt no nosso caso.
Se quiser usar símbolos do jtool2, pode usar o jsymbol.py localizado na categoria iOS→kernel também.
Exemplos completos da API estão em ghidra_kernelcache/kc.py
→ Aqui estão alguns exemplos de manipulação de objetos de classe:```py from utils.helpers import * from utils.class import * from utils.iometa import ParseIOMeta
ff = "/Users/mg/ghidra_ios/kernel.txt" iom = ParseIOMeta(ff) Obj = iom.getObjects() kc = kernelCache(Obj)
kc.process_all_classes()
kc.process_classes_for_bundle("com.apple.iokit.IOSurface")
kc.process_classes_for_bundle("kernel")
kc.process_class("IOGraphicsAccelerator2")
kc.clear_class_structures()
kc.update_classes_vtable()
kc.explore_pac()
Como pode ver, pode simbolizar total ou parcialmente o kernelcache; se a simbolização parcial for escolhida, o `ghidra_kernelcache` construirá automaticamente todas as dependências de classe antes de prosseguir.
Se executar o script contra todo o kernelcache (simbolização completa), o `ghidra_kernelcache` levará vários minutos para analisar a imagem do kernel.
Uma vez terminado, o Ghidra fornecerá o seguinte:
→ Uma nova categoria foi adicionada no Filtro de Marcadores chamada "iOS":
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image1.png" alt="image1" width="200"/>
→ Tabelas virtuais de classes IOKit são adicionadas ao Marcador 'iOS' para uma consulta melhor e mais rápida da tabela virtual; pode procurar por uma kext ou classe fornecendo letras, palavras ou o bundle da kext na barra de busca.
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image2.png" alt="image2"/>
→ Corrigindo a tabela virtual: desmonta/compila código desconhecido, corrige namespaces, re-simboliza os métodos da classe e aplica definição de função a cada método:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image3.png" alt="image3"/>
→ Criação de namespaces de classe e coloca cada método no seu próprio namespace correspondente:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image4.png" alt="image4"/>
→ Criando estrutura de classe com respeito à hierarquia de classes:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image5.png" alt="image5"/>
→ Criando vtables de classe, e cada método tem sua própria definição de método para uma melhor saída de descompilação:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image6.png" alt="image6"/>
A implementação completa pode ser encontrada em [`utils/class.py.`](https://github.com/0x36/ghidra_kernelcache/blob/master/utils/class.py)
Aqui estão algumas capturas de tela de antes/depois da simbolização usando `ghidra_kernelcache`:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image7.png" alt="image7"/>
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image8.png" alt="image8"/>
## Simbolização de Kext do macOS
---
O suporte do `ghidra_kernelcache` no macOS é tanto para simbolização de kernelcache quanto de KEXT individual para as arquiteturas ARM64e e x86_64.
**IMPORTANTE:** No momento da escrita, o Ghidra não consegue analisar todo o kernelcache do macOS, mas é possível carregá-lo no IDA para análise inicial e depois importar a base de dados (idb para xml) para o Ghidra mais tarde, mas isso está fora do escopo. Se conseguir fazê-lo, o `ghidra_kernelcache` cuidará do resto.
Existem alguns poucos passos que devem ser tomados antes de simbolizar qualquer Extensão de Kernel do macOS, pois o objetivo principal do `ghidra_kernelcache` é reconstruir a hierarquia de classes e gerenciar todas as estruturas de classe em uma única base de dados; uma extensão de kernel não cumpre esses requisitos, o que significa que a simbolização de um único KEXT requer uma simbolização do kernel e talvez de outras Extensões de Kernel das quais depende, portanto algum trabalho extra precisa ser feito aqui.
O `ghidra_kernelcache` agora fornece uma maneira poderosa de simbolizar Extensões de Kernel, incluindo o kernel, gerenciando e compartilhando estruturas de classe e definições de métodos virtuais através do poderoso *DataType Project Archive* do Ghidra
### Passos para simbolizar uma Extensão de Kernel
- Crie uma nova pasta no seu projeto Ghidra, depois carregue ` /System/Library/Kernels/kernel.release.XXXXX` nessa pasta e deixe o Ghidra analisá-la.
- Crie um novo *Project Archive*: Vá até `DataType Provider` → Clique na seta no canto superior direito da janela → `New Project Archive` → Coloque-o dentro da pasta recém-criada → Dê um nome a ele (ex.: macOS_12.1).
- Agora simbolize o kernel usando `ghidra_kernelcache`; o processo é bastante similar à simbolização do *kernelcache* do iOS.```bash
$ iometa -n -A /System/Library/Kernels/kernel.release.t8101 > /tmp/kernel.txt
KM.py :```pyfrom utils.helpers import * from utils.kext import * iom = ParseIOMeta("/tmp/kernel.txt") Obj = iom.getObjects() kc = Kext(Obj,shared_p="macOS_12.1") kc.process_kernel_kext()
- Uma vez concluído, uma associação de banco de dados foi criada entre o arquivo do kernel e o arquivo `macOS_12.1`. Agora, clique com o botão direito em `kernel.release.t8001` → `Commit DataTypes To` → `macOS_12.1`.
- Em seguida, `Clique com o botão direito` → `Selecionar Tudo` → `Commit`.
- Salve o arquivo do projeto: `Clique com o botão direito` → `Save Archive`.
Acabamos de criar um arquivo de projeto que pode ser compartilhado entre todas as extensões do kernel.
Vamos tomar como exemplo o `IOSurface` Kext para Apple Silicon :```bash
$ lipo /System/Library/Extensions/IOSurface.kext/Contents/MacOS/IOSurface -thin arm64e -output /tmp/iosurface.arm64e
$ iometa -n -A /tmp/iosurface.arm64e > /tmp/iosurface.txt
macOS_12.1: vá para Data Type Manager → Open Project Archive, então selecione o macOS_12.1KM.py:```python
from utils.helpers import *
from utils.kext import *kc = Kext(Obj,shared_p="macOS_12.1")
kc.depac()
kc.process_kernel_kext()
**Nota Importante**: às vezes `kc.process_kernel_kext()` falha porque o Ghidra não conseguiu desmistificar alguns símbolos C++. Para corrigir isso, vá ao gerenciador de scripts e execute o script `DemangleAllScript.java`, depois reinicie `kc.process_kernel_kext()` novamente.
### Classes personalizadas
Existem alguns casos em que algumas classes C++ onde `ghidra_kernelcache` e `iometa` não conseguem simbolizar, então um novo recurso foi adicionado para lidar com isso.
A reconstrução da classe `Custom()` itera por todos os símbolos `::vtable` e verifica se a classe já está definida ou não; se não estiver, cria automaticamente uma estrutura de classe, definições de função para cada método de classe identificado, um namespace e uma tabela virtual para cada classe.
A criação de classes personalizadas é suportada apenas no macOS no momento.```bash
$ iometa -n -A /System/Library/Kernels/kernel.release.t8101 > /tmp/kernel.txt
$ iometa -n -A <kext_path> >> /tmp/kernel.txt
Visualizam de forma inteligente dados complexos de cibersegurança e redes - transforma dados brutos em perspectivas acionáveis.
Exemplo do painel do Netscanner mostrando dados de tráfego de rede em tempo real
from utils.helpers import * from utils.custom_kc import *
if name == "main": default = "/tmp/kernel.txt" ff = askString("iometa symbol file","Symbol file: ",default) iom = ParseIOMeta(ff) Obj = iom.getObjects()
kc = Custom(Obj)
kc.process_all_classes()
kc.explore_pac()
## Scripts diversos
---
### Importando o Dwarf4 do KDK
Ghidra de alguma forma não consegue carregar o diretório `.dsym` correspondente, fiz um pequeno script para corrigir isso. Ele pode ser encontrado [aqui](https://github.com/0x36/ghidra_kernelcache/blob/master/dwarf4_fix.py).
**Uso**: carregue o kernel do seu caminho KDK, deixe o Ghidra terminar a análise, então execute `dwarf_fix.py`, ele carregará os símbolos e o processo pode levar vários minutos.
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image12.png" alt="image12"/>
### Resolvendo referências de chamadas de métodos virtuais
`ghidra_kernelcache` fornece duas maneiras de resolver chamadas virtuais por `kernelCache.explore_pac` ou `fix_extra_refs`
**kernelCache.explore_pac()**
Se você está trabalhando em um binário arm64e, o `ghidra_kernelcache` pode reconhecer chamadas de métodos virtuais procurando pelo valor `Pointer Authentication Code`. O processo é direto e, diferentemente do `fix_extra_refs()`, o `kernelCache.explore_pac` não depende da identificação de `Pcode` ou `varnode`, ele apenas itera por todas as instruções no programa, procura por instruções `MOVK`, busca o segundo operando e procura seu valor correspondente no banco de dados.
Uso:
Crie uma instância *KernelCache* via **kernelCache** , **Kext** ou **Custom**, então chame o método `explore_pac()`.```py
from utils.helpers import *
from utils.kext import *
if __name__ == "__main__":
default = "/tmp/kernel.txt"
ff = askString("iometa symbol file","Symbol file: ",default)
iom = ParseIOMeta(ff)
Obj = iom.getObjects()
kc = Kext(Obj)
kc.explore_pac()
fix_extra_refs() Esta função baseia-se numa análise básica de fluxo de dados para encontrar todos os métodos de chamada virtual e resolver automaticamente as suas implementações. Funciona em todas as arquiteturas e tem a capacidade de reconhecer o tipo de dados de origem a partir da saída do descompilador, resolvendo todas as referências de chamada virtual dentro da função, de modo que o utilizador possa saltar para a frente/para trás diretamente para/da implementação sem ter de a procurar manualmente.
A funcionalidade mais útil fornecida por fix_extra_refs é que mantém as referências sincronizadas em cada execução. Por exemplo, se alterar um tipo de dados de variável para um tipo de dados de classe, o fix_extra_refs reconhecerá automaticamente a alteração e percorrerá recursivamente todos os locais de chamada para resolver as suas referências, parando apenas quando a fila de locais de chamada ficar vazia.
Existem outras funcionalidades fornecidas por fix_extra_refs, tais como:
_ptmf2ptf() e resolve o seu método de chamada tanto para offsets como para endereços completos de função.FUN_) e resolve-o colocando a função alvo no seu próprio espaço de nomes (por exemplo, adicionando o ponteiro this da classe correspondente).Pode encontrar a implementação em utils/references.py. O fix_extra_refs analisa as operações pcode e procura por opcodes CALLIND e CALL. Em seguida, obtém todos os varnodes envolvidos na operação. Uma vez identificada a definição de um Varnode, recupera o seu HighVariable para identificar o tipo de objeto da classe. Se o tipo for desconhecido (ou seja, não aparenta ser uma estrutura de classe), ignora-o; caso contrário, pega no nome da classe, procura na sua tabela de chamadas virtuais e, utilizando o offset fornecido pelo Varnode, consegue obter a chamada de método virtual correta e coloca uma referência na instrução de chamada.```py
fix_extra_refs(toAddr(address))
Aqui está um exemplo de saída do uso de `fix_extra_refs`:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image9.png" alt="image9"/>
Observe que ele resolveu com sucesso as chamadas virtuais de **IOService::isOpen()**, **OSArray:getNextIndexOfObject()** e **IOStream::removeBuffer()** sem qualquer modificação manual.
Em seguida, `fix_extra_refs` descompilará **IOStream::removeBuffer()**, obterá todas as HighVariables deste método e então resolverá suas referências como no método anterior ... e assim por diante.
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image10.png" alt="image10"/>
### Correção automática de tabelas de métodos externos
Acredito que todo pesquisador tenha algum script para lidar com esta parte, já que é a principal superfície de ataque do IOKit; fazê-lo manualmente é um fardo, e deve ser automatizado de forma que o pesquisador possa mergulhar em várias tabelas de métodos externos.
Existem dois scripts fornecidos pelo `ghidra_kernelcache`: **fix_methodForIndex.py** e **fix_extMethod.py.** Você pode ativá-los como os outros scripts, conforme mostrado acima.
***Uso***: Coloque o cursor no início da tabela de despacho externa, execute o script: forneça o alvo e o número de seletores.
Exemplo para `IOStreamUserClient::getTargetAndMethodForIndex()`:
<img src="https://raw.githubusercontent.com/0x36/ghidra_kernelcache/master/screenshots/image11.png" alt="image11"/>
### namespace.py: corrigir namespaces de métodos…
Este é um script útil para popular o tipo de classe em todos os métodos encontrados, e é uma dependência para o script `extra_refs.py` para explorar recursivamente as funções chamadas e resolver suas referências.
***Uso***: Coloque o cursor na saída do descompilador da função desejada, execute o script a partir da barra de ferramentas ou pressione **Meta-Shift-N**.
### Propagação de nome e tipo de símbolo
O `ghidra_kernelcache` fornece suporte à propagação de tipos para operações básicas de Pcode, mas provavelmente falhará para algumas variáveis que usam casting complexo.
Se alguém quiser ajudar ou quiser começar a trabalhar com coisas de baixo nível no Ghidra, esta é a oportunidade.
A implementação pode ser encontrada em [ghidra_kernelcache/propagate.py](https://github.com/0x36/ghidra_kernelcache/blob/master/propagate.py)
### Carregamento de assinaturas de funções
Analisar arquivos de cabeçalho C++ no Ghidra não é possível, e ter assinaturas de funções do kernel no kernelcache pode melhorar muitas coisas na saída do descompilador.
Por exemplo, suponha que adicionamos `virtual IOMemoryMap * map(IOOptionBits options = 0 );`. O Ghidra irá reatribuir automaticamente o valor de retorno para um ponteiro `IOMemoryMap` tanto para a definição da função quanto para as assinaturas de função.
Você pode adicionar qualquer símbolo C++ no diretório **signatures/** respeitando a sintaxe e pode encontrar assinaturas de função definidas neste diretório.```c++
// Defining an instance class method
IOMemoryDescriptor * withPersistentMemoryDescriptor(IOMemoryDescriptor *originalMD);
// Defining a virtual method, it must start with "virtual" keyword
virtual IOMemoryMap * createMappingInTask(task_t intoTask, mach_vm_address_t atAddress, IOOptionBits options, mach_vm_size_t offset = 0, mach_vm_size_t length = 0);
// Defining a structure
struct task_t;
// typedef'ing a type
typedef typedef uint IOOptionBits;
// Lines begining with '//' are ignored
Uso: Após simbolizar o kernel, é altamente recomendável executar o script load_sigatnures.py para carregar todas as assinaturas de função disponíveis. Como a maioria das ferramentas anteriores, execute este script adicionando-o na barra de ferramentas ou a partir do gerenciador de plugins ou apenas pressione Meta-Shift-S .
Este script é direto, ele importa todas as estruturas, classes, typedefs, e definições de função e tudo com SourceType.USER_DEFINED de um projeto antigo para um novo.
Uso: Abra os projetos Ghidra antigo e novo na mesma ferramenta, vá para o script load_structs.py, coloque o nome do programa antigo na variável src_prog_string, e o novo na variável dst_prog_string, então execute o script.
Se você acha o projeto interessante e quer contribuir, faça um PR e eu o revisarei; entretanto, gostaria de ver contribuições nas seguintes áreas:
Gostaria de agradecer a @s1guza pelo seu incrível iometa do qual o ghidra_kernelcache depende.