iOS Exploiting

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

iOS Exploit Mitigations

1. Code Signing / Verificação de Assinatura em Tempo de Execução

Introduzido cedo (iPhone OS → iOS) Esta é uma das proteções fundamentais: todo código executável (apps, dynamic libraries, JIT-ed code, extensions, frameworks, caches) deve ser assinada criptograficamente por uma cadeia de certificados que tenha a raiz de confiança da Apple. Em tempo de execução, antes de carregar um binário na memória (ou antes de realizar saltos através de certas fronteiras), o sistema verifica sua assinatura. Se o código for modificado (bit-flipped, patched) ou não assinado, o load falha.

  • Impede: a etapa “classic payload drop + execute” em cadeias de exploit; arbitrary code injection; modificar um binário existente para inserir lógica maliciosa.
  • Detalhe do mecanismo:
  • O Mach-O loader (e o dynamic linker) verifica páginas de código, segmentos, entitlements, team IDs, e que a assinatura cobre o conteúdo do arquivo.
  • Para regiões de memória como JIT caches ou código gerado dinamicamente, a Apple exige que as páginas sejam assinadas ou validadas via APIs especiais (ex.: mprotect com verificações de code-sign).
  • A assinatura inclui entitlements e identificadores; o OS aplica que certas APIs ou capacidades privilegiadas requerem entitlements específicos que não podem ser forjados.
Example Suponha que um exploit obtenha code execution em um processo e tente escrever shellcode no heap e pular para ele. No iOS, essa página precisaria ser marcada como executável **e** satisfazer restrições de code-signature. Como o shellcode não é assinado com o certificado da Apple, o salto falha ou o sistema rejeita tornar aquela região de memória executável.

2. CoreTrust

Introduzido por volta do iOS 14+ (ou gradualmente em dispositivos mais novos / iOS posterior) CoreTrust é o subsistema que realiza a validação de assinatura em tempo de execução dos binários (incluindo binários do sistema e de usuário) contra a certificação raiz da Apple em vez de confiar em stores de confiança em userland em cache.

  • Impede: tampering pós-instalação de binários, técnicas de jailbreaking que tentam trocar ou patchar system libraries ou user apps; enganar o sistema substituindo binários confiáveis por contrapartes maliciosas.
  • Detalhe do mecanismo:
  • Em vez de confiar em um banco de dados de confiança local ou cache de certificados, o CoreTrust busca ou referencia a raiz da Apple diretamente ou verifica certificados intermediários em uma cadeia segura.
  • Garante que modificações (ex.: no filesystem) em binários existentes sejam detectadas e rejeitadas.
  • Vincula entitlements, team IDs, code signing flags e outros metadados ao binário no momento do load.
Example Um jailbreak poderia tentar substituir `SpringBoard` ou `libsystem` por uma versão patchada para ganhar persistência. Mas quando o loader do OS ou o CoreTrust verifica, ele nota o mismatch na assinatura (ou entitlements modificados) e se recusa a executar.

3. Data Execution Prevention (DEP / NX / W^X)

Introduzido em muitos OSs anteriormente; iOS teve NX-bit / w^x por muito tempo DEP impõe que páginas marcadas como writables (para dados) sejam non-executable, e páginas marcadas como executáveis sejam non-writable. Você não pode simplesmente escrever shellcode no heap ou stack e executá-lo.

  • Impede: execução direta de shellcode; classic buffer-overflow → jump para shellcode injetado.
  • Detalhe do mecanismo:
  • A MMU / flags de proteção de memória (via page tables) fazem a separação.
  • Qualquer tentativa de marcar uma página writável como executável aciona uma verificação do sistema (e é ou proibida ou requer aprovação de code-sign).
  • Em muitos casos, tornar páginas executáveis requer passar por APIs do OS que impõem restrições ou verificações adicionais.
Example Um overflow escreve shellcode no heap. O atacante tenta `mprotect(heap_addr, size, PROT_EXEC)` para torná-lo executável. Mas o sistema recusa ou valida que a nova página deve passar por constraints de code-sign (o que o shellcode não pode).

4. Address Space Layout Randomization (ASLR)

Introduzido na era iOS ~4–5 (aprox. iOS 4–5) ASLR randomiza os endereços base de regiões de memória chave: libraries, heap, stack, etc., a cada lançamento do processo. Endereços de gadgets mudam entre execuções.

  • Impede: hardcoding de endereços de gadgets para ROP/JOP; cadeias de exploit estáticas; saltos cegos para offsets conhecidos.
  • Detalhe do mecanismo:
  • Cada biblioteca / módulo dinâmico carregado é rebased em um offset randomizado.
  • Ponteiros base de stack e heap são randomizados (dentro de certos limites de entropia).
  • Às vezes outras regiões (ex.: mmap allocations) também são randomizadas.
  • Combinado com mitigations de information-leak, força o atacante a primeiro leakar um endereço ou ponteiro para descobrir endereços base em tempo de execução.
Example Uma cadeia ROP espera um gadget em `0x….lib + offset`. Mas como `lib` é relocada diferentemente a cada execução, a chain hardcoded falha. Um exploit deve primeiro leakar o endereço base do módulo antes de computar os endereços dos gadgets.

5. Kernel Address Space Layout Randomization (KASLR)

Introduzido no iOS ~ (iOS 5 / iOS 6 timeframe) Análogo ao ASLR de usuário, KASLR randomiza a base do kernel text e outras estruturas do kernel no boot.

  • Impede: exploits em nível de kernel que dependem de local fixo de código ou dados do kernel; exploits estáticos do kernel.
  • Detalhe do mecanismo:
  • A cada boot, a base do kernel é randomizada (dentro de um range).
  • Estruturas de dados do kernel (como task_structs, vm_map, etc.) também podem ser relocadas ou offsetadas.
  • Atacantes precisam primeiro leakar ponteiros do kernel ou usar vulnerabilidades de disclosure para computar offsets antes de corromper estruturas ou código do kernel.
Example Uma vulnerabilidade local visa corromper um function pointer do kernel (ex.: em `vtable`) em `KERN_BASE + offset`. Mas como `KERN_BASE` é desconhecido, o atacante deve primeiro leaká-lo (ex.: via read primitive) antes de computar o endereço correto para corromper.

6. Kernel Patch Protection (KPP / AMCC)

Introduzido em iOS mais recentes / hardware A-series (pós aprox. iOS 15–16 ou chips mais novos) KPP (aka AMCC) monitora continuamente a integridade das páginas do kernel text (via hash ou checksum). Se detectar tampering (patches, inline hooks, modificações de código) fora de janelas permitidas, ele dispara um kernel panic ou reboot.

  • Impede: patching persistente do kernel (modificar instruções do kernel), inline hooks, overwrites estáticos de funções.
  • Detalhe do mecanismo:
  • Um módulo de hardware ou firmware monitora a região de kernel text.
  • Periodicamente ou sob demanda re-hashes as páginas e compara com valores esperados.
  • Se ocorrerem mismatches fora de janelas de atualização benignas, ele panic o dispositivo (para evitar persistência maliciosa).
  • Atacantes devem ou evitar janelas de detecção ou usar caminhos legítimos de patch.
Example Um exploit tenta patchar o prologue de uma função do kernel (ex.: `memcmp`) para interceptar chamadas. Mas KPP nota que a hash da página de código não corresponde ao valor esperado e dispara um kernel panic, crashando o dispositivo antes que o patch se estabilize.

7. Kernel Text Read‐Only Region (KTRR)

Introduzido em SoCs modernos (pós ~A12 / hardware mais novo) KTRR é um mecanismo imposto por hardware: uma vez que o kernel text é bloqueado cedo durante o boot, ele torna-se read-only a partir de EL1 (o kernel), prevenindo escritas posteriores em páginas de código.

  • Impede: quaisquer modificações ao código do kernel após o boot (ex.: patching, code injection in-place) no nível de privilégio EL1.
  • Detalhe do mecanismo:
  • Durante o boot (no estágio secure/bootloader), o memory controller (ou uma unidade de hardware segura) marca as páginas físicas que contêm kernel text como read-only.
  • Mesmo se um exploit obtiver privilégios completos de kernel, não pode escrever nessas páginas para patchar instruções.
  • Para modificá-las, o atacante precisa primeiro comprometer a cadeia de boot, ou subverter o próprio KTRR.
Example Um exploit de elevação de privilégios salta para EL1 e escreve um trampoline em uma função do kernel (ex.: no handler de `syscall`). Mas porque as páginas estão bloqueadas como read-only pelo KTRR, a escrita falha (ou provoca fault), logo os patches não são aplicados.

8. Pointer Authentication Codes (PAC)

Introduzido com ARMv8.3 (hardware), Apple começando com A12 / iOS ~12+

  • PAC é uma feature de hardware introduzida no ARMv8.3-A para detectar tampering de valores de pointer (return addresses, function pointers, certos data pointers) ao embutir uma pequena assinatura criptográfica (um “MAC”) nos bits altos não usados do ponteiro.
  • A assinatura (“PAC”) é computada sobre o valor do ponteiro mais um modifier (um valor de contexto, ex.: stack pointer ou algum dado distintivo). Assim o mesmo valor de ponteiro em contextos diferentes recebe um PAC diferente.
  • No momento do uso, antes de desreferenciar ou branch via aquele pointer, uma instrução de authenticate verifica o PAC. Se válido, o PAC é removido e o ponteiro puro é obtido; se inválido, o ponteiro torna-se “poisoned” (ou um fault é levantado).
  • As chaves usadas para produzir/validar PACs vivem em registradores privilegiados (EL1, kernel) e não são diretamente legíveis do user mode.
  • Porque nem todos os 64 bits de um ponteiro são usados em muitos sistemas (ex.: espaço de endereço de 48 bits), os bits superiores são “spares” e podem conter o PAC sem alterar o endereço efetivo.

