iOS Exploiting

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

iOS Exploit Mitigations

1. Code Signing / Runtime Signature Verification

Introduced early (iPhone OS → iOS) Esta es una de las protecciones fundamentales: todo el código ejecutable (apps, dynamic libraries, JIT-ed code, extensions, frameworks, caches) debe estar firmado criptográficamente por una cadena de certificados raíz de la confianza de Apple. En tiempo de ejecución, antes de cargar un binario en memoria (o antes de realizar saltos a través de ciertos límites), el sistema verifica su firma. Si el código está modificado (bit-flipped, parcheado) o sin firmar, la carga falla.

  • Thwarts: la etapa clásica de “drop payload + execute” en cadenas de exploit; inyección de código arbitrario; modificar un binario existente para insertar lógica maliciosa.
  • Mechanism detail:
  • El Mach-O loader (y el dynamic linker) comprueba páginas de código, segmentos, entitlements, team IDs y que la firma cubra el contenido del archivo.
  • Para regiones de memoria como JIT caches o código generado dinámicamente, Apple exige que las páginas estén firmadas o validadas vía APIs especiales (p. ej. mprotect con checks de code-sign).
  • La firma incluye entitlements e identificadores; el OS fuerza que ciertas APIs o capacidades privilegiadas requieran entitlements específicos que no pueden falsificarse.
Example Supongamos que un exploit obtiene ejecución de código en un proceso e intenta escribir shellcode en el heap y saltar a él. En iOS, esa página tendría que marcarse como executable **y** cumplir las restricciones de code-signature. Dado que el shellcode no está firmado con el certificado de Apple, el salto falla o el sistema rechaza marcar esa región de memoria como ejecutable.

2. CoreTrust

Introduced around iOS 14+ era (or gradually in newer devices / later iOS) CoreTrust es el subsistema que realiza la validación de firma en tiempo de ejecución de binarios (incluyendo binarios de sistema y de usuario) contra la certificación raíz de Apple en lugar de confiar en stores de confianza cacheadas en userland.

  • Thwarts: manipulación post-instalación de binarios, técnicas de jailbreak que intentan intercambiar o parchear librerías de sistema o apps de usuario; engañar al sistema reemplazando binarios confiables por contrapartes maliciosas.
  • Mechanism detail:
  • En vez de confiar en una base de confianza local o caché de certificados, CoreTrust obtiene o refiere a la raíz de Apple directamente o verifica certificados intermedios en una cadena segura.
  • Asegura que modificaciones (p. ej. en el filesystem) a binarios existentes sean detectadas y rechazadas.
  • Ata entitlements, team IDs, flags de code signing y otros metadatos al binario en tiempo de carga.
Example Un jailbreak podría intentar reemplazar `SpringBoard` o `libsystem` por una versión parcheada para obtener persistencia. Pero cuando el loader del OS o CoreTrust verifica, detecta el desajuste de firma (o entitlements modificados) y se niega a ejecutar.

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

Introduced in many OSes earlier; iOS had NX-bit / w^x for a long time DEP obliga a que páginas marcadas como writeable (para datos) sean no-executable, y que páginas marcadas como executable sean no-writeable. No puedes simplemente escribir shellcode en heap o stack y ejecutarlo.

  • Thwarts: ejecución directa de shellcode; buffer-overflow clásico → salto a shellcode inyectado.
  • Mechanism detail:
  • La MMU / flags de protección de memoria (vía page tables) hacen cumplir la separación.
  • Cualquier intento de marcar una página writable como executable dispara una verificación del sistema (y está prohibido o requiere aprobación de code-sign).
  • En muchos casos, hacer páginas ejecutables requiere usar APIs del OS que aplican restricciones o checks adicionales.
Example Un overflow escribe shellcode en el heap. El atacante intenta `mprotect(heap_addr, size, PROT_EXEC)` para hacerlo executable. Pero el sistema se niega o valida que la nueva página debe pasar restricciones de code-sign (algo que el shellcode no puede).

4. Address Space Layout Randomization (ASLR)

Introduced in iOS ~4–5 era (roughly iOS 4–5 timeframe) ASLR randomiza las direcciones base de regiones clave de memoria: librerías, heap, stack, etc., en cada lanzamiento de proceso. Las direcciones de gadgets cambian entre ejecuciones.

  • Thwarts: hardcodear direcciones de gadgets para ROP/JOP; cadenas de exploit estáticas; saltos a offsets conocidos a ciegas.
  • Mechanism detail:
  • Cada librería / módulo dinámico cargado es rebasado en un offset aleatorio.
  • Los punteros base del stack y heap se randomizan (dentro de ciertos límites de entropía).
  • A veces otras regiones (p. ej. asignaciones via mmap) también se randomizan.
  • Combinado con mitigaciones de information-leak, obliga al atacante a primero hacer un leak de una dirección o puntero para descubrir bases en runtime.
Example Una cadena ROP espera un gadget en `0x….lib + offset`. Pero como `lib` se reubica distinto en cada ejecución, la cadena hardcodeada falla. Un exploit debe primero leakear la dirección base del módulo antes de calcular direcciones de gadgets.

5. Kernel Address Space Layout Randomization (KASLR)

Introduced in iOS ~ (iOS 5 / iOS 6 timeframe) Análogo a ASLR de usuario, KASLR randomiza la base del kernel text y otras estructuras del kernel en cada arranque.

  • Thwarts: exploits a nivel kernel que dependen de ubicaciones fijas de código o datos del kernel; exploits kernel estáticos.
  • Mechanism detail:
  • En cada boot, la dirección base del kernel se randomiza (dentro de un rango).
  • Estructuras de datos del kernel (como task_structs, vm_map, etc.) también pueden reubicarse u offsetearse.
  • Los atacantes deben primero leakear punteros del kernel o usar vulnerabilidades de divulgación de información para calcular offsets antes de manipular estructuras o código kernel.
Example Una vulnerabilidad local intenta corromper un function pointer del kernel (p. ej. en un `vtable`) en `KERN_BASE + offset`. Pero como `KERN_BASE` es desconocido, el atacante debe primero leakearlo (p. ej. mediante un read primitive) antes de calcular la dirección correcta para la corrupción.

6. Kernel Patch Protection (KPP / AMCC)

Introduced in newer iOS / A-series hardware (post around iOS 15–16 era or newer chips) KPP (aka AMCC) monitoriza continuamente la integridad de las páginas de kernel text (vía hash o checksum). Si detecta manipulación (parches, hooks inline, modificaciones de código) fuera de ventanas permitidas, dispara un kernel panic o reboot.

  • Thwarts: patching persistente del kernel (modificar instrucciones del kernel), hooks inline, sobrescritura estática de funciones.
  • Mechanism detail:
  • Un módulo hardware o firmware monitoriza la región de kernel text.
  • Periódica o bajo demanda rehasea las páginas y compara contra valores esperados.
  • Si ocurren desajustes fuera de ventanas de actualización benignas, hace panic al dispositivo (para evitar parches maliciosos persistentes).
  • Los atacantes deben evitar ventanas de detección o usar vías legítimas de parcheo.
Example Un exploit intenta parchear el prólogo de una función del kernel (p. ej. `memcmp`) para interceptar llamadas. Pero KPP detecta que el hash de la página de código ya no coincide y provoca un kernel panic, reiniciando el dispositivo antes de que el parche se estabilice.

7. Kernel Text Read‐Only Region (KTRR)

