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
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
iOS Exploit Mitigations
1. Code Signing / Verificación de firma en tiempo de ejecución
Introducido temprano (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 enraizada en 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 se modifica (bit-flipped, parcheado) o no está firmado, la carga falla.
- Impide: la etapa “classic payload drop + execute” en cadenas de exploit; inyección arbitraria de código; modificar un binario existente para insertar lógica maliciosa.
- Detalle del mecanismo:
- El Mach-O loader (y el dynamic linker) comprueban pages de código, segmentos, entitlements, team IDs, y que la firma cubra el contenido del fichero.
- 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.
mprotectcon verificaciones de code-sign). - La firma incluye entitlements e identificadores; el sistema operativo aplica que ciertas APIs o capacidades privilegiadas requieren entitlements específicos que no pueden falsificarse.
Example
Supongamos que un exploit obtiene code execution en un proceso e intenta escribir shellcode en un heap y saltar a él. En iOS, esa página tendría que marcarse como executable **y** satisfacer las restricciones de code-signature. Dado que el shellcode no está firmado con el certificado de Apple, el salto falla o el sistema rechaza hacer ejecutable esa región de memoria.2. CoreTrust
Introducido alrededor de iOS 14+ (o gradualmente en dispositivos más nuevos / versiones posteriores de 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 el certificado raíz de Apple en lugar de confiar en stores de confianza en userland cacheadas.
- Impide: manipulación post-instalación de binarios, técnicas de jailbreaking que intentan reemplazar o parchear librerías del sistema o apps de usuario; engañar al sistema reemplazando binarios confiables con contrapartes maliciosas.
- Detalle del mecanismo:
- En vez de confiar en una base local de confianza o cache de certificados, CoreTrust obtiene o se refiere directamente al root de Apple 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 otra metadata al binario en tiempo de carga.
Example
Un jailbreak podría intentar reemplazar `SpringBoard` o `libsystem` con una versión parcheada para ganar persistencia. Pero cuando el loader del OS o CoreTrust verifica, detecta el mismatch en la firma (o entitlements modificados) y se niega a ejecutar.3. Data Execution Prevention (DEP / NX / W^X)
Introducido en muchos OSes antes; iOS tuvo NX-bit / w^x durante mucho tiempo DEP impone que las páginas marcadas como writables (para datos) sean no-executables, y las páginas marcadas como ejecutables sean no-writables. No puedes simplemente escribir shellcode en un heap o stack y ejecutarlo.
- Impide: ejecución directa de shellcode; el clásico buffer-overflow → salto a shellcode inyectado.
- Detalle del mecanismo:
- 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 comprobació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 verificaciones adicionales.
Example
Un overflow escribe shellcode en el heap. El atacante intenta `mprotect(heap_addr, size, PROT_EXEC)` para hacerlo ejecutable. Pero el sistema se niega o valida que la nueva página debe pasar constraints de code-sign (que el shellcode no cumple).4. Address Space Layout Randomization (ASLR)
Introducido en iOS ~4–5 era (aprox. iOS 4–5) ASLR aleatoriza las direcciones base de regiones clave de memoria: libraries, heap, stack, etc., en cada lanzamiento de proceso. Las direcciones de los gadgets se mueven entre ejecuciones.
- Impide: hardcoding de direcciones de gadgets para ROP/JOP; cadenas de exploit estáticas; saltos a offsets conocidos a ciegas.
- Detalle del mecanismo:
- Cada librería / módulo dinámico cargado se reubica en un offset aleatorio.
- Los punteros base de stack y heap se aleatorizan (dentro de ciertos límites de entropía).
- A veces otras regiones (p. ej. mmap allocations) también se aleatorizan.
- Combinado con mitigaciones de information-leak, obliga al atacante a primero leak una dirección o puntero para descubrir bases en tiempo de ejecución.
Example
Una cadena ROP espera un gadget en `0x….lib + offset`. Pero como `lib` se reubica diferente en cada ejecución, la cadena hardcodeada falla. Un exploit debe primero leak la dirección base del módulo antes de calcular las direcciones de los gadgets.5. Kernel Address Space Layout Randomization (KASLR)
Introducido en iOS ~ (iOS 5 / iOS 6 timeframe) Análogo a ASLR de usuario, KASLR aleatoriza la base del kernel text y otras estructuras del kernel en el arranque.
- Impide: exploits a nivel kernel que dependen de localizaciones fijas de código o datos del kernel; exploits estáticos del kernel.
- Detalle del mecanismo:
- En cada boot, la dirección base del kernel se aleatoriza (dentro de un rango).
- Estructuras de datos del kernel (como
task_structs,vm_map, etc.) también pueden reubicarse o recibir offsets. - Los atacantes deben primero leak kernel pointers o usar vulnerabilidades de divulgación de información para calcular offsets antes de corromper estructuras o código del kernel.
Example
Una vulnerabilidad local pretende corromper un kernel function pointer (p. ej. en un `vtable`) en `KERN_BASE + offset`. Pero como `KERN_BASE` es desconocida, el atacante debe primero leakarla (p. ej. vía una primitive de read) antes de calcular la dirección correcta para la corrupción.6. Kernel Patch Protection (KPP / AMCC)
Introducido en iOS más recientes / hardware A-series (post alrededor de iOS 15–16 o chips más nuevos) KPP (aka AMCC) monitoriza continuamente la integridad de las páginas de kernel text (vía hash o checksum). Si detecta manipulación (parches, inline hooks, modificaciones de código) fuera de ventanas permitidas, provoca un kernel panic o reboot.
- Impide: parcheo persistente del kernel (modificar instrucciones del kernel), inline hooks, sobrescritura estática de funciones.
- Detalle del mecanismo:
- Un módulo hardware o firmware monitoriza la región de kernel text.
- Periodicamente o bajo demanda rehasea las páginas y compara con valores esperados.
- Si hay mismatches fuera de ventanas de actualización benignas, entra en panic (para evitar persistencia maliciosa).
- Los atacantes deben evitar ventanas de detección o usar rutas 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 la hash de la página de código ya no coincide con el valor esperado y provoca un kernel panic, colapsando el dispositivo antes de que el parche se estabilice.7. Kernel Text Read‐Only Region (KTRR)
Introducido en SoCs modernos (post ~A12 / hardware más nuevo) KTRR es un mecanismo aplicado por hardware: una vez que el kernel text se bloquea temprano durante el boot, se vuelve de solo-lectura desde EL1 (el kernel), impidiendo escrituras posteriores en páginas de código.
- Impide: cualquier modificación al código del kernel después del boot (p. ej. parcheo, in-place code injection) a nivel de privilegio EL1.
- Detalle del mecanismo:
- Durante el boot (en la etapa secure/bootloader), el memory controller (o una unidad hardware segura) marca las physical pages que contienen el kernel text como read-only.
- Incluso si un exploit gana privilegios completos de kernel, no puede escribir en esas páginas para parchear instrucciones.
- Para modificarlas, el atacante primero 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 handler de `syscall`). Pero como las páginas están bloqueadas como read-only por KTRR, la escritura falla (o dispara fault), por lo que los parches no se aplican.8. Pointer Authentication Codes (PAC)
Introducido con ARMv8.3 (hardware), Apple empezando con A12 / iOS ~12+
- PAC es una característica hardware introducida en ARMv8.3-A para detectar la manipulación de valores de puntero (return addresses, function pointers, ciertos data pointers) insertando una pequeña firma criptográfica (un “MAC”) en bits altos 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 o algún dato diferenciador). De ese modo, el mismo valor de puntero en diferentes contextos obtiene un PAC distinto.
- En tiempo de uso, antes de desreferenciar o hacer branch vía ese puntero, una instrucción de authenticate verifica el PAC. Si es válido, el PAC se elimina y se obtiene el puntero puro; si no, el puntero queda “poisoned” (o se levanta un fault).
- Las keys 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 superiores están “libres” y pueden contener el PAC sin alterar la dirección efectiva.
Basis arquitectónico y tipos de keys
-
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 key para instruction pointers (dominio “I”, key B)
-
APDAKey — para data pointers (dominio “D”, key A)
-
APDBKey — para data pointers (dominio “D”, key B)
-
APGAKey — key “genérica”, para firmar datos no-puntero u otros usos genéricos
-
Estas keys 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:
- El valor del puntero (porción canónica)
- Un modifier (un valor de contexto, como una salt)
- La secret key
- Alguna lógica interna de tweak Si el PAC resultante coincide con lo almacenado en los bits altos del puntero, la autenticación tiene éxito.
Familias de instrucciones
La convención de nombres es: PAC / AUT / XPAC, luego letras de dominio.
PACxxinstrucciones firman un puntero e insertan un PACAUTxxinstrucciones autentican + quitan (validan y eliminan el PAC)XPACxxinstrucciones quitan sin validar
Dominios / sufijos:
| 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 |
Hay formas especializadas / alias:
PACIASPes shorthand paraPACIA X30, SP(firmar el link register usando SP como modifier)AUTIASPesAUTIA X30, SP(autenticar el link register con SP)- Formas combinadas como
RETAA,RETAB(authenticate-and-return) oBLRAA(authenticate & branch) existen en extensiones ARM / soporte de compilador. - También variantes con modifier cero:
PACIZA/PACIZBdonde el modifier es implícitamente cero, etc.
Modifiers
El objetivo principal del modifier es atar el PAC a un contexto específico de modo que la misma dirección firmada en distintos contextos produzca PACs diferentes. 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: el stack pointer (
SP), un frame pointer, o algún ID de objeto. - Usar SP como modifier es común para el signing de return addresses: el PAC queda atado al frame específico de la pila. Si intentas reutilizar el LR en otro frame, el modifier cambia, por lo que la validación del PAC falla.
- El mismo valor de puntero firmado bajo modifiers diferentes 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.
Personalizaciones & Observaciones de Apple / iOS / XNU
- La implementación de PAC de Apple incluye diversificadores por boot de modo que keys o tweaks cambian en cada arranque, impidiendo reutilización entre boots.
- También incluyen mitigaciones cross-domain para que PACs firmados en user mode no puedan reutilizarse fácilmente en kernel mode, etc.
- En Apple M1 / Apple Silicon, la ingeniería inversa mostró que hay nueve tipos de modifier y registros de sistema específicos de Apple para control de keys.
- Apple usa PAC en muchos subsistemas del kernel: signing de return addresses, integridad de punteros en data del kernel, signed thread contexts, etc.
- Google Project Zero mostró cómo, bajo una powerful memory read/write primitive en kernel, se podían forjar kernel PACs (para keys A) en dispositivos A12-era, pero Apple parcheó muchas de esas vías.
- En el sistema de Apple, algunas keys son globales para el kernel, mientras que procesos de usuario pueden recibir randomness por proceso para keys.
PAC Bypasses
- Kernel-mode PAC: teórico vs bypasses reales
- Debido a que las kernel PAC keys y la lógica están fuertemente controladas (registros privilegiados, diversificadores, aislamiento de dominios), forjar punteros firmados arbitrarios del kernel es muy difícil.
- Azad’s 2020 “iOS Kernel PAC, One Year Later” reporta que en iOS 12-13 encontró algunos bypasses parciales (signing gadgets, reuse de signed states, indirect branches sin protección) pero no un bypass genérico completo. bazad.github.io
- Las personalizaciones “Dark Magic” de Apple reducen aún más las superficies explotables (domain switching, bits de habilitación por key). i.blackhat.com
- Hay un conocido kernel PAC bypass 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.
Por tanto, el kernel PAC se considera altamente robusto, aunque no perfecto.
- Bypasses de PAC en user-mode / runtime
Son más comunes, y explotan imperfecciones en cómo PAC se aplica o utiliza en dynamic linking / frameworks de runtime. Abajo hay clases con ejemplos.
2.1 Shared Cache / A key issues
- La dyld shared cache es un gran blob pre-linked de system frameworks y libraries. Debido a su amplio uso, function pointers dentro del shared cache están “pre-signed” 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 usarlos 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 shared libraries en userspace se encontraron insuficientemente protegidos por PAC, permitiendo que un atacante obtuviera function pointers sin cambiar su signature. (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 with A-key, diversifier zero) y luego usarlo. Dado quedlsymdevuelve 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’s “iOS 18.4 — dlsym considered harmful” describe un bug: algunos símbolos resueltos vía
dlsymen iOS 18.4 retornan punteros que están incorrectamente firmados (o con diversificadores buggy), habilitando un bypass de PAC no intencionado. Synacktiv - La lógica en dyld para dlsym incluye: cuando
result->isCode, firman el puntero retornado con__builtin_ptrauth_sign_unauthenticated(..., key_asia, 0), i.e. contexto cero. blog.epsilon-sec.com
Por ello, dlsym es un vector frecuente en bypasses de PAC en user-mode.
2.3 Otras relocaciones DYLD / runtime
- El loader DYLD y la lógica de relocación dinámica es compleja y a veces mapea páginas temporalmente como read/write para realizar relocations, luego las vuelve 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 de DYLD ahora están protegidas con SPRR / VM_FLAGS_TPRO (algunas flags de protección para dyld). Pero en versiones anteriores había defensas más débiles. Synacktiv
- En cadenas de exploit de WebKit, el loader DYLD suele ser objetivo para bypasses de PAC. Las slides mencionan que muchos bypasses han apuntado al DYLD loader (vía relocation, interposer hooks). Synacktiv
2.4 NSPredicate / NSExpression / ObjC / SLOP
- En cadenas de exploit en userland, métodos del runtime Objective-C como
NSPredicate,NSExpressionoNSInvocationse usan para contrabandear llamadas de control sin aparente forging de punteros. - En iOS más antiguo (antes de PAC), un exploit usó fake NSInvocation objects para llamar selectores arbitrarios sobre memoria controlada. Con PAC, hay que modificar esa técnica. Pero SLOP (SeLector Oriented Programming) se extiende también bajo PAC. Project Zero
- La técnica original SLOP permitía encadenar llamadas ObjC creando invocations falsas; el bypass se basa 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 / selectors / target pointers pueden no tener siempre protección PAC, dando espacio 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>Ejemplo</summary>
Un buffer overflow sobrescribe una return address 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 debido a la discrepancia del 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 tiene un primitive 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 branch indirectos**: al ejecutar `blr` o llamadas/branches indirectos, el destino debe comenzar con un **BTI landing pad** (`BTI j` o `BTI c`). Saltar a direcciones de gadget que carecen del landing pad provoca una excepción.
LLVM’s implementation notes three variants of BTI instructions and how they map to branch types.
| BTI Variant | What it permits (which branch types) | Typical placement / use case |
|-------------|----------------------------------------|-------------------------------|
| **BTI C** | Destinos de ramas indirectas de estilo *call* (p. ej. `BLR`, o `BR` usando X16/X17) | Colocado en la entrada de funciones que pueden ser llamadas indirectamente |
| **BTI J** | Destinos de ramas de estilo *jump* (p. ej. `BR` usado para tail calls) | Colocado al inicio de bloques alcanzables por jump tables o tail-calls |
| **BTI JC** | Actúa como C y J | Puede ser apuntado por branches de tipo call o jump |
- En código compilado con enforcement de branch target, los compiladores insertan una instrucción BTI (C, J o JC) en cada target válido de branch indirecto (inicios de función o bloques alcanzables por jumps) para que los branches indirectos sólo tengan éxito hacia esos lugares.
- **Direct branches / calls** (i.e. direcciones fijas `B`, `BL`) **no están restringidos** por BTI. La suposición es que las páginas de código son de confianza y un atacante no puede cambiarlas (así que los direct branches son seguros).
- Además, las instrucciones **RET / return** generalmente no están restringidas por BTI porque las return addresses están protegidas vía PAC o mecanismos de firma de retorno.
#### Mechanism and enforcement
- Cuando la CPU decodifica un **indirect branch (BLR / BR)** en una página marcada como “guarded / BTI-enabled”, verifica 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). Por eso los binarios BTI-enabled son retrocompatibles: en hardware sin soporte BTI esas instrucciones actúan como NOPs.
- Los compiler passes que añaden BTIs los insertan sólo donde se necesitan: funciones que pueden ser llamadas indirectamente, o basic blocks apuntados por jumps.
- Algunos parches y código de LLVM muestran que BTI no se inserta en *todos* los basic blocks — sólo en aquellos que son potenciales branch targets (p. ej. desde switch / jump tables).
#### BTI + PAC synergy
PAC protege el valor del puntero (la fuente) — asegura que la cadena de llamadas/returns indirectas no ha sido manipulada.
BTI asegura que incluso un puntero válido sólo puede apuntar a entry points correctamente marcados.
Combinado, un atacante necesita tanto un puntero válido con PAC correcto como que el objetivo tenga un BTI allí colocado. Esto incrementa la dificultad de construir gadgets explotables.
#### Ejemplo
<details>
<summary>Ejemplo</summary>
Un exploit intenta pivotar hacia un gadget en `0xABCDEF` que no empieza con `BTI c`. La CPU, al ejecutar `blr x0`, comprueba el destino y provoca un fallo porque la alineación/instrucción no incluye un landing pad válido. Así 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 **accesible por usuario (EL0)**, a menos que PAN se desactive explícitamente.
- La idea: incluso si el kernel es engañado o comprometido, no puede desreferenciar punteros de user-space arbitrariamente sin antes *clear* PAN, reduciendo riesgos de exploits estilo **`ret2usr`** o 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” provoca una **permission fault**.
- El kernel, cuando necesita legítimamente acceder a memoria de usuario (p. ej. copiar datos hacia/desde buffers de user), debe **deshabilitar PAN temporalmente** (o usar instrucciones de “unprivileged load/store”) para permitir ese acceso.
- En Linux en ARM64, el soporte PAN se introdujo alrededor de 2015: parches al kernel añadieron detección de la característica y reemplazaron `get_user` / `put_user` etc. por variantes que limpian PAN alrededor de los accesos a memoria de usuario.
**Key nuance / limitation / bug**
- Como señaló Siguza y otros, un bug de especificación (o comportamiento ambiguo) en el diseño de ARM significa que los **execute-only user mappings** (`--x`) pueden **no activar PAN**. En otras palabras, si una página de usuario está marcada ejecutable pero sin permiso de lectura, el intento del kernel de leerla podría sortear PAN porque la arquitectura considera “accesible en EL0” como requisito de permiso de lectura, no sólo de ejecución. Esto conduce a un bypass de PAN en ciertas configuraciones.
- Debido a eso, si iOS / XNU permite páginas de usuario execute-only (como algunos setups de JIT o code-cache podrían hacer), el kernel podría leerlas inadvertidamente incluso con PAN habilitado. Esta es un área sutil y conocida explotable en algunos sistemas ARMv8+.
#### PXN (Privileged eXecute Never)
- **PXN** es un flag en la page table (en las entradas de página, leaf o block) que indica que la página **no es ejecutable cuando se ejecuta en modo privilegiado** (i.e. cuando EL1 ejecuta).
- PXN evita que el kernel (o cualquier código privilegiado) salte a 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 de usuario desde el 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í, aunque el kernel tenga un puntero a función corrupto apuntando a memoria de usuario, si intenta branch allí, el bit PXN provocaría un fallo.
#### Memory-permission model & how PAN and PXN map to page table bits
Para entender cómo funcionan PAN / PXN, hay que ver cómo ARM realiza la traducción y el modelo de permisos (simplificado):
- Cada entrada de página o block tiene campos de atributos incluyendo **AP[2:1]** para permisos de acceso (read/write, privileged vs unprivileged) y bits **UXN / PXN** para restricciones de execute-never.
- Cuando PSTATE.PAN está en 1 (habilitado), el hardware aplica semánticas modificadas: accesos privilegiados a páginas marcadas como “accesibles por EL0” (i.e. user-accessible) están prohibidos (fault).
- Debido al bug mencionado, las páginas que están marcadas solo como ejecutables (sin permiso de lectura) pueden no contar como “accesibles por EL0” en ciertas implementaciones, y por tanto sortear PAN.
- Cuando el bit PXN de una página está puesto, incluso si el fetch de instrucciones proviene 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 hardened (como lo que Apple podría usar):
- El kernel habilita PAN por defecto (así el código privilegiado está restringido).
- En rutas que legítimamente necesitan leer o escribir buffers de usuario (p. ej. copia de syscalls, I/O, read/write de punteros de usuario), el kernel deshabilita PAN temporalmente o usa instrucciones especiales para sobreescribirlo.
- Tras terminar el acceso a datos de usuario, debe reactivar 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 kernel puede ejecutarse).
- El kernel debe asegurarse de que no existan caminos que causen ejecución en regiones de memoria de usuario (eso podría sortear PXN) — por tanto las cadenas de exploit que dependen de “saltar a shellcode de usuario” se bloquean.
Debido al bypass de PAN por execute-only pages, en un sistema real Apple podría deshabilitar o no permitir páginas execute-only de usuario, o parchear alrededor de la debilidad de la especificación.
#### Attack surfaces, bypasses, and mitigations
- **PAN bypass via execute-only pages**: como se discutió, la spec permite una grieta: 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 un camino inusual para suministrar datos vía secciones “execute-only”.
- **Temporal window exploit**: si el kernel deshabilita PAN por una ventana más larga de lo necesario, una carrera o ruta maliciosa podría explotar ese intervalo para realizar accesos a memoria de usuario no intencionados.
- **Forgotten re-enable**: si rutas de código olvidan reactivar PAN, operaciones kernel posteriores podrían acceder incorrectamente a memoria de usuario.
- **Misconfiguration of PXN**: si las page tables no ponen 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 PAN / PXN (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 control fino para permitir ciertos accesos o ejecución en regiones mapeadas a usuario; diseñar eso de forma segura bajo las restricciones PAN/PXN no es trivial.
#### Ejemplo
<details>
<summary>Ejemplo de código</summary>
Aquí hay secuencias pseudo-assembly ilustrativas que muestran habilitar/deshabilitar PAN alrededor de accesos a memoria de usuario, y cómo podría ocurrir un fault.
</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 hubiera **no** establecido PXN en esa user page, entonces el salto podría tener éxito — lo cual sería inseguro.
Si el kernel olvida volver a habilitar PAN después de acceder a memoria user, se abre una ventana en la que más lógica del kernel podría leer/escribir por accidente memoria user arbitraria.
Si el user pointer apunta a una execute-only page (user page con solo permiso de ejecución, sin lectura/escritura), bajo el bug de la especificación PAN, `ldr W2, [X1]` podría **no** fallar incluso con PAN habilitado, permitiendo un bypass, dependiendo de la implementación.
</details>
<details>
<summary>Example</summary>
Una vulnerabilidad en el kernel intenta tomar un function pointer proporcionado por el user y llamarlo en contexto kernel (p. ej. `call user_buffer`). Bajo PAN/PXN, esa operación está prohibida o provoca un fallo.
</details>
---
### 11. **Top Byte Ignore (TBI) / Pointer Tagging**
**Introduced in ARMv8.5 / newer (or optional extension)**
TBI significa que el top byte (el byte más significativo) de un puntero de 64 bits es ignorado por la traducción de direcciones. Esto permite que el OS o el hardware incrusten **tag bits** en el top byte 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 más altos** (bits 63:56) de un puntero de 64 bits cuando se realiza la **traducción de direcciones / operaciones de load/store / fetch de instrucciones**.
- En efecto, la CPU trata un puntero `0xTTxxxx_xxxx_xxxx` (donde `TT` = top byte) como `0x00xxxx_xxxx_xxxx` a efectos de la traducción de direcciones, ignorando (enmascarando) el top byte. El top byte puede ser usado por el software para almacenar **metadata / tag bits**.
- Esto proporciona al software espacio “en banda” gratuito para incrustar un byte de tag en cada puntero sin cambiar la ubicación de memoria a la que se refiere.
- La arquitectura garantiza que las operaciones de load, store y fetch de instrucciones traten el puntero con su top byte enmascarado (es decir, tag eliminado) antes de realizar el acceso real a memoria.
Así, TBI desacopla el **puntero lógico** (puntero + tag) de la **dirección física** utilizada para las operaciones de memoria.
#### Why TBI: Use cases and motivation
- Pointer tagging / metadata: Puedes almacenar metadata adicional (p. ej. tipo de objeto, versión, límites, etiquetas de integridad) en ese top byte. Cuando más tarde usas el puntero, el tag es ignorado a nivel de hardware, por lo que no necesitas retirarlo manualmente para acceder a memoria.
- Memory tagging / MTE (Memory Tagging Extension): TBI es el mecanismo base de hardware sobre el que se construye MTE. 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.
- Enhanced security & integrity: Al combinar TBI con pointer authentication (PAC) o verificaciones en tiempo de ejecución, puedes forzar 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 mismatched.
- Compatibility: Porque TBI es opcional y los bits de tag son ignorados por el hardware, el código existente sin tags sigue funcionando normalmente. Los bits de tag efectivamente se convierten en bits “don't care” para código legacy.
#### Example
<details>
<summary>Example</summary>
Un function pointer incluía un tag en su top byte (por ejemplo `0xAA`). Un exploit sobrescribe los bits bajos del puntero pero olvida el tag, así que cuando el kernel verifica o sanitiza, 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)** (algunos informes muestran PPL en macOS / Apple silicon, pero Apple está trayendo protecciones análogas a iOS)
- PPL está diseñado como una **barrera de protección intra-kernel**: incluso si el kernel (EL1) está comprometido y tiene capacidades de read/write, **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 de confianza (PPL) con **privilegios elevados** que solo él puede modificar páginas protegidas. Otro código del kernel debe invocar 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 atacante debe además de alguna forma entrar en el dominio PPL (o bypass PPL) para modificar estructuras críticas.
- En hardware Apple más nuevo (A15+ / M2+), Apple está migrando hacia **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:
#### Use of APRR / permission routing (APRR = Access Permission ReRouting)
- El hardware de Apple usa un mecanismo llamado **APRR (Access Permission ReRouting)**, que permite que los page table entries (PTEs) contengan índices pequeños, en lugar de bits de permiso completos. Esos índices se mapean mediante registros APRR a permisos efectivos. Esto permite remapeos dinámicos de permisos por dominio.
- PPL aprovecha APRR para segregar privilegios dentro del contexto del kernel: solo el dominio PPL está autorizado a actualizar el mapeo entre índices y permisos efectivos. Es decir, cuando código del kernel no-PPL escribe un PTE o intenta cambiar bits de permiso, la lógica APRR lo deniega (o impone un mapeo de solo lectura).
- El código PPL corre en una región restringida (p. ej. `__PPLTEXT`) que normalmente no es ejecutable ni escribible hasta que unas puertas de entrada la permiten temporalmente. El kernel llama a puntos de entrada PPL (“PPL routines”) para realizar operaciones sensibles.
#### Gate / Entry & Exit
- 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 valida y luego hace la transición 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 en la región PPL se configuren como **ejecutables & escribibles** dentro de PPL. Al salir, se devuelven a solo lectura / no escribibles. Esto asegura que solo rutinas PPL auditadas puedan escribir en páginas protegidas.
- Fuera de PPL, intentos de código del kernel de escribir en esas páginas protegidas fallarán (permission denied) porque el mapeo APRR para ese dominio de código no permite escritura.
#### Protected page categories
Las páginas que PPL típicamente protege incluyen:
- Estructuras de page tables (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 firmas)
- Tablas de entitlements, tablas de enforcement de firmas
- Otras estructuras de kernel de alto valor donde un parche permitiría evadir checks de firma o manipular credenciales
La idea es que, incluso si la memoria del kernel está completamente controlada, el atacante no puede simplemente parchear o reescribir estas páginas, a menos que también comprometa rutinas PPL o eluda PPL.
#### Known Bypasses & Vulnerabilities
1. **Project Zero’s PPL bypass (stale TLB trick)**
- Un writeup público de Project Zero describe un bypass que implica **entradas TLB obsoletas (stale TLB entries)**.
- La idea:
1. Allocar dos páginas físicas A y B, marcarlas como páginas PPL (por lo que están protegidas).
2. Mapear dos direcciones virtuales P y Q cuyas páginas de tabla de traducción L3 provienen de A y B.
3. Lanzar un hilo que acceda continuamente a Q, manteniendo viva su entrada TLB.
4. Llamar a `pmap_remove_options()` para eliminar mappings empezando en P; debido a un bug, el código borra erróneamente las TTEs tanto de P como de Q, pero solo invalida la entrada TLB de P, dejando la entrada stale de Q viva.
5. Reusar B (la página de la tabla de Q) para mapear memoria arbitraria (p. ej. páginas protegidas por PPL). Debido a que la entrada stale en el TLB aún mapea la vieja traducción de Q, ese mapeo permanece válido para ese contexto.
6. A través de esto, el atacante puede colocar un mapeo escribible de páginas protegidas por PPL sin pasar por la interfaz PPL.
- Este exploit requirió control fino del mapeo físico y del comportamiento del TLB. Demuestra que una frontera de seguridad basada en la corrección de TLB/mapeos debe ser extremadamente cuidadosa con las invalidaciones de TLB y la 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 eludir restricciones. Por eso 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 out-of-bounds 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 hardware (APRR, memory controller), así que solo es tan fuerte como la implementación hardware.
#### Example
<details>
<summary>Code Example</summary>
Aquí hay un pseudocódigo/ lógica simplificada que muestra cómo el kernel podría llamar a PPL para modificar páginas protegidas:
```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 mapeos protegidos o parchear código.
Ejemplo
A kernel exploit intenta sobrescribir la entitlement table, o desactivar el code-sign enforcement 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 PPL interface. Así que incluso con kernel code execution, no puedes eludir las code-sign constraints ni modificar arbitrariamente los credential data. En iOS 17+ ciertos dispositivos usan SPTM para aislar aún más las PPL-managed pages.PPL → SPTM / Reemplazos / Futuro
- En los SoCs modernos de Apple (A15 o posteriores, M2 o posteriores), Apple soporta SPTM (Secure Page Table Monitor), que reemplaza a PPL para las protecciones de la tabla de páginas.
- Apple indica 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 desplaza más la aplicación de políticas hacia un monitor de mayor privilegio fuera del control del kernel, reduciendo aún más la frontera de confianza.
MTE | EMTE | MIE
Aquí hay una descripción a alto nivel de cómo opera EMTE bajo la configuración MIE de Apple:
- Asignación de tags
- Cuando se asigna memoria (p. ej. en kernel o user space mediante allocators seguros), se asigna un secret tag a ese bloque.
- El puntero devuelto al user o kernel incluye ese tag en sus bits altos (usando TBI / top byte ignore mechanisms).
- Comprobación de tag en acceso
- Siempre que se ejecuta una carga o una store usando un puntero, el hardware verifica que el tag del puntero coincida con el tag del bloque de memoria (allocation tag). Si no coincide, provoca un fallo inmediatamente (ya que es síncrono).
- Como es síncrono, no existe una ventana de “detección retrasada”.
- Re-etiquetado al free / reuse
- Cuando la memoria se libera, el allocator cambia el tag del bloque (por lo que punteros antiguos con tags viejos ya no coinciden).
- Un use-after-free pointer, por tanto, tendría un tag obsoleto y no coincidiría al acceder.
- Diferenciación de tags entre vecinos para detectar overflows
- Las asignaciones adyacentes reciben tags distintos. Si un buffer overflow desborda hacia la memoria del vecino, la discrepancia de tags provoca un fallo.
- Esto es especialmente eficaz para detectar pequeños overflows que cruzan la frontera.
- Aplicación de confidencialidad de tags
- Apple debe prevenir que tag values sean leaked (porque si un atacante aprende el tag, podría crear punteros con los tags correctos).
- Incluyen protecciones (microarchitectural / speculative controls) para evitar side-channel leakage de los bits del tag.
- Integración entre 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 la corrupción de memoria).
- El hardware/OS garantiza que las reglas de tag se apliquen incluso cuando el kernel ejecuta en nombre del 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>
#### Limitations & challenges
- **Intrablock overflows**: Si el overflow permanece dentro de la misma asignación (no cruza la frontera) y el tag sigue siendo el mismo, 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 leaked (vía cache / ejecución especulativa), un 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**: Las comprobaciones de tag en cada load/store añaden coste; Apple debe optimizar el hardware para reducir esa sobrecarga.
- **Compatibility & fallback**: En hardware más antiguo o en componentes que no soporten EMTE, debe existir un mecanismo de fallback. Apple afirma que MIE solo está habilitado en dispositivos con soporte.
- **Complex allocator logic**: El allocator debe gestionar tags, retagging, alinear fronteras 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), lo que complica la interoperabilidad.
- **Speculative / transient attacks**: Como con muchas protecciones microarquitecturales, la ejecución especulativa o fusiones de micro-op podrían eludir comprobaciones de forma transitoria o leak bits de tag.
- **Limited to supported regions**: Apple podría aplicar EMTE únicamente en regiones selectivas y de alto riesgo (kernel, subsistemas críticos de seguridad), no de forma universal.
---
## Key enhancements / differences compared to standard MTE
Here are the improvements and changes Apple emphasizes:
| Feature | Original MTE | EMTE (Apple’s enhanced) / MIE |
|---|---|---|
| **Check mode** | Soporta modos síncrono y asíncrono. En async, las discrepancias de tag se reportan más tarde (retrasadas)| Apple insiste en **synchronous mode** por defecto—las discrepancias de tag se detectan inmediatamente, sin ventanas de delay/race permitidas.|
| **Coverage of non-tagged memory** | Los accesos a memoria no-tagged (p. ej. globals) pueden eludir comprobaciones en algunas implementaciones | EMTE exige que los accesos desde una región tagged a memoria no-tagged también validen conocimiento del tag, dificultando eludirlo mezclando asignaciones.|
| **Tag confidentiality / secrecy** | Los tags podrían ser observables o leaked vía side channels | Apple añade **Tag Confidentiality Enforcement**, que intenta prevenir la 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 la memoria se asigna o libera, los tags se gestionan a granularidad fina.|
| **Always-on by default** | En muchas plataformas, MTE es opcional o está apagado por defecto | Apple habilita EMTE / MIE por defecto en hardware soportado (p. ej. iPhone 17 / A19) para el kernel y muchos procesos de usuario.|
Debido a que Apple controla tanto el hardware como la pila de software, puede aplicar EMTE de forma estricta, evitar penalizaciones de rendimiento y cerrar vectores por side-channel.
---
## How EMTE works in practice (Apple / MIE)
Here’s a higher-level description of how EMTE operates under Apple’s MIE setup:
1. **Tag assignment**
- Cuando se asigna memoria (p. ej. en el kernel o en user space vía secure allocators), se asigna un **secret tag** al bloque.
- El puntero devuelto al usuario o al kernel incluye ese tag en sus bits altos (usando mecanismos TBI / top byte ignore).
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 (ya que es síncrono).
- Al ser síncrono, no existe una ventana de detección retardada.
3. **Retagging on free / reuse**
- Cuando la memoria se libera, 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 obsoleto y hará mismatch al acceder.
4. **Neighbor-tag differentiation to catch overflows**
- A las asignaciones adyacentes se les dan tags distintos. Si un buffer overflow se desborda hacia la memoria del vecino, el tag mismatch provoca un fallo.
- Esto es especialmente efectivo para detectar pequeños overflows que cruzan la frontera.
5. **Tag confidentiality enforcement**
- Apple debe impedir que los valores de tag sean leaked (porque si un atacante conoce el tag, podría fabricar punteros con tags correctos).
- Incluyen protecciones (controles microarquitecturales / especulativos) para evitar que los bits de tag se filtren.
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.
Porque EMTE está integrado en MIE, Apple usa EMTE en modo síncrono en superficies de ataque clave, no como opción o modo de depuración.
---
## Exception handling in XNU
When an **exception** occurs (e.g., `EXC_BAD_ACCESS`, `EXC_BAD_INSTRUCTION`, `EXC_CRASH`, `EXC_ARM_PAC`, etc.), the **Mach layer** of the XNU kernel is responsible for intercepting it before it becomes a UNIX-style **signal** (like `SIGSEGV`, `SIGBUS`, `SIGILL`, ...).
This process involves multiple layers of exception propagation and handling before reaching user space or being converted to a BSD signal.
### Exception Flow (High-Level)
1. **CPU triggers a synchronous exception** (e.g., invalid pointer dereference, PAC failure, illegal instruction, etc.).
2. **Low-level trap handler** runs (`trap.c`, `exception.c` in XNU source).
3. The trap handler calls **`exception_triage()`**, the core of the Mach exception handling.
4. `exception_triage()` decides how to route the exception:
- First to the **thread's exception port**.
- Then to the **task's exception port**.
- Then to the **host's exception port** (often `launchd` or `ReportCrash`).
If none of these ports handle the exception, the kernel may:
- **Convert it into a BSD signal** (for user-space processes).
- **Panic** (for kernel-space exceptions).
### Core Function: `exception_triage()`
The function `exception_triage()` routes Mach exceptions up the chain of possible handlers until one handles it or until it's finally fatal. It's defined in `osfmk/kern/exception.c`.
```c
void exception_triage(exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t codeCnt);
Flujo de llamadas típico:
exception_triage() └── exception_deliver() ├── exception_deliver_thread() ├── exception_deliver_task() └── exception_deliver_host()
Si todos fallan → es manejado por bsd_exception() → se traduce en una señal como SIGSEGV.
Puertos de excepción
Cada objeto Mach (thread, task, host) puede registrar puertos de excepción, 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 puerto de excepción 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) establece un exception port en la tarea o thread objetivo, normalmente usando task_set_exception_ports().
Cuando ocurre una excepción:
- El Mach message se envía al proceso debugger.
- El debugger puede decidir manejarla (resume, modificar registros, saltar instrucción) o no manejarla.
- Si el debugger no la maneja, la excepción se propaga al siguiente nivel (task → host).
Flow of EXC_BAD_ACCESS
-
El thread desreferencia un puntero inválido → la CPU lanza un Data Abort.
-
El kernel trap handler llama a
exception_triage(EXC_BAD_ACCESS, ...). -
Se envía un mensaje a:
-
Thread port → (el debugger puede interceptar el breakpoint).
-
Si el debugger ignora → Task port → (handler a nivel de proceso).
-
Si se ignora → Host port (usualmente ReportCrash).
- Si nadie la maneja →
bsd_exception()la traduce aSIGSEGV.
PAC Exceptions
Cuando Pointer Authentication (PAC) falla (mismatch de firma), se lanza una excepción Mach 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 las fallas de PAC como fatales, evitando la intercepción por el debugger. Esto es para impedir que un atacante use debuggers para eludir las comprobaciones PAC y está habilitado para platform binaries.
Software Breakpoints
Un software breakpoint (int3 en x86, brk en ARM64) se implementa causando un fault deliberado.
El debugger lo captura vía el exception port:
- Modifica el instruction pointer o la memoria.
- Restaura la instrucción original.
- Reanuda la ejecución.
Este mismo mecanismo es lo que permite “capturar” una excepción PAC — a menos que TFRO_PAC_EXC_FATAL esté establecida, 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 las Mach exceptions a 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→ Núcleo deexception_triage(),exception_deliver_*(). -
bsd/kern/kern_sig.c→ Lógica de entrega de signals. -
osfmk/arm64/trap.c→ Handlers de trap a bajo nivel. -
osfmk/mach/exc.h→ Códigos de excepción y estructuras. -
osfmk/kern/task.c→ Configuración del 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 zona solo almacenaba allocations de una única clase de tamaño.
De la captura:
| 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. |
Cómo funcionaba:
- Cada petición de allocation se redondeaba hacia arriba al tamaño de zona más cercano.
(Ej., una petición de 50 bytes cae en la zona
kalloc.64). - La memoria en cada zona se mantenía en una freelist — los chunks liberados por el kernel volvían a esa zona.
- Si sobreescribías un buffer de 64 bytes, sobrescribirías el siguiente objeto en la misma zona.
Por eso el heap spraying / feng shui era tan efectivo: podías predecir los vecinos del objeto rociando allocations de la misma clase de tamaño.
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 se liberaba un chunk, el kernel escribía un puntero al inicio de ese chunk → la dirección del siguiente chunk libre en la misma zona.
-
La zone mantenía un puntero HEAD al primer chunk libre.
-
La allocation siempre usaba el HEAD actual:
-
Pop HEAD (devolver esa memoria al caller).
-
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 freed chunk = freelist pointer, un atacante podría corromperlo:
-
Heap overflow into an adjacent freed chunk → sobrescribir su puntero “next”.
-
Use-after-free write into a freed object → sobrescribir su puntero “next”.
Then, on the next allocation of that size:
-
El allocator extrae el chunk corrompido.
-
Sigue el puntero “next” suministrado por el atacante.
-
Devuelve un puntero a memoria arbitraria, permitiendo fake object primitives o sobrescritura dirigida.
Visual example of freelist poisoning:
Before corruption:
HEAD ──► [ F1 ] ──► [ F2 ] ──► [ F3 ] ──► NULL
After attacker overwrite of F1->next:
HEAD ──► [ F1 ]
(next) ──► 0xDEAD_BEEF_CAFE_BABE (attacker-chosen)
Next alloc of this zone → kernel hands out memory at attacker-controlled address.
This freelist design made exploitation highly effective pre-hardening: predictable neighbors from heap sprays, raw pointer freelist links, and no type separation allowed attackers to escalate UAF/overflow bugs into arbitrary kernel memory control.
Heap Grooming / Feng Shui
El objetivo del Heap Grooming es dar forma a la disposición del heap de modo que cuando un atacante desencadene un overflow o use-after-free, el objeto objetivo (víctima) quede justo al lado de un objeto controlado por el atacante.
De esa manera, cuando ocurra la corrupción de memoria, el atacante puede sobrescribir de forma fiable el objeto víctima con datos controlados.
Pasos:
- Spray allocations (fill the holes)
- Con el tiempo, el kernel heap se fragmenta: algunas zonas tienen huecos donde se liberaron objetos antiguos.
- El atacante primero realiza muchas dummy allocations para rellenar estos huecos, de modo que el heap quede “empaquetado” y predecible.
- Force new pages
- Una vez que los huecos están llenos, las siguientes allocations deben provenir de nuevas páginas añadidas a la zone.
- Páginas nuevas significan que los objetos estarán agrupados, no dispersos por memoria fragmentada antigua.
- Esto le da al atacante mucho mejor control sobre los vecinos.
- Place attacker objects
- El atacante vuelve a sprayear, creando muchos objetos controlados por él en esas nuevas páginas.
- Estos objetos son predecibles en tamaño y ubicación (ya que pertenecen a la misma zone).
- Free a controlled object (make a gap)
- El atacante libera deliberadamente uno de sus propios objetos.
- Esto crea un “hueco” en el heap, que el allocator reutilizará para la siguiente allocation de ese tamaño.
- Victim object lands in the hole
- El atacante provoca que el kernel asigne el objeto víctima (el que quiere corromper).
- Dado que el hueco es la primera ranura disponible en la freelist, la víctima se coloca exactamente donde el atacante liberó su objeto.
- Overflow / UAF into victim
- Ahora el atacante tiene objetos controlados alrededor de la víctima.
- Al desbordar desde uno de sus propios objetos (o reutilizar uno liberado), puede sobrescribir de forma fiable los campos de memoria de la víctima con valores elegidos.
Por qué funciona:
- Predictabilidad del zone allocator: las allocations del mismo tamaño siempre provienen de la misma zone.
- Comportamiento de la freelist: las nuevas allocations reutilizan el chunk liberado más recientemente primero.
- Heap sprays: el atacante llena la memoria con contenido predecible y controla la disposición.
- Resultado final: el atacante controla dónde se coloca el objeto víctima y qué datos quedan adyacentes.
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:
- Try per-CPU cache.
- If empty, pull from the global freelist.
- 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:
- Type lookup →
OSDatamaps tokalloc_type_osdatazone (size 64 bytes). - 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).
- 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
| 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)
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:
- malloc / calloc / realloc / typed alloc is invoked with a size and type ID.
- The allocator uses the type ID to pick the correct segment group / zone.
- 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.
- The metadata slab is updated (free bit cleared, bookkeeping).
- If memory tagging (EMTE) is in play, the returned block gets a tag assigned, and metadata is updated to reflect its “live” state.
- 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:
| 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)
- 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
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 y watcher de PREYHUNTER
-
Watcher anti-analysis: Un binario watcher dedicado perfila continuamente el dispositivo y aborta la kill-chain cuando se detecta un entorno de investigación. Inspecciona
security.mac.amfi.developer_mode_status, la presencia de una consoladiagnosticd, localesUSoIL, trazas de jailbreak como Cydia, procesos comobash,tcpdump,frida,sshdocheckrain, apps móviles de AV (McAfee, AvastMobileSecurity, NortonMobileSecurity), configuraciones HTTP proxy personalizadas y custom root CAs. Si falla cualquier comprobación bloquea la entrega adicional de payloads. -
Helper surveillance hooks: El componente helper se comunica con otras etapas a través de
/tmp/helper.sock, y luego carga conjuntos de hooks llamados DMHooker y UMHooker. Estos hooks interceptan rutas de audio VOIP (las grabaciones se almacenan en/private/var/tmp/l/voip_%lu_%u_PART.m4a), implementan un keylogger a nivel de sistema, capturan fotos sin UI, y aplican hooks en SpringBoard para suprimir las notificaciones que esas acciones normalmente generarían. Por tanto, el helper actúa como una capa sigilosa de validación + vigilancia ligera antes de desplegar implantes más pesados como Predator. -
HiddenDot indicator suppression in SpringBoard: Con inyección de código a nivel de kernel, Predator hookea
SBSensorActivityDataProvider._handleNewDomainData:(el punto de agregación para la actividad de sensores). El hook pone a cero el puntero Objective-Cself(x0) de modo que la llamada se convierte en[nil _handleNewDomainData:newData], descartando actualizaciones de cámara/micrófono y suprimiendo tanto el punto verde como el naranja. -
Mach exception-based hooking flow (DMHooker): Los hooks se implementan vía
EXC_BREAKPOINT+ exception ports, luegothread_set_statemuta los registros y la ejecución continúa. El código de retorno2significa “continuar con el estado de hilo modificado.” -
PAC-aware redirection for camera access checks: En
mediaserverd, un pattern-scan (p.ej.,memmem) localiza una rutina privada cerca deFigVideoCaptureSourceCreateWithSourceInfodentro deCMCapture.framework. El hook devuelve3para redirigir usando una dirección de retorno en caché PAC pre-firmada, satisfaciendo PAC mientras se evita la comprobación. -
VoIP capture pipeline in
mediaserverd: HookeaAudioConverterNewyAudioConverterConvertComplexBuffer+52para interceptar buffers, inferir la sample rate a partir del tamaño de los buffers, convertir float32 PCM → int16 con NEON, downmix de 4 canales a estéreo, y persistir medianteExtAudioFileWrite(). El módulo VoIP en sí no suprime indicadores, por lo que los operadores deben habilitar HiddenDot por separado.
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
Referencias
- https://www.jamf.com/blog/predator-spyware-ios-recording-indicator-bypass-analysis/
- Google Threat Intelligence – Intellexa zero-day exploits continue
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
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.