Base Arquitetural & Tipos de Chave

  • ARMv8.3 introduz cinco chaves de 128-bit (cada uma implementada via dois registradores de 64-bit) para pointer authentication.

  • APIAKey — para instruction pointers (domínio “I”, chave A)

  • APIBKey — segunda chave de instruction pointer (domínio “I”, chave B)

  • APDAKey — para data pointers (domínio “D”, chave A)

  • APDBKey — para data pointers (domínio “D”, chave B)

  • APGAKey — chave “generic”, para assinar dados não-ponteiro ou outros usos genéricos

  • Essas chaves são armazenadas em registradores de sistema privilegiados (acessíveis somente em EL1/EL2 etc.), não acessíveis do user mode.

  • O PAC é computado via uma função criptográfica (ARM sugere QARMA como algoritmo) usando:

  1. O valor do ponteiro (porção canônica)
  2. Um modifier (um valor de contexto, como um salt)
  3. A chave secreta
  4. Alguma lógica interna de tweak Se o PAC resultante corresponder ao que está armazenado nos bits altos do ponteiro, a autenticação tem sucesso.

Famílias de Instruções

A convenção de nomes é: PAC / AUT / XPAC, depois letras de domínio.

  • PACxx instruções assinaml um ponteiro e inserem um PAC
  • AUTxx instruções autenticam + stripam (validam e removem o PAC)
  • XPACxx instruções removem sem validar

Domínios / sufixos:

MnemonicMeaning / DomainKey / DomainExample Usage in Assembly
PACIASign instruction pointer with APIAKey“I, A”PACIA X0, X1 — sign pointer in X0 using APIAKey with modifier X1
PACIBSign instruction pointer with APIBKey“I, B”PACIB X2, X3
PACDASign data pointer with APDAKey“D, A”PACDA X4, X5
PACDBSign data pointer with APDBKey“D, B”PACDB X6, X7
PACG / PACGAGeneric (non-pointer) signing with APGAKey“G”PACGA X8, X9, X10 (sign X9 with modifier X10 into X8)
AUTIAAuthenticate APIA-signed instruction pointer & strip PAC“I, A”AUTIA X0, X1 — check PAC on X0 using modifier X1, then strip
AUTIBAuthenticate APIB domain“I, B”AUTIB X2, X3
AUTDAAuthenticate APDA-signed data pointer“D, A”AUTDA X4, X5
AUTDBAuthenticate APDB-signed data pointer“D, B”AUTDB X6, X7
AUTGAAuthenticate generic / blob (APGA)“G”AUTGA X8, X9, X10 (validate generic)
XPACIStrip PAC (instruction pointer, no validation)“I”XPACI X0 — remove PAC from X0 (instruction domain)
XPACDStrip PAC (data pointer, no validation)“D”XPACD X4 — remove PAC from data pointer in X4

Há formas especializadas / aliases:

  • PACIASP é atalho para PACIA X30, SP (assinar o link register usando SP como modifier)
  • AUTIASP é AUTIA X30, SP (autenticar o link register com SP)
  • Formas combinadas como RETAA, RETAB (authenticate-and-return) ou BLRAA (authenticate & branch) existem em extensões ARM / suporte de compilador.
  • Também variantes com modifier zero: PACIZA / PACIZB onde o modifier é implicitamente zero, etc.

Modifiers

O objetivo principal do modifier é vincular o PAC a um contexto específico para que o mesmo endereço assinado em diferentes contextos gere PACs diferentes. Isso evita o simples reuso de pointers entre frames ou objetos. É como adicionar um salt a um hash.

Portanto:

  • O modifier é um valor de contexto (outro registrador) que é misturado no cálculo do PAC. Escolhas típicas: stack pointer (SP), um frame pointer, ou algum object ID.
  • Usar SP como modifier é comum para signing de return addresses: o PAC fica amarrado ao stack frame específico. Se você tentar reutilizar o LR em um frame diferente, o modifier muda, então a validação do PAC falha.
  • O mesmo valor de ponteiro assinado sob modifiers diferentes gera PACs diferentes.
  • O modifier não precisa ser secreto, mas idealmente não é controlado pelo atacante.
  • Para instruções que assinam ou verificam ponteiros onde não existe um modifier significativo, algumas formas usam zero ou uma constante implícita.

Personalizações & Observações da Apple / iOS / XNU

  • A implementação de PAC da Apple inclui diversificadores por boot de modo que chaves ou tweaks mudam a cada boot, evitando reuso entre boots.
  • Eles também incluem mitigações cross-domain para que PACs assinados em user mode não possam ser facilmente reutilizados em kernel mode, etc.
  • No Apple M1 / Apple Silicon, engenharia reversa mostrou que existem nove tipos de modifier e registradores de sistema específicos da Apple para controle de chaves.
  • A Apple usa PAC em muitos subsistemas do kernel: signing de return address, integridade de pointers em dados do kernel, signed thread contexts, etc.
  • Google Project Zero mostrou como, sob um poderoso primitive de leitura/escrita de memória no kernel, poderia-se forjar kernel PACs (para A keys) em dispositivos A12-era, mas a Apple patchou muitos desses caminhos.
  • No sistema da Apple, algumas chaves são globais ao kernel, enquanto processos de usuário podem obter randomness de chave por-processo.

PAC Bypasses

  1. Kernel-mode PAC: teórico vs bypasses reais
  • Porque chaves e lógica de PAC do kernel são rigidamente controladas (registradores privilegiados, diversificadores, isolamento de domínio), forjar pointers assinados arbitrários do kernel é muito difícil.
  • Azad’s 2020 “iOS Kernel PAC, One Year Later” relata que em iOS 12-13 ele encontrou alguns bypasses parciais (signing gadgets, reuse de signed states, indirect branches não protegidas) mas nenhum bypass genérico completo. bazad.github.io
  • As customizações “Dark Magic” da Apple estreitaram ainda mais as superfícies exploráveis (domain switching, per-key enabling bits). i.blackhat.com
  • Há um conhecido kernel PAC bypass CVE-2023-32424 em Apple silicon (M1/M2) reportado por Zecao Cai et al. i.blackhat.com
  • Mas esses bypasses frequentemente dependem de gadgets muito específicos ou bugs de implementação; não são bypasses de uso geral.

Portanto kernel PAC é considerado altamente robusto, embora não perfeito.

  1. Técnicas de bypass de PAC em user-mode / runtime

Estas são mais comuns, e exploram imperfeições em como PAC é aplicado ou usado em dynamic linking / runtime frameworks. Abaixo classes, com exemplos.

2.1 Shared Cache / problemas com A key

  • A dyld shared cache é um grande blob pré-linkado de system frameworks e libraries. Porque é tão amplamente compartilhada, function pointers dentro do shared cache são “pre-signed” e então usados por muitos processos. Atacantes miram nesses pointers já assinados como “PAC oracles”.
  • Algumas técnicas de bypass tentam extrair ou reutilizar pointers assinados com A-key presentes no shared cache e reutilizá-los em gadgets.
  • O talk “No Clicks Required” descreve construir um oracle sobre o shared cache para inferir endereços relativos e combinar isso com pointers assinados para bypassar PAC. saelo.github.io
  • Além disso, imports de function pointers de shared libraries em userspace foram encontradas com proteção PAC insuficiente, permitindo que um atacante pegue function pointers sem alterar sua assinatura. (Project Zero bug entry) bugs.chromium.org

2.2 dlsym(3) / dynamic symbol resolution

  • Um bypass conhecido é chamar dlsym() para obter um function pointer já assinado (signed com A-key, diversifier zero) e então usá-lo. Como dlsym retorna um pointer legitimamente assinado, usá-lo contorna a necessidade de forjar PAC.
  • O blog da Epsilon detalha como alguns bypasses exploram isso: chamar dlsym("someSym") retorna um pointer assinado e que pode ser usado para chamadas indiretas. blog.epsilon-sec.com
  • O Synacktiv’s “iOS 18.4 — dlsym considered harmful” descreve um bug: alguns símbolos resolvidos via dlsym no iOS 18.4 retornam pointers que são assinados incorretamente (ou com diversifiers bugados), habilitando bypasses não intencionados de PAC. Synacktiv
  • A lógica em dyld para dlsym inclui: quando result->isCode, eles assinam o pointer retornado com __builtin_ptrauth_sign_unauthenticated(..., key_asia, 0), i.e. contexto zero. blog.epsilon-sec.com

Assim, dlsym é um vetor frequente em bypasses de PAC em user-mode.

2.3 Outras relocations do DYLD / runtime

  • O loader DYLD e a lógica de dynamic relocation são complexos e às vezes mapeiam páginas temporariamente como read/write para realizar relocations, depois as retornam para read-only. Atacantes exploram essas janelas. A talk do Synacktiv descreve “Operation Triangulation”, um bypass baseado em timing do PAC via relocations dinâmicas. Synacktiv
  • Páginas do DYLD agora são protegidas com SPRR / VM_FLAGS_TPRO (algumas flags de proteção para dyld). Mas versões anteriores tinham guardas mais fracos. Synacktiv
  • Em cadeias de exploit do WebKit, o DYLD loader é frequentemente um alvo para bypass de PAC. Os slides mencionam que muitos bypasses de PAC miraram o DYLD loader (via relocation, interposer hooks). Synacktiv

2.4 NSPredicate / NSExpression / ObjC / SLOP

  • Em cadeias de exploit userland, métodos do Objective-C runtime como NSPredicate, NSExpression ou NSInvocation são usados para contrabandear chamadas de controle sem aparente forja de pointers.
  • Em iOS mais antigos (antes do PAC), um exploit usou fake NSInvocation objects para chamar selectors arbitrários em memória controlada. Com PAC, modificações são necessárias. Mas a técnica SLOP (SeLector Oriented Programming) foi estendida sob PAC também. Project Zero
  • A técnica original SLOP permitia encadear chamadas ObjC criando invocations falsas; o bypass depende do fato de que ISA ou selector pointers às vezes não são totalmente protegidos por PAC. Project Zero
  • Em ambientes onde pointer authentication é aplicada parcialmente, métodos / selectors / target pointers podem não ter sempre proteção PAC, dando espaço para bypass.