Introduced in modern SoCs (post ~A12 / newer hardware) KTRR es un mecanismo impuesto por hardware: una vez que el kernel text se bloquea temprano durante el boot, se vuelve de solo lectura desde EL1 (el kernel), impidiendo más escrituras en páginas de código.

  • Thwarts: cualquier modificación al código del kernel después del boot (p. ej. patching, in-place code injection) a nivel de privilegio EL1.
  • Mechanism detail:
  • Durante el arranque (en etapa secure/bootloader), el controlador de memoria (o una unidad de hardware segura) marca las páginas físicas que contienen el kernel text como read-only.
  • Incluso si un exploit gana privilegios completos del kernel, no puede escribir en esas páginas para parchear instrucciones.
  • Para modificarlas, el atacante tendría que comprometer la cadena de arranque o subvertir KTRR mismo.
Example Un exploit de escalada de privilegios salta a EL1 y escribe un trampoline en una función del kernel (p. ej. en el manejador de `syscall`). Pero porque las páginas están bloqueadas como read-only por KTRR, la escritura falla (o provoca una fault), por lo que los parches no se aplican.

8. Pointer Authentication Codes (PAC)

Introduced with ARMv8.3 (hardware), Apple beginning with A12 / iOS ~12+

  • PAC es una característica de hardware introducida en ARMv8.3-A para detectar la manipulación de valores pointer (return addresses, function pointers, ciertos data pointers) embebiendo una pequeña firma criptográfica (un “MAC”) en bits superiores no usados del puntero.
  • La firma (“PAC”) se calcula sobre el valor del puntero más un modifier (un valor de contexto, p. ej. stack pointer u otros datos distintivos). De ese modo, el mismo valor de puntero en distintos contextos obtiene un PAC distinto.
  • En el momento de usarlo, una instrucción de authenticate comprueba el PAC. Si es válido, se quita el PAC y se obtiene el puntero puro; si es inválido, el puntero queda “poisoned” (o se levanta una fault).
  • Las claves usadas para producir/validar PACs viven en registros privilegiados (EL1, kernel) y no son directamente legibles desde user mode.
  • Debido a que no se usan todos los 64 bits de un puntero en muchos sistemas (p. ej. espacio de direcciones de 48 bits), los bits altos “libres” pueden contener el PAC sin alterar la dirección efectiva.

Architectural Basis & Key Types

  • ARMv8.3 introduce cinco claves de 128-bit (cada una implementada vía dos registros de sistema de 64-bit) para pointer authentication.

  • APIAKey — para instruction pointers (dominio “I”, key A)

  • APIBKey — segunda clave para instruction pointers (dominio “I”, key B)

  • APDAKey — para data pointers (dominio “D”, key A)

  • APDBKey — para data pointers (dominio “D”, key B)

  • APGAKey — clave “genérica”, para firmar datos no-pointer u otros usos genéricos

  • Estas claves se almacenan en registros de sistema privilegiados (accesibles solo en EL1/EL2, etc.), no accesibles desde user mode.

  • El PAC se calcula mediante una función criptográfica (ARM sugiere QARMA como algoritmo) usando:

  1. El valor del puntero (porción canónica)
  2. Un modifier (un valor de contexto, como una sal)
  3. La clave secreta
  4. Algo de lógica interna de tweak Si el PAC resultante coincide con lo almacenado en los bits superiores del puntero, la autenticación tiene éxito.

Instruction Families

La convención de nombres es: PAC / AUT / XPAC, luego letras de dominio.

  • PACxx instrucciones firman un puntero e insertan un PAC
  • AUTxx instrucciones autentican + quitan (validan y remueven el PAC)
  • XPACxx instrucciones quitan sin validar

Domains / sufijos:

MnemonicSignificado / DominioClave / DominioExample Usage in Assembly
PACIAFirmar instruction pointer con APIAKey“I, A”PACIA X0, X1 — sign pointer in X0 using APIAKey with modifier X1
PACIBFirmar instruction pointer con APIBKey“I, B”PACIB X2, X3
PACDAFirmar data pointer con APDAKey“D, A”PACDA X4, X5
PACDBFirmar data pointer con APDBKey“D, B”PACDB X6, X7
PACG / PACGAFirma genérica (no-pointer) con APGAKey“G”PACGA X8, X9, X10 (sign X9 with modifier X10 into X8)
AUTIAAutenticar instruction pointer APIA & quitar PAC“I, A”AUTIA X0, X1 — check PAC on X0 using modifier X1, then strip
AUTIBAutenticar dominio APIB“I, B”AUTIB X2, X3
AUTDAAutenticar data pointer APDA“D, A”AUTDA X4, X5
AUTDBAutenticar data pointer APDB“D, B”AUTDB X6, X7
AUTGAAutenticar genérico / blob (APGA)“G”AUTGA X8, X9, X10 (validate generic)
XPACIQuitar PAC (instruction pointer, sin validación)“I”XPACI X0 — remove PAC from X0 (instruction domain)
XPACDQuitar PAC (data pointer, sin validación)“D”XPACD X4 — remove PAC from data pointer in X4

Hay formas especializadas / alias:

  • PACIASP es abreviatura de PACIA X30, SP (firmar el link register usando SP como modifier)
  • AUTIASP es AUTIA X30, SP (autenticar el link register con SP)
  • Formas combinadas como RETAA, RETAB (authenticate-and-return) o BLRAA (authenticate & branch) existen en extensiones ARM / soporte de compilador.
  • También variantes con modifier cero: PACIZA / PACIZB donde el modifier es implícitamente cero, etc.

Modifiers

El objetivo principal del modifier es vincular el PAC a un contexto específico de modo que la misma dirección firmada en distintos contextos produzca PACs distintos. Es como añadir una salt a un hash.

Por lo tanto:

  • El modifier es un valor de contexto (otro registro) que se mezcla en el cálculo del PAC. Elecciones típicas: stack pointer (SP), frame pointer, o algún ID de objeto.
  • Usar SP como modifier es común para firmar return addresses: el PAC queda atado al stack frame específico. Si intentas reutilizar el LR en otro frame, el modifier cambia y la validación del PAC falla.
  • El mismo valor de puntero firmado bajo modifiers distintos produce PACs distintos.
  • El modifier no necesita ser secreto, pero idealmente no está controlado por el atacante.
  • Para instrucciones que firman o verifican punteros donde no existe un modifier significativo, algunas formas usan cero o una constante implícita.

Apple / iOS / XNU Customizations & Observations

  • La implementación de PAC de Apple incluye diversificadores por arranque para que las claves o tweaks cambien en cada boot, evitando la reutilización entre boots.
  • También incluyen mitigaciones cross-domain para que PACs firmados en user mode no se puedan reutilizar fácilmente en kernel mode, etc.
  • En Apple M1 / Apple Silicon, la ingeniería inversa mostró que existen nueve tipos de modifier y registros de sistema específicos de Apple para control de claves.
  • Apple usa PAC en muchos subsistemas del kernel: firma de return addresses, integridad de pointers en datos del kernel, contextos de threads firmados, etc.
  • Google Project Zero mostró cómo bajo un potente read/write primitive en kernel se podía forjar PACs del kernel (para A keys) en dispositivos A12, pero Apple parcheó muchos de esos caminos.
  • En el sistema de Apple, algunas claves son globales en el kernel, mientras que procesos de usuario pueden recibir aleatoriedad de clave por proceso.

