
CORUNA: ANATOMIA DE UN EXPLOIT CHAIN CONTRA iOS
CORUNA: ANATOMIA DE UN EXPLOIT CHAIN CONTRA iOS
Alguien dejo un kit de espionaje en un servidor publico. Sin querer. Con nombres internos en ingles: Cassowary, Neutron, Photon.
Asi se descubrio CORUNA: un kit de explotacion contra iPhones con 23 vulnerabilidades encadenadas, cinco cadenas de ataque completas, y una tecnica para forjar firmas de codigo que no estaba documentada en ninguna investigacion publica anterior.
Esto no es un articulo sobre quien lo hizo ni a quien se lo robaron. Es un articulo sobre como funciona. Cada etapa del ataque. Cada clase de vulnerabilidad. Cada truco que hace posible ir desde un sitio web en Safari hasta root persistente en el kernel de un iPhone.
LA CADENA COMPLETA: 12 PASOS DEL BROWSER AL KERNEL
Antes de entrar en detalle, esta es la secuencia completa que ejecuta CORUNA cuando un iPhone visita un sitio comprometido. Son 12 pasos, sin interaccion del usuario. Zero-click: el usuario no tiene que tocar nada.
- Fingerprinting (identificacion) del dispositivo (version iOS, deteccion de Corellium, modo Lockdown)
- iFrame oculto carga el JavaScript de explotacion
- Ejecucion remota de codigo en WebKit (CVE-2024-23222, confusion de tipos conocido como type-confusion)
- Bypass de ASLR (Address Space Layout Randomization, la randomnizacion de direcciones en memoria) en el proceso del navegador
- Bypass de PAC (Pointer Authentication Codes, firmas criptograficas en punteros) via confused deputy (usa codigo legitimo de Apple como intermediario involuntario)
- Escape de la JIT cage (la jaula del compilador Just-In-Time, forjando firmas PACDB en JavaScript)
- Escape del sandbox (del proceso; secuestro del despacho de llamadas WebAssembly + mach_vm_allocate RWX)
- Explotacion del kernel (CVE-2023-41974, use-after-free (uso de memoria ya liberada) en IOSurfaceRoot)
- Forja de entitlements (permisos especiales del sistema) + bypass de AMFI (Apple Mobile File Integrity)
- Remontaje del rootfs (sistema de archivos raiz) como escritura
- Despliegue del payload PLASMAGRID (ejecuta como root en el daemon powerd, un proceso en segundo plano)
- Inyeccion de CorePayload en el proceso locationd
Cada paso depende del anterior. Si uno falla, la cadena se detiene. Y cada paso explota una clase diferente de vulnerabilidad. Esa es la complejidad real: no es un exploit, es una plataforma de ataque modular.