Example Flow

Example Signing & Authenticating ``` ; Example: function prologue / return address protection my_func: stp x29, x30, [sp, #-0x20]! ; push frame pointer + LR mov x29, sp PACIASP ; sign LR (x30) using SP as modifier ; … body … mov sp, x29 ldp x29, x30, [sp], #0x20 ; restore AUTIASP ; authenticate & strip PAC ret

; Example: indirect function pointer stored in a struct ; suppose X1 contains a function pointer PACDA X1, X2 ; sign data pointer X1 with context X2 STR X1, [X0] ; store signed pointer

; later retrieval: LDR X1, [X0] AUTDA X1, X2 ; authenticate & strip BLR X1 ; branch to valid target

; Example: stripping for comparison (unsafe) LDR X1, [X0] XPACI X1 ; strip PAC (instruction domain) CMP X1, #some_label_address BEQ matched_label

</details>

<details>
<summary>Exemplo</summary>
Um buffer overflow sobrescreve um endereço de retorno na stack. O atacante escreve o endereço do gadget alvo mas não consegue computar o PAC correto. Quando a função retorna, a instrução `AUTIA` da CPU falha por causa do mismatch do PAC. A cadeia falha.
A análise do Project Zero sobre A12 (iPhone XS) mostrou como o PAC da Apple é usado e métodos de forjar PACs se um atacante tem primitives de leitura/escrita de memória.
</details>


### 9. **Branch Target Identification (BTI)**
**Introduzido com ARMv8.5 (hardware mais recentes)**
BTI é uma funcionalidade de hardware que verifica **targets de branch indireto**: ao executar `blr` ou chamadas/jumps indiretos, o destino deve começar com um **BTI landing pad** (`BTI j` ou `BTI c`). Pular para endereços de gadget que não tenham o landing pad aciona uma exceção.

A implementação do LLVM nota três variantes de instruções BTI e como elas mapeiam para tipos de branch.

| BTI Variant | O que permite (quais tipos de branch) | Colocação típica / caso de uso |
|-------------|----------------------------------------|-------------------------------|
| **BTI C** | Targets de branches indiretos no estilo *call* (ex. `BLR`, ou `BR` usando X16/X17) | Colocado na entrada de funções que podem ser chamadas indiretamente |
| **BTI J** | Targets de branches no estilo *jump* (ex. `BR` usado para tail calls) | Colocado no início de blocos acessíveis por jump tables ou tail-calls |
| **BTI JC** | Atua como C e J | Pode ser alvo tanto por call quanto por jump branches |

- Em código compilado com branch target enforcement, os compiladores inserem uma instrução BTI (C, J ou JC) em cada target válido de branch indireto (inícios de função ou blocos alcançáveis por jumps) de modo que branches indiretos só tenham sucesso para esses locais.
- **Branches / calls diretas** (isto é, `B`, `BL` com endereço fixo) **não são restringidos** pelo BTI. A suposição é que páginas de código são confiáveis e o atacante não pode alterá-las (portanto branches diretos são seguros).
- Além disso, instruções de **RET / return** geralmente não são restringidas pelo BTI porque endereços de retorno são protegidos via PAC ou mecanismos de assinatura de retorno.

#### Mecanismo e enforcement

- Quando a CPU decodifica um **branch indireto (BLR / BR)** em uma página marcada como “guarded / BTI-enabled”, ela verifica se a primeira instrução do endereço alvo é um BTI válido (C, J, ou JC conforme permitido). Se não for, ocorre uma **Branch Target Exception**.
- O encoding da instrução BTI foi desenhado para reutilizar opcodes previamente reservados para NOPs (em versões anteriores do ARM). Assim, binários com BTI permanecem compatíveis para trás: em hardware sem suporte a BTI, essas instruções atuam como NOPs.
- Os passes do compilador que adicionam BTIs os inserem apenas onde necessário: funções que podem ser chamadas indiretamente, ou blocos básicos alvo de jumps.
- Alguns patches e código do LLVM mostram que o BTI não é inserido para *todos* os blocos básicos — apenas aqueles que são potenciais targets de branch (ex. a partir de switch / jump tables).

#### Sinergia BTI + PAC

PAC protege o valor do ponteiro (a origem) — garante que a cadeia de chamadas/retornos indiretos não foi adulterada.

BTI assegura que mesmo um ponteiro válido só pode apontar para entry points devidamente marcados.

Combinados, um atacante precisa tanto de um ponteiro válido com PAC correto quanto que o target tenha um BTI colocado ali. Isso aumenta a dificuldade de construir gadgets de exploit.

#### Exemplo


<details>
<summary>Exemplo</summary>
Um exploit tenta pivotar para um gadget em `0xABCDEF` que não começa com `BTI c`. A CPU, ao executar `blr x0`, checa o target e falha porque o alinhamento/instrução inicial não inclui um landing pad válido. Assim muitos gadgets tornam-se inutilizáveis a menos que incluam o prefixo BTI.
</details>


### 10. **Privileged Access Never (PAN) & Privileged Execute Never (PXN)**
**Introduzido em extensões ARMv8 mais recentes / suporte iOS (para kernel hardened)**

#### PAN (Privileged Access Never)

- **PAN** é uma funcionalidade introduzida no **ARMv8.1-A** que previne que código privilegiado (EL1 ou EL2) **leia ou escreva** memória marcada como **user-accessible (EL0)**, a menos que o PAN seja explicitamente desabilitado.
- A ideia: mesmo que o kernel seja enganado ou comprometido, ele não pode desreferenciar arbitrariamente ponteiros de user-space sem primeiro *limpar* o PAN, reduzindo riscos de exploits estilo **`ret2usr`** ou uso indevido de buffers controlados pelo usuário.
- Quando PAN está habilitado (PSTATE.PAN = 1), qualquer instrução privilegiada de load/store que acesse um endereço virtual que seja “acessível em EL0” aciona uma **permission fault**.
- O kernel, quando precisa legitimamente acessar memória do user-space (ex. copiar dados de/para buffers do usuário), deve **desabilitar temporariamente o PAN** (ou usar instruções de “unprivileged load/store”) para permitir esse acesso.
- No Linux em ARM64, suporte a PAN foi introduzido por volta de 2015: patches do kernel adicionaram detecção da feature e substituíram `get_user` / `put_user` etc. por variantes que limpam o PAN ao redor de acessos à memória do user.

**Nuance / limitação / bug importante**
- Como notado por Siguza e outros, um bug de especificação (ou comportamento ambíguo) no design do ARM significa que mapeamentos user execute-only (`--x`) podem **não acionar o PAN**. Em outras palavras, se uma página de usuário for marcada como executável mas sem permissão de leitura, a tentativa do kernel de ler dela pode contornar o PAN porque a arquitetura considera “acessível em EL0” requerer permissão de leitura, não apenas execução. Isso leva a um bypass de PAN em certas configurações.
- Por causa disso, se iOS / XNU permitir páginas de usuário execute-only (como alguns setups de JIT ou code-cache podem fazer), o kernel pode acidentalmente ler delas mesmo com PAN habilitado. Esta é uma área sutil conhecida e explorável em alguns sistemas ARMv8+.

#### PXN (Privileged eXecute Never)

- **PXN** é um bit nas entradas da page table (em entradas leaf ou block) que indica que a página é **não-executável quando rodando em modo privilegiado** (i.e. quando EL1 executa).
- PXN impede que o kernel (ou qualquer código privilegiado) salte para ou execute instruções vindas de páginas de user-space mesmo se o controle for desviado. Na prática, impede redirecionamentos de controle no nível do kernel para memória de usuário.
- Combinado com PAN, isso assegura que:
1. O kernel não pode (por padrão) ler ou escrever dados de user-space (PAN)
2. O kernel não pode executar código de user-space (PXN)
- No formato de page table do ARMv8, as entradas leaf têm um bit `PXN` (e também `UXN` para unprivileged execute-never) nos seus bits de atributo.

Mesmo que o kernel tenha um ponteiro de função corrompido apontando para memória de usuário, e tente fazer branch para lá, o bit PXN causará uma falha.

#### Modelo de permissão de memória & como PAN e PXN se mapeiam para bits da page table

Para entender como PAN / PXN funcionam, é preciso ver como o modelo de tradução e permissões do ARM funciona (simplificado):

- Cada entrada de página ou block tem campos de atributo incluindo **AP[2:1]** para permissões de acesso (read/write, privilegiado vs não-privilegiado) e bits **UXN / PXN** para restrições de execute-never.
- Quando PSTATE.PAN = 1 (habilitado), o hardware aplica semânticas modificadas: acessos privilegiados a páginas marcadas como “acessíveis por EL0” (i.e. acessíveis pelo usuário) são proibidos (fault).
- Por causa do bug mencionado, páginas marcadas apenas como executáveis (sem permissão de leitura) podem não contar como “acessíveis por EL0” em certas implementações, permitindo o bypass do PAN.
- Quando o bit PXN de uma página está setado, mesmo que o fetch de instrução venha de um nível de privilégio superior, a execução é proibida.

#### Uso do kernel de PAN / PXN em um OS hardened (ex. iOS / XNU)

Em um design de kernel hardened (como o que a Apple pode usar):

- O kernel habilita PAN por padrão (portanto código privilegiado é restringido).
- Em caminhos que legitimamente precisam ler ou escrever buffers do usuário (ex. copy de syscall, I/O, read/write de ponteiro do usuário), o kernel desabilita PAN temporariamente ou usa instruções especiais para sobrepor.
- Após terminar o acesso aos dados do usuário, deve reabilitar o PAN.
- PXN é aplicado via page tables: páginas de usuário têm PXN = 1 (assim o kernel não pode executá-las), páginas do kernel não têm PXN (assim código do kernel pode executar).
- O kernel deve garantir que nenhum caminho de código cause fluxo de execução para regiões de memória de usuário (isso contornaria PXN) — então cadeias de exploit que dependem de “pular para shellcode controlado pelo usuário” são bloqueadas.

Devido ao bypass do PAN via páginas execute-only, num sistema real a Apple pode desabilitar ou proibir páginas de usuário execute-only, ou aplicar correções em torno da fraqueza na especificação.

#### Superfícies de ataque, bypasses e mitigações

- **PAN bypass via execute-only pages**: como discutido, a spec deixa uma lacuna: páginas de usuário com execute-only (sem permissão de leitura) podem não ser consideradas “acessíveis em EL0”, então PAN não bloqueará leituras do kernel dessas páginas em algumas implementações. Isso dá ao atacante um caminho incomum para alimentar dados via seções “execute-only”.
- **Exploit de janela temporal**: se o kernel desabilita PAN por uma janela maior do que o necessário, uma race ou caminho malicioso pode explorar essa janela para realizar acessos não intencionais à memória do usuário.
- **Esquecer de reabilitar**: se caminhos de código falham em reabilitar o PAN, operações subsequentes do kernel podem acessar incorretamente memória de usuário.
- **Má configuração de PXN**: se as page tables não setarem PXN nas páginas de usuário ou mapear incorretamente páginas de código do usuário, o kernel pode ser enganado a executar código do user-space.
- **Speculation / side-channels**: análogo a bypasses especulativos, podem existir efeitos microarquiteturais transitórios que causem violação transitória das checagens PAN / PXN (embora tais ataques dependam fortemente do design da CPU).
- **Interações complexas**: em features mais avançadas (ex. JIT, shared memory, regiões de código just-in-time), o kernel pode precisar de controle fino para permitir certos acessos ou execução em regiões mapeadas ao user; projetar isso de forma segura sob as restrições PAN/PXN é não trivial.

#### Exemplo

<details>
<summary>Code Example</summary>
Here are illustrative pseudo-assembly sequences showing enabling/disabling PAN around user memory access, and how a fault might occur.

// Suppose kernel entry point, PAN is enabled (privileged code cannot access user memory by default)

; Kernel receives a syscall with user pointer in X0 ; wants to read an integer from user space mov X1, X0 ; X1 = user pointer

; disable PAN to allow privileged access to user memory MSR PSTATE.PAN, #0 ; clear PAN bit, disabling the restriction

ldr W2, [X1] ; now allowed load from user address

; re-enable PAN before doing other kernel logic MSR PSTATE.PAN, #1 ; set PAN

; … further kernel work …

; Later, suppose an exploit corrupts a pointer to a user-space code page and jumps there BR X3 ; branch to X3 (which points into user memory)

; Because the target page is marked PXN = 1 for privileged execution, ; the CPU throws an exception (fault) and rejects execution

Se o kernel tivesse **não** definido PXN nessa página de usuário, então o branch poderia ter sucesso — o que seria inseguro.

Se o kernel esquecer de reativar PAN após o acesso à memória do usuário, isso abre uma janela onde lógica adicional do kernel pode acidentalmente read/write arbitrary user memory.

Se o ponteiro do usuário apontar para uma página execute-only (página de usuário com apenas permissão de execução, sem read/write), sob o bug da especificação PAN, `ldr W2, [X1]` pode **não** faultar mesmo com PAN habilitado, permitindo um exploit de bypass, dependendo da implementação.

</details>

<details>
<summary>Example</summary>
Uma vulnerabilidade no kernel tenta pegar um ponteiro de função fornecido pelo usuário e chamá-lo em contexto de kernel (i.e. `call user_buffer`). Sob PAN/PXN, essa operação é proibida ou causa fault.
</details>

---

### 11. **Top Byte Ignore (TBI) / Pointer Tagging**
**Introduced in ARMv8.5 / newer (or optional extension)**
TBI significa que o byte superior (mais significativo) de um ponteiro 64-bit é ignorado pela tradução de endereços. Isso permite que o SO ou o hardware embuta bits de **tag** no byte superior do ponteiro sem afetar o endereço real.

- TBI stands for **Top Byte Ignore** (sometimes called *Address Tagging*). É um recurso de hardware (disponível em muitas implementações ARMv8+) que **ignora os 8 bits mais altos** (bits 63:56) de um ponteiro 64-bit ao realizar **address translation / load/store / instruction fetch**.
- Na prática, a CPU trata um ponteiro `0xTTxxxx_xxxx_xxxx` (onde `TT` = top byte) como `0x00xxxx_xxxx_xxxx` para efeitos de tradução de endereços, ignorando (mascarando) o byte superior. O byte superior pode ser usado pelo software para armazenar **metadata / tag bits**.
- Isso dá ao software espaço "gratuito" in-band para embutir um byte de tag em cada ponteiro sem alterar qual localização de memória ele referencia.
- A arquitetura garante que loads, stores e instruction fetch tratem o ponteiro com seu byte superior mascarado (i.e. tag removido) antes de realizar o acesso real à memória.

Assim, TBI desacopla o **logical pointer** (pointer + tag) do **physical address** usado para operações de memória.

#### Why TBI: Use cases and motivation

- **Pointer tagging / metadata**: Você pode armazenar metadados adicionais (ex.: tipo do objeto, versão, bounds, tags de integridade) nesse byte superior. Quando você usar o ponteiro, a tag é ignorada a nível de hardware, então não é preciso removê-la manualmente para o acesso à memória.
- **Memory tagging / MTE (Memory Tagging Extension)**: TBI é o mecanismo de hardware base sobre o qual MTE é construído. Em ARMv8.5, a **Memory Tagging Extension** usa os bits 59:56 do ponteiro como uma **logical tag** e os verifica contra uma **allocation tag** armazenada na memória.
- **Enhanced security & integrity**: Ao combinar TBI com pointer authentication (PAC) ou verificações em runtime, você pode forçar não só o valor do ponteiro mas também a tag a estarem corretos. Um atacante que sobrescrever um ponteiro sem a tag correta produzirá uma tag mismatched.
- **Compatibility**: Como TBI é opcional e os bits de tag são ignorados pelo hardware, código legado sem tags continua a operar normalmente. Os bits de tag tornam-se efetivamente bits “don't care” para código antigo.

#### Example
<details>
<summary>Example</summary>
Um ponteiro de função continha uma tag no seu byte superior (por exemplo `0xAA`). Um exploit sobrescreve os bits baixos do ponteiro mas negligencia a tag, então quando o kernel verifica ou sanitiza, o ponteiro falha ou é rejeitado.
</details>

---

### 12. **Page Protection Layer (PPL)**
**Introduced in late iOS / modern hardware (iOS ~17 / Apple silicon / high-end models)** (alguns relatos mostram PPL em macOS / Apple silicon, mas a Apple está trazendo proteções análogas para iOS)

- PPL é projetado como uma **fronteira de proteção intra-kernel**: mesmo se o kernel (EL1) for comprometido e tiver capacidades de leitura/gravação, **não deveria poder modificar livremente** certas **páginas sensíveis** (especialmente page tables, metadata de code-signing, páginas de código do kernel, entitlements, trust caches, etc.).
- Ele cria efetivamente um **“kernel dentro do kernel”** — um componente menor e confiável (PPL) com **privilégios elevados** que sozinho pode modificar páginas protegidas. Outro código do kernel deve chamar rotinas PPL para efetuar mudanças.
- Isso reduz a superfície de ataque para exploits de kernel: mesmo com R/W/execute arbitrário em modo kernel, o código de exploit também precisa de alguma forma entrar no domínio PPL (ou contornar PPL) para modificar estruturas críticas.
- Em Apple silicon mais recentes (A15+ / M2+), a Apple está migrando para **SPTM (Secure Page Table Monitor)**, que em muitos casos substitui PPL para proteção de page-tables nessas plataformas.

Aqui está como se acredita que o PPL opere, baseado em análise pública:

#### Use of APRR / permission routing (APRR = Access Permission ReRouting)

- O hardware Apple usa um mecanismo chamado **APRR (Access Permission ReRouting)**, que permite que entradas de page table (PTEs) contenham pequenos índices, em vez de bits completos de permissão. Esses índices são mapeados via registradores APRR para permissões efetivas. Isso permite remapeamento dinâmico de permissões por domínio.
- PPL aproveita APRR para segregar privilégios dentro do contexto do kernel: somente o domínio PPL tem permissão para atualizar o mapeamento entre índices e permissões efetivas. Ou seja, quando código não-PPL do kernel escreve um PTE ou tenta alterar bits de permissão, a lógica APRR impede isso (ou aplica um mapeamento somente-leitura).
- O código PPL em si roda em uma região restrita (ex.: `__PPLTEXT`) que normalmente é não-executável ou não-escrevível até que portas de entrada temporárias permitam. O kernel chama pontos de entrada PPL (“PPL routines”) para realizar operações sensíveis.

#### Gate / Entry & Exit

- Quando o kernel precisa modificar uma página protegida (ex.: mudar permissões de uma página de código do kernel, ou modificar page tables), ele chama uma rotina wrapper do PPL, que faz validação e então transiciona para o domínio PPL. Fora desse domínio, as páginas protegidas são efetivamente somente-leitura ou não-modificáveis pelo kernel principal.
- Durante a entrada no PPL, os mapeamentos APRR são ajustados de modo que as páginas de memória na região PPL sejam configuradas como **executáveis & graváveis** dentro do PPL. Ao sair, elas retornam a somente-leitura / não-graváveis. Isso garante que apenas rotinas PPL bem auditadas possam escrever nas páginas protegidas.
- Fora do PPL, tentativas do código do kernel de escrever nessas páginas protegidas gerarão fault (permission denied) porque o mapeamento APRR para aquele domínio de código não permite escrita.

#### Protected page categories

As páginas que o PPL normalmente protege incluem:

- Estruturas de page table (translation table entries, metadata de mapeamento)
- Páginas de código do kernel, especialmente aquelas contendo lógica crítica
- Metadata de code-sign (trust caches, blobs de assinatura)
- Tabelas de entitlements, tabelas de enforcement de assinatura
- Outras estruturas de alto valor do kernel onde um patch permitiria contornar checagens de assinatura ou manipular credenciais

A ideia é que mesmo se a memória do kernel estiver totalmente controlada, o atacante não pode simplesmente patchar ou reescrever essas páginas, a menos que também comprometa rotinas PPL ou contorne o PPL.

#### Known Bypasses & Vulnerabilities

1. **Project Zero’s PPL bypass (stale TLB trick)**

- Um writeup público do Project Zero descreve um bypass envolvendo **stale TLB entries**.
- A ideia:

1. Alocar duas páginas físicas A e B, marcá-las como páginas PPL (portanto protegidas).
2. Mapear dois endereços virtuais P e Q cujas páginas de tradução L3 venham de A e B.
3. Rodar uma thread que acesse continuamente Q, mantendo sua entrada TLB viva.
4. Chamar `pmap_remove_options()` para remover mapeamentos a partir de P; devido a um bug, o código remove por engano os TTEs tanto para P quanto para Q, mas invalida a entrada TLB somente para P, deixando a entrada stale de Q viva.
5. Reusar B (a página da tabela de Q) para mapear memória arbitrária (ex.: páginas protegidas pelo PPL). Como a entrada stale do TLB ainda mapeia a antiga configuração de Q, esse mapeamento permanece válido para aquele contexto.
6. Através disso, o atacante pode colocar um mapeamento gravável de páginas protegidas pelo PPL sem passar pela interface PPL.

- Esse exploit requereu controle fino do mapeamento físico e do comportamento do TLB. Demonstra que uma fronteira de segurança que depende da correção de TLB / mapeamento deve ser extremamente cuidadosa sobre invalidações de TLB e consistência de mapeamentos.

- O Project Zero comentou que bypasses assim são sutis e raros, mas possíveis em sistemas complexos. Ainda assim, eles consideram o PPL uma mitigação sólida.

2. **Other potential hazards & constraints**

- Se um exploit de kernel puder entrar diretamente nas rotinas PPL (via chamadas aos wrappers PPL), ele pode contornar as restrições. Portanto a validação de argumentos é crítica.
- Bugs no próprio código PPL (ex.: overflow aritmético, checagens de limites) podem permitir modificações out-of-bounds dentro do PPL. O Project Zero observou que tal bug em `pmap_remove_options_internal()` foi explorado em seu bypass.
- A fronteira PPL está irrevogavelmente ligada à aplicação do hardware (APRR, memory controller), então é tão forte quanto a implementação de hardware.

#### Example
<details>
<summary>Code Example</summary>
Here’s a simplified pseudocode / logic showing how a kernel might call into PPL to modify protected pages:
```c
// In kernel (outside PPL domain)
function kernel_modify_pptable(pt_addr, new_entry) {
// validate arguments, etc.
return ppl_call_modify(pt_addr, new_entry)  // call PPL wrapper
}