PAC Bypasses

  1. Kernel-mode PAC: theoretical vs real bypasses
  • Debido a que las claves y la lógica de PAC en kernel están fuertemente controladas (registros privilegiados, diversificadores, aislamiento de dominios), forjar punteros kernel firmados arbitrariamente es muy difícil.
  • Azad en 2020 “iOS Kernel PAC, One Year Later” reportó que en iOS 12-13 encontró algunos bypass parciales (gadgets para firmar, reutilización de estados firmados, ramas indirectas sin protección) pero no un bypass genérico completo. bazad.github.io
  • Las personalizaciones “Dark Magic” de Apple afinan aún más las superficies explotables (domain switching, bits por-clave para habilitar). i.blackhat.com
  • Hay un bypass conocido de kernel PAC CVE-2023-32424 en Apple silicon (M1/M2) reportado por Zecao Cai et al. i.blackhat.com
  • Pero estos bypasses a menudo dependen de gadgets muy específicos o bugs de implementación; no son bypasses de propósito general.

Así, PAC en kernel se considera altamente robusto, aunque no perfecto.

  1. User-mode / runtime PAC bypass techniques

Estos son más comunes y explotan imperfecciones en cómo PAC se aplica o usa en dynamic linking / runtime frameworks. Abajo hay clases con ejemplos.

2.1 Shared Cache / A key issues

  • El dyld shared cache es un gran blob pre-linkado de frameworks y librerías del sistema. Debido a que se comparte ampliamente, los function pointers dentro del shared cache están “pre-firmados” y luego usados por muchos procesos. Los atacantes apuntan a estos punteros ya firmados como “PAC oracles”.
  • Algunas técnicas de bypass intentan extraer o reutilizar punteros firmados con A-key presentes en el shared cache y reusarlos en gadgets.
  • La charla “No Clicks Required” describe construir un oracle sobre el shared cache para inferir direcciones relativas y combinar eso con punteros firmados para bypassear PAC. saelo.github.io
  • Además, imports de function pointers desde librerías compartidas en userspace se encontraron insuficientemente protegidos por PAC, permitiendo a un atacante obtener function pointers sin cambiar su firma. (entrada de bug de Project Zero) bugs.chromium.org

2.2 dlsym(3) / dynamic symbol resolution

  • Un bypass conocido es llamar a dlsym() para obtener un function pointer ya firmado (signed con A-key, diversifier cero) y luego usarlo. Como dlsym devuelve un puntero legítimamente firmado, usarlo evita la necesidad de forjar PAC.
  • El blog de Epsilon detalla cómo algunos bypasses explotan esto: llamar dlsym("someSym") produce un puntero firmado y puede usarse para llamadas indirectas. blog.epsilon-sec.com
  • Synacktiv en “iOS 18.4 — dlsym considered harmful” describe un bug: algunos símbolos resueltos vía dlsym en iOS 18.4 retornan punteros que están incorrectamente firmados (o con diversificadores buggeados), habilitando un bypass involuntario de PAC. Synacktiv
  • La lógica en dyld para dlsym incluye: cuando result->isCode, firman el puntero retornado con __builtin_ptrauth_sign_unauthenticated(..., key_asia, 0), es decir, contexto cero. blog.epsilon-sec.com

Así, dlsym es un vector frecuente en bypasses de PAC en user-mode.

2.3 Other DYLD / runtime relocations

  • El loader DYLD y la lógica de relocaciones dinámicas es compleja y a veces mapea páginas temporalmente como read/write para realizar relocaciones, luego las vuelve a read-only. Los atacantes explotan estas ventanas. La charla de Synacktiv describe “Operation Triangulation”, un bypass basado en timing de PAC vía relocaciones dinámicas. Synacktiv
  • Las páginas DYLD ahora están protegidas con SPRR / VM_FLAGS_TPRO (algunas flags de protección para dyld). Pero versiones anteriores tenían guards más débiles. Synacktiv
  • En cadenas de exploit de WebKit, el loader DYLD suele ser un objetivo para bypass de PAC. Las slides mencionan que muchos bypass han apuntado al DYLD loader (vía relocación, interposer hooks). Synacktiv

2.4 NSPredicate / NSExpression / ObjC / SLOP

  • En cadenas de exploit userland, métodos del runtime Objective-C como NSPredicate, NSExpression o NSInvocation se usan para camuflar llamadas de control sin necesidad evidente de forjar pointers.
  • En iOS más antiguos (antes de PAC), un exploit usó fake NSInvocation objects para llamar selectores arbitrarios sobre memoria controlada. Con PAC, la técnica necesita adaptaciones. Pero la técnica SLOP (SeLector Oriented Programming) se extendió bajo PAC también. Project Zero
  • La técnica original SLOP permitía encadenar llamadas ObjC creando invocations falsas; el bypass se apoya en que ISA o selector pointers a veces no están totalmente protegidos por PAC. Project Zero
  • En entornos donde pointer authentication se aplica parcialmente, métodos / selectores / target pointers pueden no tener siempre protección PAC, dejando margen para bypass.

Example Flow