CONFUSION DE TIPOS EN WEBKIT: EL PUNTO DE ENTRADA
El primer paso es conseguir ejecucion de codigo en el proceso de Safari. CORUNA usa CVE-2024-23222, una confusion de tipos (type confusion) en el compilador JIT DFG de JavaScriptCore.
Como funciona la confusion de tipos en JSC:
JavaScriptCore codifica cada valor en 64 bits usando una tecnica llamada NaN-boxing (empaquetado dentro de valores NaN). IEEE 754 tiene multiples patrones de bits que representan NaN (Not a Number, “no es un numero”). JSC aprovecha ese espacio para codificar punteros a objetos JavaScript dentro de valores que el motor de JavaScript trata como doubles (numeros de punto flotante de 64 bits).
Si los bits superiores tienen un patron especifico, JSC lo interpreta como puntero a un objeto. Si no, lo trata como un numero de punto flotante. El truco: si podes construir un valor que tiene la forma de un double pero JSC interpreta como puntero, controlaste el objeto al que apunta.
El exploit construye estos valores sinteticos usando dos vistas distintas sobre el mismo ArrayBuffer (una como enteros, otra como decimales):
const buf = new ArrayBuffer(64);
const u32 = new Uint32Array(buf);
const f64 = new Float64Array(buf);
// Escribe enteros que forman un patron de bits especifico
u32[1] = (structureID << 20) | (4 << 16) | cellType;
u32[0] = (flags << 24) | butterfly;
// Lee como float64: JSC interpreta esto como puntero a objeto
return f64[0];Escribis enteros en la vista Uint32, lees como Float64. El resultado es un valor que parece un double pero JSC interpreta como referencia a un objeto. Eso es un fakeobj primitive (primitiva de objeto falso): un puntero a un “objeto” cuyo contenido controlas vos.
La race condition que lo hace posible:
El bug real esta en el compilador DFG. Cuando compila en un thread de fondo, la funcion tryGetConstantProperty() lee una propiedad del objeto bajo un bloqueo (lock), libera el bloqueo, y sigue trabajando con el valor. Pero entre que libera el lock y usa el valor, el main thread puede cambiar la propiedad y disparar el garbage collector (recolector de basura):
// DFGGraph.cpp — codigo vulnerable (reconstruido del PoC publico):
JSValue result;
{
Locker cellLock { object->cellLock() };
result = object->getDirectConcurrently(cellLock, structure, offset);
}
// Lock liberado. El main thread cambia la propiedad y dispara GC.
// result ahora apunta a memoria ya liberada.
return result;Es un clasico TOCTOU (Time-of-check-to-time-of-use): se verifica en un momento, se usa en otro, y el mundo cambio en el medio. El compilador chequea bajo bloqueo, libera, y el estado cambia antes de que use el resultado.
De fakeobj a lectura/escritura arbitraria:
Con el fakeobj primitive, el exploit consigue dos operaciones fundamentales:
// addrOf: lee la direccion en memoria de cualquier objeto JS
function addrOf(obj) {
writeObj.x = obj;
return f2i(victimObj[2]);
}
// read: lee memoria arbitraria apuntando el butterfly a la direccion target
function read(addr) {
victimObj[1] = i2f(addr);
return f2i(writeObj[0]);
}addrOf convierte un objeto JS en su direccion de memoria cruda. read/write manipulan el butterfly pointer (puntero a las propiedades almacenadas fuera del objeto) de un objeto falso para leer y escribir en direcciones arbitrarias del proceso. A partir de aca, el proceso de Safari es territorio conquistado.