// In PPL (trusted domain)
function ppl_call_modify(pt_addr, new_entry) {
// temporarily enable write access to protected pages (via APRR adjustments)
aprr_set_index_for_write(PPL_INDEX)
// perform the modification
*pt_addr = new_entry
// restore permissions (make pages read-only again)
aprr_restore_default()
return success
}

// If kernel code outside PPL does:
*pt_addr = new_entry  // a direct write
// It will fault because APRR mapping for non-PPL domain disallows write to that page

O kernel pode executar muitas operações normais, mas somente através das rotinas ppl_call_* ele pode alterar mapeamentos protegidos ou patchar código.

Exemplo Um exploit do kernel tenta sobrescrever a entitlement table, ou desabilitar a enforcement de code-sign modificando um kernel signature blob. Como essa página é protegida por PPL, a escrita é bloqueada a menos que passe pela interface PPL. Assim, mesmo com execução de código no kernel, você não pode contornar as restrições de code-sign nem modificar dados de credenciais arbitrariamente. Em iOS 17+ certos dispositivos usam SPTM para isolar ainda mais páginas gerenciadas pelo PPL.

PPL → SPTM / Substituições / Futuro

  • Nos SoCs modernos da Apple (A15 ou posteriores, M2 ou posteriores), a Apple suporta SPTM (Secure Page Table Monitor), que substitui o PPL para proteções da tabela de páginas.
  • A Apple destaca na documentação: “Page Protection Layer (PPL) and Secure Page Table Monitor (SPTM) enforce execution of signed and trusted code … PPL manages the page table permission overrides … Secure Page Table Monitor replaces PPL on supported platforms.”
  • A arquitetura SPTM provavelmente desloca mais aplicação de políticas para um monitor de maior privilégio fora do controle do kernel, reduzindo ainda mais a fronteira de confiança.