Ejemplo: Firma y Autenticación ``` ; 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>
Un buffer overflow sobrescribe una dirección de retorno en la stack. El atacante escribe la dirección del gadget objetivo pero no puede calcular el PAC correcto. Cuando la función retorna, la instrucción `AUTIA` de la CPU falla porque hay una discrepancia en el PAC. La cadena falla.
El análisis de Project Zero sobre A12 (iPhone XS) mostró cómo se usa el PAC de Apple y métodos para forjar PACs si un atacante dispone de una primitiva de lectura/escritura de memoria.
</details>


### 9. **Branch Target Identification (BTI)**
**Introduced with ARMv8.5 (later hardware)**
BTI es una característica de hardware que verifica los **targets de ramas indirectas**: al ejecutar `blr` o llamadas/saltos indirectos, el objetivo debe comenzar con un **BTI landing pad** (`BTI j` o `BTI c`). Saltar a direcciones de gadgets que carecen del landing pad provoca una excepción.

Las notas de implementación de LLVM describen tres variantes de instrucciones BTI y cómo se asignan a tipos de ramas.

| 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) | Put at entry of functions that may be called indirectly |
| **BTI J** | Targets of *jump*-style branches (e.g. `BR` used for tail calls) | Placed at the beginning of blocks reachable by jump tables or tail-calls |
| **BTI JC** | Acts as both C and J | Can be targeted by either call or jump branches |

- En código compilado con branch target enforcement, los compiladores insertan una instrucción BTI (C, J o JC) en cada target válido de rama indirecta (inicios de funciones o bloques alcanzables por jumps) de modo que las ramas indirectas solo tengan éxito hacia esos lugares.
- **Las ramas / llamadas directas** (i.e. direcciones fijas `B`, `BL`) **no están restringidas** por BTI. La suposición es que las páginas de código son de confianza y el atacante no puede cambiarlas (por eso las ramas directas son seguras).
- Además, las instrucciones **RET / return** generalmente no están restringidas por BTI porque las direcciones de retorno están protegidas mediante PAC o mecanismos de firma de retorno.

#### Mechanism and enforcement

- Cuando la CPU decodifica una **rama indirecta (BLR / BR)** en una página marcada como “guarded / BTI-enabled”, comprueba si la primera instrucción de la dirección objetivo es un BTI válido (C, J o JC según lo permitido). Si no lo es, ocurre una **Branch Target Exception**.
- El encoding de la instrucción BTI está diseñado para reutilizar opcodes previamente reservados para NOPs (en versiones anteriores de ARM). Así que los binarios con BTI siguen siendo backward-compatible: en hardware sin soporte BTI, esas instrucciones actúan como NOPs.
- Los passes del compilador que añaden BTIs los insertan solo donde hace falta: funciones que pueden ser llamadas indirectamente o bloques básicos dirigidos por jumps.
- Algunos parches y código de LLVM muestran que BTI no se inserta en *todos* los bloques básicos — solo en aquellos que son potenciales objetivos de branch (p.ej. desde switch / jump tables).

#### BTI + PAC synergy

PAC protege el valor del puntero (la fuente) — asegura que la cadena de llamadas indirectas / retornos no haya sido manipulada.

BTI asegura que incluso un puntero válido solo puede apuntar a entry points marcados correctamente.

Combinados, un atacante necesita tanto un puntero válido con el PAC correcto como que el objetivo tenga un BTI colocado allí. Esto incrementa la dificultad de construir gadgets explotables.

#### Example


<details>
<summary>Example</summary>
Un exploit intenta pivotar hacia un gadget en `0xABCDEF` que no comienza con `BTI c`. La CPU, al ejecutar `blr x0`, comprueba el objetivo y falla porque la alineación de la instrucción no incluye un landing pad válido. Por tanto muchos gadgets se vuelven inutilizables a menos que incluyan el prefijo BTI.
</details>


### 10. **Privileged Access Never (PAN) & Privileged Execute Never (PXN)**
**Introduced in more recent ARMv8 extensions / iOS support (for hardened kernel)**

#### PAN (Privileged Access Never)

- **PAN** es una característica introducida en **ARMv8.1-A** que impide que el código privilegiado (EL1 o EL2) **lea o escriba** memoria marcada como **user-accessible (EL0)**, a menos que PAN sea explícitamente desactivado.
- La idea: incluso si el kernel es engañado o comprometido, no puede desreferenciar arbitrariamente punteros de user-space sin primero *limpiar* PAN, reduciendo los riesgos de exploits estilo **`ret2usr`** o el uso indebido de buffers controlados por el usuario.
- Cuando PAN está habilitado (PSTATE.PAN = 1), cualquier instrucción privilegiada de load/store que acceda a una dirección virtual “accesible en EL0” desencadena una **falla de permiso**.
- El kernel, cuando necesita legítimamente acceder a memoria de user-space (p.ej. copiar datos hacia/desde buffers de usuario), debe **deshabilitar temporalmente PAN** (o usar instrucciones de “unprivileged load/store”) para permitir ese acceso.
- En Linux sobre ARM64, el soporte para PAN se introdujo alrededor de 2015: parches del kernel añadieron detección de la característica y reemplazaron `get_user` / `put_user`, etc., por variantes que limpian PAN alrededor de accesos a memoria de usuario.

**Key nuance / limitation / bug**
- Como han señalado Siguza y otros, un bug de especificación (o comportamiento ambiguo) en el diseño de ARM significa que los mappings de usuario execute-only (`--x`) pueden **no activar PAN**. En otras palabras, si una página de usuario está marcada como ejecutable pero sin permiso de lectura, el intento del kernel de leerla podría evitar PAN porque la arquitectura considera que “accesible en EL0” requiere permiso de lectura, no solo de ejecución. Esto lleva a una evasión de PAN en ciertas configuraciones.
- Debido a eso, si iOS / XNU permite páginas de usuario execute-only (como pueden usar algunos JIT o code-cache), el kernel podría leer accidentalmente desde ellas incluso con PAN habilitado. Esta es un área sutil y conocida explotable en algunos sistemas ARMv8+.

#### PXN (Privileged eXecute Never)

- **PXN** es un flag de la tabla de páginas (en las entradas de page table, leaf o block entries) que indica que la página es **no ejecutable cuando se ejecuta en modo privilegiado** (i.e. cuando EL1 ejecuta).
- PXN evita que el kernel (o cualquier código privilegiado) salte o ejecute instrucciones desde páginas de user-space incluso si el control es desviado. En efecto, impide una redirección de control a código en user-space a nivel kernel.
- Combinado con PAN, esto asegura que:
1. El kernel no puede (por defecto) leer o escribir datos de user-space (PAN)
2. El kernel no puede ejecutar código de user-space (PXN)
- En el formato de page table de ARMv8, las entradas leaf tienen un bit `PXN` (y también `UXN` para unprivileged execute-never) en sus bits de atributos.

Así, incluso si el kernel tiene un puntero de función corrupto apuntando a memoria de usuario y trata de ramparse allí, el bit PXN causaría una falla.

#### Memory-permission model & how PAN and PXN map to page table bits

Para entender cómo funcionan PAN / PXN, necesitas ver cómo funciona el modelo de traducción y permisos de ARM (simplificado):

- Cada entrada de página o bloque tiene campos de atributo incluyendo **AP[2:1]** para permisos de acceso (lectura/escritura, privilegiado vs no privilegiado) y bits **UXN / PXN** para restricciones de execute-never.
- Cuando PSTATE.PAN es 1 (habilitado), el hardware aplica semánticas modificadas: los accesos privilegiados a páginas marcadas como “accesibles por EL0” (i.e. accesibles por usuario) son denegados (falla).
- Por el bug mencionado, las páginas que están marcadas solo como ejecutables (sin permiso de lectura) pueden no contarse como “accesibles por EL0” bajo ciertas implementaciones, con lo que se puede evadir PAN.
- Cuando el bit PXN de una página está establecido, incluso si el fetch de instrucción viene de un nivel de privilegio superior, la ejecución está prohibida.

#### Kernel usage of PAN / PXN in a hardened OS (e.g. iOS / XNU)

En un diseño de kernel hardening (como el que Apple podría usar):

- El kernel habilita PAN por defecto (por lo que el código privilegiado está restringido).
- En rutas que legítimamente necesitan leer o escribir buffers de usuario (p.ej. copia de buffer de syscall, I/O, read/write de punteros de usuario), el kernel deshabilita temporalmente PAN o usa instrucciones especiales para sobrescribirlo.
- Después de terminar el acceso a datos de usuario, debe volver a habilitar PAN.
- PXN se aplica vía page tables: las páginas de usuario tienen PXN = 1 (así el kernel no puede ejecutarlas), las páginas del kernel no tienen PXN (así el código del kernel puede ejecutarse).
- El kernel debe asegurarse de que ningún camino de código cause flujo de ejecución hacia regiones de memoria de usuario (eso bypassearía PXN) — por lo que cadenas de explotación que dependen de “saltar a shellcode controlado por el usuario” quedan bloqueadas.

Debido a la evasión de PAN vía páginas execute-only mencionada, en un sistema real Apple podría deshabilitar o no permitir páginas de usuario execute-only, o parchear alrededor de la debilidad de la especificación.

#### Attack surfaces, bypasses, and mitigations

- **PAN bypass via execute-only pages**: como se ha discutido, la especificación permite una laguna: páginas de usuario con execute-only (sin permiso de lectura) podrían no contarse como “accesibles en EL0”, por lo que PAN no bloqueará lecturas del kernel desde esas páginas en algunas implementaciones. Esto da al atacante una vía inusual para suministrar datos mediante secciones “execute-only”.
- **Temporal window exploit**: si el kernel deshabilita PAN por una ventana más larga de la necesaria, una carrera o ruta maliciosa podría explotar esa ventana para realizar accesos a memoria de usuario no intencionados.
- **Forgotten re-enable**: si rutas de código no vuelven a habilitar PAN, operaciones kernel posteriores podrían acceder incorrectamente a memoria de usuario.
- **Misconfiguration of PXN**: si las page tables no establecen PXN en páginas de usuario o mapean incorrectamente páginas de código de usuario, el kernel podría ser engañado para ejecutar código de user-space.
- **Speculation / side-channels**: análogo a bypasses especulativos, puede haber efectos microarquitecturales transitorios que violen las comprobaciones de PAN / PXN de forma temporal (aunque tales ataques dependen mucho del diseño del CPU).
- **Complex interactions**: en características más avanzadas (p.ej. JIT, shared memory, regiones de código just-in-time), el kernel puede necesitar un control fino para permitir ciertos accesos a memoria o ejecución en regiones mapeadas por usuario; diseñar eso de forma segura bajo las restricciones PAN/PXN no es trivial.

#### Example

<details>
<summary>Code Example</summary>
Aquí hay secuencias ilustrativas en pseudo-assembly que muestran habilitar/deshabilitar PAN alrededor del acceso a memoria de usuario, y cómo podría ocurrir una falla.
</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

Si el kernel no hubiera establecido PXN en esa página de usuario, entonces la rama podría tener éxito — lo cual sería inseguro.

Si el kernel se olvida de volver a habilitar PAN después de acceder a memoria de usuario, se abre una ventana donde lógica adicional del kernel podría accidentalmente leer/escribir memoria de usuario arbitraria.

Si el puntero de usuario apunta a una página de solo ejecución (página de usuario con solo permiso de ejecución, sin lectura/escritura), bajo el bug de la especificación PAN, `ldr W2, [X1]` podría **no** producir un fault incluso con PAN habilitado, permitiendo un exploit de bypass, dependiendo de la implementación.

</details>

<details>
<summary>Ejemplo</summary>
Una vulnerabilidad del kernel intenta tomar un puntero a función proporcionado por el usuario y llamarlo en contexto de kernel (p. ej. `call user_buffer`). Bajo PAN/PXN, esa operación está prohibida o provoca un fault.
</details>

---

### 11. **Top Byte Ignore (TBI) / Pointer Tagging**
**Introduced in ARMv8.5 / newer (or optional extension)**
TBI significa que el byte superior (el byte más significativo) de un puntero de 64 bits se ignora en la traducción de direcciones. Esto permite que el OS o el hardware inserten bits de **tag** en el byte superior del puntero sin afectar la dirección real.

- TBI stands for **Top Byte Ignore** (sometimes called *Address Tagging*). Es una característica de hardware (disponible en muchas implementaciones ARMv8+) que **ignora los 8 bits superiores** (bits 63:56) de un puntero de 64 bits al realizar la **traducción de direcciones / load/store / instruction fetch**.
- En efecto, la CPU trata un puntero `0xTTxxxx_xxxx_xxxx` (donde `TT` = byte superior) como `0x00xxxx_xxxx_xxxx` para propósitos de traducción de direcciones, ignorando (enmascarando) el byte superior. El byte superior puede ser usado por el software para almacenar **metadata / tag bits**.
- Esto da al software un espacio “gratuito” in-band para insertar un byte de tag en cada puntero sin alterar la ubicación de memoria a la que hace referencia.
- La arquitectura garantiza que loads, stores y instruction fetch traten el puntero con su byte superior enmascarado (es decir, tag removido) antes de realizar el acceso real a memoria.

Así, TBI desacopla el **puntero lógico** (puntero + tag) de la **dirección física** usada para operaciones de memoria.

#### Por qué TBI: casos de uso y motivación

- **Pointer tagging / metadata**: Puedes almacenar metadata adicional (p. ej. tipo de objeto, versión, límites, tags de integridad) en ese byte superior. Cuando luego usas el puntero, el tag se ignora a nivel de hardware, por lo que no necesitas removerlo manualmente para el acceso a memoria.
- **Memory tagging / MTE (Memory Tagging Extension)**: TBI es el mecanismo de hardware base sobre el que MTE se construye. En ARMv8.5, la **Memory Tagging Extension** usa los bits 59:56 del puntero como un **tag lógico** y lo compara con un **allocation tag** almacenado en memoria.
- **Seguridad e integridad mejoradas**: Combinando TBI con pointer authentication (PAC) o verificaciones en tiempo de ejecución, puedes exigir no solo que el valor del puntero sea correcto sino también que el tag lo sea. Un atacante que sobrescriba un puntero sin el tag correcto producirá un tag desajustado.
- **Compatibilidad**: Dado que TBI es opcional y los bits de tag son ignorados por el hardware, el código legado sin tags continúa funcionando normalmente. Los bits de tag efectivamente se convierten en bits “no relevantes” para código heredado.

#### Ejemplo
<details>
<summary>Ejemplo</summary>
Un puntero a función incluía un tag en su byte superior (digamos `0xAA`). Un exploit sobrescribe los bits bajos del puntero pero olvida el tag, así que cuando el kernel verifica o sanea, el puntero falla o es rechazado.
</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 está diseñado como una **frontera de protección intra-kernel**: incluso si el kernel (EL1) está comprometido y tiene capacidades de lectura/escritura, **no debería poder modificar libremente** ciertas **páginas sensibles** (especialmente page tables, metadata de code-signing, páginas de código del kernel, entitlements, trust caches, etc.).
- Efectivamente crea un **“kernel dentro del kernel”** — un componente más pequeño y confiable (PPL) con **privilegios elevados** que solo él puede usar para modificar páginas protegidas. Otro código del kernel debe llamar a rutinas PPL para efectuar cambios.
- Esto reduce la superficie de ataque para exploits de kernel: incluso con R/W/execute arbitrario en modo kernel, el código de exploit debe además entrar en el dominio PPL (o eludir PPL) para modificar estructuras críticas.
- En Apple silicon más recientes (A15+ / M2+), Apple está migrando a **SPTM (Secure Page Table Monitor)**, que en muchos casos reemplaza a PPL para la protección de page tables en esas plataformas.

Así es como se cree que opera PPL, basado en análisis públicos:

#### Uso de APRR / permission routing (APRR = Access Permission ReRouting)

- El hardware de Apple usa un mecanismo llamado **APRR (Access Permission ReRouting)**, que permite que las entradas de las page tables (PTEs) contengan pequeños índices, en lugar de bits de permiso completos. Esos índices se mapean vía registros APRR a permisos efectivos reales. Esto permite el remapeo dinámico de permisos por dominio.
- PPL aprovecha APRR para segregar privilegios dentro del contexto del kernel: solo el dominio PPL está permitido para actualizar el mapeo entre índices y permisos efectivos. Es decir, cuando código no-PPL del kernel escribe una PTE o intenta cambiar bits de permiso, la lógica APRR lo impide (o aplica un mapeo de solo lectura).
- El código PPL en sí se ejecuta en una región restringida (p. ej. `__PPLTEXT`) que normalmente no es ejecutable o no es escribible hasta que puertas de entrada permiten temporalmente el acceso. El kernel llama a puntos de entrada PPL (“rutinas PPL”) para realizar operaciones sensibles.

#### Entrada / salida de la puerta

- Cuando el kernel necesita modificar una página protegida (p. ej. cambiar permisos de una página de código del kernel, o modificar page tables), llama a una rutina wrapper de PPL, que hace validación y luego transiciona al dominio PPL. Fuera de ese dominio, las páginas protegidas son efectivamente de solo lectura o no modificables por el kernel principal.
- Durante la entrada a PPL, los mapeos APRR se ajustan para que las páginas de la región PPL estén marcadas como **executable & writable** dentro de PPL. Al salir, se retornan a solo lectura / no escribibles. Esto asegura que solo las rutinas PPL auditadas puedan escribir en páginas protegidas.
- Fuera de PPL, intentos por parte del código del kernel de escribir en esas páginas protegidas producirán un fault (permiso denegado) porque el mapeo APRR para ese dominio de código no permite escritura.

#### Categorías de páginas protegidas

Las páginas que PPL típicamente protege incluyen:

- Estructuras de page table (translation table entries, metadata de mapeo)
- Páginas de código del kernel, especialmente las que contienen lógica crítica
- Metadata de code-sign (trust caches, blobs de firma)
- Tablas de entitlements, tablas de enforcement de firma
- Otras estructuras de alto valor del kernel donde parchearlas permitiría eludir verificaciones de firma o manipular credenciales

La idea es que incluso si la memoria del kernel está totalmente controlada, el atacante no puede simplemente parchear o reescribir estas páginas, a menos que también comprometa las rutinas PPL o eluda PPL.

#### Bypasses & vulnerabilidades conocidas

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

- Un writeup público de Project Zero describe un bypass que involucra **stale TLB entries**.
- La 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.

- Este exploit requirió control fino del mapeo físico y del comportamiento del TLB. Demuestra que una frontera de seguridad que depende de la corrección de TLB / mapeos debe ser extremadamente cuidadosa sobre invalidaciones de TLB y consistencia de mapeos.

- Project Zero comentó que bypasses como este son sutiles y raros, pero posibles en sistemas complejos. Aun así, consideran a PPL como una mitigación sólida.

2. **Other potential hazards & constraints**

- Si un exploit de kernel puede entrar directamente en rutinas PPL (vía llamadas a los wrappers PPL), podría evadir las restricciones. Por tanto la validación de argumentos es crítica.
- Bugs en el propio código PPL (p. ej. overflow aritmético, checks de límites) pueden permitir modificaciones fuera de rango dentro de PPL. Project Zero observó que un bug en `pmap_remove_options_internal()` fue explotado en su bypass.
- La frontera PPL está irrevocablemente ligada a la aplicación por hardware (APRR, memory controller), por lo que es tan fuerte como la implementación hardware.

#### Ejemplo
<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

El kernel puede realizar muchas operaciones normales, pero solo a través de las rutinas ppl_call_* puede cambiar mappings protegidos o parchear código.

Ejemplo Un kernel exploit intenta sobrescribir la entitlement table, o deshabilitar la enforcement de code-sign modificando un kernel signature blob. Como esa página está PPL-protected, la escritura se bloquea a menos que se haga a través de la interfaz PPL. Así que, incluso con kernel code execution, no puedes bypass las restricciones de code-sign ni modificar arbitrariamente datos de credential. En iOS 17+ ciertos dispositivos usan SPTM para aislar aún más las páginas gestionadas por PPL.

PPL → SPTM / Reemplazos / Futuro

  • En los SoCs modernos de Apple (A15 o posteriores, M2 o posteriores), Apple soporta SPTM (Secure Page Table Monitor), que replaces PPL para page table protections.
  • Apple señala en la documentación: “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.”
  • La arquitectura SPTM probablemente traslada más policy enforcement a un monitor de mayor privilegio fuera del control del kernel, reduciendo aún más el trust boundary.

MTE | EMTE | MIE

Aquí hay una descripción de alto nivel de cómo opera EMTE bajo la configuración MIE de Apple:

  1. Asignación de tag
  • Cuando se asigna memoria (p. ej. en kernel o user space vía secure allocators), se asigna a ese bloque un secret tag.
  • El pointer devuelto al user o al kernel incluye ese tag en sus high bits (usando TBI / top byte ignore mechanisms).
  1. Comprobación de tag al acceder
  • Siempre que se ejecuta un load o store usando un pointer, el hardware verifica que el tag del pointer coincida con el tag del bloque de memoria (allocation tag). Si no coincide, falla inmediatamente (porque es synchronous).
  • Debido a que es synchronous, no existe una ventana de “detección retrasada”.
  1. Retagging al free / reuse
  • Cuando la memoria se libera, el allocator cambia el tag del bloque (por lo que pointers antiguos con tags viejos ya no coinciden).
  • Un use-after-free pointer por tanto tendrá un tag obsoleto y no coincidirá al acceder.
  1. Diferenciación de tags entre vecinos para detectar overflows
  • Las allocations adyacentes reciben tags distintos. Si un buffer overflow se desborda en la memoria del vecino, el mismatch de tags provoca un fault.
  • Esto es especialmente eficaz para detectar pequeños overflows que cruzan el boundary.
  1. Aplicación de confidencialidad de tags
  • Apple debe prevenir que los valores de tag sean leaked (porque si attacker aprende el tag, podría fabricar pointers con tags correctos).
  • Incluyen protecciones (microarchitectural / speculative controls) para evitar side-channel leakage de los bits de tag.
  1. Integración kernel y user space
  • Apple usa EMTE no solo en user space sino también en componentes críticos del kernel/OS (para proteger el kernel contra memory corruption).
  • El hardware/OS asegura que las reglas de tag se apliquen incluso cuando el kernel está ejecutando en nombre de user space.
Ejemplo ``` 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>