EL BYPASS DE PAC MAS ELEGANTE QUE EXISTE
Con lectura y escritura arbitraria en el proceso de Safari, el siguiente obstaculo es PAC: Pointer Authentication Codes. Es el mecanismo de Apple para firmar criptograficamente los punteros en ARM64e. Cada puntero lleva una firma en los bits superiores, verificada por hardware antes de usarlo. Si modificas un puntero sin la firma correcta, el CPU lo rechaza.
Las keys PAC estan en registros especiales del CPU, inaccesibles desde userland (espacio de usuario). No podes leerlas ni copiarlas, ni siquiera con lectura arbitraria en memoria.
CORUNA no intenta romper PAC. Lo usa en su contra con dos tecnicas.
Tecnica 1: Confused deputy via GOT-swap
La Global Offset Table (GOT) en el segmento __DATA contiene punteros planos, sin firma PAC. Son punteros que el linker (enlazador) resolvio al cargar el binario. Codigo legitimo de Apple, completamente firmado con PAC y con control-flow integro, lee estos punteros GOT para llamar funciones.
CORUNA intercambia temporalmente las entradas GOT con direcciones controladas por el atacante, dispara el codigo firmado de Apple que las lee, y el codigo de Apple ejecuta la funcion del atacante con autenticacion PAC perfecta.
// GOT-swap pattern (reconstruido de NadSec):
saved_Yl = read(GOT[Yl]);
saved_Wl = read(GOT[Wl]);
write(GOT[Yl], _HTTPConnectionFinalize); // Swapea
write(GOT[Wl], _dlfcn_globallookup); // Swapea
// Triggerea via Intl.Segmenter JIT:
// sigue fake object chain -> dispatch al target
triggerPACAuthenticatedCallPath();
write(GOT[Yl], saved_Yl); // Restaura
write(GOT[Wl], saved_Wl); // RestauraUsa 7 simbolos del dyld shared cache (la cache compartida de bibliotecas de Apple) (CoreGraphics, libxml2, ActionKit) como puntos de anclaje. Construye vtables (tablas de funciones virtuales) falsas de 768 bytes y objetos anidados que guian el despacho de llamadas hacia el objetivo del atacante. Una locura.
Tecnica 2: Forja de firmas JIT con PACDB
Esta es la tecnica que NadSec describe como “no documentada en ninguna investigacion publica anterior”. Es lo mas sofisticado del kit.
El JIT cage de WebKit requiere que las paginas de codigo JIT tengan un hash criptografico del contenido, computado con la instruccion hardware PACDB. El kernel verifica este hash antes de marcar una pagina como ejecutable. Sin el hash correcto, no podes ejecutar tu codigo.
CORUNA reimplemento el algoritmo de hashing en JavaScript puro:
// PACDB rolling hash (reconstruido de NadSec):
for each instruction_word:
val = (instruction XOR running_hash)
h = PACDB(val, context_1) >> 7
t = PACDB(val, context_2)
hash = (h XOR (t >> 23 | t << 9))
write_hash_to_buffer(hash)Propiedades del hash: es acumulativo (cada palabra depende de todas las anteriores via XOR), usa mezcla de doble contexto (dos discriminadores PAC por palabra), y esta inicializado por el desplazamiento (offset) de la pagina en memoria. Lo critico: usa la instruccion hardware PACDB, que usa las claves PAC por proceso del propio CPU del dispositivo objetivo.
El resultado: una pagina JIT con codigo arbitrario y un hash valido computado con la key PAC correcta. El kernel no puede distinguir esto de una compilacion JIT legitima. Apple necesita cambios arquitecturales para mitigar esto.

IOSURFACE: LA SUPERFICIE DE ATAQUE FAVORITA DE LOS 0-DAY
Para saltar del proceso de Safari al kernel, CORUNA necesita una vulnerabilidad en un driver accesible desde el sandbox del navegador. IOSurfaceRootUserClient es la eleccion obvia: es un driver IOKit que gestiona memorias intermedias (buffers) de GPU compartidas entre procesos, y el perfil de sandbox de Safari lo permite explicitamente.
No es la primera vez. Project Zero exploto IOSurface en 2019 (chain 2). Operation Triangulation lo exploto en 2023 (CVE-2023-32434). CORUNA lo explota en 2024 (CVE-2023-41974). Tres campanas distintas, mismo driver.
Por que IOSurface sigue siendo vulnerable:
IOSurface tiene una interface compleja. Acepta propiedades en formato XML parseadas por OSUnserializeXML() en kernel space. Los buffers de GPU se comparten via Mach ports entre procesos. El conteo de referencias (reference counting) de recursos es manual y propenso a errores. Apple no puede simplificar la interface sin romper la funcionalidad de GPU.
CVE-2023-41974: Use-After-Free
El bug: IOSurfaceRootUserClient almacena un puntero a la task struct del proceso que lo creo sin tomar una referencia (sin incrementar el reference count). Si el proceso muere y la task struct se libera, el puntero queda colgando (dangling): apunta a memoria que ya fue devuelta al asignador de memoria.
En C, el patron vulnerable se ve asi:
// Patron UAF en IOKit (especulativo basado en la clase de vulnerabilidad):
struct IOSurfaceClient {
task_t owner_task; // Puntero sin referencia
};
IOReturn IOSurfaceClient::create(task_t task) {
this->owner_task = task; // NO hace task_reference(task)
return kIOReturnSuccess;
}
// Si el task muere y se libera:
// this->owner_task ahora apunta a memoria liberada
// Cualquier acceso a this->owner_task es use-after-freeDe UAF a kernel read/write:
El patron de explotacion es un clasico de iOS kernel exploitation:
- Crear un IOSurface y dispara el UAF para liberar el objeto objetivo
- Hacer heap spray (inundacion del heap con datos controlados) via propiedades de IOSurface: crear 1024 propiedades de 56 bytes cada una. Estas alocationes en
kalloc.64reemplazan el slot liberado - Leer las propiedades de vuelta con
IOSurfaceCopyValue. Si una propiedad contiene datos del objeto liberado en vez de los datos originales, encontraste una fuga de informacion (information leak) - Usar la fuga para derrotar KASLR (Kernel ASLR: aleatorizacion de direcciones en el kernel) y obtener direcciones del kernel
- Escalar a lectura/escritura arbitraria en kernel via manipulacion de Mach ports con
task_get_special_port/task_set_special_port
El binario del exploit kernel en CORUNA es un DYLIB ARM64 de 2MB con 649 funciones y 265 funciones importadas de IOKit, Mach/VM, CoreFoundation y CommonCrypto. Escanea 39 patrones de instrucciones ARM64 para derrotar KASLR y parchea AMFI directamente en memoria del kernel para forjar entitlements (permisos del sistema).