MTE | EMTE | MIE

Aqui está uma descrição em nível mais alto de como o EMTE opera sob o setup MIE da Apple:

  1. Atribuição de tag
  • Quando memória é alocada (por exemplo no kernel ou em user space via allocators seguros), uma secret tag é atribuída a esse bloco.
  • O ponteiro retornado ao usuário ou kernel inclui essa tag nos bits altos (usando TBI / top byte ignore mechanisms).
  1. Verificação de tag no acesso
  • Sempre que um load ou store é executado usando um ponteiro, o hardware verifica se a tag do ponteiro bate com a tag do bloco de memória (allocation tag). Se houver mismatch, ele falha imediatamente (por ser síncrono).
  • Por ser síncrono, não existe janela de “detecção atrasada”.
  1. Retagging ao free / reuse
  • Quando a memória é freed, o allocator muda a tag do bloco (assim ponteiros antigos com tags antigas não casam mais).
  • Um ponteiro use-after-free teria então uma tag stale e falharia ao ser acessado.
  1. Diferenciação de tags entre vizinhos para pegar overflows
  • Alocações adjacentes recebem tags distintas. Se um buffer overflow vazar para a memória do vizinho, o mismatch de tag causa uma falha.
  • Isto é especialmente poderoso para detectar pequenos overflows que cruzam a fronteira.
  1. Aplicação de confidencialidade das tags
  • A Apple precisa evitar que valores de tag sejam leaked (porque, se um atacante aprende a tag, ele poderia forjar ponteiros com tags corretas).
  • Eles incluem proteções (controles microarchitecturais / speculative) para evitar vazamentos por canais laterais das bits de tag.
  1. Integração kernel e user-space
  • A Apple usa EMTE não apenas em user-space mas também em componentes críticos do kernel/OS (para proteger o kernel contra corrupção de memória).
  • O hardware/OS garante que as regras de tag se apliquem mesmo quando o kernel está executando em nome do user space.