#### Limitaciones & desafíos

- **Intrablock overflows**: Si el overflow se mantiene dentro de la misma allocation (no cruza el boundary) y el tag sigue siendo el mismo, el tag mismatch no lo detecta.
- **Tag width limitation**: Solo unos pocos bits (p. ej. 4 bits, o un dominio pequeño) están disponibles para el tag—espacio de nombres limitado.
- **Side-channel leaks**: Si los bits del tag pueden ser leak (vía cache / speculative execution), el atacante podría aprender tags válidos y eludir la protección. La Tag Confidentiality Enforcement de Apple está pensada para mitigar esto.
- **Performance overhead**: Los Tag checks en cada load/store añaden coste; Apple debe optimizar el hardware para mantener bajo el overhead.
- **Compatibility & fallback**: En hardware antiguo o partes que no soportan EMTE, debe existir un fallback. Apple afirma que MIE solo está habilitado en dispositivos con soporte.
- **Complex allocator logic**: El allocator debe gestionar tags, retagging, alinear boundaries y evitar colisiones por mis-tag. Bugs en la lógica del allocator podrían introducir vulnerabilidades.
- **Mixed memory / hybrid areas**: Parte de la memoria puede permanecer untagged (legacy), complicando la interoperabilidad.
- **Speculative / transient attacks**: Como con muchas protecciones microarquitecturales, la speculative execution o micro-op fusions podrían eludir checks de forma transitoria o leak bits del tag.
- **Limited to supported regions**: Apple podría aplicar EMTE solo en zonas selectivas y de alto riesgo (kernel, security-critical subsystems), no de forma universal.