COMO SE ENCUENTRAN ESTOS BUGS
Un investigador que busca vulnerabilidades en iOS kernel no empieza con el codigo fuente (no existe, o es parcial en XNU open source). Empieza con binarios.
El setup:
- checkra1n (checkm8 BootROM exploit, dispositivos A11 o menor): jailbreak hardware no parcheable por software. Te da acceso al filesystem, capacidad de instalar herramientas, y desactivar mitigaciones para analisis
- Ghidra o IDA Pro: desde iOS 10, Apple distribuye el kernel cache sin cifrar. Se puede desensamblar directamente. Los simbolos son muy reducidos pero las estructuras de IOKit son reconocibles
- Frida: instrumentacion dinamica. Hookeas funciones de un driver IOKit en tiempo de ejecucion, trazas las llamadas a metodos externos, e inspeccionas los parametros que pasa el espacio de usuario
- LLDB: depuracion de kernel via cable (con jailbreak) o depuracion de procesos en espacio de usuario
La metodologia para IOKit:
Un sistema de fuzzing (pruebas con entradas aleatorias para provocar fallos) de IOKit sigue estos pasos (documentado por Alibaba en Black Hat 2015):
- Exportar todas las subclases de OSObject en los kexts (extensiones del kernel), incluyendo cada IOUserClient
- Extraer info de cada driver: nombre de clase, tamano, direccion de vtable (tabla de funciones virtuales), herencia, metodos virtuales
- Identificar los pares Client-Selector: cada external method que el driver expone al userspace
- Generar entradas aleatorias que pasan el chequeo basico de parametros de IOKit
- Bombardear cada par Client-Selector via
IOConnectCallStructMethod()yIOConnectCallAsyncMethod() - Si hay kernel panic, el recolector post-reinicio recoge el ultimo par C-S que se ejecuto
IOSurface es un objetivo prioritario porque es accesible desde el sandbox de Safari. Un investigador que busca UAFs (use-after-free) en IOKit empieza por los drivers accesibles desde procesos en sandbox, mapea los metodos externos, y busca patrones de conteo de referencias manual: task_t almacenado sin task_reference(), ipc_port_t sin ipc_port_reference(), objetos IOKit sin retain().
El analisis de CORUNA por NadSec:
NadSec publico el analisis tecnico mas completo del kit. Su metodologia:
- Obtener el volcado publico de los 28 modulos JavaScript (disponible en GitHub via matteyeux)
- Deobfuscar mas de 1250 cadenas cifradas con XOR (191 unicas, 64 claves XOR distintas en rango ASCII 45-122)
- Extraer los modulos WebAssembly embebidos
- Reconstruir los escaneadores de gadgets (fragmentos de instrucciones reutilizables) ARM64 embebidos en el JavaScript
- Parsear las cabeceras Mach-O de los binarios del kit (numero magico 0xFEEDFACF, arquitectura ARM64)
- Documentar las 649 funciones del exploit de kernel y los 265 imports (funciones importadas)
El resultado: 6,630 lineas de analisis cubriendo 8 vulnerabilidades, con codigo desofuscado de cada modulo.