Exemplo ``` Allocate A = 0x1000, assign tag T1 Allocate B = 0x2000, assign tag T2

// pointer P points into A with tag T1 P = (T1 << 56) | 0x1000

// Valid store *(P + offset) = value // tag T1 matches allocation → allowed

// Overflow attempt: P’ = P + size_of_A (into B region) *(P’ + delta) = value → pointer includes tag T1 but memory block has tag T2 → mismatch → fault

// Free A, allocator retags it to T3 free(A)

// Use-after-free: *(P) = value → pointer still has old tag T1, memory region is now T3 → mismatch → fault

</details>

#### Limitações & desafios

- **Transbordamentos intrablocos**: Se o overflow permanecer dentro da mesma alocação (não atravessa a boundary) e a tag permanecer igual, tag mismatch não o detecta.
- **Limitação da largura da tag**: Apenas alguns bits (e.g. 4 bits, or small domain) estão disponíveis para a tag—namespace limitado.
- **Side-channel leaks**: Se os bits de tag puderem ser leaked (via cache / speculative execution), o atacante pode aprender tags válidas e contornar. A aplicação da Tag Confidentiality pela Apple visa mitigar isto.
- **Overhead de performance**: As verificações de tag em cada load/store adicionam custo; a Apple precisa otimizar o hardware para reduzir esse overhead.
- **Compatibilidade & fallback**: Em hardware mais antigo ou em partes que não suportam EMTE, deve existir um fallback. A Apple afirma que MIE só é ativado em dispositivos com suporte.
- **Lógica complexa do allocator**: O allocator precisa gerir tags, retagging, alinhar boundaries e evitar colisões de mis-tag. Bugs na lógica do allocator podem introduzir vulnerabilidades.
- **Memória mista / áreas híbridas**: Parte da memória pode permanecer untagged (legacy), tornando a interoperabilidade mais complicada.
- **Ataques especulativos / transitórios**: Como com muitas proteções microarquiteturais, speculative execution ou micro-op fusions podem contornar checagens transitoriamente ou leak tag bits.
- **Limitado a regiões suportadas**: A Apple pode aplicar EMTE apenas em áreas seletivas e de alto risco (kernel, subsistemas críticos de segurança), não universalmente.

---

## Principais melhorias / diferenças comparado ao MTE padrão

Aqui estão as melhorias e mudanças que a Apple enfatiza:

| Feature | Original MTE | EMTE (Apple’s enhanced) / MIE |
|---|---|---|
| **Check mode** | Suporta modos síncrono e assíncrono. Em async, tag mismatches são reportados mais tarde (delayed) | A Apple insiste no **modo síncrono** por padrão—tag mismatches são detectados imediatamente, sem janelas de delay/race. |
| **Coverage of non-tagged memory** | Acessos a non-tagged memory (e.g. globals) podem contornar checagens em algumas implementações | EMTE exige que acessos de uma região taggeada para non-tagged memory também validem conhecimento da tag, dificultando bypass por mistura de alocações. |
| **Tag confidentiality / secrecy** | Tags podem ser observáveis ou leaked via side channels | A Apple adiciona **Tag Confidentiality Enforcement**, que tenta prevenir leakage de valores de tag (via speculative side-channels etc.). |
| **Allocator integration & retagging** | MTE deixa grande parte da lógica do allocator para o software | Os allocators tipados seguros da Apple (kalloc_type, xzone malloc, etc.) integram-se com EMTE: quando memória é alocada ou liberada, as tags são geridas em granularidade fina. |
| **Always-on by default** | Em muitas plataformas, MTE é opcional ou desligado por padrão | A Apple habilita EMTE / MIE por padrão em hardware suportado (e.g. iPhone 17 / A19) para kernel e muitos processos de usuário. |

Porque a Apple controla tanto o hardware quanto a stack de software, ela pode aplicar EMTE de forma rigorosa, evitar pitfalls de performance e fechar vetores de side-channel.

---

## Como o EMTE funciona na prática (Apple / MIE)

Aqui está uma descrição em alto nível de como o EMTE opera sob o setup MIE da Apple:

1. **Tag assignment**
- Quando memória é alocada (e.g. no kernel ou user space via secure allocators), uma **secret tag** é atribuída ao bloco.
- O ponteiro retornado ao usuário ou kernel inclui essa tag nos bits altos (usando TBI / top byte ignore mechanisms).

2. **Tag checking on access**
- Sempre que um load ou store é executado usando um ponteiro, o hardware verifica se a tag do ponteiro bate com a tag do bloco de memória (allocation tag). Se houver mismatch, ocorre fault imediatamente (já que é síncrono).
- Por ser síncrono, não existe janela de “detecção atrasada”.

3. **Retagging on free / reuse**
- Quando memória é liberada, o allocator altera a tag do bloco (assim ponteiros antigos com tags antigas não batem mais).
- Um ponteiro use-after-free terá, portanto, uma tag stale e mismatch ao ser acessado.

4. **Neighbor-tag differentiation to catch overflows**
- Alocações adjacentes recebem tags distintas. Se um buffer overflow transborda para a memória do vizinho, tag mismatch causa fault.
- Isto é especialmente eficaz para capturar pequenos overflows que atravessam boundaries.

5. **Tag confidentiality enforcement**
- A Apple precisa evitar que valores de tag sejam leak (porque se o atacante souber a tag, ele poderia forjar ponteiros com tags corretas).
- Eles incluem proteções (controles microarquiteturais / especulativos) para evitar side-channel leakage de bits de tag.

6. **Kernel and user-space integration**
- A Apple usa EMTE não só no user-space, mas também no kernel / componentes críticos do SO (para proteger o kernel contra corrupção de memória).
- O hardware/OS garante que as regras de tag se apliquem mesmo quando o kernel está executando em nome do user space.

Como o EMTE está embutido no MIE, a Apple usa EMTE em modo síncrono nas principais superfícies de ataque, não como uma opção opt-in ou modo de debugging.

---

## Exception handling in XNU

Quando uma **exception** ocorre (e.g., `EXC_BAD_ACCESS`, `EXC_BAD_INSTRUCTION`, `EXC_CRASH`, `EXC_ARM_PAC`, etc.), a **Mach layer** do kernel XNU é responsável por interceptá‑la antes de ela se tornar um estilo UNIX de **signal** (como `SIGSEGV`, `SIGBUS`, `SIGILL`, ...).

Esse processo envolve múltiplas camadas de propagação e tratamento de exceção antes de atingir o user space ou ser convertido em um BSD signal.

### Fluxo de Exceção (Alto Nível)

1.  **CPU triggers a synchronous exception** (e.g., invalid pointer dereference, PAC failure, illegal instruction, etc.).

2.  **Low-level trap handler** executa (`trap.c`, `exception.c` no source do XNU).

3.  O trap handler chama **`exception_triage()`**, o núcleo do tratamento de exceções Mach.

4.  `exception_triage()` decide como rotear a exceção:

-   Primeiro para o **thread's exception port**.

-   Depois para o **task's exception port**.

-   Depois para o **host's exception port** (frequentemente `launchd` ou `ReportCrash`).

Se nenhuma dessas ports tratar a exceção, o kernel pode:

-   **Convertê‑la em um BSD signal** (para processos em user-space).

-   **Panic** (para exceções em kernel-space).

### Função Principal: `exception_triage()`

A função `exception_triage()` roteia as exceções Mach pela cadeia de possíveis handlers até que uma as trate ou até que seja finalmente fatal. Está definida em `osfmk/kern/exception.c`.
```c
void exception_triage(exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt);

Fluxo de Chamada Típico:

exception_triage() └── exception_deliver() ├── exception_deliver_thread() ├── exception_deliver_task() └── exception_deliver_host()

Se tudo falhar → tratado por bsd_exception() → traduzido em um sinal como SIGSEGV.

Exception Ports

Cada objeto Mach (thread, task, host) pode registrar exception ports, para onde as mensagens de exceção são enviadas.

Eles são definidos pela API:

task_set_exception_ports()
thread_set_exception_ports()
host_set_exception_ports()

Cada exception port tem:

  • Uma máscara (quais exceções quer receber)
  • Um port name (Mach port para receber mensagens)
  • Um comportamento (como o kernel envia a mensagem)
  • Um flavor (qual thread state incluir)

Depuradores e Tratamento de Exceções

Um debugger (por exemplo, LLDB) configura um exception port na task ou thread alvo, normalmente usando task_set_exception_ports().

Quando uma exceção ocorre:

  • A mensagem Mach é enviada para o processo do debugger.
  • O debugger pode decidir lidar com ela (resume, modificar registradores, pular instrução) ou não lidar com a exceção.
  • Se o debugger não a tratar, a exceção se propaga para o próximo nível (task → host).

Fluxo de EXC_BAD_ACCESS

  1. Thread desreferencia ponteiro inválido → CPU gera Data Abort.

  2. O handler de trap do kernel chama exception_triage(EXC_BAD_ACCESS, ...).

  3. Mensagem enviada para:

  • Thread port → (o debugger pode interceptar o breakpoint).

  • Se o debugger ignora → Task port → (handler a nível de processo).

  • Se ignorado → Host port (normalmente ReportCrash).

  1. Se ninguém tratar → bsd_exception() traduz para SIGSEGV.

PAC Exceptions

Quando a Pointer Authentication (PAC) falha (signature mismatch), uma exceção Mach especial é gerada:

  • EXC_ARM_PAC (tipo)
  • Codes podem incluir detalhes (por exemplo, tipo de key, tipo de pointer).

Se o binário tem a flag TFRO_PAC_EXC_FATAL, o kernel trata falhas de PAC como fatals, contornando a interceptação pelo debugger. Isso evita que atacantes usem debuggers para contornar checagens de PAC e está habilitado para platform binaries.

Software Breakpoints

Um software breakpoint (int3 em x86, brk em ARM64) é implementado provocando uma falha deliberada.
O debugger captura isso via exception port:

  • Modifica o instruction pointer ou a memória.
  • Restaura a instrução original.
  • Resume a execução.

Esse mesmo mecanismo é o que permite “capturar” uma PAC exception — a menos que TFRO_PAC_EXC_FATAL esteja definido, caso em que nunca chega ao debugger.

Conversão para BSD Signals

Se nenhum handler aceitar a exceção:

  • O kernel chama task_exception_notify() → bsd_exception().

  • Isso mapeia Mach exceptions para signals:

Mach ExceptionSignal
EXC_BAD_ACCESSSIGSEGV or SIGBUS
EXC_BAD_INSTRUCTIONSIGILL
EXC_ARITHMETICSIGFPE
EXC_SOFTWARESIGTRAP
EXC_BREAKPOINTSIGTRAP
EXC_CRASHSIGKILL
EXC_ARM_PACSIGILL (on non-fatal)

Arquivos-chave no código fonte do XNU

  • osfmk/kern/exception.c → Núcleo de exception_triage(), exception_deliver_*().

  • bsd/kern/kern_sig.c → Lógica de entrega de signals.

  • osfmk/arm64/trap.c → Handlers de trap de baixo nível.

  • osfmk/mach/exc.h → Códigos e estruturas de exception.

  • osfmk/kern/task.c → Configuração de task exception ports.


Old Kernel Heap (Pré-iOS 15 / Era pré-A12)

O kernel usava um zone allocator (kalloc) dividido em “zones” de tamanho fixo.
Cada zone armazena apenas alocações de uma única classe de tamanho.

Da captura de tela:

Nome da ZoneElement SizeExample Use
default.kalloc.1616 bytesVery small kernel structs, pointers.
default.kalloc.3232 bytesSmall structs, object headers.
default.kalloc.6464 bytesIPC messages, tiny kernel buffers.
default.kalloc.128128 bytesMedium objects like parts of OSObject.
default.kalloc.12801280 bytesLarge structures, IOSurface/graphics metadata.

Como funcionava:

  • Cada pedido de alocação é arredondado para cima ao tamanho da zone mais próxima. (Ex.: um pedido de 50 bytes cai na zone kalloc.64).
  • A memória em cada zone era mantida numa freelist — chunks liberados pelo kernel voltavam para essa zone.
  • Se você overflowasse um buffer de 64 bytes, sobrescreveria o próximo objeto na mesma zone.

Por isso heap spraying / feng shui era tão eficaz: você podia prever vizinhos de objetos pulverizando alocações da mesma classe de tamanho.

A freelist

Dentro de cada kalloc zone, objetos liberados não eram retornados diretamente ao sistema — eles iam para uma freelist, uma lista ligada de chunks disponíveis.

  • Quando um chunk era liberado, o kernel escrevia um ponteiro no início daquele chunk → o endereço do próximo chunk livre na mesma zone.

  • A zone mantinha um ponteiro HEAD para o primeiro chunk livre.

  • A alocação sempre usava o HEAD atual:

  1. Pop HEAD (retorna essa memória ao chamador).

  2. Atualiza HEAD = HEAD->next (armazenado no header do chunk liberado).

  • Liberar empurrava chunks de volta:

  • freed_chunk->next = HEAD

  • HEAD = freed_chunk

Então a freelist era apenas uma lista ligada construída dentro da própria memória liberada.

Estado normal:

Zone page (64-byte chunks for example):
[ A ] [ F ] [ F ] [ A ] [ F ] [ A ] [ F ]