---

## Principales mejoras / diferencias con respecto al MTE estándar

Aquí están las mejoras y cambios que Apple enfatiza:

| Característica | Original MTE | EMTE (Apple’s enhanced) / MIE |
|---|---|---|
| **Check mode** | Soporta modos síncrono y asíncrono. En async, los tag mismatches se reportan más tarde (delayed) | Apple insiste en **modo síncrono** por defecto—los tag mismatches se detectan inmediatamente, sin ventanas de delay/race permitidas.|
| **Coverage of non-tagged memory** | Accesses to non-tagged memory (e.g. globals) may bypass checks in some implementations | EMTE requiere que los accesos desde una región tagged a non-tagged memory también validen conocimiento del tag, dificultando el bypass por mezcla de allocations.|
| **Tag confidentiality / secrecy** | Tags might be observable or leak via side channels | Apple añade **Tag Confidentiality Enforcement**, que intenta prevenir el leakage de valores de tag (vía side-channels especulativos, etc.).|
| **Allocator integration & retagging** | MTE deja gran parte de la lógica del allocator al software | Los secure typed allocators de Apple (kalloc_type, xzone malloc, etc.) se integran con EMTE: cuando se allocate o free memoria, los tags se gestionan con granularidad fina.|
| **Always-on by default** | En muchas plataformas, MTE es opcional o está off por defecto | Apple habilita EMTE / MIE por defecto en hardware soportado (p. ej. iPhone 17 / A19) para kernel y muchos procesos de usuario.|