28 MODULOS, 1250 STRINGS, Y UN SISTEMA DE OFUSCACION
El kit tiene una ingenieria de software impresionante. 28 archivos JavaScript (~1.2MB ofuscados) con un sistema de modulos personalizado que usa hashes SHA-1 como identificadores y resolucion de dependencias automatica.
La ofuscacion usa multiples capas:
// XOR string decoding (patron real del kit):
[16, 22, 0, 69, ...].map(x => String.fromCharCode(x ^ 101))
// 64 keys XOR unicas (rango ASCII '-' a 'z')
// Numeros ofuscados como pares XOR:
const value = 1111970405 ^ 1111966034; // Resultado: valor real
// Variables minificadas: bvVGhS, PtqWRQ, etc.Los ~42 offsets adaptativos a estructuras internas de JavaScriptCore se ajustan automaticamente con 3 umbrales de version del engine (builds 170000, 170100, 170200). El kit detecta que version de WebKit esta corriendo y selecciona los offsets correctos. Si la version no coincide con ninguno, aborta silenciosamente.
Las cargas utiles (payloads) post-explotacion estan cifrados con ChaCha20, empaquetadas con una cabecera personalizada (magic 0xF00DBEEF), y comprimidos con LZMA. Las 13 cargas originales siguen accesibles en b27.icu, servidos via CloudFront, identicas byte a byte al volcado publico.
POR QUE IMPORTA ENTENDER ESTO
CORUNA no es un caso aislado. Es la evolucion directa de Operation Triangulation (2023). Kaspersky confirmo que los 5 exploits kernel de CORUNA comparten el mismo framework y codigo comun con Triangulation, con actualizaciones para procesadores A17, M3, M3 Pro y M3 Max.
Estudiar este kit te ensena encadenamiento de exploits (exploit chaining) real contra una de las plataformas mas protegidas del mundo. No teoria. No CTFs simplificados. Una cadena de 12 pasos que comprometio decenas de miles de dispositivos.
Cada etapa usa una clase distinta de vulnerabilidad: confusion de tipos, condiciones de carrera, use-after-free (uso de memoria liberada), confused deputy (intermediario involuntario), forja criptografica. Si entendes como funciona cada una, podes reconocerlas en otros contextos. La confusion de tipos en JSC aplica a cualquier engine con JIT. El UAF en IOKit aplica a cualquier driver con conteo de referencias manual. El truco del GOT-swap aplica a cualquier binario con punteros sin firmar en secciones de datos.
Las herramientas estan disponibles. El codigo fuente del kit esta publicado. Los analisis tecnicos son exhaustivos. Lo que CORUNA ensena no se aprende en ningun curso. Se aprende leyendo el codigo.
RECURSOS
- NadSec: Analisis tecnico de CORUNA — descompilacion completa de los 28 modulos (en ingles)
- NadSec: Technical Teardown — ingenieria inversa del exploit de kernel (DYLIB ARM64, 649 funciones)
- Google TAG: CORUNA Powerful iOS Exploit Kit — analisis original de Google Threat Intelligence
- Kaspersky/Securelist: Conexion CORUNA-Triangulation — evidencia de framework compartido
- FuzzySecurity: Cassowary PoC (CVE-2024-23222) — prueba de concepto x86_64 de la confusion de tipos
- Exodus Intelligence: Safari Type Confusion — analisis de la primitiva NaN-boxing
- NVD: CVE-2024-23222 — WebKit type confusion, CVSS 8.8
- NVD: CVE-2023-41974 — IOSurfaceRoot UAF, CVSS 7.8
- Project Zero: JITSploitation III — tecnicas de explotacion JIT en JSC
- Secfault Security: iOS Exploit Chain Analysis — heap spray via IOSurface properties
Si te resulto util esta informacion, te invito a ver el video relacionado
y dejar tu comentario. Tu participacion ayuda a que este contenido llegue
a mas personas y siga creciendo esta comunidad.