Freelist view:
HEAD ──► [ F ] ──► [ F ] ──► [ F ] ──► [ F ] ──► NULL
(next ptrs stored at start of freed chunks)

Explorando a freelist

Porque os primeiros 8 bytes de um free chunk correspondem ao freelist pointer, um atacante poderia corrompê-lo:

  1. Heap overflow into an adjacent freed chunk → overwrite its “next” pointer.

  2. Use-after-free write into a freed object → overwrite its “next” pointer.

Then, on the next allocation of that size:

  • The allocator pops the corrupted chunk.

  • Follows the attacker-supplied “next” pointer.

  • Returns a pointer to arbitrary memory, enabling fake object primitives or targeted overwrite.

Visual example of freelist poisoning:

Before corruption:
HEAD ──► [ F1 ] ──► [ F2 ] ──► [ F3 ] ──► NULL

After attacker overwrite of F1->next:
HEAD ──► [ F1 ]
(next) ──► 0xDEAD_BEEF_CAFE_BABE  (attacker-chosen)

Next alloc of this zone → kernel hands out memory at attacker-controlled address.

This freelist design made exploitation highly effective pre-hardening: predictable neighbors from heap sprays, raw pointer freelist links, and no type separation allowed attackers to escalate UAF/overflow bugs into arbitrary kernel memory control.

Heap Grooming / Feng Shui

O objetivo do Heap Grooming é modelar o layout do heap para que, quando um atacante desencadear um overflow ou use-after-free, o objeto alvo (vítima) fique imediatamente adjacente a um objeto controlado pelo atacante.
Dessa forma, quando ocorrer a corrupção de memória, o atacante pode sobrescrever de forma confiável o objeto vítima com dados controlados.

Passos:

  1. Spray allocations (fill the holes)
  • Com o tempo, o kernel heap fica fragmentado: algumas zonas têm buracos onde objetos antigos foram liberados.
  • O atacante primeiro faz muitas alocações dummy para preencher essas lacunas, fazendo com que o heap fique “compactado” e previsível.
  1. Force new pages
  • Uma vez que os buracos estejam preenchidos, as próximas alocações devem vir de novas páginas adicionadas à zona.
  • Páginas recém-alocadas significam que objetos estarão agrupados, não espalhados por memória fragmentada antiga.
  • Isso dá ao atacante um controle muito melhor sobre vizinhos.
  1. Place attacker objects
  • O atacante agora faz spray novamente, criando muitos objetos controlados nas novas páginas.
  • Esses objetos têm tamanho e posicionamento previsíveis (já que pertencem à mesma zona).
  1. Free a controlled object (make a gap)
  • O atacante libera deliberadamente um de seus próprios objetos.
  • Isso cria um “buraco” no heap, que o allocator irá reutilizar para a próxima alocação daquele tamanho.
  1. Victim object lands in the hole
  • O atacante faz o kernel alocar o objeto vítima (aquele que quer corromper).
  • Como o buraco é o primeiro slot disponível na freelist, a vítima é colocada exatamente onde o atacante havia liberado seu objeto.
  1. Overflow / UAF into victim
  • Agora o atacante tem objetos controlados ao redor da vítima.
  • Ao transbordar a partir de um de seus próprios objetos (ou reutilizar um objeto liberado), ele pode sobrescrever de forma confiável os campos de memória da vítima com valores escolhidos.

Por que funciona:

  • Zone allocator predictability: alocações do mesmo tamanho sempre vêm da mesma zona.
  • Freelist behavior: novas alocações reutilizam primeiro o chunk mais recentemente liberado.
  • Heap sprays: o atacante preenche a memória com conteúdo previsível e controla o layout.
  • Resultado final: o atacante controla onde o objeto vítima será alocado e quais dados ficam ao lado dele.

Modern Kernel Heap (iOS 15+/A12+ SoCs)

Apple hardened the allocator and made heap grooming much harder:

1. From Classic kalloc to kalloc_type

  • Before: a single kalloc.<size> zone existed for each size class (16, 32, 64, … 1280, etc.). Any object of that size was placed there → attacker objects could sit next to privileged kernel objects.
  • Now:
  • Kernel objects are allocated from typed zones (kalloc_type).
  • Each type of object (e.g., ipc_port_t, task_t, OSString, OSData) has its own dedicated zone, even if they’re the same size.
  • The mapping between object type ↔ zone is generated from the kalloc_type system at compile time.

Um atacante não pode mais garantir que dados controlados (OSData) fiquem adjacentes a objetos kernel sensíveis (task_t) do mesmo tamanho.

2. Slabs and Per-CPU Caches

  • O heap é dividido em slabs (páginas de memória esculpidas em chunks de tamanho fixo para aquela zona).
  • Cada zona tem um per-CPU cache para reduzir contenção.
  • Caminho de alocação:
  1. Tentar o per-CPU cache.
  2. Se vazio, puxar da freelist global.
  3. Se a freelist estiver vazia, alocar um novo slab (uma ou mais páginas).
  • Benefício: Essa descentralização torna heap sprays menos determinísticos, já que alocações podem ser satisfeitas a partir de caches de CPUs diferentes.

3. Randomization inside zones

  • Dentro de uma zona, elementos liberados não são retornados em ordem FIFO/LIFO simples.
  • O XNU moderno usa encoded freelist pointers (estilo safe-linking como no Linux, introduzido ~iOS 14).
  • Cada freelist pointer é XOR-encoded com um cookie secreto por zona.
  • Isso impede que atacantes forjem um ponteiro da freelist se obtiverem um primitive de escrita.
  • Algumas alocações são randomizadas em sua colocação dentro de um slab, portanto spraying não garante adjacência.

4. Guarded Allocations

  • Certos objetos kernel críticos (ex.: credenciais, estruturas de task) são alocados em guarded zones.
  • Essas zonas inserem guard pages (memória não mapeada) entre slabs ou usam redzones ao redor dos objetos.
  • Qualquer overflow para a guard page dispara uma falha → pânico imediato ao invés de corrupção silenciosa.

5. Page Protection Layer (PPL) and SPTM

  • Mesmo se você controlar um objeto liberado, não pode modificar toda a memória do kernel:
  • PPL (Page Protection Layer) impõe que certas regiões (ex.: dados de code signing, entitlements) sejam read-only mesmo para o próprio kernel.
  • Em dispositivos A15/M2+, esse papel é substituído/aperfeiçoado por SPTM (Secure Page Table Monitor) + TXM (Trusted Execution Monitor).
  • Essas camadas reforçadas por hardware significam que atacantes não conseguem escalar de uma corrupção de heap para patching arbitrário de estruturas críticas de segurança.
  • (Added / Enhanced): também é usado PAC (Pointer Authentication Codes) no kernel para proteger pointers (especialmente function pointers, vtables) tornando mais difícil forjá-los ou corrompê-los.
  • (Added / Enhanced): zonas podem impor zone_require / zone enforcement, ou seja, que um objeto liberado só possa ser retornado através da sua zona tipada correta; frees cruzadas inválidas podem causar panic ou serem rejeitadas. (A Apple alude a isso em posts sobre memory safety)

6. Large Allocations

  • Nem todas as alocações passam por kalloc_type.
  • Requisições muito grandes (acima de ~16 KB) contornam typed zones e são servidas diretamente do kernel VM (kmem) via alocações de página.
  • Essas são menos previsíveis, mas também menos exploráveis, já que não compartilham slabs com outros objetos.

7. Allocation Patterns Attackers Target

Mesmo com essas proteções, atacantes ainda buscam:

  • Reference count objects: se você consegue alterar contadores retain/release, pode causar use-after-free.
  • Objects with function pointers (vtables): corromper um ainda fornece controle de fluxo.
  • Shared memory objects (IOSurface, Mach ports): continuam sendo alvos porque fazem ponte user ↔ kernel.

Mas — ao contrário do passado — você não pode simplesmente sprayar OSData e esperar que fique vizinho de um task_t. Você precisa de bugs específicos de tipo ou info leaks para ter sucesso.

Example: Allocation Flow in Modern Heap

Suppose userspace calls into IOKit to allocate an OSData object:

  1. Type lookupOSData maps to kalloc_type_osdata zone (size 64 bytes).
  2. Check per-CPU cache for free elements.
  • If found → return one.
  • If empty → go to global freelist.
  • If freelist empty → allocate a new slab (page of 4KB → 64 chunks of 64 bytes).
  1. Return chunk to caller.

Freelist pointer protection:

  • Each freed chunk stores the address of the next free chunk, but encoded with a secret key.
  • Overwriting that field with attacker data won’t work unless you know the key.

Comparison Table

FeatureOld Heap (Pre-iOS 15)Modern Heap (iOS 15+ / A12+)
Allocation granularityFixed size buckets (kalloc.16, kalloc.32, etc.)Size + type-based buckets (kalloc_type)
Placement predictabilityHigh (same-size objects side by side)Low (same-type grouping + randomness)
Freelist managementRaw pointers in freed chunks (easy to corrupt)Encoded pointers (safe-linking style)
Adjacent object controlEasy via sprays/frees (feng shui predictable)Hard — typed zones separate attacker objects
Kernel data/code protectionsFew hardware protectionsPPL / SPTM protect page tables & code pages, and PAC protects pointers
Allocation reuse validationNone (freelist pointers raw)zone_require / zone enforcement
Exploit reliabilityHigh with heap spraysMuch lower, requires logic bugs or info leaks
Large allocations handlingAll small allocations managed equallyLarge ones bypass zones → handled via VM

Modern Userland Heap (iOS, macOS — type-aware / xzone malloc)

In recent Apple OS versions (especially iOS 17+), Apple introduced a more secure userland allocator, xzone malloc (XZM). This is the user-space analog to the kernel’s kalloc_type, applying type awareness, metadata isolation, and memory tagging safeguards.