Porque Apple controla tanto el hardware como la pila de software, puede aplicar EMTE de forma estricta, evitar problemas de rendimiento y cerrar huecos de side-channels.

---

## Cómo funciona EMTE en la práctica (Apple / MIE)

Aquí hay una descripción a alto nivel de cómo opera EMTE bajo la configuración MIE de Apple:

1. **Tag assignment**
- Cuando se allocate memoria (p. ej. en kernel o user space vía secure allocators), se asigna un **secret tag** a ese bloque.
- El puntero devuelto al usuario o al kernel incluye ese tag en sus bits altos (usando TBI / top byte ignore mechanisms).

2. **Tag checking on access**
- Siempre que se ejecuta un load o store usando un puntero, el hardware comprueba que el tag del puntero coincida con el tag del bloque de memoria (allocation tag). Si hay mismatch, falla inmediatamente (al ser síncrono).
- Al ser síncrono, no existe una ventana de “detección retrasada”.

3. **Retagging on free / reuse**
- Cuando se libera memoria, el allocator cambia el tag del bloque (por lo que punteros antiguos con tags viejos ya no coinciden).
- Un puntero use-after-free tendrá por tanto un tag stale y producirá mismatch al acceder.

4. **Neighbor-tag differentiation to catch overflows**
- Allocations adyacentes reciben tags distintos. Si un buffer overflow derrama en la memoria del vecino, el tag mismatch provoca fallo.
- Esto es especialmente efectivo para detectar pequeños overflows que cruzan boundaries.

5. **Tag confidentiality enforcement**
- Apple debe evitar que los valores de tag sean leak (porque si un atacante descubre el tag, podría forjar punteros con tags correctos).
- Incluyen protecciones (controles microarquitecturales / especulativos) para evitar el leak de bits de tag.

6. **Kernel and user-space integration**
- Apple usa EMTE no solo en user-space sino también en kernel / componentes críticos del OS (para proteger el kernel contra corrupción de memoria).
- El hardware/OS asegura que las reglas de tag se apliquen incluso cuando el kernel ejecuta en nombre de user space.

Dado que EMTE está integrado en MIE, Apple emplea EMTE en modo síncrono en superficies de ataque clave, no como modo opcional o de depuración.

---

## Manejo de excepciones en XNU

Cuando ocurre una **excepción** (p. ej., `EXC_BAD_ACCESS`, `EXC_BAD_INSTRUCTION`, `EXC_CRASH`, `EXC_ARM_PAC`, etc.), la capa **Mach** del kernel XNU es responsable de interceptarla antes de que se convierta en una señal de estilo UNIX (como `SIGSEGV`, `SIGBUS`, `SIGILL`, ...).

Este proceso implica múltiples capas de propagación y manejo de excepciones antes de llegar a user space o de convertirse en una BSD signal.


### Flujo de excepciones (Alto nivel)

1.  **La CPU dispara una excepción síncrona** (p. ej., desreferencia de puntero inválido, PAC failure, instrucción ilegal, etc.).

2.  **Se ejecuta el low-level trap handler** (`trap.c`, `exception.c` en el source de XNU).

3.  El trap handler llama a **`exception_triage()`**, el núcleo del manejo de excepciones Mach.

4.  `exception_triage()` decide cómo enrutar la excepción:

-   Primero al **thread's exception port**.

-   Luego al **task's exception port**.

-   Luego al **host's exception port** (a menudo `launchd` o `ReportCrash`).

Si ninguno de estos ports maneja la excepción, el kernel puede:

-   **Convertirla en una BSD signal** (para procesos en user-space).

-   **Provocar un panic** (para excepciones en kernel-space).


### Función central: `exception_triage()`

La función `exception_triage()` enruta las Mach exceptions por la cadena de posibles manejadores hasta que uno la atiende o hasta que resulta finalmente fatal. Está definida en `osfmk/kern/exception.c`.
```c
void exception_triage(exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt);

Flujo típico de llamadas:

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

Si todos fallan → manejado por bsd_exception() → traducido a una señal como SIGSEGV.

Exception Ports

Cada objeto Mach (thread, task, host) puede registrar exception ports, donde se envían los mensajes de excepción.

Están definidos por la API:

task_set_exception_ports()
thread_set_exception_ports()
host_set_exception_ports()

Cada exception port tiene:

  • Una mask (qué excepciones quiere recibir)
  • Un port name (Mach port para recibir mensajes)
  • Un behavior (cómo el kernel envía el mensaje)
  • Un flavor (qué thread state incluir)

Debuggers and Exception Handling

Un debugger (p. ej., LLDB) configura un exception port en la tarea o hilo objetivo, normalmente usando task_set_exception_ports().

Cuando ocurre una excepción:

  • El Mach message se envía al proceso del debugger.
  • El debugger puede decidir manejar (resume, modificar registros, saltar instrucción) o no manejar la excepción.
  • Si el debugger no la maneja, la excepción se propaga al siguiente nivel (task → host).

Flow of EXC_BAD_ACCESS

  1. El thread desreferencia un puntero inválido → la CPU genera un Data Abort.

  2. El kernel trap handler llama a exception_triage(EXC_BAD_ACCESS, ...).

  3. Mensaje enviado a:

  • Thread port → (el debugger puede interceptar breakpoint).

  • Si el debugger lo ignora → Task port → (handler a nivel de proceso).

  • Si se ignora → Host port (usualmente ReportCrash).

  1. Si nadie lo maneja → bsd_exception() lo traduce a SIGSEGV.

PAC Exceptions

Cuando falla Pointer Authentication (PAC) (desajuste de firma), se lanza una Mach exception especial:

  • EXC_ARM_PAC (tipo)
  • Los codes pueden incluir detalles (p. ej., tipo de key, tipo de pointer).

Si el binario tiene la flag TFRO_PAC_EXC_FATAL, el kernel trata fallos de PAC como fatales, evitando la intercepción por el debugger. Esto es para impedir que atacantes usen debuggers para evadir las comprobaciones PAC y está habilitado para platform binaries.

Software Breakpoints

Un breakpoint por software (int3 en x86, brk en ARM64) se implementa provocando un fallo deliberado.
El debugger captura esto vía el exception port:

  • Modifica instruction pointer o memoria.
  • Restaura la instrucción original.
  • Reanuda la ejecución.

Este mismo mecanismo es lo que te permite “capturar” una PAC exception — a menos que TFRO_PAC_EXC_FATAL esté establecido, en cuyo caso nunca llega al debugger.

Conversion to BSD Signals

Si ningún handler acepta la excepción:

  • El kernel llama a task_exception_notify() → bsd_exception().

  • Esto mapea Mach exceptions a señales:

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

### Key Files in XNU Source

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

  • bsd/kern/kern_sig.c → Lógica de entrega de señales.

  • osfmk/arm64/trap.c → Handlers de trap a bajo nivel.

  • osfmk/mach/exc.h → Códigos y estructuras de excepción.

  • osfmk/kern/task.c → Configuración de task exception port.


Old Kernel Heap (Pre-iOS 15 / Pre-A12 era)

El kernel usaba un zone allocator (kalloc) dividido en “zones” de tamaño fijo.
Cada zone solo almacena allocations de una sola clase de tamaño.

Desde la captura de pantalla:

Zone NameElement 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.

Cómo funcionaba:

  • Cada petición de allocation se redondeaba hacia arriba al tamaño de zone más cercano. (P. ej., una petición de 50 bytes cae en la zone kalloc.64).
  • La memoria en cada zone se mantenía en una freelist — los chunks liberados por el kernel volvían a esa zone.
  • Si sobreescribías un buffer de 64 bytes, sobrescribirías el siguiente objeto en la misma zone.

Por eso heap spraying / feng shui era tan efectivo: podías predecir vecinos de objetos al hacer allocations del mismo size class.

The freelist

Dentro de cada kalloc zone, los objetos liberados no se devolvían directamente al sistema — iban a una freelist, una lista enlazada de chunks disponibles.

  • Cuando un chunk se liberaba, el kernel escribía un pointer al inicio de ese chunk → la dirección del siguiente chunk libre en la misma zone.

  • La zone mantenía un puntero HEAD al primer chunk libre.

  • La allocation siempre usaba el HEAD actual:

  1. Pop HEAD (devolver esa memoria al caller).

  2. Actualizar HEAD = HEAD->next (almacenado en el header del chunk liberado).

  • Liberar empujaba los chunks de vuelta:

  • freed_chunk->next = HEAD

  • HEAD = freed_chunk

Así que la freelist era simplemente una lista enlazada construida dentro de la propia memoria 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)

Explotando el freelist

Porque los primeros 8 bytes de un free chunk = freelist pointer, un atacante podría corromperlo:

  1. Heap overflow en un freed chunk adyacente → sobrescribir su “next” pointer.

  2. Use-after-free escribir en un freed object → sobrescribir su “next” pointer.

Then, on the next allocation of that size:

  • El allocator pops el corrupted chunk.

  • Sigue el “next” pointer suministrado por el atacante.

  • Devuelve un pointer a memoria arbitraria, permitiendo fake object primitives o targeted overwrite.

Ejemplo visual de 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: vecinos previsibles por heap sprays, enlaces raw pointer freelist, y la falta de separación por tipo permitían a los atacantes escalar bugs de UAF/overflow hasta el control arbitrario de memoria del kernel.

Heap Grooming / Feng Shui

The goal of heap grooming is to shape the heap layout so that when an attacker triggers an overflow or use-after-free, the target (victim) object sits right next to an attacker-controlled object.
That way, when memory corruption happens, the attacker can reliably overwrite the victim object with controlled data.

Steps:

  1. Spray allocations (fill the holes)
  • Over time, the kernel heap gets fragmented: some zones have holes where old objects were freed.
  • The attacker first makes lots of dummy allocations to fill these gaps, so the heap becomes “packed” and predictable.
  1. Force new pages
  • Once the holes are filled, the next allocations must come from new pages added to the zone.
  • Fresh pages mean objects will be clustered together, not scattered across old fragmented memory.
  • This gives the attacker much better control of neighbors.
  1. Place attacker objects
  • The attacker now sprays again, creating lots of attacker-controlled objects in those new pages.
  • These objects are predictable in size and placement (since they all belong to the same zone).
  1. Free a controlled object (make a gap)
  • The attacker deliberately frees one of their own objects.
  • This creates a “hole” in the heap, which the allocator will later reuse for the next allocation of that size.
  1. Victim object lands in the hole
  • The attacker triggers the kernel to allocate the victim object (the one they want to corrupt).
  • Since the hole is the first available slot in the freelist, the victim is placed exactly where the attacker freed their object.
  1. Overflow / UAF into victim
  • Now the attacker has attacker-controlled objects around the victim.
  • By overflowing from one of their own objects (or reusing a freed one), they can reliably overwrite the victim’s memory fields with chosen values.

Why it works:

  • Zone allocator predictability: allocations of the same size always come from the same zone.
  • Freelist behavior: new allocations reuse the most recently freed chunk first.
  • Heap sprays: attacker fills memory with predictable content and controls layout.
  • End result: attacker controls where the victim object lands and what data sits next to it.

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.

An attacker can no longer guarantee that controlled data (OSData) ends up adjacent to sensitive kernel objects (task_t) of the same size.

2. Slabs and Per-CPU Caches

  • The heap is divided into slabs (pages of memory carved into fixed-size chunks for that zone).
  • Each zone has a per-CPU cache to reduce contention.
  • Allocation path:
  1. Try per-CPU cache.
  2. If empty, pull from the global freelist.
  3. If freelist is empty, allocate a new slab (one or more pages).
  • Benefit: This decentralization makes heap sprays less deterministic, since allocations may be satisfied from different CPUs’ caches.

3. Randomization inside zones

  • Within a zone, freed elements are not handed back in simple FIFO/LIFO order.
  • Modern XNU uses encoded freelist pointers (safe-linking like Linux, introduced ~iOS 14).
  • Each freelist pointer is XOR-encoded with a per-zone secret cookie.
  • This prevents attackers from forging a fake freelist pointer if they gain a write primitive.
  • Some allocations are randomized in their placement within a slab, so spraying doesn’t guarantee adjacency.

4. Guarded Allocations

  • Certain critical kernel objects (e.g., credentials, task structures) are allocated in guarded zones.
  • These zones insert guard pages (unmapped memory) between slabs or use redzones around objects.
  • Any overflow into the guard page triggers a fault → immediate panic instead of silent corruption.

5. Page Protection Layer (PPL) and SPTM

  • Even if you control a freed object, you can’t modify all of kernel memory:
  • PPL (Page Protection Layer) enforces that certain regions (e.g., code signing data, entitlements) are read-only even to the kernel itself.
  • On A15/M2+ devices, this role is replaced/enhanced by SPTM (Secure Page Table Monitor) + TXM (Trusted Execution Monitor).
  • These hardware-enforced layers mean attackers can’t escalate from a single heap corruption to arbitrary patching of critical security structures.
  • (Added / Enhanced): also, PAC (Pointer Authentication Codes) is used in the kernel to protect pointers (especially function pointers, vtables) so that forging or corrupting them becomes harder.
  • (Added / Enhanced): zones may enforce zone_require / zone enforcement, i.e. that an object freed can only be returned through its correct typed zone; invalid cross-zone frees may panic or be rejected. (Apple alludes to this in their memory safety posts)

6. Large Allocations

  • Not all allocations go through kalloc_type.
  • Very large requests (above ~16 KB) bypass typed zones and are served directly from kernel VM (kmem) via page allocations.
  • These are less predictable, but also less exploitable, since they don’t share slabs with other objects.

7. Allocation Patterns Attackers Target

Even with these protections, attackers still look for:

  • Reference count objects: if you can tamper with retain/release counters, you may cause use-after-free.
  • Objects with function pointers (vtables): corrupting one still yields control flow.
  • Shared memory objects (IOSurface, Mach ports): these are still attack targets because they bridge user ↔ kernel.

But — unlike before — you can’t just spray OSData and expect it to neighbor a task_t. You need type-specific bugs or info leaks to succeed.

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.

If you like, I can also generate a cheat-sheet or diagram of xzone internals for your book. Do you want me to do that next?
: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 install 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.

iMessage/Media Parser Zero-Click Chains

Imessage Media Parser Zero Click Coreaudio Pac Bypass

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks