Exploração iOS
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
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
Mitigações de Exploits no iOS
1. Code Signing / Runtime Signature Verification
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 assinado criptograficamente por uma cadeia de certificados com raiz na trust da Apple. Em runtime, antes de carregar um binário na memória (ou antes de executar 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 carregamento falha.
- Impede: a etapa “classic payload drop + execute” em cadeias de exploit; injeção arbitrária de código; modificar um binário existente para inserir lógica maliciosa.
- Detalhe do mecanismo:
- O loader Mach-O (e o dynamic linker) verifica páginas de código, segmentos, entitlements, team IDs, e que a signature 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.:
mprotectcom checagens de code-sign). - A signature inclui entitlements e identifiers; o SO aplica que certas APIs ou capacidades privilegiadas exigem 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 saltar para ele. No iOS, essa página precisaria ser marcada como executável **e** satisfazer constraints de code-signature. Como o shellcode não é assinado com o certificado da Apple, o salto falha ou o sistema rejeita tornar essa região de memória executável.2. CoreTrust
Introduzido por volta da era iOS 14+ (ou gradualmente em dispositivos mais novos / iOS posteriores) CoreTrust é o subsistema que realiza a validação de assinatura em runtime de binários (incluindo binários do sistema e de usuário) contra o certificado raiz da Apple em vez de confiar em caches de trust userland.
- Impede: post-install tampering de binários, técnicas de jailbreaking que tentam trocar ou patchar bibliotecas do sistema ou apps de usuário; enganar o sistema substituindo binários confiáveis por equivalentes maliciosos.
- Detalhe do mecanismo:
- Em vez de confiar em um banco 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, flags de code signing e outros metadados ao binário no momento do load.
Example
Um jailbreak pode tentar substituir `SpringBoard` ou `libsystem` por uma versão patchada para ganhar persistência. Mas quando o loader do SO ou CoreTrust verifica, ele nota a mismatch na signature (ou entitlements modificados) e recusa executar.3. Data Execution Prevention (DEP / NX / W^X)
Introduzido em muitos SOs anteriormente; o iOS teve NX-bit / w^x por bastante tempo DEP impõe que páginas marcadas como writable (para dados) sejam non-executable, e páginas marcadas como executable sejam non-writable. Não se pode simplesmente escrever shellcode em um heap ou stack e executá-lo.
- Impede: execução direta de shellcode; o clássico 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 writable como executable dispara uma checagem do sistema (e é ou proibida ou requer aprovação de code-sign).
- Em muitos casos, tornar páginas executáveis exige passar por APIs do SO que aplicam restriçõ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 (que o shellcode não atende).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 execução do processo. Os endereços de gadgets mudam entre execuções.
- Impede: hardcoding de endereços de gadgets para ROP/JOP; cadeias de exploit estáticas; saltos c egos para offsets conhecidos.
- Detalhe do mecanismo:
- Cada library / 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 leak um endereço ou ponteiro para descobrir endereços-base em runtime.
Example
Uma cadeia ROP espera um gadget em `0x….lib + offset`. Mas como `lib` é relocada de forma diferente a cada execução, a cadeia hardcoded falha. Um exploit deve primeiro leak a base do módulo antes de calcular endereços dos gadgets.5. Kernel Address Space Layout Randomization (KASLR)
Introduzido no iOS ~ (era iOS 5 / iOS 6) Análogo ao ASLR de usuário, o KASLR randomiza a base do kernel text e outras estruturas do kernel no boot.
- Impede: exploits ao nível do kernel que dependem de localização fixa do código ou dados do kernel; exploits kernel estáticos.
- Detalhe do mecanismo:
- A cada boot, o endereço-base do kernel é randomizado (dentro de um range).
- Estruturas de dados do kernel (como
task_structs,vm_map, etc.) também podem ser relocadas ou offsetadas. - Atacantes devem primeiro leak kernel pointers 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 um `vtable`) em `KERN_BASE + offset`. Mas como `KERN_BASE` é desconhecido, o atacante deve primeiro leaká-lo (ex.: via primitive de leitura) antes de calcular o endereço correto para corromper.6. Kernel Patch Protection (KPP / AMCC)
Introduzido em iOS mais recentes / hardware A-series (após aprox. iOS 15–16 ou chips mais novos) KPP (aka AMCC) monitora continuamente a integridade das pages de kernel text (via hash ou checksum). Se detectar tampering (patches, inline hooks, modificações de código) fora de janelas permitidas, ele provoca 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 on-demand re-hasha as páginas e compara com valores esperados.
- Se ocorrerem mismatches fora de janelas benignas de atualização, ele panica o dispositivo (para evitar patchs maliciosos persistentes).
- Atacantes devem evitar janelas de detecção ou usar caminhos legítimos de patch.
Example
Um exploit tenta patchar o prólogo 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 coincide com o valor esperado e provoca um kernel panic, travando o dispositivo antes que o patch se estabilize.7. Kernel Text Read‐Only Region (KTRR)
Introduzido em SoCs modernos (após ~A12 / hardware mais novo) KTRR é um mecanismo imposto por hardware: uma vez que o kernel text é locked early durante o boot, ele torna-se read-only desde EL1 (o kernel), impedindo escritas posteriores nas páginas de código.
- Impede: qualquer modificação no código do kernel depois do boot (ex.: patching, in-place code injection) ao nível de privilégio EL1.
- Detalhe do mecanismo:
- Durante o boot (na fase secure/bootloader), o memory controller (ou uma unidade de hardware segura) marca as páginas físicas que contêm o kernel text como read-only.
- Mesmo que um exploit ganhe privilégios completos do 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 escalonamento de privilégio 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 travadas como read-only pelo KTRR, a escrita falha (ou causa 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 ponteiro (return addresses, function pointers, certos data pointers) embutindo uma pequena assinatura criptográfica (um “MAC”) em bits altos não usados do ponteiro.
- A assinatura (“PAC”) é calculada 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 obtém um PAC diferente.
- No momento do uso, antes de desreferenciar ou branch via esse ponteiro, uma instrução de authenticate checa o PAC. Se válido, o PAC é removido e obtém-se o ponteiro puro; se inválido, o ponteiro fica “poisoned” (ou um fault é levantado).
- As chaves usadas para produzir/validar PACs residem em registradores privilegiados (EL1, kernel) e não são diretamente legíveis de 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 “spare” e podem segurar o PAC sem alterar o endereço efetivo.
Base Arquitetural & Tipos de Chaves
-
ARMv8.3 introduz cinco chaves de 128 bits (cada uma implementada via dois registradores de sistema de 64 bits) 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 “genérica”, para sign de dados não-ponteiro ou usos genéricos
-
Essas chaves são armazenadas em registradores privilegiados de sistema (acessíveis somente em EL1/EL2 etc.), não acessíveis do user mode.
-
O PAC é calculado via uma função criptográfica (ARM sugere QARMA como algoritmo) usando:
- O valor do ponteiro (porção canônica)
- Um modifier (um valor de contexto, tipo um salt)
- A chave secreta
- 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 as letras de domínio.
PACxxinstructions assinam um ponteiro e inserem um PACAUTxxinstructions autenticam + removem (validam e removem o PAC)XPACxxinstructions removem sem validar
Domínios / sufixos:
| Mnemonic | Meaning / Domain | Key / Domain | Example Usage in Assembly |
|---|---|---|---|
| PACIA | Sign instruction pointer with APIAKey | “I, A” | PACIA X0, X1 — sign pointer in X0 using APIAKey with modifier X1 |
| PACIB | Sign instruction pointer with APIBKey | “I, B” | PACIB X2, X3 |
| PACDA | Sign data pointer with APDAKey | “D, A” | PACDA X4, X5 |
| PACDB | Sign data pointer with APDBKey | “D, B” | PACDB X6, X7 |
| PACG / PACGA | Generic (non-pointer) signing with APGAKey | “G” | PACGA X8, X9, X10 (sign X9 with modifier X10 into X8) |
| AUTIA | Authenticate APIA-signed instruction pointer & strip PAC | “I, A” | AUTIA X0, X1 — check PAC on X0 using modifier X1, then strip |
| AUTIB | Authenticate APIB domain | “I, B” | AUTIB X2, X3 |
| AUTDA | Authenticate APDA-signed data pointer | “D, A” | AUTDA X4, X5 |
| AUTDB | Authenticate APDB-signed data pointer | “D, B” | AUTDB X6, X7 |
| AUTGA | Authenticate generic / blob (APGA) | “G” | AUTGA X8, X9, X10 (validate generic) |
| XPACI | Strip PAC (instruction pointer, no validation) | “I” | XPACI X0 — remove PAC from X0 (instruction domain) |
| XPACD | Strip PAC (data pointer, no validation) | “D” | XPACD X4 — remove PAC from data pointer in X4 |
Existem formas especializadas / aliases:
PACIASPé shorthand paraPACIA X30, SP(assinar o link register usando SP como modifier)AUTIASPéAUTIA X30, SP(autenticar link register com SP)- Formas combinadas como
RETAA,RETAB(authenticate-and-return) ouBLRAA(authenticate & branch) existem em extensões ARM / suporte do compilador. - Também variantes com modifier zero:
PACIZA/PACIZBonde o modifier é implicitamente zero, etc.
Modifiers
O objetivo principal do modifier é ligar o PAC a um contexto específico para que o mesmo endereço assinado em frames ou objetos diferentes gere PACs distintos. É 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), frame pointer, ou algum ID de objeto. - Usar SP como modifier é comum para assinatura de return addresses: o PAC fica atrelado ao frame de stack específico. Se você tentar reutilizar o LR em outro frame, o modifier muda, então a validação do PAC falha.
- O mesmo valor de ponteiro assinado com modifiers diferentes produz 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.
Customizações & Observações da Apple / iOS / XNU
- A implementação de PAC da Apple inclui diversificadores por boot para que chaves ou tweaks mudem a cada boot, impedindo reutilização 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 Apple-specific para controle de chaves.
- A Apple usa PAC em muitos subsistemas do kernel: assinatura de return addresses, integridade de ponteiros em dados do kernel, signed thread contexts, etc.
- Google Project Zero demonstrou como, sob um poderoso memory read/write primitive no kernel, alguém poderia forjar PACs do kernel (para A keys) em dispositivos da era A12, mas a Apple corrigiu muitos desses vetores.
- No sistema da Apple, algumas chaves são globais ao kernel, enquanto processos de usuário podem obter entropia de chave por processo.
PAC Bypasses
- Kernel-mode PAC: teórico vs bypasses reais
- Como as chaves e lógica do kernel PAC são rigidamente controladas (registradores privilegiados, diversificadores, isolamento de domínio), forjar ponteiros assinados arbitrários do kernel é muito difícil.
- Azad em 2020 (“iOS Kernel PAC, One Year Later”) relata que em iOS 12-13 ele encontrou alguns bypasses parciais (signing gadgets, reuse de estados assinados, indirect branches desprotegidos) mas nenhum bypass genérico completo. bazad.github.io
- As customizações da Apple (“Dark Magic”) estreitaram ainda mais as superfícies exploráveis (domain switching, per-key enabling bits). i.blackhat.com
- Existe 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 geralmente dependem de gadgets muito específicos ou bugs de implementação; não são bypasses de propósito geral.
Logo, o kernel PAC é considerado altamente robusto, embora não perfeito.
- User-mode / runtime PAC bypass techniques
Estas são mais comuns, e exploram imperfeições em como PAC é aplicado ou usado no dynamic linking / runtime frameworks. A seguir estão classes com exemplos.
2.1 Shared Cache / A key issues
- A dyld shared cache é um grande blob pré-linkado de system frameworks e libraries. Porque é altamente compartilhado, function pointers dentro do shared cache são “pre-signed” e usados por muitos processos. Atacantes visam esses ponteiros já assinados como “PAC oracles”.
- Algumas técnicas de bypass tentam extrair ou reutilizar ponteiros assinados com A-key presentes no shared cache e reaproveitá-los em gadgets.
- A talk “No Clicks Required” descreve construir um oracle sobre o shared cache para inferir endereços relativos e combinar isso com ponteiros assinados para burlar PAC. saelo.github.io
- Também, imports de function pointers de shared libraries em userspace foram encontrados insuficientemente protegidos por PAC, permitindo que um atacante obtenha function pointers sem alterar sua assinatura. (entrada do Project Zero) bugs.chromium.org
2.2 dlsym(3) / dynamic symbol resolution
- Um bypass conhecido é chamar
dlsym()para obter um ponteiro de função já assinado (signed com A-key, diversifier zero) e então usá-lo. Comodlsymretorna um ponteiro legitimamente assinado, usá-lo contorna a necessidade de forjar PAC. - O blog da Epsilon detalha como alguns bypasses exploram isso: chamar
dlsym("someSym")produz um ponteiro assinado que pode ser usado para chamadas indiretas. blog.epsilon-sec.com - A Synacktiv em “iOS 18.4 — dlsym considered harmful” descreve um bug: alguns símbolos resolvidos via
dlsymno iOS 18.4 retornam ponteiros que estão incorretamente assinados (ou com diversifiers buggy), permitindo bypass não intencional de PAC. Synacktiv - A lógica em dyld para dlsym inclui: quando
result->isCode, eles assinam o ponteiro 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 relocação dinâmica são complexos e às vezes mapeiam páginas temporariamente como read/write para realizar relocations, depois as retornam a read-only. Atacantes exploram essas janelas. A talk da Synacktiv descreve “Operation Triangulation”, um bypass baseado em timing de 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 de WebKit, o loader DYLD é frequentemente alvo para bypass de PAC. Os slides mencionam que muitos bypasses de PAC miraram o loader DYLD (via relocação, interposer hooks). Synacktiv
2.4 NSPredicate / NSExpression / ObjC / SLOP
- Em cadeias de exploit userland, métodos do runtime Objective-C como
NSPredicate,NSExpressionouNSInvocationsão usados para contrabandear chamadas de controle sem ponteiros forjados óbvios. - Em iOS mais antigos (antes de PAC), um exploit usou fake NSInvocation objects para chamar selectors arbitrários em memória controlada. Com PAC, são necessárias modificações. 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 fake invocations; o bypass depende do fato de ISA ou selector pointers às vezes não serem totalmente protegidos por PAC. Project Zero
- Em ambientes onde pointer authentication é aplicado parcialmente, métodos / selectors / target pointers podem não ter proteção PAC completa, 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>Example</summary>
A buffer overflow sobrescreve um endereço de retorno na stack. O atacante escreve o endereço do gadget alvo mas não consegue calcular o PAC correto. Quando a função retorna, a instrução `AUTIA` do CPU falha por causa do mismatch do PAC. A chain 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 um primitive de leitura/escrita de memória.
</details>
### 9. **Branch Target Identification (BTI)**
**Introduzido com ARMv8.5 (hardware posterior)**
BTI é uma funcionalidade de hardware que verifica os **indirect branch targets**: ao executar `blr` ou chamadas/pulos indiretos, o destino deve começar com um **BTI landing pad** (`BTI j` ou `BTI c`). Saltar para endereços de gadget que não têm o landing pad dispara 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 | What it permits (which branch types) | Typical placement / use case |
|-------------|----------------------------------------|-------------------------------|
| **BTI C** | Targets of *call*-style indirect branches (e.g. `BLR`, or `BR` using X16/X17) | Colocado na entrada de funções que podem ser chamadas indiretamente |
| **BTI J** | Targets of *jump*-style branches (e.g. `BR` used for tail calls) | Colocado no início de blocos alcançáveis por jump tables ou tail-calls |
| **BTI JC** | Acts as both C and 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 indirect-branch target válido (inícios de função ou blocos alcançáveis por jumps) para que branches indiretos só tenham sucesso nesses locais.
- **Direct branches / calls** (i.e. endereços fixos `B`, `BL`) **não são restringidos** pelo BTI. A suposição é que páginas de código são confiáveis e um atacante não pode mudá-las (logo direct branches 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 return signing.
#### Mecanismo e aplicação
- Quando o CPU decodifica um **indirect branch (BLR / BR)** numa página marcada como “guarded / BTI-enabled”, ele 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 mantêm compatibilidade retroativa: em hardware sem suporte a BTI, essas instruções atuam como NOPs.
- As compiler passes que adicionam BTIs os inserem apenas onde necessário: funções que podem ser chamadas indiretamente, ou basic blocks alvos de jumps.
- Alguns patches e código do LLVM mostram que o BTI não é inserido em *todos* os basic blocks — apenas naqueles que são potenciais branch targets (e.g. provenientes de switch / jump tables).
#### Sinergia BTI + PAC
PAC protege o valor do ponteiro (a fonte) — garante que a cadeia de chamadas/retornos indiretos não foi adulterada.
BTI garante 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 alvo tenha um BTI colocado ali. Isso aumenta a dificuldade de construir gadgets de exploit.
#### Example
<details>
<summary>Example</summary>
Um exploit tenta pivotar para um gadget em `0xABCDEF` que não começa com `BTI c`. O CPU, ao executar `blr x0`, verifica o alvo e falha porque o alinhamento/instrução 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 impede que código privilegiado (EL1 ou EL2) **leia ou escreva** memória marcada como **user-accessible (EL0)**, a menos que PAN seja explicitamente desabilitado.
- A ideia: mesmo se o kernel for enganado ou comprometido, ele não pode desreferenciar ponteiros do espaço do usuário arbitrariamente sem primeiro *limpar* PAN, reduzindo riscos de exploits estilo **`ret2usr`** ou uso indevido de buffers controlados por usuário.
- Quando PAN está habilitado (PSTATE.PAN = 1), qualquer instrução privilegiada de load/store que acesse um endereço virtual “acessível em EL0” dispara uma **permission fault**.
- O kernel, quando precisa legitimamente acessar memória user-space (e.g. copiar dados de/para buffers do usuário), deve **desabilitar PAN temporariamente** (ou usar instruções de “unprivileged load/store”) para permitir esse acesso.
- No Linux em ARM64, o suporte a PAN foi introduzido por volta de 2015: patches de kernel adicionaram detecção da feature e substituíram `get_user` / `put_user` etc. por variantes que limpam PAN ao redor dos acessos à memória do usuário.
**Ponto-chave / limitação / bug**
- Como notado por Siguza e outros, um bug de especificação (ou comportamento ambíguo) no desenho do ARM significa que mapeamentos de usuário execute-only (`--x`) podem **não acionar PAN**. Em outras palavras, se uma página de usuário é marcada como executável mas sem permissão de leitura, a tentativa do kernel de ler pode contornar PAN porque a arquitetura considera “accessible at EL0” como exigindo 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. Isso é uma área sutil e conhecida que pode ser explorada em alguns sistemas ARMv8+.
#### PXN (Privileged eXecute Never)
- **PXN** é um bit na 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 de páginas do espaço do usuário mesmo se o controle for desviado. Na prática, evita redirecionamento de control-flow do kernel para memória do usuário.
- Combinado com PAN, isso assegura que:
1. O kernel não pode (por padrão) ler ou escrever dados do usuário (PAN)
2. O kernel não pode executar código do usuário (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.
Então, mesmo que o kernel tenha um ponteiro de função corrompido apontando para memória do usuário e tente dar um branch para lá, o bit PXN causaria uma falha.
#### Modelo de permissão de memória & como PAN e PXN mapeiam aos bits de page table
Para entender como PAN / PXN funcionam, é preciso ver como a tradução e o modelo de permissão do ARM funcionam (simplificado):
- Cada entrada de página ou bloco tem campos de atributo incluindo **AP[2:1]** para permissões de acesso (read/write, privileged vs unprivileged) e bits **UXN / PXN** para restrições de execute-never.
- Quando PSTATE.PAN está 1 (habilitado), o hardware aplica semânticas modificadas: acessos privilegiados a páginas marcadas como “accessible by EL0” (i.e. acessíveis ao 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 “accessible by EL0” em certas implementações, descrevendo assim um bypass de 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 PAN / PXN no kernel de um OS hardened (e.g. iOS / XNU)
Em um design de kernel hardened (tal como o que a Apple pode usar):
- O kernel habilita PAN por padrão (assim o código privilegiado fica mais limitado).
- Em caminhos que precisam legitimamente ler/escrever buffers do usuário (e.g. cópia de syscall, I/O, read/write de user pointer), o kernel desabilita PAN temporariamente ou usa instruções especiais para sobrescrever a restrição.
- Após terminar o acesso aos dados do usuário, ele deve reabilitar PAN.
- PXN é aplicado via page tables: páginas de usuário têm PXN = 1 (então o kernel não pode executá-las), páginas do kernel não têm PXN (o código do kernel pode executar).
- O kernel deve garantir que nenhum caminho de código permita fluxo de execução em regiões de memória do usuário (o que contornaria PXN) — então chains de exploit que dependem de “jump into user-controlled shellcode” são bloqueadas.
Devido ao bypass de PAN através de páginas execute-only, em um sistema real a Apple pode desabilitar ou não permitir páginas execute-only do usuário, ou mitigar essa fraqueza da especificação.
#### Superfícies de ataque, bypasses, e mitigações
- PAN bypass via execute-only pages: como discutido, a especificação permite uma lacuna: páginas de usuário com execute-only (sem permissão de leitura) podem não ser consideradas “accessible at EL0”, então PAN não bloqueará leituras do kernel delas em algumas implementações. Isso dá ao atacante um caminho incomum para alimentar dados via seções “execute-only”.
- Temporal window exploit: se o kernel desabilita PAN por uma janela mais longa do que o necessário, uma race ou caminho malicioso pode explorar essa janela para realizar acessos indevidos à memória do usuário.
- Forgotten re-enable: se caminhos de código esquecerem de reabilitar PAN, operações subsequentes do kernel podem acessar incorretamente memória do usuário.
- Misconfiguration of PXN: se as page tables não setarem PXN nas páginas de usuário ou mapearem 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, pode haver efeitos microarquiteturais transitórios que causem violações transitórias das checagens PAN / PXN (embora tais ataques dependam muito do design do CPU).
- Complex interactions: em features avançadas (e.g. JIT, shared memory, regiões de código just-in-time), o kernel pode precisar de controle fino para permitir certos acessos ou execuções em regiões mapeadas ao usuário; projetar isso de forma segura sob as restrições PAN/PXN é não-trivial.
#### Example
<details>
<summary>Code Example</summary>
Aqui estão sequências ilustrativas pseudo-assembly mostrando habilitar/desabilitar PAN ao redor de acessos à memória do usuário, e como uma fault pode ocorrer.
</details>
// 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
If the kernel had **not** set PXN on that user page, then the branch might succeed — which would be insecure.
Se o kernel não tivesse definido PXN nessa página de usuário, então o branch poderia ter sucesso — o que seria inseguro.
If the kernel forgets to re-enable PAN after user memory access, it opens a window where further kernel logic might accidentally read/write arbitrary user memory.
Se o kernel esquecer de reativar PAN após o acesso à memória do usuário, abre-se uma janela onde lógica adicional do kernel pode acidentalmente ler/escrever memória de usuário arbitrária.
If the user pointer is into an execute-only page (user page with only execute permission, no read/write), under the PAN spec bug, `ldr W2, [X1]` might **not** fault even with PAN enabled, enabling a bypass exploit, depending on implementation.
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 de especificação do PAN, `ldr W2, [X1]` pode **não** gerar fault mesmo com PAN ativado, possibilitando um exploit de bypass, dependendo da implementação.
</details>
<details>
<summary>Example</summary>
A kernel vulnerability tries to take a user-provided function pointer and call it in kernel context (i.e. `call user_buffer`). Under PAN/PXN, that operation is disallowed or faults.
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 means the top byte (most-significant byte) of a 64-bit pointer is ignored by address translation. This lets OS or hardware embed **tag bits** in the pointer’s top byte without affecting the actual address.
- TBI stands for **Top Byte Ignore** (sometimes called *Address Tagging*). It is a hardware feature (available in many ARMv8+ implementations) that **ignores the top 8 bits** (bits 63:56) of a 64-bit pointer when performing **address translation / load/store / instruction fetch**.
- In effect, the CPU treats a pointer `0xTTxxxx_xxxx_xxxx` (where `TT` = top byte) as `0x00xxxx_xxxx_xxxx` for the purposes of address translation, ignoring (masking off) the top byte. The top byte can be used by software to store **metadata / tag bits**.
- This gives software “free” in-band space to embed a byte of tag in each pointer without altering which memory location it refers to.
- The architecture ensures that loads, stores, and instruction fetch treat the pointer with its top byte masked (i.e. tag stripped off) before performing the actual memory access.
Thus TBI decouples the **logical pointer** (pointer + tag) from the **physical address** used for memory operations.
#### Why TBI: Use cases and motivation
- **Pointer tagging / metadata**: You can store extra metadata (e.g. object type, version, bounds, integrity tags) in that top byte. When you later use the pointer, the tag is ignored at hardware level, so you don’t need to strip manually for the memory access.
- **Memory tagging / MTE (Memory Tagging Extension)**: TBI is the base hardware mechanism that MTE builds on. In ARMv8.5, the **Memory Tagging Extension** uses bits 59:56 of the pointer as a **logical tag** and checks it against an **allocation tag** stored in memory.
- **Enhanced security & integrity**: By combining TBI with pointer authentication (PAC) or runtime checks, you can force not just the pointer value but also the tag to be correct. An attacker overwriting a pointer without the correct tag will produce a mismatched tag.
- **Compatibility**: Because TBI is optional and tag bits are ignored by hardware, existing untagged code continues to operate normally. The tag bits effectively become “don’t care” bits for legacy code.
#### Example
<details>
<summary>Example</summary>
A function pointer included a tag in its top byte (say `0xAA`). An exploit overwrites the pointer low bits but neglects the tag, so when the kernel verifies or sanitizes, the pointer fails or is rejected.
Um ponteiro de função incluiu uma tag no seu top byte (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)** (some reports show PPL circa macOS / Apple silicon, but Apple is bringing analogous protections to iOS)
- PPL is designed as an **intra-kernel protection boundary**: even if the kernel (EL1) is compromised and has read/write capabilities, **it should not be able to freely modify** certain **sensitive pages** (especially page tables, code-signing metadata, kernel code pages, entitlements, trust caches, etc.).
- It effectively creates a **“kernel within the kernel”** — a smaller trusted component (PPL) with **elevated privileges** that alone can modify protected pages. Other kernel code must call into PPL routines to effect changes.
- This reduces the attack surface for kernel exploits: even with full arbitrary R/W/execute in kernel mode, exploit code must also somehow get into the PPL domain (or bypass PPL) to modify critical structures.
- On newer Apple silicon (A15+ / M2+), Apple is transitioning to **SPTM (Secure Page Table Monitor)**, which in many cases replaces PPL for page-table protection on those platforms.
Here’s how PPL is believed to operate, based on public analysis:
#### Use of APRR / permission routing (APRR = Access Permission ReRouting)
- Apple hardware uses a mechanism called **APRR (Access Permission ReRouting)**, which allows page table entries (PTEs) to contain small indices, rather than full permission bits. Those indices are mapped via APRR registers to actual permissions. This allows dynamic remapping of permissions per domain.
- PPL leverages APRR to segregate privilege within kernel context: only the PPL domain is permitted to update the mapping between indices and effective permissions. That is, when non-PPL kernel code writes a PTE or tries to flip permission bits, the APRR logic disallows it (or enforces read-only mapping).
- PPL code itself runs in a restricted region (e.g. `__PPLTEXT`) which is normally non-executable or non-writable until entry gates temporarily allow it. The kernel calls PPL entry points (“PPL routines”) to perform sensitive operations.
#### Gate / Entry & Exit
- When the kernel needs to modify a protected page (e.g. change permissions of a kernel code page, or modify page tables), it calls into a **PPL wrapper** routine, which does validation and then transitions into the PPL domain. Outside that domain, the protected pages are effectively read-only or non-modifiable by the main kernel.
- During PPL entry, the APRR mappings are adjusted so that memory pages in the PPL region are set to **executable & writable** within PPL. Upon exit, they are returned to read-only / non-writable. This ensures that only well-audited PPL routines can write to protected pages.
- Outside PPL, attempts by kernel code to write to those protected pages will fault (permission denied) because the APRR mapping for that code domain doesn’t permit writing.
#### Protected page categories
The pages that PPL typically protects include:
- Page table structures (translation table entries, mapping metadata)
- Kernel code pages, especially those containing critical logic
- Code-sign metadata (trust caches, signature blobs)
- Entitlement tables, signature enforcement tables
- Other high-value kernel structures where a patch would allow bypassing signature checks or credentials manipulation
The idea is that even if the kernel memory is fully controlled, the attacker cannot simply patch or rewrite these pages, unless they also compromise PPL routines or bypass PPL.
#### Known Bypasses & Vulnerabilities
1. **Project Zero’s PPL bypass (stale TLB trick)**
- A public writeup by Project Zero describes a bypass involving **stale TLB entries**.
- The idea:
1. Allocate two physical pages A and B, mark them as PPL pages (so they are protected).
2. Map two virtual addresses P and Q whose L3 translation table pages come from A and B.
3. Spin a thread to continuously access Q, keeping its TLB entry alive.
4. Call `pmap_remove_options()` to remove mappings starting at P; due to a bug, the code mistakenly removes the TTEs for both P and Q, but only invalidates the TLB entry for P, leaving Q’s stale entry live.
5. Reuse B (page Q’s table) to map arbitrary memory (e.g. PPL-protected pages). Because the stale TLB entry still maps Q’s old mapping, that mapping remains valid for that context.
6. Through this, the attacker can put writable mapping of PPL-protected pages in place without going through PPL interface.
- This exploit required fine control of physical mapping and TLB behavior. It demonstrates that a security boundary relying on TLB / mapping correctness must be extremely careful about TLB invalidations and mapping consistency.
- Project Zero commented that bypasses like this are subtle and rare, but possible in complex systems. Still, they regard PPL as a solid mitigation.
2. **Other potential hazards & constraints**
- If a kernel exploit can directly enter PPL routines (via calling the PPL wrappers), it might bypass restrictions. Thus argument validation is critical.
- Bugs in the PPL code itself (e.g. arithmetic overflow, boundary checks) can allow out-of-bounds modifications inside PPL. Project Zero observed that such a bug in `pmap_remove_options_internal()` was exploited in their bypass.
- The PPL boundary is irrevocably tied to hardware enforcement (APRR, memory controller), so it's only as strong as the hardware implementation.
#### Example
<details>
<summary>Code Example</summary>
Here’s a simplified pseudocode / logic showing how a kernel might call into PPL to modify protected pages:
Aqui está um pseudocódigo simplificado / lógica mostrando como um kernel pode chamar o PPL para modificar páginas protegidas:
</details>
```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 realizar muitas operações normais, mas apenas através das rotinas ppl_call_* ele pode alterar mapeamentos protegidos ou aplicar patches em código.
Exemplo
Um exploit de kernel tenta sobrescrever a entitlement table, ou desabilitar a enforcement do code-sign modificando um kernel signature blob. Como essa página é protegida por PPL, a escrita é bloqueada a menos que passe pela interface PPL. Portanto, mesmo com execução de código no kernel, você não pode contornar as restrições de code-sign nem modificar arbitrariamente dados de credenciais. No iOS 17+ certos dispositivos usam SPTM para isolar ainda mais páginas gerenciadas por PPL.PPL → SPTM / Substituições / Futuro
- Nos SoCs modernos da Apple (A15 ou posterior, M2 ou posterior), 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 a aplicação de políticas para um monitor com privilégio mais alto fora do controle do kernel, reduzindo ainda mais o limite de confiança.
MTE | EMTE | MIE
Aqui está uma descrição de alto nível de como o EMTE opera sob a configuração MIE da Apple:
- Atribuição de tag
- Quando a memória é alocada (p.ex. no kernel ou no espaço de usuário via alocadores seguros), uma secret tag é atribuída a esse bloco.
- O ponteiro retornado ao usuário ou kernel inclui essa tag em seus bits mais altos (usando TBI / top byte ignore mechanisms).
- Verificação de tag no acesso
- Sempre que um load ou store é executado usando um ponteiro, o hardware verifica que a tag do ponteiro corresponde à tag do bloco de memória (allocation tag). Se houver incompatibilidade, ocorre uma falha imediatamente (por ser síncrono).
- Por ser síncrono, não existe uma janela de “detecção adianta/retardada”.
- Reatribuição de tag ao free / reuse
- Quando a memória é liberada, o alocador altera a tag do bloco (assim ponteiros antigos com tags desatualizadas não correspondem mais).
- Um ponteiro use-after-free, portanto, teria uma tag obsoleta e causaria mismatch ao ser acessado.
- Diferenciação de tag entre vizinhos para detectar overflows
- Alocações adjacentes recebem tags distintas. Se um buffer overflow vazar para a memória do vizinho, a incompatibilidade de tag causa uma falha.
- Isso é especialmente eficaz para capturar pequenos overflows que atravessam o limite.
- Aplicação de confidencialidade das tags
- A Apple deve evitar que os valores de tag sejam leaked (porque, se o atacante descobrir a tag, ele poderia forjar ponteiros com as tags corretas).
- Eles incluem proteções (microarquiteturais / controles especulativos) para evitar vazamentos por side-channel dos bits de tag.
- Integração kernel e espaço do usuário
- A Apple usa EMTE não apenas no espaço do usuário, 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 espaço de usuário.
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
- **Intrablock overflows**: Se o overflow permanece dentro da mesma alocação (não cruza a fronteira) e o tag permanece o mesmo, o tag mismatch não o detecta.
- **Tag width limitation**: Apenas alguns bits (por exemplo, 4 bits, ou domínio pequeno) estão disponíveis para o tag—namespace limitado.
- **Side-channel leaks**: Se os bits do tag puderem ser leakados (via cache / speculative execution), o atacante pode aprender tags válidos e contornar. A Tag Confidentiality Enforcement da Apple visa mitigar isso.
- **Performance overhead**: Verificações de tag a cada load/store adicionam custo; a Apple precisa otimizar hardware para reduzir o overhead.
- **Compatibility & fallback**: Em hardware mais antigo ou partes que não suportam EMTE, deve existir fallback. A Apple afirma que MIE é habilitado apenas em dispositivos com suporte.
- **Complex allocator logic**: O allocator precisa gerenciar tags, retagging, alinhação de fronteiras e evitar colisões de mis-tag. Bugs na lógica do allocator podem introduzir vulnerabilidades.
- **Mixed memory / hybrid areas**: Parte da memória pode permanecer untagged (legado), tornando a interoperabilidade mais complicada.
- **Speculative / transient attacks**: Como com muitas proteções microarquiteturais, speculative execution ou micro-op fusions podem contornar verificações transientemente ou vazar bits de tag.
- **Limited to supported regions**: A Apple pode aplicar EMTE apenas em regiões seletivas e de alto risco (kernel, subsistemas críticos de segurança), não universalmente.
---
## Principais melhorias / diferenças comparadas ao MTE padrão
Aqui estão as melhorias e alterações que a Apple enfatiza:
| Feature | Original MTE | EMTE (Apple’s enhanced) / MIE |
|---|---:|---|
| **Check mode** | Supports synchronous and asynchronous modes. In async, tag mismatches are reported later (delayed)| Apple insists on **synchronous mode** by default—tag mismatches are caught immediately, no delay/race windows allowed.|
| **Coverage of non-tagged memory** | Accesses to non-tagged memory (e.g. globals) may bypass checks in some implementations | EMTE requires that accesses from a tagged region to non-tagged memory also validate tag knowledge, making it harder to bypass by mixing allocations.|
| **Tag confidentiality / secrecy** | Tags might be observable or leaked via side channels | Apple adds **Tag Confidentiality Enforcement**, which attempts to prevent leakage of tag values (via speculative side-channels etc.).|
| **Allocator integration & retagging** | MTE leaves much of allocator logic to software | Apple’s secure typed allocators (kalloc_type, xzone malloc, etc.) integrate with EMTE: when memory is allocated or freed, tags are managed at fine granularity.|
| **Always-on by default** | In many platforms, MTE is optional or off by default | Apple enables EMTE / MIE by default on supported hardware (e.g. iPhone 17 / A19) for kernel and many user processes.|
Porque a Apple controla tanto o hardware quanto a stack de software, ela pode impor o EMTE de forma rigorosa, evitar penalidades de desempenho e fechar buracos de side-channel.
---
## Como EMTE funciona na prática (Apple / MIE)
Aqui está uma descrição em alto nível de como o EMTE opera sob a configuração MIE da Apple:
1. **Tag assignment**
- Quando a memória é alocada (por exemplo, no kernel ou em user space via secure allocators), um **secret tag** é atribuído a esse bloco.
- O ponteiro retornado ao usuário ou kernel inclui esse tag em seus 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 o tag do ponteiro corresponde ao tag do bloco de memória (allocation tag). Se houver mismatch, ocorre fault imediatamente (já que é synchronous).
- Por ser synchronous, não existe janela de “detecção atrasada”.
3. **Retagging on free / reuse**
- Quando a memória é liberada, o allocator muda o tag do bloco (para que ponteiros antigos com tags velhos não correspondam mais).
- Um ponteiro use-after-free terá, portanto, um tag obsoleto e causará mismatch ao ser acessado.
4. **Neighbor-tag differentiation to catch overflows**
- Alocações adjacentes recebem tags distintos. Se um buffer overflow vazar para a memória vizinha, o tag mismatch causa fault.
- Isso é especialmente eficaz para detectar pequenos overflows que cruzam fronteira.
5. **Tag confidentiality enforcement**
- A Apple precisa evitar que valores de tag sejam leakados (porque se o atacante descobrir o tag, ele poderia forjar ponteiros com tags corretos).
- Eles incluem proteções (controles microarquiteturais / especulativos) para evitar vazamento de bits de tag.
6. **Kernel and user-space integration**
- A Apple usa EMTE não apenas no user-space mas também no kernel / componentes críticos do 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.
Porque EMTE está integrado ao MIE, a Apple usa EMTE em modo synchronous através de superfícies de ataque chave, não como modo opt-in ou de depuração.
---
## Exception handling in XNU
Quando uma **exception** ocorre (por exemplo, `EXC_BAD_ACCESS`, `EXC_BAD_INSTRUCTION`, `EXC_CRASH`, `EXC_ARM_PAC`, etc.), a **Mach layer** do kernel XNU é responsável por interceptá-la antes que ela se torne um signal ao estilo UNIX (como `SIGSEGV`, `SIGBUS`, `SIGILL`, ...).
Esse processo envolve múltiplas camadas de propagação e tratamento de exceção antes de alcançar o espaço do usuário ou ser convertido em um signal BSD.
### Exception Flow (High-Level)
1. **CPU triggers a synchronous exception** (por exemplo, desreferência de ponteiro inválido, falha de PAC, instrução ilegal, etc.).
2. **Low-level trap handler** runs (`trap.c`, `exception.c` in XNU source).
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 a **thread's exception port**.
- Depois para a **task's exception port**.
- Depois para a **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 user-space).
- **Panic** (para exceções em kernel-space).
### Core Function: `exception_triage()`
A função `exception_triage()` roteia Mach exceptions pela cadeia de possíveis handlers até que um 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 todas falharem → tratado por bsd_exception() → traduzido em um sinal como SIGSEGV.
Portas de Exceção
Cada Mach object (thread, task, host) pode registrar exception ports, onde 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()
Each exception port has:
- A mask (which exceptions it wants to receive)
- A port name (Mach port to receive messages)
- A behavior (how the kernel sends the message)
- A flavor (which thread state to include)
Debuggers and Exception Handling
A debugger (e.g., LLDB) sets an exception port on the target task or thread, usually using task_set_exception_ports().
When an exception occurs:
- The Mach message is sent to the debugger process.
- The debugger can decide to handle (resume, modify registers, skip instruction) or not handle the exception.
- If the debugger doesn’t handle it, the exception propagates to the next level (task → host).
Flow of EXC_BAD_ACCESS
-
Thread dereferences invalid pointer → CPU raises Data Abort.
-
Kernel trap handler calls
exception_triage(EXC_BAD_ACCESS, ...). -
Message sent to:
-
Thread port → (debugger can intercept breakpoint).
-
If debugger ignores → Task port → (process-level handler).
-
If ignored → Host port (usually ReportCrash).
- If no one handles →
bsd_exception()translates toSIGSEGV.
PAC Exceptions
When Pointer Authentication (PAC) fails (signature mismatch), a special Mach exception is raised:
EXC_ARM_PAC(type)- Codes may include details (e.g., key type, pointer type).
If the binary has the flag TFRO_PAC_EXC_FATAL, the kernel treats PAC failures as fatal, bypassing debugger interception. This is to prevent attackers from using debuggers to bypass PAC checks and it’s enabled for platform binaries.
Software Breakpoints
A software breakpoint (int3 on x86, brk on ARM64) is implemented by causing a deliberate fault.
The debugger catches this via the exception port:
- Modifies instruction pointer or memory.
- Restores original instruction.
- Resumes execution.
This same mechanism is what allows you to “catch” a PAC exception — unless TFRO_PAC_EXC_FATAL is set, in which case it never reaches the debugger.
Conversion to BSD Signals
If no handler accepts the exception:
-
Kernel calls
task_exception_notify() → bsd_exception(). -
This maps Mach exceptions to signals:
| Mach Exception | Signal |
|---|---|
| EXC_BAD_ACCESS | SIGSEGV or SIGBUS |
| EXC_BAD_INSTRUCTION | SIGILL |
| EXC_ARITHMETIC | SIGFPE |
| EXC_SOFTWARE | SIGTRAP |
| EXC_BREAKPOINT | SIGTRAP |
| EXC_CRASH | SIGKILL |
| EXC_ARM_PAC | SIGILL (on non-fatal) |
### Key Files in XNU Source
-
osfmk/kern/exception.c→ Core ofexception_triage(),exception_deliver_*(). -
bsd/kern/kern_sig.c→ Signal delivery logic. -
osfmk/arm64/trap.c→ Low-level trap handlers. -
osfmk/mach/exc.h→ Exception codes and structures. -
osfmk/kern/task.c→ Task exception port setup.
Old Kernel Heap (Pre-iOS 15 / Pre-A12 era)
The kernel used a zone allocator (kalloc) divided into fixed-size “zones.”
Each zone only stores allocations of a single size class.
From the screenshot:
| Zone Name | Element Size | Example Use |
|---|---|---|
default.kalloc.16 | 16 bytes | Very small kernel structs, pointers. |
default.kalloc.32 | 32 bytes | Small structs, object headers. |
default.kalloc.64 | 64 bytes | IPC messages, tiny kernel buffers. |
default.kalloc.128 | 128 bytes | Medium objects like parts of OSObject. |
| … | … | … |
default.kalloc.1280 | 1280 bytes | Large structures, IOSurface/graphics metadata. |
How it worked:
- Each allocation request gets rounded up to the nearest zone size.
(E.g., a 50-byte request lands in the
kalloc.64zone). - Memory in each zone was kept in a free list — chunks freed by the kernel went back into that zone.
- If you overflowed a 64-byte buffer, you’d overwrite the next object in the same zone.
This is why heap spraying / feng shui was so effective: you could predict object neighbors by spraying allocations of the same size class.
The freelist
Inside each kalloc zone, freed objects weren’t returned directly to the system — they went into a freelist, a linked list of available chunks.
-
When a chunk was freed, the kernel wrote a pointer at the start of that chunk → the address of the next free chunk in the same zone.
-
The zone kept a HEAD pointer to the first free chunk.
-
Allocation always used the current HEAD:
-
Pop HEAD (return that memory to the caller).
-
Update HEAD = HEAD->next (stored in the freed chunk’s header).
-
Freeing pushed chunks back:
-
freed_chunk->next = HEAD -
HEAD = freed_chunk
So the freelist was just a linked list built inside the freed memory itself.
Normal state:
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 chunk livre = freelist pointer, um atacante poderia corrompê-lo:
-
Heap overflow em um chunk liberado adjacente → sobrescrever seu ponteiro “next”.
-
Use-after-free: escrever em um objeto liberado → sobrescrever seu ponteiro “next”.
Então, na próxima alocação desse tamanho:
- O allocator remove o chunk corrompido.
- Segue o ponteiro “next” fornecido pelo atacante.
- Retorna um ponteiro para memória arbitrária, permitindo fake object primitives ou uma sobrescrita direcionada.
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 tornou a exploração altamente eficaz antes do hardening: vizinhos previsíveis vindos de heap sprays, links de freelist com ponteiros raw, e ausência de segregação por tipo permitiam a atacantes escalar bugs UAF/overflow para controle arbitrário da memória do kernel.
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 ao lado de um objeto controlado pelo atacante.
Dessa forma, quando ocorrer corrupção de memória, o atacante pode sobrescrever de forma confiável o objeto vítima com dados controlados.
Passos:
- Spray allocations (fill the holes)
- Com o tempo, o heap do kernel fica fragmentado: algumas zonas têm lacunas onde objetos antigos foram liberados.
- O atacante primeiro faz muitas alocações dummy para preencher essas lacunas, de modo que o heap fique “compactado” e previsível.
- Force new pages
- Uma vez que as lacunas são preenchidas, as próximas alocações devem vir de páginas novas adicionadas à zone.
- Páginas novas significam que objetos serão agrupados juntos, não espalhados por memória fragmentada antiga.
- Isso dá ao atacante muito melhor controle sobre vizinhanças.
- Place attacker objects
- O atacante agora faz spray novamente, criando muitos objetos controlados nessas páginas novas.
- Esses objetos são previsíveis em tamanho e posicionamento (já que pertencem à mesma zone).
- Free a controlled object (make a gap)
- O atacante deliberadamente libera um de seus próprios objetos.
- Isso cria um “buraco” no heap, que o allocator reutilizará mais tarde para a próxima alocação desse tamanho.
- Victim object lands in the hole
- O atacante desencadeia o kernel para alocar o objeto vítima (o que ele quer corromper).
- Como o buraco é o primeiro slot disponível na freelist, a vítima é colocada exatamente onde o atacante liberou seu objeto.
- Overflow / UAF into victim
- Agora o atacante tem objetos controlados ao redor da vítima.
- Ao overflowar a partir de um dos seus próprios objetos (ou reutilizar um liberado), ele pode sobrescrever de forma confiável os campos de memória da vítima com valores escolhidos.
Por que funciona:
- Predictabilidade do zone allocator: alocações do mesmo tamanho vêm sempre da mesma zone.
- 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 cai e quais dados ficam ao lado dele.
Modern Kernel Heap (iOS 15+/A12+ SoCs)
A Apple reforçou o allocator e tornou o heap grooming muito mais difícil:
1. From Classic kalloc to kalloc_type
- Before: existia uma única zone
kalloc.<size>para cada classe de tamanho (16, 32, 64, … 1280, etc.). Qualquer objeto daquele tamanho era colocado lá → objetos do atacante podiam ficar ao lado de objetos privilegiados do kernel. - Now:
- Objetos do kernel são alocados a partir de typed zones (
kalloc_type). - Cada tipo de objeto (por exemplo,
ipc_port_t,task_t,OSString,OSData) tem sua própria zone dedicada, mesmo que tenham o mesmo tamanho. - O mapeamento entre tipo de objeto ↔ zone é gerado a partir do kalloc_type system em tempo de compilação.
Um atacante não pode mais garantir que dados controlados (OSData) acabem adjacentes a objetos sensíveis do kernel (task_t) do mesmo tamanho.
2. Slabs and Per-CPU Caches
- O heap é dividido em slabs (páginas de memória cortadas em chunks de tamanho fixo para aquela zone).
- Cada zone tem um per-CPU cache para reduzir contenção.
- Caminho de alocação:
- Tenta o per-CPU cache.
- Se vazio, puxa da global freelist.
- Se a freelist estiver vazia, aloca um novo slab (uma ou mais páginas).
- Benefício: essa descentralização torna os heap sprays menos determinísticos, já que alocações podem ser satisfeitas a partir dos caches de CPUs diferentes.
3. Randomization inside zones
- Dentro de uma zone, elementos liberados não são devolvidos em ordem FIFO/LIFO simples.
- O XNU moderno usa encoded freelist pointers (safe-linking como no Linux, introduzido ~iOS 14).
- Cada ponteiro da freelist é XOR-encodado com um cookie secreto por zone.
- Isso impede que atacantes forjem um ponteiro de freelist falso se conseguirem um primitive de escrita.
- Algumas alocações são randomizadas em seu posicionamento dentro de um slab, então spray não garante adjacência.
4. Guarded Allocations
- Certos objetos críticos do kernel (por ex., credenciais, estruturas de task) são alocados em guarded zones.
- Essas zones inserem guard pages (memória não mapeada) entre slabs ou usam redzones ao redor de objetos.
- Qualquer overflow na guard page dispara uma falha → panic imediato em vez de corrupção silenciosa.
5. Page Protection Layer (PPL) and SPTM
- Mesmo que você controle um objeto liberado, você 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 impostas por hardware significam que atacantes não conseguem escalar a partir de uma única corrupção de heap para patch arbitrário de estruturas críticas de segurança.
- (Added / Enhanced): também, PAC (Pointer Authentication Codes) é usado no kernel para proteger ponteiros (especialmente ponteiros de função, vtables) de modo que forjá-los ou corrompê-los fique mais difícil.
- (Added / Enhanced): zones podem impor zone_require / zone enforcement, i.e. que um objeto liberado só possa ser retornado através da sua zone tipada correta; frees cross-zone inválidos podem causar panic ou serem rejeitados. (A Apple alude a isso em seus posts sobre memory safety)
6. Large Allocations
- Nem todas as alocações passam por
kalloc_type. - Requisições muito grandes (acima de ~16 KB) desviam das typed zones e são servidas diretamente do kernel VM (kmem) via alocações de página.
- Estas 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 procuram:
- Reference count objects: se você pode manipular contadores retain/release, pode causar use-after-free.
- Objects with function pointers (vtables): corromper um ainda pode dar controle de fluxo.
- Shared memory objects (IOSurface, Mach ports): continuam sendo alvos porque fazem ponte entre user ↔ kernel.
Mas — ao contrário de antes — você não pode simplesmente sprayar OSData e esperar que fique ao lado de um task_t. Você precisa de bugs específicos de tipo ou info leaks para ter sucesso.
Example: Allocation Flow in Modern Heap
Suponha que userspace chame IOKit para alocar um objeto OSData:
- Type lookup →
OSDatamapeia para a zonekalloc_type_osdata(tamanho 64 bytes). - Checa o per-CPU cache por elementos livres.
- Se encontrado → retorna um.
- Se vazio → vai para a global freelist.
- Se a freelist estiver vazia → aloca um novo slab (página de 4KB → 64 chunks de 64 bytes).
- Retorna o chunk para o chamador.
Proteção de ponteiro da freelist:
- Cada chunk liberado armazena o endereço do próximo chunk livre, mas codificado com uma chave secreta.
- Sobrescrever esse campo com dados do atacante não funcionará a menos que você conheça a chave.
Comparison Table
| Feature | Old Heap (Pre-iOS 15) | Modern Heap (iOS 15+ / A12+) |
|---|---|---|
| Allocation granularity | Fixed size buckets (kalloc.16, kalloc.32, etc.) | Size + type-based buckets (kalloc_type) |
| Placement predictability | High (same-size objects side by side) | Low (same-type grouping + randomness) |
| Freelist management | Raw pointers in freed chunks (easy to corrupt) | Encoded pointers (safe-linking style) |
| Adjacent object control | Easy via sprays/frees (feng shui predictable) | Hard — typed zones separate attacker objects |
| Kernel data/code protections | Few hardware protections | PPL / SPTM protect page tables & code pages, and PAC protects pointers |
| Allocation reuse validation | None (freelist pointers raw) | zone_require / zone enforcement |
| Exploit reliability | High with heap sprays | Much lower, requires logic bugs or info leaks |
| Large allocations handling | All small allocations managed equally | Large ones bypass zones → handled via VM |
Modern Userland Heap (iOS, macOS — type-aware / xzone malloc)
Em versões recentes dos sistemas Apple (especialmente iOS 17+), a Apple introduziu um allocator de userland mais seguro, xzone malloc (XZM). Este é o análogo em user-space do kalloc_type do kernel, aplicando type awareness, isolamento de metadata e salvaguardas de memory tagging.
Goals & Design Principles
- Type segregation / type awareness: agrupar alocações por tipo ou uso (pointer vs data) para prevenir type confusion e reuse entre tipos.
- Metadata isolation: separar metadata do heap (ex.: free lists, bits de tamanho/estado) dos payloads dos objetos para que writes OOB tenham menos chance de corromper metadata.
- Guard pages / redzones: inserir páginas não mapeadas ou padding ao redor das alocações para capturar overflows.
- Memory tagging (EMTE / MIE): funcionar em conjunto com tagging de hardware para detectar use-after-free, OOB e acessos inválidos.
- Scalable performance: manter overhead baixo, evitar fragmentação excessiva e suportar muitas alocações por segundo com baixa latência.
Architecture & Components
Abaixo estão os principais elementos do allocator xzone:
Segment Groups & Zones
- Segment groups particionam o address space por categorias de uso: ex.
data,pointer_xzones,data_large,pointer_large. - Cada segment group contém segments (ranges de VM) que hospedam alocações para aquela categoria.
- Associado a cada segment está um metadata slab (área VM separada) que armazena metadata (ex.: bits free/used, classes de tamanho) para aquele segment. Essa metadata out-of-line (OOL) garante que a metadata não se misture com os payloads dos objetos, mitigando corrupção por overflows.
- Segments são cortados em chunks (slices) que por sua vez são subdivididos em blocks (unidades de alocação). Um chunk está ligado a uma classe de tamanho e segment group específica (i.e. todos os blocks em um chunk compartilham o mesmo tamanho & categoria).
- Para alocações small/medium, usa chunks de tamanho fixo; para large/huge, pode mapear separadamente.
Chunks & Blocks
- Um chunk é uma região (freq. várias páginas) dedicada a alocações de uma classe de tamanho dentro de um grupo.
- Dentro de um chunk, blocks são slots disponíveis para alocações. Blocks liberados são rastreados via metadata slab — ex.: via bitmaps ou free lists armazenadas out-of-line.
- Entre chunks (ou dentro deles), podem ser inseridos guard slices / guard pages (ex.: slices não mapeadas) para capturar writes OOB.
Type / Type ID
- Cada site de alocação (ou chamada para malloc, calloc, etc.) está associado a um type identifier (um
malloc_type_id_t) que codifica que tipo de objeto está sendo alocado. Esse type ID é passado ao allocator, que o usa para selecionar qual zone / segment vai servir a alocação. - Por causa disso, mesmo que duas alocações tenham o mesmo tamanho, elas podem ir para zones totalmente diferentes se seus tipos divergirem.
- Em versões iniciais do iOS 17, nem todas as APIs (ex.: CFAllocator) eram totalmente type-aware; a Apple abordou algumas dessas fraquezas no iOS 18.
Allocation & Freeing Workflow
Aqui está um fluxo de alto nível de como alocação e desalocação operam no xzone:
- malloc / calloc / realloc / typed alloc é invocado com um tamanho e type ID.
- O allocator usa o type ID para escolher o segment group / zone correta.
- Dentro dessa zone/segment, busca um chunk que tenha blocks livres do tamanho requisitado.
- Pode consultar local caches / per-thread pools ou free block lists da metadata.
- Se não houver block livre disponível, pode alocar um novo chunk nessa zone.
- A metadata slab é atualizada (bit de free limpo, bookkeeping).
- Se memory tagging (EMTE) estiver ativo, o block retornado recebe uma tag atribuída, e a metadata é atualizada para refletir seu estado “live”.
- Quando
free()é chamado:
- O block é marcado como liberado na metadata (via OOL slab).
- O block pode ser colocado em uma free list ou pooled para reuse.
- Opcionalmente, o conteúdo do block pode ser limpo ou envenenado para reduzir leaks de dados ou exploração de use-after-free.
- A tag de hardware associada ao block pode ser invalidada ou re-tagged.
- Se um chunk inteiro ficar livre (todos os blocks liberados), o allocator pode reclaim esse chunk (unmap ou devolver ao OS) sob pressão de memória.
Security Features & Hardening
Estas são as defesas incorporadas no xzone userland:
| Feature | Purpose | Notes |
|---|---|---|
| Metadata decoupling | Prevent overflow from corrupting metadata | Metadata lives in separate VM region (metadata slab) |
| Guard pages / unmapped slices | Catch out-of-bounds writes | Helps detect buffer overflows rather than silently corrupting adjacent blocks |
| Type-based segregation | Prevent cross-type reuse & type confusion | Even same-size allocations from different types go to different zones |
| Memory Tagging (EMTE / MIE) | Detect invalid access, stale references, OOB, UAF | xzone works in concert with hardware EMTE in synchronous mode (“Memory Integrity Enforcement”) |
| Delayed reuse / poisoning / zap | Reduce chance of use-after-free exploitation | Freed blocks may be poisoned, zeroed, or quarantined before reuse |
| Chunk reclamation / dynamic unmapping | Reduce memory waste and fragmentation | Entire chunks may be unmapped when unused |
| Randomization / placement variation | Prevent deterministic adjacency | Blocks in a chunk and chunk selection may have randomized aspects |
| Segregation of “data-only” allocations | Separate allocations that don’t store pointers | Reduces attacker control over metadata or control fields |
Interaction with Memory Integrity Enforcement (MIE / EMTE)
- O MIE (Memory Integrity Enforcement) da Apple é o framework hardware + OS que traz a Enhanced Memory Tagging Extension (EMTE) para modo sempre-ativo e síncrono nas principais superfícies de ataque.
- O allocator xzone é uma base fundamental do MIE no user space: alocações feitas via xzone recebem tags, e acessos são checados pelo hardware.
- No MIE, o allocator, atribuição de tags, gerenciamento de metadata e enforcement de confidencialidade das tags são integrados para garantir que erros de memória (ex.: leituras stale, OOB, UAF) sejam detectados imediatamente, em vez de serem explorados posteriormente.
- Se quiser, também posso gerar um cheat-sheet ou diagrama dos internos do xzone para seu livro. Quer que eu faça isso a seguir?
- :contentReference[oai:20]{index=20}
(Old) Physical Use-After-Free via 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
- Go to the page https://ipsw.me/ and download the iOS versions you want to diff. These will be
.ipswfiles. - Decompress until you get the bin format of the kernelcache of both
.ipswfiles. You have information on how to do this on:
macOS Kernel Extensions & Kernelcache
- Open Ghidra with
ghidraRun, create a new project and load the kernelcaches. - Open each kernelcache so they are automatically analyzed by Ghidra.
- Then, on the project Window of Ghidra, right click each kernelcache, select
Export, select formatBinary BinExport (v2) for BinDiffand export them. - 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
Módulos helper e watcher do PREYHUNTER
- Watcher anti-analysis: Um binário watcher dedicado perfila 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 consolediagnosticd, localidadesUSouIL, vestígios de jailbreak como Cydia, processos comobash,tcpdump,frida,sshd, oucheckrain, apps de AV móvel (McAfee, AvastMobileSecurity, NortonMobileSecurity), configurações HTTP proxy customizadas e CAs raiz customizadas. A falha em qualquer verificação bloqueia a entrega de payloads adicionais. - Helper surveillance hooks: O componente helper comunica-se com outras etapas através de
/tmp/helper.sock, e então carrega conjuntos de hooks chamados DMHooker e UMHooker. Esses hooks interceptam caminhos de áudio VOIP (as gravações são salvas 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 as notificações que essas ações normalmente gerariam. Portanto, o helper atua como uma camada furtiva de validação + vigilância leve antes que implantes mais pesados, como Predator, sejam entregues.
WebKit DFG Store-Barrier UAF + ANGLE PBO OOB (iOS 26.1)
Webkit Dfg Store Barrier Uaf Angle Oob
iMessage/Media Parser Zero-Click Chains
Imessage Media Parser Zero Click Coreaudio Pac Bypass
Referências
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
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.