Goals & Design Principles

  • Type segregation / type awareness: group allocations by type or usage (pointer vs data) to prevent type confusion and cross-type reuse.
  • Metadata isolation: separate heap metadata (e.g. free lists, size/state bits) from object payloads so that out-of-bounds writes are less likely to corrupt metadata.
  • Guard pages / redzones: insert unmapped pages or padding around allocations to catch overflows.
  • Memory tagging (EMTE / MIE): work in conjunction with hardware tagging to detect use-after-free, out-of-bounds, and invalid accesses.
  • Scalable performance: maintain low overhead, avoid excessive fragmentation, and support many allocations per second with low latency.

Architecture & Components

Below are the main elements in the xzone allocator:

Segment Groups & Zones

  • Segment groups partition the address space by usage categories: e.g. data, pointer_xzones, data_large, pointer_large.
  • Each segment group contains segments (VM ranges) that host allocations for that category.
  • Associated with each segment is a metadata slab (separate VM area) that stores metadata (e.g. free/used bits, size classes) for that segment. This out-of-line (OOL) metadata ensures that metadata is not intermingled with object payloads, mitigating corruption from overflows.
  • Segments are carved into chunks (slices) which in turn are subdivided into blocks (allocation units). A chunk is tied to a specific size class and segment group (i.e. all blocks in a chunk share the same size & category).
  • For small / medium allocations, it will use fixed-size chunks; for large/huges, it may map separately.

Chunks & Blocks

  • A chunk is a region (often several pages) dedicated to allocations of one size class within a group.
  • Inside a chunk, blocks are slots available for allocations. Freed blocks are tracked via the metadata slab — e.g. via bitmaps or free lists stored out-of-line.
  • Between chunks (or within), guard slices / guard pages may be inserted (e.g. unmapped slices) to catch out-of-bounds writes.

Type / Type ID

  • Every allocation site (or call to malloc, calloc, etc.) is associated with a type identifier (a malloc_type_id_t) which encodes what kind of object is being allocated. That type ID is passed to the allocator, which uses it to select which zone / segment to serve the allocation.
  • Because of this, even if two allocations have the same size, they may go into entirely different zones if their types differ.
  • In early iOS 17 versions, not all APIs (e.g. CFAllocator) were fully type-aware; Apple addressed some of those weaknesses in iOS 18.

Allocation & Freeing Workflow

Here is a high-level flow of how allocation and deallocation operate in xzone:

  1. malloc / calloc / realloc / typed alloc is invoked with a size and type ID.
  2. The allocator uses the type ID to pick the correct segment group / zone.
  3. Within that zone/segment, it seeks a chunk that has free blocks of the requested size.
  • It may consult local caches / per-thread pools or free block lists from metadata.
  • If no free block is available, it may allocate a new chunk in that zone.
  1. The metadata slab is updated (free bit cleared, bookkeeping).
  2. If memory tagging (EMTE) is in play, the returned block gets a tag assigned, and metadata is updated to reflect its “live” state.
  3. When free() is called:
  • The block is marked as freed in metadata (via OOL slab).
  • The block may be placed into a free list or pooled for reuse.
  • Optionally, block contents may be cleared or poisoned to reduce data leaks or use-after-free exploitation.
  • The hardware tag associated with the block may be invalidated or re-tagged.
  • If an entire chunk becomes free (all blocks freed), the allocator may reclaim that chunk (unmap it or return to OS) under memory pressure.

Security Features & Hardening

These are the defenses built into modern userland xzone:

FeaturePurposeNotes
Metadata decouplingPrevent overflow from corrupting metadataMetadata lives in separate VM region (metadata slab)
Guard pages / unmapped slicesCatch out-of-bounds writesHelps detect buffer overflows rather than silently corrupting adjacent blocks
Type-based segregationPrevent cross-type reuse & type confusionEven same-size allocations from different types go to different zones
Memory Tagging (EMTE / MIE)Detect invalid access, stale references, OOB, UAFxzone works in concert with hardware EMTE in synchronous mode (“Memory Integrity Enforcement”)
Delayed reuse / poisoning / zapReduce chance of use-after-free exploitationFreed blocks may be poisoned, zeroed, or quarantined before reuse
Chunk reclamation / dynamic unmappingReduce memory waste and fragmentationEntire chunks may be unmapped when unused
Randomization / placement variationPrevent deterministic adjacencyBlocks in a chunk and chunk selection may have randomized aspects
Segregation of “data-only” allocationsSeparate allocations that don’t store pointersReduces attacker control over metadata or control fields

Interaction with Memory Integrity Enforcement (MIE / EMTE)

  • Apple’s MIE (Memory Integrity Enforcement) is the hardware + OS framework that brings Enhanced Memory Tagging Extension (EMTE) into always-on, synchronous mode across major attack surfaces.
  • xzone allocator is a fundamental foundation of MIE in user space: allocations done via xzone get tags, and accesses are checked by hardware.
  • In MIE, the allocator, tag assignment, metadata management, and tag confidentiality enforcement are integrated to ensure that memory errors (e.g. stale reads, OOB, UAF) are caught immediately, not exploited later.

Se quiser, também posso gerar um cheat-sheet ou diagrama dos internos do xzone para seu livro. Você quer que eu faça isso a seguir?
:contentReference[oai:20]{index=20}

(Old) Physical Use-After-Free via IOSurface

ios Physical UAF - IOSurface


Ghidra Install BinDiff

Download BinDiff DMG from https://www.zynamics.com/bindiff/manual and install it.

Open Ghidra with ghidraRun and go to File –> Install Extensions, press the add button and select the path /Applications/BinDiff/Extra/Ghidra/BinExport and click OK and isntall it even if there is a version mismatch.

Using BinDiff with Kernel versions

  1. Go to the page https://ipsw.me/ and download the iOS versions you want to diff. These will be .ipsw files.
  2. Decompress until you get the bin format of the kernelcache of both .ipsw files. You have information on how to do this on:

macOS Kernel Extensions & Kernelcache

  1. Open Ghidra with ghidraRun, create a new project and load the kernelcaches.
  2. Open each kernelcache so they are automatically analyzed by Ghidra.
  3. Then, on the project Window of Ghidra, right click each kernelcache, select Export, select format Binary BinExport (v2) for BinDiff and export them.
  4. Open BinDiff, create a new workspace and add a new diff indicating as primary file the kernelcache that contains the vulnerability and as secondary file the patched kernelcache.

Finding the right XNU version

If you want to check for vulnerabilities in a specific version of iOS, you can check which XNU release version the iOS version uses at [https://www.theiphonewiki.com/wiki/kernel]https://www.theiphonewiki.com/wiki/kernel).

For example, the versions 15.1 RC, 15.1 and 15.1.1 use the version Darwin Kernel Version 21.1.0: Wed Oct 13 19:14:48 PDT 2021; root:xnu-8019.43.1~1/RELEASE_ARM64_T8006.

JSKit-Based Safari Chains and PREYHUNTER Stagers

Renderer RCE abstraction with JSKit

  • Reusable entry: Recent in-the-wild chains abused a WebKit JIT bug (patched as CVE-2023-41993) purely to gain JavaScript-level arbitrary read/write. The exploit immediately pivots into a purchased framework called JSKit, so any future Safari bug only needs to deliver the same primitive.
  • Version abstraction & PAC bypasses: JSKit bundles support for a wide range of iOS releases together with multiple, selectable Pointer Authentication Code bypass modules. The framework fingerprints the target build, selects the appropriate PAC bypass logic, and verifies every step (primitive validation, shellcode launch) before progressing.
  • Manual Mach-O mapping: JSKit parses Mach-O headers directly from memory, resolves the symbols it needs inside dyld-cached images, and can manually map additional Mach-O payloads without writing them to disk. This keeps the renderer process in-memory only and evades code-signature checks tied to filesystem artifacts.
  • Portfolio model: Debug strings such as “exploit number 7” show that the suppliers maintain multiple interchangeable WebKit exploits. Once the JS primitive matches JSKit’s interface, the rest of the chain is unchanged across campaigns.

Kernel bridge: IPC UAF -> code-sign bypass pattern

  • Kernel IPC UAF (CVE-2023-41992): The second stage, still running inside the Safari context, triggers a kernel use-after-free in IPC code, re-allocates the freed object from userland, and abuses the dangling pointers to pivot into arbitrary kernel read/write. The stage also reuses PAC bypass material previously computed by JSKit instead of re-deriving it.
  • Code-signing bypass (CVE-2023-41991): With kernel R/W available, the exploit patches the trust cache / code-signing structures so unsigned payloads execute as system. The stage then exposes a lightweight kernel R/W service to later payloads.
  • Composed pattern: This chain demonstrates a reusable recipe that defenders should expect going forward:
WebKit renderer RCE -> kernel IPC UAF -> kernel arbitrary R/W -> code-sign bypass -> unsigned system stager

PREYHUNTER helper & watcher modules

  • Watcher anti-analysis: Um binário watcher dedicado analisa continuamente o dispositivo e aborta a kill-chain quando um ambiente de pesquisa é detectado. Ele inspeciona security.mac.amfi.developer_mode_status, a presença de um console diagnosticd, locales US ou IL, vestígios de jailbreak como Cydia, processos como bash, tcpdump, frida, sshd, ou checkrain, apps AV móveis (McAfee, AvastMobileSecurity, NortonMobileSecurity), configurações HTTP proxy customizadas e CAs raiz customizadas. Falhar em qualquer verificação bloqueia further payload delivery.
  • Helper surveillance hooks: O componente helper se comunica com outros estágios através de /tmp/helper.sock, depois carrega conjuntos de hooks chamados DMHooker e UMHooker. Esses hooks acessam caminhos de áudio VOIP (as gravações ficam em /private/var/tmp/l/voip_%lu_%u_PART.m4a), implementam um keylogger de sistema, capturam fotos sem UI e fazem hook em SpringBoard para suprimir notificações que essas ações normalmente gerariam. O helper, portanto, atua como uma camada stealthy de validação + vigilância leve antes de implantes mais pesados, como Predator, serem dropped.

iMessage/Media Parser Zero-Click Chains

Imessage Media Parser Zero Click Coreaudio Pac Bypass

References

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks